diff --git a/README.md b/README.md index 20fe7b3..5f8ca8a 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ const options = { tables: { // Include only whitelisted tables include: [/authors/, "books"], - // Similar exclude can be used to blacklist + // Similarly exclude can be used to blacklist }, fieldMappings: [ { diff --git a/src/field-mappings.ts b/src/field-mappings.ts index 9c1ea6b..8dd7966 100644 --- a/src/field-mappings.ts +++ b/src/field-mappings.ts @@ -25,7 +25,7 @@ export const ImportedItemSchema = z.object({ /** * Whether this is a relative import * - * @default true + * @default true */ isRelative: z.boolean().nullish() }); @@ -37,7 +37,7 @@ export const ImportedItemSchema = z.object({ * resolved relative to the cwd from where generator is invoked and * then converted to a relative path relative to the generated file * - * Examples: + * Examples: * When generated file is located at src/db/tables/some-table.ts and generator * is run from project root * diff --git a/src/generator-options.ts b/src/generator-options.ts index 9461823..949a592 100644 --- a/src/generator-options.ts +++ b/src/generator-options.ts @@ -1,5 +1,5 @@ import * as z from "zod"; -import { FieldMappingSchema, StrOrRegExpSchema } from "./field-mappings"; +import { FieldMappingSchema, ImportedItemSchema, StrOrRegExpSchema } from "./field-mappings"; export const TableInclusionSchema = z.object({ /** @@ -292,6 +292,11 @@ export const RawContentSchema = z.object({ export interface RawContent extends z.TypeOf {} +export const TypeWrapperSchema = z.object({ + typeName: StrOrRegExpSchema, + wrapper: ImportedItemSchema +}); + export const GeneratorOptsSchema = z.object({ /** Simulate the generation and print the outcome without actually modifying any files */ dryRun: z.boolean().nullish(), @@ -386,6 +391,16 @@ export const GeneratorOptsSchema = z.object({ * @see RawContent */ rawContent: RawContentSchema.nullish(), + + /** + * Wrap inferred types before exporting - this is useful to restrict + * the types used for insert/update etc. beyond what the database permits. + * + * Eg. We can hint that updatedAt must be set whenever record is updated + * + * @see TypeWapper + */ + typeWrappers: TypeWrapperSchema.array().nullish(), }); /** diff --git a/src/generator.ts b/src/generator.ts index f351677..b4ee67b 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -13,7 +13,7 @@ import { } from "./field-mappings"; import { FileRemover } from "./file-remover"; import { GeneratorOpts, GeneratorOptsSchema, NamingOptions, NamingOptionsSchema } from "./generator-options"; -import { doesMatchNameOrPattern } from "./matcher"; +import { doesMatchNameOrPattern, doesMatchNameOrPatternNamespaced } from "./matcher"; import { Logger } from "./logger" import { Column, Table, TblsSchema } from "./tbls-types"; @@ -46,6 +46,11 @@ interface ImportTmplInput { isDefault: boolean; } +interface RepoInput { + className: string + methods: Record +} + /** * Generator class for programmatic codegen. * @@ -116,7 +121,7 @@ export class Generator { if ( filter?.include && filter.include.findIndex((it) => - doesMatchNameOrPattern(it, table.name) + doesMatchNameOrPatternNamespaced(it, table.name) ) < 0 ) { return false; @@ -124,13 +129,14 @@ export class Generator { if ( filter?.exclude && filter.exclude.findIndex((it) => - doesMatchNameOrPattern(it, table.name) + doesMatchNameOrPatternNamespaced(it, table.name) ) >= 0 ) { return false; } return true; } + protected getTableKind(table: Table): TableKind | null { return match(table.type.toLowerCase()) .with("base table", () => "Table" as const) @@ -141,92 +147,20 @@ export class Generator { protected async getTableTemplateInput(table: Table, tableKind: TableKind, filePath: string) { // Qualified table name with schema prefix - const tableName = last(table.name.split(".")) as string; + const tableName = this.extractTableName(table.name); const pkCol = this.findPrimaryKey(table); - const fields: FieldTmplInput[] = table.columns - .filter((col) => { - return !this.isColumnOmitted(table.name, col); - }) - .map((col) => { - const isOptional = this.isColumnOptional(table.name, col); - const hasDefault = this.doesColumnHaveDefault(table.name, col); - const isComputed = this.isColumnComputed(table.name, col); - let isPK = false; - let columnMethod!: ColumnMethod; - if (col === pkCol) { - let isAutoGenerated = - col.default ?? - this.opts.common?.primaryKey?.isAutoGenerated ?? - false; - columnMethod = isAutoGenerated - ? "autogeneratedPrimaryKey" - : "primaryKey"; - isPK = true; - } else if (isComputed) { - if (isOptional) { - columnMethod = "optionalComputedColumn"; - } else { - columnMethod = "computedColumn"; - } - } else if (!isOptional && !hasDefault) { - columnMethod = "column"; - } else if (isOptional && !hasDefault) { - columnMethod = "optionalColumn"; - } else if (isOptional && hasDefault) { - columnMethod = "optionalColumnWithDefaultValue"; - } else if (!isOptional && hasDefault) { - columnMethod = "columnWithDefaultValue"; - } - return { - name: this.getFieldNameForColumn(table.name, col), - columnName: col.name, - comment: this.formatComment(col.comment), - isOptional, - hasDefault, - columnMethod, - fieldType: this.getFieldType(table.name, col), - includeDBTypeWhenIsOptional: this.opts.includeDBTypeWhenIsOptional || false, - isPK, - }; - }); + const fields = this.getFieldsInput(table, pkCol); const dbConnectionSource = this.getConnectionSourceImportPath(filePath); const exportTableClass = this.opts.export?.tableClasses ?? true; - const exportRowTypes = this.opts.export?.rowTypes - ? ({} as any) - : false; - const pascalName = this.getPascalCasedTableName(tableName); - if (exportRowTypes !== false) { - exportRowTypes.selected = this.naming.selectedRowTypeNamePrefix + pascalName + this.naming.selectedRowTypeNameSuffix; - if (tableKind !== "View") { - exportRowTypes.insertable = this.naming.insertableRowTypeNamePrefix + pascalName + this.naming.insertableRowTypeNameSuffix; - exportRowTypes.updatable = this.naming.updatableRowTypeNamePrefix + pascalName + this.naming.updatableRowTypeNameSuffix; - } - } - const exportValuesTypes = this.opts.export?.valuesTypes ? ({} as any) : false; - if (exportValuesTypes !== false) { - exportValuesTypes.selected = this.naming.selectedValuesTypeNamePrefix + pascalName + this.naming.selectedValuesTypeNameSuffix; - if (tableKind !== "View") { - exportValuesTypes.insertable = this.naming.insertableValuesTypeNamePrefix + pascalName + this.naming.insertableValuesTypeNameSuffix; - exportValuesTypes.updatable = this.naming.updatableValuesTypeNamePrefix + pascalName + this.naming.updatableValuesTypeNameSuffix; - } - } - const className = this.getClassNameFromTableName(table.name, tableKind); + const wrapperTypeImports: ImportTmplInput[] = []; + const className = this.getTableMapperClassName(table.name, tableKind); + const rowTypes = this.getRowTypeInputs(tableName, tableKind, className, wrapperTypeImports); + const valuesTypes = this.getValuesTypeInputs(tableName, tableKind, className, wrapperTypeImports); const pkField = fields.find(it => it.isPK) const repo = this.getRepoInput(tableName, tableKind, pkField) - const exportColMapping = (this.opts.export?.columnTypeMappingInterface || repo) - ? ({} as any) - : false; - if (exportColMapping !== false) { - exportColMapping.name = pascalName + this.naming.columnTypeMappingInterfaceNameSuffix - } - - const colSetName = this.opts.export?.extractedColumns - ? this.getColumnsObjectNameFromTableName(table.name, tableKind) - : null; - const instName = - this.opts.export?.tableInstances || colSetName - ? this.getInstanceNameFromTableName(table.name, tableKind) - : null; + const colMapping = this.getColMappingInput(tableName, !!repo) + const colSetName = this.getColSetName(tableName, tableKind); + const instName = this.getTableMapperInstName(tableName, tableKind) const idPrefix = this.getIdPrefix(table); const rowTypePrefix = this.getRowTypePrefix(tableName); @@ -245,8 +179,9 @@ export class Generator { }, imports: [ ...utilImports, - ...adapterImports, + ...adapterImports, ...typeImports, + ...wrapperTypeImports, ], dbConnectionSource, className, @@ -254,18 +189,69 @@ export class Generator { fields, adapterImports, exportTableClass, - exportRowTypes, - exportValuesTypes, - importExtraTypes: exportRowTypes || exportValuesTypes, + rowTypes, + valuesTypes, + importExtraTypes: rowTypes || valuesTypes, rowTypePrefix, colSetName, - exportColMapping, + colMapping, pkField, repo }); } - protected getRepoInput(tableName: string, tableKind: TableKind, pkField?: FieldTmplInput) { + protected getFieldsInput(table: Table, pkCol: Column | null): FieldTmplInput[] { + return table.columns + .filter((col) => { + return !this.isColumnOmitted(table.name, col); + }) + .map((col) => this.getFieldInput(col, table, pkCol)); + } + + protected getFieldInput(col: Column, table: Table, pkCol: Column | null) { + const isOptional = this.isColumnOptional(table.name, col); + const hasDefault = this.doesColumnHaveDefault(table.name, col); + const isComputed = this.isColumnComputed(table.name, col); + let isPK = false; + let columnMethod!: ColumnMethod; + if (col === pkCol) { + const isAutoGenerated = + col.default ?? + this.opts.common?.primaryKey?.isAutoGenerated ?? + false; + columnMethod = isAutoGenerated + ? "autogeneratedPrimaryKey" + : "primaryKey"; + isPK = true; + } else if (isComputed) { + if (isOptional) { + columnMethod = "optionalComputedColumn"; + } else { + columnMethod = "computedColumn"; + } + } else if (!isOptional && !hasDefault) { + columnMethod = "column"; + } else if (isOptional && !hasDefault) { + columnMethod = "optionalColumn"; + } else if (isOptional && hasDefault) { + columnMethod = "optionalColumnWithDefaultValue"; + } else if (!isOptional && hasDefault) { + columnMethod = "columnWithDefaultValue"; + } + return { + name: this.getFieldNameForColumn(table.name, col), + columnName: col.name, + comment: this.formatComment(col.comment), + isOptional, + hasDefault, + columnMethod, + fieldType: this.getFieldType(table.name, col), + includeDBTypeWhenIsOptional: this.opts.includeDBTypeWhenIsOptional || false, + isPK, + }; + } + + protected getRepoInput(tableName: string, tableKind: TableKind, pkField?: FieldTmplInput): RepoInput | null { if (!pkField) return null; if (!this.opts.export?.crudRepository) return null; const pkFSuffix = upperFirst(pkField.name); @@ -293,7 +279,7 @@ export class Generator { methods.deleteMany = `deleteManyBy${pkFSuffix}`; } return { - className: this.getCrudRepoNameFromTableName(tableName), + className: this.getCrudRepoName(tableName), methods, } } @@ -450,7 +436,10 @@ export class Generator { importedItem: ImportedItem ) { if (importedItem.isRelative === false) return importPath; - const result: string = path.relative(path.dirname(filePath), path.resolve(importPath)); + const result: string = path.relative( + path.dirname(filePath), + path.resolve(importPath) + ); if (result.startsWith(".")) { return result; } else { @@ -493,15 +482,21 @@ export class Generator { return sections.join('\n'); } - protected getCrudRepoNameFromTableName(tableName: string) { - return this.naming.crudRepositoryClassNamePrefix + this.getPascalCasedTableName(tableName) + this.naming.crudRepositoryClassNameSuffix; + protected getCrudRepoName(tableName: string) { + return this.naming.crudRepositoryClassNamePrefix + + this.getPascalCasedTableName(tableName) + + this.naming.crudRepositoryClassNameSuffix; } - protected getClassNameFromTableName(tableName: string, tableKind: TableKind) { + protected getTableMapperClassName(tableName: string, tableKind: TableKind) { if (tableKind === 'Table') { - return this.naming.tableClassNamePrefix + this.getPascalCasedTableName(tableName) + this.naming.tableClassNameSuffix; + return this.naming.tableClassNamePrefix + + this.getPascalCasedTableName(tableName) + + this.naming.tableClassNameSuffix; } else { - return this.naming.viewClassNamePrefix + this.getPascalCasedTableName(tableName) + this.naming.viewClassNameSuffix; + return this.naming.viewClassNamePrefix + + this.getPascalCasedTableName(tableName) + + this.naming.viewClassNameSuffix; } } @@ -509,26 +504,36 @@ export class Generator { return this.getPascalCasedTableName(tableName); } - protected getInstanceNameFromTableName(tableName: string, tableKind: TableKind) { + protected getTableMapperInstanceName(tableName: string, tableKind: TableKind) { if (tableKind === 'Table') { - return this.naming.tableInstanceNamePrefix + this.getPascalCasedTableName(tableName) + this.naming.tableInstanceNameSuffix; + return this.naming.tableInstanceNamePrefix + + this.getPascalCasedTableName(tableName) + + this.naming.tableInstanceNameSuffix; } else { - return this.naming.viewInstanceNamePrefix + this.getPascalCasedTableName(tableName) + this.naming.viewInstanceNameSuffix; + return this.naming.viewInstanceNamePrefix + + this.getPascalCasedTableName(tableName) + + this.naming.viewInstanceNameSuffix; } } - protected getColumnsObjectNameFromTableName(tableName: string, tableKind: TableKind) { + protected getColumnsObjectName(tableName: string, tableKind: TableKind) { if (tableKind === 'Table') { if (this.naming.tableColumnsNamePrefix) { - return this.naming.tableColumnsNamePrefix + this.getPascalCasedTableName(tableName) + this.naming.tableColumnsNameSuffix; + return this.naming.tableColumnsNamePrefix + + this.getPascalCasedTableName(tableName) + + this.naming.tableColumnsNameSuffix; } else { - return this.getCamelCasedTableName(tableName) + this.naming.tableColumnsNameSuffix; + return this.getCamelCasedTableName(tableName) + + this.naming.tableColumnsNameSuffix; } } else { if (this.naming.viewColumnsNamePrefix) { - return this.naming.viewColumnsNamePrefix + this.getPascalCasedTableName(tableName) + this.naming.viewColumnsNameSuffix; + return this.naming.viewColumnsNamePrefix + + this.getPascalCasedTableName(tableName) + + this.naming.viewColumnsNameSuffix; } else { - return this.getCamelCasedTableName(tableName) + this.naming.viewColumnsNameSuffix; + return this.getCamelCasedTableName(tableName) + + this.naming.viewColumnsNameSuffix; } } } @@ -545,9 +550,9 @@ export class Generator { const mapping = this.getFieldMappings().find( (it) => it.generatedField === false && - doesMatchNameOrPattern(it.columnName, col.name) && - doesMatchNameOrPattern(it.tableName, tableName) && - doesMatchNameOrPattern(it.columnType, col.type) + doesMatchNameOrPatternNamespaced(it.columnName, col.name) && + doesMatchNameOrPatternNamespaced(it.tableName, tableName) && + doesMatchNameOrPatternNamespaced(it.columnType, col.type) ); return !!mapping; } @@ -557,9 +562,9 @@ export class Generator { (it) => it.generatedField && it.generatedField.isOptional != null && - doesMatchNameOrPattern(it.columnName, col.name) && - doesMatchNameOrPattern(it.tableName, tableName) && - doesMatchNameOrPattern(it.columnType, col.type) + doesMatchNameOrPatternNamespaced(it.columnName, col.name) && + doesMatchNameOrPatternNamespaced(it.tableName, tableName) && + doesMatchNameOrPatternNamespaced(it.columnType, col.type) ); if (mapping?.generatedField) { return mapping.generatedField.isOptional === true; @@ -573,9 +578,9 @@ export class Generator { (it) => it.generatedField && it.generatedField.hasDefault != null && - doesMatchNameOrPattern(it.columnName, col.name) && - doesMatchNameOrPattern(it.tableName, tableName) && - doesMatchNameOrPattern(it.columnType, col.type) + doesMatchNameOrPatternNamespaced(it.columnName, col.name) && + doesMatchNameOrPatternNamespaced(it.tableName, tableName) && + doesMatchNameOrPatternNamespaced(it.columnType, col.type) ); if (mapping?.generatedField) { return mapping.generatedField.hasDefault === true; @@ -589,9 +594,9 @@ export class Generator { (it) => it.generatedField && it.generatedField.isComputed != null && - doesMatchNameOrPattern(it.columnName, col.name) && - doesMatchNameOrPattern(it.tableName, tableName) && - doesMatchNameOrPattern(it.columnType, col.type) + doesMatchNameOrPatternNamespaced(it.columnName, col.name) && + doesMatchNameOrPatternNamespaced(it.tableName, tableName) && + doesMatchNameOrPatternNamespaced(it.columnType, col.type) ); if (mapping?.generatedField) { return mapping.generatedField.isComputed === true; @@ -604,9 +609,9 @@ export class Generator { (it) => it.generatedField && it.generatedField?.name && - doesMatchNameOrPattern(it.columnName, col.name) && - doesMatchNameOrPattern(it.tableName, tableName) && - doesMatchNameOrPattern(it.columnType, col.type) + doesMatchNameOrPatternNamespaced(it.columnName, col.name) && + doesMatchNameOrPatternNamespaced(it.tableName, tableName) && + doesMatchNameOrPatternNamespaced(it.columnType, col.type) ); return ( (mapping?.generatedField as GeneratedField)?.name ?? camelCase(col.name) @@ -618,9 +623,9 @@ export class Generator { (it) => it.generatedField && it.generatedField?.type && - doesMatchNameOrPattern(it.columnName, col.name) && - doesMatchNameOrPattern(it.tableName, tableName) && - doesMatchNameOrPattern(it.columnType, col.type) + doesMatchNameOrPatternNamespaced(it.columnName, col.name) && + doesMatchNameOrPatternNamespaced(it.tableName, tableName) && + doesMatchNameOrPatternNamespaced(it.columnType, col.type) ); if (!mapping) { throw new Error( @@ -652,7 +657,7 @@ export class Generator { } protected getOutputFileName(table: Table, tableKind: TableKind) { - return this.getClassNameFromTableName(table.name, tableKind) + ".ts"; + return this.getTableMapperClassName(table.name, tableKind) + ".ts"; } protected findPrimaryKey(table: Table) { @@ -666,11 +671,167 @@ export class Generator { (it) => it.type === "PRIMARY KEY" ); if (pkConstraint && pkConstraint.columns.length === 1) { - return table.columns.find((it) => it.name === pkConstraint.columns[0]); + return table.columns.find((it) => it.name === pkConstraint.columns[0]) ?? null; } } return null; } + + protected wrapType(typeExpr: string, wrapper?: string | null) { + if (!wrapper) return typeExpr; + return `${wrapper}<${typeExpr}>`; + } + + protected getTypeWrapper(typeName: string) { + return this.opts.typeWrappers + ?.find(it => { + return doesMatchNameOrPattern(it.typeName, typeName) + }) + ?.wrapper + } + + protected extractTableName(configTableName: string) { + return last(configTableName.split(".")) as string; + } + + protected getSelectedRowTypeName(tableName: string) { + return this.naming.selectedRowTypeNamePrefix + + this.getPascalCasedTableName(tableName) + + this.naming.selectedRowTypeNameSuffix; + } + + protected getInsertableRowTypeName(tableName: string) { + return this.naming.insertableRowTypeNamePrefix + + this.getPascalCasedTableName(tableName) + + this.naming.insertableRowTypeNameSuffix; + } + + protected getUpdatableRowTypeName(tableName: string) { + return this.naming.updatableRowTypeNamePrefix + + this.getPascalCasedTableName(tableName) + + this.naming.updatableRowTypeNameSuffix; + } + + protected getSelectedValuesTypeName(tableName: string) { + return this.naming.selectedValuesTypeNamePrefix + + this.getPascalCasedTableName(tableName) + + this.naming.selectedValuesTypeNameSuffix; + } + + protected getInsertableValuesTypeName(tableName: string) { + return this.naming.insertableValuesTypeNamePrefix + + this.getPascalCasedTableName(tableName) + + this.naming.insertableValuesTypeNameSuffix; + } + + protected getUpdatableValuesTypeName(tableName: string) { + return this.naming.updatableValuesTypeNamePrefix + + this.getPascalCasedTableName(tableName) + + this.naming.updatableValuesTypeNameSuffix; + } + + protected getColMappingObjName(tableName: string) { + return this.getPascalCasedTableName(tableName) + this.naming.columnTypeMappingInterfaceNameSuffix; + } + + protected getWrappedTypeInput(name: string, baseExpr: string, imports: ImportTmplInput[]) { + const selectedWrapper = this.getTypeWrapper(name) + if (selectedWrapper?.importPath) + imports.push({ + importPath: selectedWrapper.importPath, + imported: [selectedWrapper.name], + isDefault: !!selectedWrapper.isDefault + }); + return { + name, + expr: this.wrapType( + baseExpr, + selectedWrapper?.name + ) + }; + } + + protected getRowTypeInputs( + tableName: string, + tableKind: TableKind, + mapperClassName: string, + imports: ImportTmplInput[] + ) { + const rowTypes = this.opts.export?.rowTypes + ? ({} as any) + : false; + if (rowTypes !== false) { + rowTypes.selected = this.getWrappedTypeInput( + this.getSelectedRowTypeName(tableName), + `SelectedRow<${mapperClassName}>`, + imports + ); + if (tableKind !== "View") { + rowTypes.insertable = this.getWrappedTypeInput( + this.getInsertableRowTypeName(tableName), + `InsertableRow<${mapperClassName}>`, + imports + ); + rowTypes.updatable = this.getWrappedTypeInput( + this.getUpdatableRowTypeName(tableName), + `UpdatableRow<${mapperClassName}>`, + imports + ); + } + } + return rowTypes + } + + protected getValuesTypeInputs( + tableName: string, + tableKind: TableKind, + mapperClassName: string, + imports: ImportTmplInput[] + ) { + const valuesTypes = this.opts.export?.valuesTypes ? ({} as any) : false; + if (valuesTypes !== false) { + valuesTypes.selected = this.getWrappedTypeInput( + this.getSelectedValuesTypeName(tableName), + `SelectedValues<${mapperClassName}>`, + imports + ); + if (tableKind !== "View") { + valuesTypes.insertable = this.getWrappedTypeInput( + this.getInsertableValuesTypeName(tableName), + `InsertableValues<${mapperClassName}>`, + imports + ) + valuesTypes.updatable = this.getWrappedTypeInput( + this.getUpdatableValuesTypeName(tableName), + `UpdatableValues<${mapperClassName}>`, + imports + ); + } + } + return valuesTypes; + } + + protected getColSetName(tableName: string, tableKind: TableKind) { + return this.opts.export?.extractedColumns + ? this.getColumnsObjectName(tableName, tableKind) + : null; + } + + protected getTableMapperInstName(tableName: string, tableKind: TableKind) { + return this.opts.export?.tableInstances || this.opts.export?.extractedColumns + ? this.getTableMapperInstanceName(tableName, tableKind) + : null; + } + + protected getColMappingInput(tableName: string, didGenerateRepo: boolean) { + const exportColMapping = (this.opts.export?.columnTypeMappingInterface || didGenerateRepo) + ? ({} as any) + : false; + if (exportColMapping !== false) { + exportColMapping.name = this.getColMappingObjName(tableName) + } + return exportColMapping + } } diff --git a/src/matcher.ts b/src/matcher.ts index 574e21d..5cbe910 100644 --- a/src/matcher.ts +++ b/src/matcher.ts @@ -1,6 +1,17 @@ export const doesMatchNameOrPattern = ( matcher: undefined | null | string | RegExp, target: string +) => { + if (matcher == null) return true; + if (typeof matcher === "string") { + return matcher === target; + } + return target.match(matcher); +}; + +export const doesMatchNameOrPatternNamespaced = ( + matcher: undefined | null | string | RegExp, + target: string ) => { if (matcher == null) return true; if (typeof matcher === "string") { diff --git a/src/template.ts.hbs b/src/template.ts.hbs index 7bd9db7..c2cd068 100644 --- a/src/template.ts.hbs +++ b/src/template.ts.hbs @@ -9,24 +9,24 @@ import { {{table.kind}} } from "ts-sql-query/{{table.kind}}"; import type { DBConnection } from "{{dbConnectionSource}}"; {{#if importExtraTypes}} import { - {{#if exportRowTypes.insertable}} + {{#if rowTypes.insertable}} InsertableRow, {{/if}} - {{#if exportRowTypes.updatable}} + {{#if rowTypes.updatable}} UpdatableRow, {{/if}} - {{#if exportRowTypes.selected}} + {{#if rowTypes.selected}} SelectedRow, {{else if repo}} SelectedRow, {{/if}} - {{#if exportValuesTypes.insertable}} + {{#if valuesTypes.insertable}} InsertableValues, {{/if}} - {{#if exportValuesTypes.updatable}} + {{#if valuesTypes.updatable}} UpdatableValues, {{/if}} - {{#if exportValuesTypes.selected}} + {{#if valuesTypes.selected}} SelectedValues, {{/if}} } from "ts-sql-query/extras/types"; @@ -76,9 +76,9 @@ import { super('{{table.name}}'); } } -{{#if exportColMapping}} +{{#if colMapping}} -export type {{exportColMapping.name}} = { +export type {{colMapping.name}} = { {{#each fields}} {{#if fieldType.kind}} {{name}}: ['{{fieldType.kind}}', {{fieldType.tsType.name}}] @@ -93,24 +93,12 @@ export type {{exportColMapping.name}} = { export const {{instName}} = new {{className}}(); {{/if}} -{{#if exportRowTypes.insertable}} -export type {{exportRowTypes.insertable}} = InsertableRow<{{className}}>; -{{/if}} -{{#if exportRowTypes.updatable}} -export type {{exportRowTypes.updatable}} = UpdatableRow<{{className}}>; -{{/if}} -{{#if exportRowTypes.selected}} -export type {{exportRowTypes.selected}} = SelectedRow<{{className}}>; -{{/if}} -{{#if exportValuesTypes.insertable}} -export type {{exportValuesTypes.insertable}} = InsertableValues<{{className}}>; -{{/if}} -{{#if exportValuesTypes.updatable}} -export type {{exportValuesTypes.updatable}} = UpdatableValues<{{className}}>; -{{/if}} -{{#if exportValuesTypes.selected}} -export type {{exportValuesTypes.selected}} = SelectedValues<{{className}}>; -{{/if}} +{{#each rowTypes}} +export type {{name}} = {{{expr}}}; +{{/each}} +{{#each valuesTypes}} +export type {{name}} = {{{expr}}}; +{{/each}} {{#if colSetName}} export const {{colSetName}} = extractColumnsFrom({{instName}}); {{/if}} @@ -133,7 +121,7 @@ export class {{repo.className}} { {{/if}} {{#if repo.methods.selectWhere}} - selectWhere(cond: DynamicCondition<{{exportColMapping.name}}>, conn = this.getConnection()) { + selectWhere(cond: DynamicCondition<{{colMapping.name}}>, conn = this.getConnection()) { return this.select(conn) .where(conn.dynamicConditionFor(this.tableCols).withValues(cond)) .select(this.tableCols) diff --git a/test/__snapshots__/Generator/allows-customizing-naming.expected.txt b/test/__snapshots__/Generator/allows-customizing-naming.expected.txt index 5f94cd7..369527c 100644 --- a/test/__snapshots__/Generator/allows-customizing-naming.expected.txt +++ b/test/__snapshots__/Generator/allows-customizing-naming.expected.txt @@ -33,12 +33,12 @@ class TCPAuthorsTCS extends Table { export const TIPAuthorsTIS = new TCPAuthorsTCS(); +export type SRPAuthorsSRS = SelectedRow; export type IRPAuthorsIRS = InsertableRow; export type URPAuthorsURS = UpdatableRow; -export type SRPAuthorsSRS = SelectedRow; +export type SVPAuthorsSVS = SelectedValues; export type IVPAuthorsIVS = InsertableValues; export type UVPAuthorsUVS = UpdatableValues; -export type SVPAuthorsSVS = SelectedValues; export const TCsPAuthorsTCsS = extractColumnsFrom(TIPAuthorsTIS); @@ -82,12 +82,12 @@ class TCPBooksTCS extends Table { export const TIPBooksTIS = new TCPBooksTCS(); +export type SRPBooksSRS = SelectedRow; export type IRPBooksIRS = InsertableRow; export type URPBooksURS = UpdatableRow; -export type SRPBooksSRS = SelectedRow; +export type SVPBooksSVS = SelectedValues; export type IVPBooksIVS = InsertableValues; export type UVPBooksUVS = UpdatableValues; -export type SVPBooksSVS = SelectedValues; export const TCsPBooksTCsS = extractColumnsFrom(TIPBooksTIS); @@ -136,12 +136,12 @@ class TCPChaptersTCS extends Table { export const TIPChaptersTIS = new TCPChaptersTCS(); +export type SRPChaptersSRS = SelectedRow; export type IRPChaptersIRS = InsertableRow; export type URPChaptersURS = UpdatableRow; -export type SRPChaptersSRS = SelectedRow; +export type SVPChaptersSVS = SelectedValues; export type IVPChaptersIVS = InsertableValues; export type UVPChaptersUVS = UpdatableValues; -export type SVPChaptersSVS = SelectedValues; export const TCsPChaptersTCsS = extractColumnsFrom(TIPChaptersTIS); diff --git a/test/__snapshots__/Generator/allows-exporting-crud-repositories.expected.txt b/test/__snapshots__/Generator/allows-exporting-crud-repositories.expected.txt index e4e9106..d9f32e4 100644 --- a/test/__snapshots__/Generator/allows-exporting-crud-repositories.expected.txt +++ b/test/__snapshots__/Generator/allows-exporting-crud-repositories.expected.txt @@ -204,7 +204,7 @@ export type BooksCols = { authorId: 'int' releasedAt: 'localDate' timeToRead: 'int' - genre: 'genre' + genre: ['enum', Genre] weightGrams: 'double' } @@ -350,7 +350,7 @@ export type ChaptersCols = { id: 'int' name: 'string' bookId: 'uuid' - metadata: 'jsonb' + metadata: ['custom', ChapterMetadata] title: 'string' description: 'string' } diff --git a/test/__snapshots__/Generator/allows-exporting-row-types.expected.txt b/test/__snapshots__/Generator/allows-exporting-row-types.expected.txt index 0932421..2a439f0 100644 --- a/test/__snapshots__/Generator/allows-exporting-row-types.expected.txt +++ b/test/__snapshots__/Generator/allows-exporting-row-types.expected.txt @@ -26,6 +26,6 @@ export class AuthorsTable extends Table { } +export type AuthorsSRow = SelectedRow; export type AuthorsIRow = InsertableRow; export type AuthorsURow = UpdatableRow; -export type AuthorsSRow = SelectedRow; diff --git a/test/__snapshots__/Generator/allows-exporting-value-types.expected.txt b/test/__snapshots__/Generator/allows-exporting-value-types.expected.txt index 857e859..d8a5905 100644 --- a/test/__snapshots__/Generator/allows-exporting-value-types.expected.txt +++ b/test/__snapshots__/Generator/allows-exporting-value-types.expected.txt @@ -26,6 +26,6 @@ export class AuthorsTable extends Table { } +export type Authors = SelectedValues; export type InsertableAuthors = InsertableValues; export type UpdatableAuthors = UpdatableValues; -export type Authors = SelectedValues; diff --git a/test/__snapshots__/Generator/allows-wrapping-exported-types.expected.txt b/test/__snapshots__/Generator/allows-wrapping-exported-types.expected.txt new file mode 100644 index 0000000..7c6e3de --- /dev/null +++ b/test/__snapshots__/Generator/allows-wrapping-exported-types.expected.txt @@ -0,0 +1,43 @@ +// AuthorsTable.ts : +/** + * DO NOT EDIT: + * + * This file has been auto-generated from database schema using ts-sql-codegen. + * Any changes will be overwritten. + */ +import { Table } from "ts-sql-query/Table"; +import type { DBConnection } from "../helpers/connection-source"; +import { + InsertableRow, + UpdatableRow, + SelectedRow, + InsertableValues, + UpdatableValues, + SelectedValues, +} from "ts-sql-query/extras/types"; +import { + EnforceAuthorInsertProps, +} from "../type-helpers"; +import { + EnforceUpdateProps, +} from "../type-helpers"; + +export class AuthorsTable extends Table { + id = this.primaryKey('id', 'int'); + name = this.optionalColumn('name', 'string'); + dob = this.optionalColumn('dob', 'localDate'); + createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); + updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); + + constructor() { + super('authors'); + } +} + + +export type AuthorsSRow = SelectedRow; +export type AuthorsIRow = EnforceAuthorInsertProps>; +export type AuthorsURow = EnforceUpdateProps>; +export type Authors = SelectedValues; +export type InsertableAuthors = InsertableValues; +export type UpdatableAuthors = UpdatableValues; diff --git a/test/test.ts b/test/test.ts index 68a2fc6..b3663e0 100644 --- a/test/test.ts +++ b/test/test.ts @@ -555,6 +555,37 @@ describe("Generator", function () { await generator.generate(); await snap(await readAllGenerated()); }) + + it("allows wrapping exported types", async () => { + const generator = new Generator({ + schemaPath, + connectionSourcePath, + outputDirPath, + tables: { + include: ["authors"], + }, + export: { + rowTypes: true, + valuesTypes: true, + }, + typeWrappers: [{ + typeName: /URow$/, + wrapper: { + name: 'EnforceUpdateProps', + importPath: '../type-helpers' + } + }, { + typeName: 'AuthorsIRow', + wrapper: { + name: 'EnforceAuthorInsertProps', + importPath: '../type-helpers' + } + }] + }); + await generator.generate(); + await snap(await readAllGenerated()); + }) + }); const readAllGenerated = async () => {