diff --git a/package.json b/package.json index 9610ef1..1f2b197 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "typecheck": "tsc --noEmit --project .", "lint": "eslint '**/*.{j,t}s{,on{,c,5}}'", "lint:fix": "yarn -s lint --fix", - "test": "jest", + "test": "jest --runInBand", "fullvalidate": "yarn install && yarn typecheck && yarn lint && yarn test", "prepack": "yarn build" }, diff --git a/src/generator/Generator.ts b/src/generator/Generator.ts index 870aefb..859579e 100644 --- a/src/generator/Generator.ts +++ b/src/generator/Generator.ts @@ -25,12 +25,16 @@ import { CaseFunction, Transformations } from './types' import UtilityTypesBuilder from './builders/UtilityTypesBuilder' import ZodSchemaBuilder from './builders/ZodSchemaBuilder' import SingleNamedImportBuilder from './builders/SingleNamedImportBuilder' +import InfoBuilder from './builders/InfoBuilder' +import UpdateSchemaBuilder from './builders/UpdateSchemaBuilder' export interface GeneratorOptions { schema: SchemaInfo genSelectSchemas?: boolean genInsertSchemas?: boolean + genUpdateSchemas?: boolean genTableMetadata?: boolean + genInfos?: boolean disableEslint?: boolean /** @deprecated */ genEnums?: boolean @@ -57,7 +61,9 @@ export default class Generator { public readonly generate: { selectSchemas: boolean insertSchemas: boolean + updateSchemas: boolean tableMetadata: boolean + infos: boolean disableEslint: boolean /** @deprecated */ enums: boolean @@ -77,7 +83,9 @@ export default class Generator { schema, genSelectSchemas: selectSchemas = true, genInsertSchemas: insertSchemas = true, + genUpdateSchemas: updateSchemas = true, genTableMetadata: tableMetadata = true, + genInfos: infos = true, disableEslint = true, genEnums = false, genInsertTypes = false, @@ -88,6 +96,17 @@ export default class Generator { transformEnumMembers = 'pascal', transformTypeNames = 'pascal', }: GeneratorOptions) { + if ( + infos && + (!selectSchemas || !insertSchemas || !updateSchemas || !tableMetadata) + ) { + const message = + 'Cannot generate Info without insert, select, and update schemas \ + and table metadata' + console.error(message) + throw new Error(message) + } + this.printer = createPrinter({ newLine: NewLineKind.LineFeed, removeComments: false, @@ -99,7 +118,9 @@ export default class Generator { this.generate = Object.freeze({ selectSchemas, insertSchemas, + updateSchemas, tableMetadata, + infos, disableEslint, enums: genEnums, insertTypes: genInsertTypes, @@ -231,6 +252,28 @@ export default class Generator { builders.push(builder) } + if (this.generate.updateSchemas) { + const builder = new UpdateSchemaBuilder( + tableInfo, + this.types, + this.transform + ) + builders.push(builder) + } + + if (this.generate.infos) { + const builder = new InfoBuilder( + tableInfo, + this.types, + this.transform, + new InsertSchemaBuilder(tableInfo, this.types, this.transform), + new SelectSchemaBuilder(tableInfo, this.types, this.transform), + new UpdateSchemaBuilder(tableInfo, this.types, this.transform), + new TableMetadataBuilder(tableInfo, this.types, this.transform) + ) + builders.push(builder) + } + if (this.generate.tables) { const builder = new TableBuilder(tableInfo, this.types, this.transform) this.types.add(builder.name, builder.typename().text, 'table') diff --git a/src/generator/__tests__/Generator.test.ts b/src/generator/__tests__/Generator.test.ts index 601b397..1df15a7 100644 --- a/src/generator/__tests__/Generator.test.ts +++ b/src/generator/__tests__/Generator.test.ts @@ -43,6 +43,8 @@ describe(Generator, () => { genTableMetadata: false, genSelectSchemas: false, genInsertSchemas: false, + genUpdateSchemas: false, + genInfos: false, genEnums: false, genInsertTypes: false, genTables: false, @@ -61,6 +63,8 @@ describe(Generator, () => { genTableMetadata: false, genSelectSchemas: false, genInsertSchemas: false, + genUpdateSchemas: false, + genInfos: false, genEnums: false, genInsertTypes: false, genTables: true, @@ -80,6 +84,8 @@ describe(Generator, () => { genTableMetadata: false, genSelectSchemas: false, genInsertSchemas: false, + genUpdateSchemas: false, + genInfos: false, genEnums: true, genInsertTypes: false, genTables: false, @@ -98,6 +104,8 @@ describe(Generator, () => { genTableMetadata: false, genSelectSchemas: false, genInsertSchemas: false, + genUpdateSchemas: false, + genInfos: false, genEnums: false, genInsertTypes: true, genTables: false, @@ -117,6 +125,8 @@ describe(Generator, () => { genTableMetadata: false, genSelectSchemas: false, genInsertSchemas: false, + genUpdateSchemas: false, + genInfos: false, genEnums: false, genInsertTypes: false, genTables: false, @@ -136,6 +146,8 @@ describe(Generator, () => { genTableMetadata: false, genSelectSchemas: false, genInsertSchemas: false, + genUpdateSchemas: false, + genInfos: false, genEnums: false, genInsertTypes: false, genTables: false, diff --git a/src/generator/__tests__/__snapshots__/Generator.test.ts.snap b/src/generator/__tests__/__snapshots__/Generator.test.ts.snap index 9fda1b3..712d722 100644 --- a/src/generator/__tests__/__snapshots__/Generator.test.ts.snap +++ b/src/generator/__tests__/__snapshots__/Generator.test.ts.snap @@ -23,6 +23,16 @@ export const DEFAULT = Symbol("DEFAULT"); export const SomehowDuplicatedTypeName$Metadata = {} as const; export const SomehowDuplicatedTypeName$SelectSchema = z.object({}); export const SomehowDuplicatedTypeName$InsertSchema = z.object({}); +export const SomehowDuplicatedTypeName$UpdateSchema = SomehowDuplicatedTypeName$InsertSchema.partial(); +export const SomehowDuplicatedTypeNameInfo = { + metadata: SomehowDuplicatedTypeName$Metadata, + name: "somehow_duplicated_type_name", + schemas: { + insert: SomehowDuplicatedTypeName$InsertSchema, + select: SomehowDuplicatedTypeName$SelectSchema, + update: SomehowDuplicatedTypeName$UpdateSchema + } +}; export interface SomehowDuplicatedTypeName { } " @@ -51,6 +61,16 @@ export const DEFAULT = Symbol("DEFAULT"); export const TableWithNoColumns$Metadata = {} as const; export const TableWithNoColumns$SelectSchema = z.object({}); export const TableWithNoColumns$InsertSchema = z.object({}); +export const TableWithNoColumns$UpdateSchema = TableWithNoColumns$InsertSchema.partial(); +export const TableWithNoColumnsInfo = { + metadata: TableWithNoColumns$Metadata, + name: "table_with_no_columns", + schemas: { + insert: TableWithNoColumns$InsertSchema, + select: TableWithNoColumns$SelectSchema, + update: TableWithNoColumns$UpdateSchema + } +}; export const TableWithNumericId$Metadata = { id: { nativeName: "id", @@ -63,6 +83,16 @@ export const TableWithNumericId$SelectSchema = z.object({ export const TableWithNumericId$InsertSchema = z.object({ id: z.number().optional().or(z.literal(DEFAULT)) }); +export const TableWithNumericId$UpdateSchema = TableWithNumericId$InsertSchema.partial(); +export const TableWithNumericIdInfo = { + metadata: TableWithNumericId$Metadata, + name: "table_with_numeric_id", + schemas: { + insert: TableWithNumericId$InsertSchema, + select: TableWithNumericId$SelectSchema, + update: TableWithNumericId$UpdateSchema + } +}; export const TableWithCustomTypes$Metadata = { enum_type: { nativeName: "enum_type", @@ -93,6 +123,16 @@ export const TableWithCustomTypes$InsertSchema = z.object({ table_type: z.unknown(), table_array_type: z.array(z.unknown()) }); +export const TableWithCustomTypes$UpdateSchema = TableWithCustomTypes$InsertSchema.partial(); +export const TableWithCustomTypesInfo = { + metadata: TableWithCustomTypes$Metadata, + name: "table_with_custom_types", + schemas: { + insert: TableWithCustomTypes$InsertSchema, + select: TableWithCustomTypes$SelectSchema, + update: TableWithCustomTypes$UpdateSchema + } +}; export const TableWithUuidId$Metadata = { id: { nativeName: "id", @@ -105,6 +145,16 @@ export const TableWithUuidId$SelectSchema = z.object({ export const TableWithUuidId$InsertSchema = z.object({ id: z.string().optional().or(z.literal(DEFAULT)) }); +export const TableWithUuidId$UpdateSchema = TableWithUuidId$InsertSchema.partial(); +export const TableWithUuidIdInfo = { + metadata: TableWithUuidId$Metadata, + name: "table_with_uuid_id", + schemas: { + insert: TableWithUuidId$InsertSchema, + select: TableWithUuidId$SelectSchema, + update: TableWithUuidId$UpdateSchema + } +}; export const TableWithNullableFields$Metadata = { nullable: { nativeName: "nullable", @@ -135,6 +185,16 @@ export const TableWithNullableFields$InsertSchema = z.object({ nullable_array: z.array(z.string()).nullable().optional(), nullable_array_with_default: z.array(z.string()).nullable().optional().or(z.literal(DEFAULT)) }); +export const TableWithNullableFields$UpdateSchema = TableWithNullableFields$InsertSchema.partial(); +export const TableWithNullableFieldsInfo = { + metadata: TableWithNullableFields$Metadata, + name: "table_with_nullable_fields", + schemas: { + insert: TableWithNullableFields$InsertSchema, + select: TableWithNullableFields$SelectSchema, + update: TableWithNullableFields$UpdateSchema + } +}; export const TableWithJsonJsonb$Metadata = { json: { nativeName: "json", @@ -153,6 +213,16 @@ export const TableWithJsonJsonb$InsertSchema = z.object({ json: z.unknown(), jsonb: z.unknown().nullable().optional().or(z.literal(DEFAULT)) }); +export const TableWithJsonJsonb$UpdateSchema = TableWithJsonJsonb$InsertSchema.partial(); +export const TableWithJsonJsonbInfo = { + metadata: TableWithJsonJsonb$Metadata, + name: "table_with_json_jsonb", + schemas: { + insert: TableWithJsonJsonb$InsertSchema, + select: TableWithJsonJsonb$SelectSchema, + update: TableWithJsonJsonb$UpdateSchema + } +}; " `; @@ -171,6 +241,16 @@ export const DEFAULT = Symbol("DEFAULT"); export const TableWithNoColumns$Metadata = {} as const; export const TableWithNoColumns$SelectSchema = z.object({}); export const TableWithNoColumns$InsertSchema = z.object({}); +export const TableWithNoColumns$UpdateSchema = TableWithNoColumns$InsertSchema.partial(); +export const TableWithNoColumnsInfo = { + metadata: TableWithNoColumns$Metadata, + name: "table_with_no_columns", + schemas: { + insert: TableWithNoColumns$InsertSchema, + select: TableWithNoColumns$SelectSchema, + update: TableWithNoColumns$UpdateSchema + } +}; export const TableWithNumericId$Metadata = { id: { nativeName: "id", @@ -183,6 +263,16 @@ export const TableWithNumericId$SelectSchema = z.object({ export const TableWithNumericId$InsertSchema = z.object({ id: z.number().optional().or(z.literal(DEFAULT)) }); +export const TableWithNumericId$UpdateSchema = TableWithNumericId$InsertSchema.partial(); +export const TableWithNumericIdInfo = { + metadata: TableWithNumericId$Metadata, + name: "table_with_numeric_id", + schemas: { + insert: TableWithNumericId$InsertSchema, + select: TableWithNumericId$SelectSchema, + update: TableWithNumericId$UpdateSchema + } +}; export const TableWithCustomTypes$Metadata = { enumType: { nativeName: "enum_type", @@ -213,6 +303,16 @@ export const TableWithCustomTypes$InsertSchema = z.object({ tableType: z.unknown(), tableArrayType: z.array(z.unknown()) }); +export const TableWithCustomTypes$UpdateSchema = TableWithCustomTypes$InsertSchema.partial(); +export const TableWithCustomTypesInfo = { + metadata: TableWithCustomTypes$Metadata, + name: "table_with_custom_types", + schemas: { + insert: TableWithCustomTypes$InsertSchema, + select: TableWithCustomTypes$SelectSchema, + update: TableWithCustomTypes$UpdateSchema + } +}; export const TableWithUuidId$Metadata = { id: { nativeName: "id", @@ -225,6 +325,16 @@ export const TableWithUuidId$SelectSchema = z.object({ export const TableWithUuidId$InsertSchema = z.object({ id: z.string().optional().or(z.literal(DEFAULT)) }); +export const TableWithUuidId$UpdateSchema = TableWithUuidId$InsertSchema.partial(); +export const TableWithUuidIdInfo = { + metadata: TableWithUuidId$Metadata, + name: "table_with_uuid_id", + schemas: { + insert: TableWithUuidId$InsertSchema, + select: TableWithUuidId$SelectSchema, + update: TableWithUuidId$UpdateSchema + } +}; export const TableWithNullableFields$Metadata = { nullable: { nativeName: "nullable", @@ -255,6 +365,16 @@ export const TableWithNullableFields$InsertSchema = z.object({ nullableArray: z.array(z.string()).nullable().optional(), nullableArrayWithDefault: z.array(z.string()).nullable().optional().or(z.literal(DEFAULT)) }); +export const TableWithNullableFields$UpdateSchema = TableWithNullableFields$InsertSchema.partial(); +export const TableWithNullableFieldsInfo = { + metadata: TableWithNullableFields$Metadata, + name: "table_with_nullable_fields", + schemas: { + insert: TableWithNullableFields$InsertSchema, + select: TableWithNullableFields$SelectSchema, + update: TableWithNullableFields$UpdateSchema + } +}; export const TableWithJsonJsonb$Metadata = { json: { nativeName: "json", @@ -273,6 +393,16 @@ export const TableWithJsonJsonb$InsertSchema = z.object({ json: z.unknown(), jsonb: z.unknown().nullable().optional().or(z.literal(DEFAULT)) }); +export const TableWithJsonJsonb$UpdateSchema = TableWithJsonJsonb$InsertSchema.partial(); +export const TableWithJsonJsonbInfo = { + metadata: TableWithJsonJsonb$Metadata, + name: "table_with_json_jsonb", + schemas: { + insert: TableWithJsonJsonb$InsertSchema, + select: TableWithJsonJsonb$SelectSchema, + update: TableWithJsonJsonb$UpdateSchema + } +}; " `; diff --git a/src/generator/__tests__/__snapshots__/integration.test.ts.snap b/src/generator/__tests__/__snapshots__/integration.test.ts.snap index bc4c51e..10cea12 100644 --- a/src/generator/__tests__/__snapshots__/integration.test.ts.snap +++ b/src/generator/__tests__/__snapshots__/integration.test.ts.snap @@ -36,6 +36,16 @@ export const TestGenerationDefaults$InsertSchema = z.object({ generated_column: z.undefined().or(z.literal(DEFAULT)), generated_id: z.undefined().or(z.literal(DEFAULT)) }); +export const TestGenerationDefaults$UpdateSchema = TestGenerationDefaults$InsertSchema.partial(); +export const TestGenerationDefaultsInfo = { + metadata: TestGenerationDefaults$Metadata, + name: "test_generation_defaults", + schemas: { + insert: TestGenerationDefaults$InsertSchema, + select: TestGenerationDefaults$SelectSchema, + update: TestGenerationDefaults$UpdateSchema + } +}; export interface TestGenerationDefaults { generated_column: number; generated_id: number; @@ -60,6 +70,16 @@ export const TestTableOrder$SelectSchema = z.object({ export const TestTableOrder$InsertSchema = z.object({ subsequent_table_type: z.lazy(() => TestTableStandard$InsertSchema.nullable().optional()) }); +export const TestTableOrder$UpdateSchema = TestTableOrder$InsertSchema.partial(); +export const TestTableOrderInfo = { + metadata: TestTableOrder$Metadata, + name: "test_table_order", + schemas: { + insert: TestTableOrder$InsertSchema, + select: TestTableOrder$SelectSchema, + update: TestTableOrder$UpdateSchema + } +}; export interface TestTableOrder { subsequent_table_type: TestTableStandard | null; } @@ -129,6 +149,16 @@ export const TestTableStandard$InsertSchema = z.object({ id: z.number().optional().or(z.literal(DEFAULT)), jsonb_test: z.unknown().nullable().optional() }); +export const TestTableStandard$UpdateSchema = TestTableStandard$InsertSchema.partial(); +export const TestTableStandardInfo = { + metadata: TestTableStandard$Metadata, + name: "test_table_standard", + schemas: { + insert: TestTableStandard$InsertSchema, + select: TestTableStandard$SelectSchema, + update: TestTableStandard$UpdateSchema + } +}; export interface TestTableStandard { arr_test: string[] | null; caseTest_upper: string | null; @@ -186,6 +216,16 @@ export const TestTableTypes$InsertSchema = z.object({ table_arr_test: z.lazy(() => z.array(TestTableStandard$InsertSchema).optional().or(z.literal(DEFAULT))), table_test: z.lazy(() => TestTableStandard$InsertSchema.nullable().optional()) }); +export const TestTableTypes$UpdateSchema = TestTableTypes$InsertSchema.partial(); +export const TestTableTypesInfo = { + metadata: TestTableTypes$Metadata, + name: "test_table_types", + schemas: { + insert: TestTableTypes$InsertSchema, + select: TestTableTypes$SelectSchema, + update: TestTableTypes$UpdateSchema + } +}; export interface TestTableTypes { id: string; table_arr_test: TestTableStandard[]; diff --git a/src/generator/__tests__/integration.test.ts b/src/generator/__tests__/integration.test.ts index 19394e1..02fcfb6 100644 --- a/src/generator/__tests__/integration.test.ts +++ b/src/generator/__tests__/integration.test.ts @@ -76,6 +76,8 @@ beforeEach(() => { schema: new SchemaInfo(pool, SCHEMA), genInsertSchemas: true, genSelectSchemas: true, + genUpdateSchemas: true, + genInfos: true, genTableMetadata: true, genEnums: true, genInsertTypes: true, diff --git a/src/generator/builders/InfoBuilder.ts b/src/generator/builders/InfoBuilder.ts new file mode 100644 index 0000000..107becd --- /dev/null +++ b/src/generator/builders/InfoBuilder.ts @@ -0,0 +1,82 @@ +import { + factory, + VariableStatement, + NodeFlags, + ObjectLiteralExpression, +} from 'typescript' +import { z } from 'zod' +import { TableInfoWithColumns, TypeRegistry } from '../database' +import { Transformations } from '../types' +import InsertSchemaBuilder from './InsertSchemaBuilder' +import { ExportKeyword } from './NodeBuilder' +import SelectSchemaBuilder from './SelectSchemaBuilder' +import TableMetadataBuilder from './TableMetadataBuilder' + +import TypeBuilder from './TypeBuilder' +import UpdateSchemaBuilder from './UpdateSchemaBuilder' + +export default class InfoBuilder extends TypeBuilder { + constructor( + options: z.infer, + protected types: TypeRegistry, + transform: Transformations, + private readonly insertSchemaBuilder: InsertSchemaBuilder, + private readonly selectSchemaBuilder: SelectSchemaBuilder, + private readonly updateSchemaBuilder: UpdateSchemaBuilder, + private readonly tableMetadataBuilder: TableMetadataBuilder + ) { + super(options.name, types, transform) + } + + public buildNode(): VariableStatement { + const declaration = factory.createVariableDeclaration( + `${this.typename().text}Info`, + undefined, + undefined, + this.buildInfoDefinition() + ) + + const declarationList = factory.createVariableDeclarationList( + [declaration], + NodeFlags.Const + ) + + return factory.createVariableStatement([ExportKeyword], declarationList) + } + + protected buildInfoDefinition(): ObjectLiteralExpression { + return factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + this.createIdentifier('metadata'), + this.tableMetadataBuilder.typename() + ), + factory.createPropertyAssignment( + factory.createIdentifier('name'), + factory.createStringLiteral(this.name) + ), + factory.createPropertyAssignment( + factory.createIdentifier('schemas'), + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + this.createIdentifier('insert'), + this.insertSchemaBuilder.typename() + ), + factory.createPropertyAssignment( + this.createIdentifier('select'), + this.selectSchemaBuilder.typename() + ), + factory.createPropertyAssignment( + this.createIdentifier('update'), + this.updateSchemaBuilder.typename() + ), + ], + true + ) + ), + ], + true + ) + } +} diff --git a/src/generator/builders/InsertSchemaBuilder.ts b/src/generator/builders/InsertSchemaBuilder.ts index 53bbc35..d9a18ba 100644 --- a/src/generator/builders/InsertSchemaBuilder.ts +++ b/src/generator/builders/InsertSchemaBuilder.ts @@ -3,7 +3,7 @@ import { factory, Identifier, Expression, CallExpression } from 'typescript' import SelectSchemaBuilder from './SelectSchemaBuilder' export default class InsertSchemaBuilder extends SelectSchemaBuilder { - protected override readonly suffix = '$InsertSchema' + protected override readonly suffix: string = '$InsertSchema' protected override buildSinglePropertyEntry( columnInfo: typeof this.columns[0] diff --git a/src/generator/builders/UpdateSchemaBuilder.ts b/src/generator/builders/UpdateSchemaBuilder.ts new file mode 100644 index 0000000..7e465b5 --- /dev/null +++ b/src/generator/builders/UpdateSchemaBuilder.ts @@ -0,0 +1,49 @@ +import { factory, VariableStatement, NodeFlags } from 'typescript' +import { z } from 'zod' + +import InsertSchemaBuilder from './InsertSchemaBuilder' +import SelectSchemaBuilder from './SelectSchemaBuilder' +import { ExportKeyword } from './NodeBuilder' +import { TableInfoWithColumns, TypeRegistry } from '../database' +import { Transformations } from '../types' + +export default class UpdateSchemaBuilder extends SelectSchemaBuilder { + protected override readonly suffix: string = '$UpdateSchema' + insertSchemabuilder: InsertSchemaBuilder + + constructor( + options: z.infer, + protected types: TypeRegistry, + transform: Transformations + ) { + super(options, types, transform) + this.insertSchemabuilder = new InsertSchemaBuilder( + options, + types, + transform + ) + } + + public buildNode(): VariableStatement { + const declaration = factory.createVariableDeclaration( + this.typename(), + undefined, + undefined, + factory.createCallExpression( + factory.createPropertyAccessExpression( + this.insertSchemabuilder.typename(), + factory.createIdentifier('partial') + ), + undefined, + [] + ) + ) + + const declarationList = factory.createVariableDeclarationList( + [declaration], + NodeFlags.Const + ) + + return factory.createVariableStatement([ExportKeyword], declarationList) + } +}