Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
222 lines
9.4 KiB
Plaintext
222 lines
9.4 KiB
Plaintext
import { existsSync } from 'fs';
|
|
import { writeFile } from 'fs/promises';
|
|
import path from 'path';
|
|
/**
|
|
* @example
|
|
* console.log(sanitizeObjectKey("oneTwo")); // oneTwo
|
|
* console.log(sanitizeObjectKey("one-two")); // 'one-two'
|
|
* console.log(sanitizeObjectKey("_one$Two3")); // _one$Two3
|
|
* console.log(sanitizeObjectKey("3invalid")); // '3invalid'
|
|
*/ const sanitizeObjectKey = (key)=>{
|
|
// Regular expression for a valid identifier
|
|
const identifierRegex = /^[a-z_$][\w$]*$/i;
|
|
if (identifierRegex.test(key)) {
|
|
return key;
|
|
}
|
|
return `'${key}'`;
|
|
};
|
|
/**
|
|
* @example
|
|
* (columns default-valuesID) -> columns['default-valuesID']
|
|
* (columns defaultValues) -> columns.defaultValues
|
|
*/ const accessProperty = (objName, key)=>{
|
|
const sanitized = sanitizeObjectKey(key);
|
|
if (sanitized.startsWith("'")) {
|
|
return `${objName}[${sanitized}]`;
|
|
}
|
|
return `${objName}.${key}`;
|
|
};
|
|
export const createSchemaGenerator = ({ columnToCodeConverter, corePackageSuffix, defaultOutputFile, enumImport, schemaImport, tableImport })=>{
|
|
return async function generateSchema({ log = true, outputFile = defaultOutputFile, prettify = true } = {}) {
|
|
const importDeclarations = {};
|
|
const tableDeclarations = [];
|
|
const enumDeclarations = [];
|
|
const relationsDeclarations = [];
|
|
const addImport = (from, name)=>{
|
|
if (!importDeclarations[from]) {
|
|
importDeclarations[from] = new Set();
|
|
}
|
|
importDeclarations[from].add(name);
|
|
};
|
|
const corePackage = `${this.packageName}/drizzle/${corePackageSuffix}`;
|
|
let schemaDeclaration = null;
|
|
if (this.schemaName) {
|
|
addImport(corePackage, schemaImport);
|
|
schemaDeclaration = `export const db_schema = ${schemaImport}('${this.schemaName}')`;
|
|
}
|
|
const enumFn = this.schemaName ? `db_schema.enum` : enumImport;
|
|
const enumsList = [];
|
|
const addEnum = (name, options)=>{
|
|
if (enumsList.some((each)=>each === name)) {
|
|
return;
|
|
}
|
|
enumsList.push(name);
|
|
enumDeclarations.push(`export const ${name} = ${enumFn}('${name}', [${options.map((option)=>`'${option}'`).join(', ')}])`);
|
|
};
|
|
if (this.payload.config.localization && enumImport) {
|
|
addEnum('enum__locales', this.payload.config.localization.localeCodes);
|
|
}
|
|
const tableFn = this.schemaName ? `db_schema.table` : tableImport;
|
|
if (!this.schemaName) {
|
|
addImport(corePackage, tableImport);
|
|
}
|
|
addImport(corePackage, 'index');
|
|
addImport(corePackage, 'uniqueIndex');
|
|
addImport(corePackage, 'foreignKey');
|
|
addImport(`${this.packageName}/drizzle`, 'sql');
|
|
addImport(`${this.packageName}/drizzle`, 'relations');
|
|
for(const tableName in this.rawTables){
|
|
const table = this.rawTables[tableName];
|
|
const extrasDeclarations = [];
|
|
if (table.indexes) {
|
|
for(const key in table.indexes){
|
|
const index = table.indexes[key];
|
|
let indexDeclaration = `${index.unique ? 'uniqueIndex' : 'index'}('${index.name}')`;
|
|
indexDeclaration += `.on(${typeof index.on === 'string' ? `${accessProperty('columns', index.on)}` : `${index.on.map((on)=>`${accessProperty('columns', on)}`).join(', ')}`}),`;
|
|
extrasDeclarations.push(indexDeclaration);
|
|
}
|
|
}
|
|
if (table.foreignKeys) {
|
|
for(const key in table.foreignKeys){
|
|
const foreignKey = table.foreignKeys[key];
|
|
let foreignKeyDeclaration = `foreignKey({
|
|
columns: [${foreignKey.columns.map((col)=>`columns['${col}']`).join(', ')}],
|
|
foreignColumns: [${foreignKey.foreignColumns.map((col)=>`${accessProperty(col.table, col.name)}`).join(', ')}],
|
|
name: '${foreignKey.name}'
|
|
})`;
|
|
if (foreignKey.onDelete) {
|
|
foreignKeyDeclaration += `.onDelete('${foreignKey.onDelete}')`;
|
|
}
|
|
if (foreignKey.onUpdate) {
|
|
foreignKeyDeclaration += `.onUpdate('${foreignKey.onDelete}')`;
|
|
}
|
|
foreignKeyDeclaration += ',';
|
|
extrasDeclarations.push(foreignKeyDeclaration);
|
|
}
|
|
}
|
|
const tableCode = `
|
|
export const ${tableName} = ${tableFn}('${tableName}', {
|
|
${Object.entries(table.columns).map(([key, column])=>` ${sanitizeObjectKey(key)}: ${columnToCodeConverter({
|
|
adapter: this,
|
|
addEnum,
|
|
addImport,
|
|
column,
|
|
locales: this.payload.config.localization ? this.payload.config.localization.localeCodes : undefined,
|
|
tableKey: tableName
|
|
})},`).join('\n')}
|
|
}${extrasDeclarations.length ? `, (columns) => [
|
|
${extrasDeclarations.join(' ')}
|
|
]` : ''}
|
|
)
|
|
`;
|
|
tableDeclarations.push(tableCode);
|
|
}
|
|
for(const tableName in this.rawRelations){
|
|
const relations = this.rawRelations[tableName];
|
|
const properties = [];
|
|
for(const key in relations){
|
|
const relation = relations[key];
|
|
let declaration;
|
|
if (relation.type === 'one') {
|
|
declaration = `${sanitizeObjectKey(key)}: one(${relation.to}, {
|
|
${relation.fields.some((field)=>field.table !== tableName) ? '// @ts-expect-error Drizzle TypeScript bug for ONE relationships with a field in different table' : ''}
|
|
fields: [${relation.fields.map((field)=>`${accessProperty(field.table, field.name)}`).join(', ')}],
|
|
references: [${relation.references.map((col)=>`${accessProperty(relation.to, col)}`).join(', ')}],
|
|
${relation.relationName ? `relationName: '${relation.relationName}',` : ''}
|
|
}),`;
|
|
} else {
|
|
declaration = `${sanitizeObjectKey(key)}: many(${relation.to}, {
|
|
${relation.relationName ? `relationName: '${relation.relationName}',` : ''}
|
|
}),`;
|
|
}
|
|
properties.push(declaration);
|
|
}
|
|
// beautify / lintify relations callback output, when no many for example, don't add it
|
|
const args = [];
|
|
if (Object.values(relations).some((rel)=>rel.type === 'one')) {
|
|
args.push('one');
|
|
}
|
|
if (Object.values(relations).some((rel)=>rel.type === 'many')) {
|
|
args.push('many');
|
|
}
|
|
const arg = args.length ? `{ ${args.join(', ')} }` : '';
|
|
const declaration = `export const relations_${tableName} = relations(${tableName}, (${arg}) => ({
|
|
${properties.join('\n ')}
|
|
}))`;
|
|
relationsDeclarations.push(declaration);
|
|
}
|
|
if (enumDeclarations.length && !this.schemaName) {
|
|
addImport(corePackage, enumImport);
|
|
}
|
|
const importDeclarationsSanitized = [];
|
|
for(const moduleName in importDeclarations){
|
|
const moduleImports = importDeclarations[moduleName];
|
|
importDeclarationsSanitized.push(`import { ${Array.from(moduleImports).join(', ')} } from '${moduleName}'`);
|
|
}
|
|
const schemaType = `
|
|
type DatabaseSchema = {
|
|
${[
|
|
this.schemaName ? 'db_schema' : null,
|
|
...enumsList,
|
|
...Object.keys(this.rawTables),
|
|
...Object.keys(this.rawRelations).map((table)=>`relations_${table}`)
|
|
].filter(Boolean).map((name)=>`${name}: typeof ${name}`).join('\n ')}
|
|
}
|
|
`;
|
|
const finalDeclaration = `
|
|
declare module '${this.packageName}' {
|
|
export interface GeneratedDatabaseSchema {
|
|
schema: DatabaseSchema
|
|
}
|
|
}
|
|
`;
|
|
const warning = `
|
|
/* tslint:disable */
|
|
/* eslint-disable */
|
|
/**
|
|
* This file was automatically generated by Payload.
|
|
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
|
* and re-run \`payload generate:db-schema\` to regenerate this file.
|
|
*/
|
|
`;
|
|
const importTypes = `import type {} from '${this.packageName}'`;
|
|
let code = [
|
|
warning,
|
|
importTypes,
|
|
...importDeclarationsSanitized,
|
|
schemaDeclaration,
|
|
...enumDeclarations,
|
|
...tableDeclarations,
|
|
...relationsDeclarations,
|
|
schemaType,
|
|
finalDeclaration
|
|
].filter(Boolean).join('\n');
|
|
if (!outputFile) {
|
|
const cwd = process.cwd();
|
|
const srcDir = path.resolve(cwd, 'src');
|
|
if (existsSync(srcDir)) {
|
|
outputFile = path.resolve(srcDir, 'payload-generated-schema.ts');
|
|
} else {
|
|
outputFile = path.resolve(cwd, 'payload-generated-schema.ts');
|
|
}
|
|
}
|
|
if (prettify) {
|
|
try {
|
|
const prettier = await eval('import("prettier")');
|
|
const configPath = await prettier.resolveConfigFile();
|
|
const config = configPath ? await prettier.resolveConfig(configPath) : {};
|
|
code = await prettier.format(code, {
|
|
...config,
|
|
parser: 'typescript'
|
|
});
|
|
} catch {
|
|
/* empty */ }
|
|
}
|
|
await writeFile(outputFile, code, 'utf-8');
|
|
if (log) {
|
|
this.payload.logger.info(`Written ${outputFile}`);
|
|
}
|
|
};
|
|
};
|
|
|
|
//# sourceMappingURL=createSchemaGenerator.js.map |