From c4325cf59dada69c2bc5053e3d916303e7e1bad1 Mon Sep 17 00:00:00 2001 From: Leonardo Mosciatti Date: Sun, 2 Nov 2025 17:53:54 +0100 Subject: [PATCH 01/12] initial commit --- package.json | 1 + src/server/server.ts | 3 + src/server/templates/dart.ts | 932 +++++++++++++++++++++++++++++++++++ 3 files changed, 936 insertions(+) create mode 100644 src/server/templates/dart.ts diff --git a/package.json b/package.json index 58b95839..335696e5 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "gen:types:go": "PG_META_GENERATE_TYPES=go node --loader ts-node/esm src/server/server.ts", "gen:types:swift": "PG_META_GENERATE_TYPES=swift node --loader ts-node/esm src/server/server.ts", "gen:types:python": "PG_META_GENERATE_TYPES=python node --loader ts-node/esm src/server/server.ts", + "gen:types:dart": "PG_META_GENERATE_TYPES=dart node --loader ts-node/esm src/server/server.ts", "start": "node dist/server/server.js", "dev": "trap 'npm run db:clean' INT && run-s db:clean db:run && run-s dev:code", "dev:code": "nodemon --exec node --loader ts-node/esm src/server/server.ts | pino-pretty --colorize", diff --git a/src/server/server.ts b/src/server/server.ts index 68fbb54c..759ec792 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -19,6 +19,7 @@ import { apply as applyTypescriptTemplate } from './templates/typescript.js' import { apply as applyGoTemplate } from './templates/go.js' import { apply as applySwiftTemplate } from './templates/swift.js' import { apply as applyPythonTemplate } from './templates/python.js' +import { apply as applyDartTemplate } from './templates/dart.js' const logger = pino({ formatters: { @@ -146,6 +147,8 @@ async function getTypeOutput(): Promise { return applyGoTemplate(config) case 'python': return applyPythonTemplate(config) + case 'dart': + return applyDartTemplate(config) default: throw new Error(`Unsupported language for GENERATE_TYPES: ${GENERATE_TYPES}`) } diff --git a/src/server/templates/dart.ts b/src/server/templates/dart.ts new file mode 100644 index 00000000..117b4441 --- /dev/null +++ b/src/server/templates/dart.ts @@ -0,0 +1,932 @@ +import type { + PostgresColumn, + PostgresMaterializedView, + PostgresSchema, + PostgresTable, + PostgresType, + PostgresView, +} from '../../lib/index.js' +import type { GeneratorMetadata } from '../../lib/generators.js' +import { Console } from 'console' +const console = new Console(process.stderr) + +type Operation = 'Select' | 'Insert' | 'Update' + +function formatForDartClassName(name: string): string { + return name + .split(/[^a-zA-Z0-9]/) + .map((word) => `${word[0].toUpperCase()}${word.slice(1)}`) + .join('') +} + +function formatForDartPropertyName(name: string): string { + const className = formatForDartClassName(name) + return className[0].toLowerCase() + className.slice(1) +} + +interface Typeable { + generateType(): string +} + +interface Declarable { + generateDeclaration(): string +} + +interface JsonEncodable { + generateJsonEncoding(): string +} + +interface JsonDecodable { + generateJsonDecoding(inputParameter: string): string +} + +type DartType = Typeable & JsonEncodable & JsonDecodable + +type BuiltinDartTypeKeyword = 'int' | 'double' | 'bool' | 'String' | 'dynamic' + +class BuiltinDartType implements DartType { + keyword: BuiltinDartTypeKeyword + + constructor(keyword: BuiltinDartTypeKeyword) { + this.keyword = keyword + } + + generateType(): string { + return this.keyword + } + + generateJsonEncoding(): string { + return '' + } + + generateJsonDecoding(inputParameter: string): string { + return `${inputParameter} as ${this.keyword}` + } +} + +class DatetimeDartType implements DartType { + generateType(): string { + return 'DateTime' + } + + generateJsonEncoding(): string { + return '.toIso8601String()' + } + + generateJsonDecoding(inputParameter: string): string { + return `DateTime.parse(${inputParameter})` + } +} + +class DurationDartType implements DartType { + generateType(): string { + return 'Duration' + } + + generateJsonEncoding(): string { + return '.inSeconds' + } + + generateJsonDecoding(inputParameter: string): string { + return `parsePostgresInterval(${inputParameter})` + } +} + +class ListDartType implements DartType { + containedType: DartType + + constructor(containedType: DartType) { + this.containedType = containedType + } + + generateType(): string { + return `List<${this.containedType.generateType()}>` + } + + generateJsonEncoding(): string { + return `.map((v) => v${this.containedType.generateJsonEncoding()}).toList()` + } + + generateJsonDecoding(inputParameter: string): string { + return `(${inputParameter} as List).map((v) => ${this.containedType.generateJsonDecoding('v')}).toList()` + } +} + +class NullDartType implements DartType { + containedType: DartType + + constructor(containedType: DartType) { + if (containedType instanceof NullDartType) { + this.containedType = containedType.containedType + } else { + this.containedType = containedType + } + } + generateType(): string { + return `${this.containedType.generateType()}?` + } + + generateJsonEncoding(): string { + return `${this.containedType.generateJsonEncoding()}` + } + + generateJsonDecoding(inputParameter: string): string { + return `${inputParameter} == null ? null : ${this.containedType.generateJsonDecoding(inputParameter)}` + } +} + +class MapDartType implements DartType { + keyType: DartType + valueType: DartType + + constructor(keyType: DartType, valueType: DartType) { + this.keyType = keyType + this.valueType = valueType + } + + generateType(): string { + return `Map<${this.keyType.generateType()}, ${this.valueType.generateType()}>` + } + + generateJsonEncoding(): string { + return '' + } + + generateJsonDecoding(inputParameter: string): string { + return `${inputParameter} as Map` + } +} + +class EnumDartConstruct implements DartType, Declarable { + originalName: string + values: string[] + + constructor(name: string, values: string[], comment: string | null) { + this.originalName = name + this.values = values + } + + generateType(): string { + return formatForDartClassName(this.originalName) + } + + generateJsonEncoding(): string { + return '.toJson()' + } + + generateJsonDecoding(inputParameter: string): string { + return `${formatForDartClassName(this.originalName)}.fromJson(${inputParameter})` + } + + generateDeclaration(): string { + return `enum ${formatForDartClassName(this.originalName)} { +${this.values.map((v) => ` ${formatForDartPropertyName(v)}`).join(',\n')}; + + String toJson() { + switch(this) { +${this.values + .map( + (v) => + ` case ${formatForDartClassName(this.originalName)}.${formatForDartPropertyName(v)}: + return '${v}';` + ) + .join('\n')} + } + } + + factory ${formatForDartClassName(this.originalName)}.fromJson(String name) { + switch(name) { +${this.values + .map( + (v) => + ` case '${v}': + return ${formatForDartClassName(this.originalName)}.${formatForDartPropertyName(v)};` + ) + .join('\n')} + } + throw ArgumentError.value(name, "name", "No enum value with that name"); + } +} +` + } +} + +class ClassDartConstructForCompositeType implements DartType, Declarable { + postgresType: PostgresType + ptdMap: PostgresToDartMap + name: string + + constructor(postgresType: PostgresType, ptdMap: PostgresToDartMap) { + this.postgresType = postgresType + this.ptdMap = ptdMap + this.name = `${formatForDartClassName(this.postgresType.name)}` + } + + generateType(): string { + return this.name + } + + generateDeclaration(): string { + return `class ${this.name} implements JsonSerializable {${this.postgresType.attributes.map( + (attr) => ` + final ${new NullDartType(this.ptdMap[attr.type_id][1]).generateType()} ${formatForDartPropertyName(attr.name)};` + ).join('')} + + const ${this.name}({${this.postgresType.attributes.map( + attr => { + return ` + this.${formatForDartPropertyName(attr.name)}` + }) + .join(',')} + }); + + static Map _generateMap({${this.postgresType.attributes + .map(attr => { + return ` + ${new NullDartType(this.ptdMap[attr.type_id][1]).generateType()} ${formatForDartPropertyName(attr.name)}` + }) + .join(',')} + }) => {${this.postgresType.attributes + .map(attr => { + return ` + if (${formatForDartPropertyName(attr.name)} != null) '${attr.name}': ${formatForDartPropertyName(attr.name)}${this.ptdMap[attr.type_id][1].generateJsonEncoding()}` + }) + .join(',')} + }; + + @override + Map toJson() => _generateMap(${this.postgresType.attributes + .map((attr) => { + return ` + ${formatForDartPropertyName(attr.name)}: ${formatForDartPropertyName(attr.name)}` + }) + .join(',')} + ); + + ${this.name} copyWith({${this.postgresType.attributes + .map((attr) => { + return ` + ${new NullDartType(this.ptdMap[attr.type_id][1]).generateType()} ${formatForDartPropertyName(attr.name)}` + }) + .join(',')} + }) { + return ${this.name}(${this.postgresType.attributes + .map((attr) => { + return ` + ${formatForDartPropertyName(attr.name)}: ${formatForDartPropertyName(attr.name)} ?? this.${formatForDartPropertyName(attr.name)}` + }) + .join(',')} + ); + } + + @override + factory ${this.name}.fromJson(Map jsonObject) { + return ${this.name}(${this.postgresType.attributes + .map((attr) => { + return ` + ${formatForDartPropertyName(attr.name)}: ${new NullDartType(this.ptdMap[attr.type_id][1]).generateJsonDecoding(`jsonObject['${attr.name}']`)}` + }) + .join(',')} + ); + } +}` + } + + generateJsonEncoding(): string { + return '.toJson()' + } + + generateJsonDecoding(inputParameter: string): string { + return `${this.name}.fromJson(${inputParameter})` + } +} + +class ClassDartConstruct implements Declarable { + rowableName: string + rowClassName: string + entityClassName: string + operations: Operation[] + columns: PostgresColumn[] + ptdMap: PostgresToDartMap + + constructor( + rowableName: string, + operations: Operation[], + columns: PostgresColumn[], + ptdMap: PostgresToDartMap + ) { + this.rowableName = rowableName + this.rowClassName = `${formatForDartClassName(rowableName)}Row` + this.entityClassName = `${formatForDartClassName(rowableName)}Entity` + this.operations = operations + this.columns = columns + this.ptdMap = ptdMap + } + + generateDeclaration(): string { + return `class ${this.rowClassName} implements JsonSerializable { + static const tableName = '${this.rowableName}'; + ${this.columns + .map((column) => { + return ` + final ${buildDartTypeFromPostgresColumn(column, this.ptdMap).generateType()} ${formatForDartPropertyName(column.name)};` + }) + .join('')} + + const ${this.rowClassName}({${this.columns + .map((column) => { + return ` + ${buildDartTypeFromPostgresColumn(column, this.ptdMap) instanceof NullDartType ? '' : 'required '}this.${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }); + + static Map _generateMap({${this.columns + .map((column) => { + return ` + ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap)).generateType()} ${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }) => {${this.columns + .map((column) => { + return ` + if (${formatForDartPropertyName(column.name)} != null) '${column.name}': ${formatForDartPropertyName(column.name)}${buildDartTypeFromPostgresColumn(column, this.ptdMap).generateJsonEncoding()}` + }) + .join(',')} + }; + + @override + Map toJson() => _generateMap(${this.columns + .map((column) => { + return ` + ${formatForDartPropertyName(column.name)}: ${formatForDartPropertyName(column.name)}` + }) + .join(',')} + ); + + @override + factory ${this.rowClassName}.fromJson(Map jsonObject) { + return ${this.rowClassName}(${this.columns + .map((column) => { + return ` + ${formatForDartPropertyName(column.name)}: ${buildDartTypeFromPostgresColumn(column, this.ptdMap).generateJsonDecoding(`jsonObject['${column.name}']`)}` + }) + .join(',')} + ); + } + + ${this.rowClassName} copyWith({${this.columns + .map((column) => { + return ` + ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap)).generateType()} ${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }) { + return ${this.rowClassName}(${this.columns + .map((column) => { + return ` + ${formatForDartPropertyName(column.name)}: ${formatForDartPropertyName(column.name)} ?? this.${formatForDartPropertyName(column.name)}` + }) + .join(',')} + ); + } +${ + this.operations.indexOf('Insert') !== -1 + ? ` + static Map forInsert({${this.columns + .map((column) => { + return ` + ${(buildDartTypeFromPostgresColumn(column, this.ptdMap, true) instanceof NullDartType) ? '' : 'required '}${buildDartTypeFromPostgresColumn(column, this.ptdMap, true).generateType()} ${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }) => _generateMap(${this.columns + .map((column) => { + return ` + ${formatForDartPropertyName(column.name)}: ${formatForDartPropertyName(column.name)}` + }) + .join(',')} + ); +` + : '' +} +} + +class ${this.entityClassName} implements JsonSerializable { + static const tableName = '${this.rowableName}'; + ${this.columns + .map((column) => { + return ` + ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap)).generateType()} ${formatForDartPropertyName(column.name)};` + }) + .join('')} + + ${this.entityClassName}({${this.columns + .map((column) => { + return ` + this.${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }); + + ${this.entityClassName} copyWith({${this.columns + .map((column) => { + return ` + ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap)).generateType()} ${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }) { + return ${this.entityClassName}(${this.columns + .map((column) => { + return ` + ${formatForDartPropertyName(column.name)}: ${formatForDartPropertyName(column.name)} ?? this.${formatForDartPropertyName(column.name)}` + }) + .join(',')} + ); + } + + static Map _generateMap({${this.columns + .map((column) => { + return ` + ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap)).generateType()} ${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }) => {${this.columns + .map((column) => { + return ` + if (${formatForDartPropertyName(column.name)} != null) '${column.name}': ${formatForDartPropertyName(column.name)}${buildDartTypeFromPostgresColumn(column, this.ptdMap).generateJsonEncoding()}` + }) + .join(',')} + }; + + @override + Map toJson() => _generateMap(${this.columns + .map((column) => { + return ` + ${formatForDartPropertyName(column.name)}: ${formatForDartPropertyName(column.name)}` + }) + .join(',')} + ); + + factory ${this.entityClassName}.fromRow(${this.rowClassName} ${this.rowClassName[0].toLowerCase()}${this.rowClassName.slice(1)}) => ${this.entityClassName}(${this.columns + .map((column) => { + return ` + ${formatForDartPropertyName(column.name)}: ${this.rowClassName[0].toLowerCase()}${this.rowClassName.slice(1)}.${formatForDartPropertyName(column.name)}` + }) + .join(',')} + ); +}` + } +} + +type PostgresToDartMap = Record + +const PGTYPE_TO_DARTTYPE_MAP: Record = { + // Bool + bool: new BuiltinDartType('bool'), + + // Numbers + int2: new BuiltinDartType('int'), + int4: new BuiltinDartType('int'), + int8: new BuiltinDartType('int'), + float4: new BuiltinDartType('double'), + float8: new BuiltinDartType('double'), + numeric: new BuiltinDartType('double'), + + // Time + time: new DatetimeDartType(), + timetz: new DatetimeDartType(), + timestamp: new DatetimeDartType(), + timestamptz: new DatetimeDartType(), + date: new DatetimeDartType(), + interval: new DurationDartType(), + + uuid: new BuiltinDartType('String'), + text: new BuiltinDartType('String'), + varchar: new BuiltinDartType('String'), + jsonb: new MapDartType(new BuiltinDartType('String'), new BuiltinDartType('dynamic')), + json: new MapDartType(new BuiltinDartType('String'), new BuiltinDartType('dynamic')), + regclass: new BuiltinDartType('String'), +} + +class ClassDartConstructForJsonschema implements DartType, Declarable { + name: string + props: PostgresToDartMap + + constructor(name: string, props: PostgresToDartMap) { + this.name = `${formatForDartClassName(name)}` + this.props = props; + } + + generateType(): string { + return this.name + } + + generateDeclaration(): string { + return `class ${this.name} implements JsonSerializable {${Object.entries(this.props).map(([name, [pgType, dartType]]) => ` + final ${dartType.generateType()} ${formatForDartPropertyName(name)};` + ).join('')} + + const ${this.name}({${Object.entries(this.props).map(([name, [pgType, dartType]]) => { + return ` + ${dartType instanceof NullDartType ? '' : 'required '}this.${formatForDartPropertyName(name)}` + }) + .join(',')} + }); + + static Map _generateMap({${Object.entries(this.props).map(([name, [pgType, dartType]]) => { + return ` + ${new NullDartType(dartType).generateType()} ${formatForDartPropertyName(name)}` + }) + .join(',')} + }) => {${Object.entries(this.props).map(([name, [pgType, dartType]]) => { + return ` + if (${formatForDartPropertyName(name)} != null) '${name}': ${formatForDartPropertyName(name)}${dartType.generateJsonEncoding()}` + }) + .join(',')} + }; + + @override + Map toJson() => _generateMap(${Object.entries(this.props).map(([name, [pgType, dartType]]) => { + return ` + ${formatForDartPropertyName(name)}: ${formatForDartPropertyName(name)}` + }) + .join(',')} + ); + + @override + factory ${this.name}.fromJson(Map jsonObject) { + return ${this.name}(${Object.entries(this.props).map(([name, [pgType, dartType]]) => { + return ` + ${formatForDartPropertyName(name)}: ${dartType.generateJsonDecoding(`jsonObject['${name}']`)}` + }) + .join(',')} + ); + } + + ${this.name} copyWith({${Object.entries(this.props).map(([name, [pgType, dartType]]) => { + return ` + ${new NullDartType(dartType).generateType()} ${formatForDartPropertyName(name)}` + }) + .join(',')} + }) { + return ${this.name}(${Object.entries(this.props).map(([name, [pgType, dartType]]) => { + return ` + ${formatForDartPropertyName(name)}: ${formatForDartPropertyName(name)} ?? this.${formatForDartPropertyName(name)}` + }) + .join(',')} + ); + } +}` + } + + generateJsonEncoding(): string { + return '.toJson()' + } + + generateJsonDecoding(inputParameter: string): string { + return `${this.name}.fromJson(${inputParameter})` + } +} + +function buildDartTypeFromJsonschema(schema: any, name: string | undefined = undefined, ptdMap: PostgresToDartMap): DartType { + if (!schema || typeof schema !== 'object') { + return new MapDartType(new BuiltinDartType('String'), new BuiltinDartType('dynamic')); + } + + // Handle array type + if (schema.type === 'array' && schema.items) { + let newTypeName = `${name}_element` + let newType = buildDartTypeFromJsonschema(schema.items, newTypeName, ptdMap) + ptdMap[newTypeName] = [undefined, newType] + return new ListDartType(newType); + } + + // Handle object type + if (schema.type === 'object' && schema.properties) { + if(name === undefined) { + console.log(`No name (${name}) provided for ${JSON.stringify(schema)}`) + return new MapDartType(new BuiltinDartType('String'), new BuiltinDartType('dynamic')); + } + let classProps: PostgresToDartMap = {} + for(var p in schema.properties) { + let newTypeName = `${p}` + let newType = buildDartTypeFromJsonschema(schema.properties[p], newTypeName, ptdMap) + if('required' in schema.properties && !(p in schema.properties.required)) { + newType = new NullDartType(newType) + } + ptdMap[newTypeName] = [undefined, newType] + classProps[newTypeName] = [undefined, newType] + } + return new ClassDartConstructForJsonschema( + name, + classProps + ); + } + + // Handle primitive types + switch (schema.type) { + case 'string': + return new BuiltinDartType('String'); + case 'integer': + return new BuiltinDartType('int'); + case 'number': + return new BuiltinDartType('double'); + case 'boolean': + return new BuiltinDartType('bool'); + case 'null': + return new NullDartType(new BuiltinDartType('dynamic')); + default: + return new BuiltinDartType('dynamic'); + } +} + +function buildDartTypeFromPostgresColumn( + postgresColumn: PostgresColumn, + ptdMap: PostgresToDartMap, + forInsert: boolean = false, +): DartType { + let dartType = ptdMap[postgresColumn.format][1] + if(postgresColumn.format === 'jsonb' && postgresColumn.name in ptdMap) { + dartType = ptdMap[postgresColumn.name][1] + } + if(postgresColumn.is_nullable || (forInsert && (postgresColumn.is_generated || postgresColumn.is_identity || postgresColumn.default_value !== null))) { + return new NullDartType(dartType) + } + return dartType +} + +function buildDartTypeFromPostgresType( + postgresType: PostgresType, + ptdMap: PostgresToDartMap +): DartType { + const sanitizedTypeName = postgresType.name.startsWith('_') + ? postgresType.name.slice(1) + : postgresType.name + + if (postgresType.name.startsWith('_')) { + const existingDartType = ptdMap[sanitizedTypeName] ? ptdMap[sanitizedTypeName][1] : undefined + if (existingDartType) { + return new ListDartType(existingDartType) + } + } + + // Builtin type + const dartTypeFromStaticMap = PGTYPE_TO_DARTTYPE_MAP[sanitizedTypeName] + if (dartTypeFromStaticMap) { + return postgresType.name.startsWith('_') + ? new ListDartType(dartTypeFromStaticMap) + : dartTypeFromStaticMap + } + + // Enum + if (postgresType.enums.length > 0) { + const enumConstruct = new EnumDartConstruct( + postgresType.name, + postgresType.enums, + postgresType.comment + ) + return postgresType.name.startsWith('_') ? new ListDartType(enumConstruct) : enumConstruct + } + + // Composite type + if (postgresType.attributes.length > 0) { + const compositeType = new ClassDartConstructForCompositeType(postgresType, ptdMap) + return postgresType.name.startsWith('_') ? new ListDartType(compositeType) : compositeType + } + + console.log(`Could not find matching type for: ${JSON.stringify(postgresType)}`) + return new BuiltinDartType('dynamic') +} + +/** + * Sorts PostgreSQL types by their dependencies, ensuring that types referenced + * in attributes come before the types that reference them. + * + * @param types Array of PostgreSQL types to sort + * @returns Sorted array of types, or throws error if circular dependency detected + */ +export function sortTypesByDependency(types: PostgresType[]): PostgresType[] { + interface TypeNode { + type: PostgresType + dependencies: Set + visited: boolean + inStack: boolean + } + + // Create adjacency list representation + const typeMap = new Map() + + // Initialize graph nodes + for (const type of types) { + typeMap.set(type.id, { + type, + dependencies: new Set( + type.attributes.map((attr) => attr.type_id).filter((id) => id !== type.id) // Exclude self-references + ), + visited: false, + inStack: false, + }) + } + + const sorted: PostgresType[] = [] + + /** + * Performs depth-first search to detect cycles and build topological sort + */ + function dfs(nodeId: number): void { + const node = typeMap.get(nodeId) + if (!node) return + + // Check for circular dependency + if (node.inStack) { + throw new Error(`Circular dependency detected involving type ${node.type.name}`) + } + + // Skip if already visited in another branch + if (node.visited) return + + // Mark node as being processed + node.inStack = true + + // Process all dependencies first + for (const depId of node.dependencies) { + dfs(depId) + } + + // Mark as visited and remove from stack + node.visited = true + node.inStack = false + + // Add to sorted output + sorted.push(node.type) + } + + // Process all nodes + for (const nodeId of typeMap.keys()) { + if (!typeMap.get(nodeId)!.visited) { + dfs(nodeId) + } + } + + // sort the arrays for last + return sorted.sort(({ name: a }, { name: b }) => + a.startsWith('_') ? 1 : b.startsWith('_') ? -1 : 0 + ) +} + +export function getRequiredTypes( + allTypes: PostgresType[], + columns: PostgresColumn[] +): PostgresType[] { + // Create maps for quick lookups + const typesById = new Map(allTypes.map((type) => [type.id, type])) + const typesByName = new Map(allTypes.map((type) => [type.name, type])) + + // Get all directly referenced types from columns + const directTypeIds = new Set() + + for (const column of columns) { + const type = typesByName.get(column.format) + if (type) { + directTypeIds.add(type.id) + } else { + console.log( + `Type not found for column ${column.name}: format: ${column.format}\tdata_type: ${column.data_type}` + ) + } + const nonArrayType = column.format.startsWith('_') + ? typesByName.get(column.format.slice(1)) + : null + if (nonArrayType) { + directTypeIds.add(nonArrayType.id) + } + } + + // Recursively collect dependent types + const allRequiredTypeIds = new Set() + + function collectDependencies(typeId: number): void { + if (allRequiredTypeIds.has(typeId)) return + + const type = typesById.get(typeId) + if (!type) return + + allRequiredTypeIds.add(typeId) + + for (const attr of type.attributes) { + collectDependencies(attr.type_id) + } + if(type.name.startsWith('_')) { + const nonArrayType = typesByName.get(type.name.slice(1)) + if(nonArrayType) { + collectDependencies(nonArrayType.id) + } + } + } + + // Process each direct type + for (const typeId of directTypeIds) { + collectDependencies(typeId) + } + + // Get the actual type objects and sort them + const requiredTypes = Array.from(allRequiredTypeIds).map((id) => typesById.get(id)!) + + return sortTypesByDependency(requiredTypes) +} + +export const apply = ({ schemas, tables, views, columns, types }: GeneratorMetadata): string => { + const columnsByTableId = columns + .sort(({ name: a }, { name: b }) => a.localeCompare(b)) + .reduce( + (acc, curr) => { + acc[curr.table_id] ??= [] + acc[curr.table_id].push(curr) + return acc + }, + {} as Record + ) + + let declarableTypes: Declarable[] = [] + const requiredTypes = getRequiredTypes(types, columns) + let ptdMap: PostgresToDartMap = {} + for (const t of requiredTypes) { + const newDartType = buildDartTypeFromPostgresType(t, ptdMap) + ptdMap[t.id] = [t, newDartType] + ptdMap[t.name] = [t, newDartType] + if(newDartType instanceof EnumDartConstruct || newDartType instanceof ClassDartConstructForCompositeType) { + declarableTypes.push(newDartType) + } + } + + for(const c of columns) { + if(c.format === 'jsonb' && c.check && c.check.startsWith('jsonb_matches_schema')) { + let jsonschemaDartType = buildDartTypeFromJsonschema(JSON.parse(c.check.substring('jsonb_matches_schema(\''.length, c.check.lastIndexOf('}')+1)), c.name, ptdMap) + if(c.is_nullable) jsonschemaDartType = new NullDartType(jsonschemaDartType) + ptdMap[c.name] = [undefined, jsonschemaDartType] + } + } + for(const k in ptdMap) { + const t = ptdMap[k][1] + if(t instanceof ClassDartConstructForJsonschema) { + declarableTypes.push(t) + } + } + + const tableClassConstructs = tables + .filter((table) => schemas.some((schema) => schema.name === table.schema)) + .map( + (table) => + new ClassDartConstruct( + table.name, + ['Select', 'Insert', 'Update'], + columnsByTableId[table.id], + ptdMap + ) + ) + + const viewClassConstructs = views + .filter((view) => schemas.some((schema) => schema.name === view.schema)) + .map((view) => new ClassDartConstruct(view.name, ['Select'], columnsByTableId[view.id], ptdMap)) + + let result = ` +Duration parsePostgresInterval(String interval) { + // Regular expression to match HH:MM:SS format + final regex = RegExp(r'^([0-9]{2}):([0-5][0-9]):([0-5][0-9])$'); + final match = regex.firstMatch(interval); + + if (match == null) { + throw FormatException('Invalid interval format. Expected HH:MM:SS'); + } + + final hours = int.parse(match.group(1)!); + final minutes = int.parse(match.group(2)!); + final seconds = int.parse(match.group(3)!); + + return Duration( + hours: hours, + minutes: minutes, + seconds: seconds, + ); +} + +abstract class JsonSerializable { + Map toJson(); + + // We can't declare a constructor in an interface, but we can declare + // a factory constructor that implementing classes must provide + factory JsonSerializable.fromJson(Map json) { + throw UnimplementedError(); + } +} + +${declarableTypes + .map((t) => t.generateDeclaration()) + .join('\n\n')} + +${tableClassConstructs.map((classConstruct) => classConstruct.generateDeclaration()).join('\n\n')} + +${viewClassConstructs.map((classConstruct) => classConstruct.generateDeclaration()).join('\n\n')} +` + return result +} From e62749aaa3291b1f86def1c79428bb340fbb41ca Mon Sep 17 00:00:00 2001 From: Leonardo Mosciatti Date: Sun, 2 Nov 2025 18:03:16 +0100 Subject: [PATCH 02/12] remove json schema generation capability to simplify code --- src/server/templates/dart.ts | 146 ----------------------------------- 1 file changed, 146 deletions(-) diff --git a/src/server/templates/dart.ts b/src/server/templates/dart.ts index 117b4441..0f81de20 100644 --- a/src/server/templates/dart.ts +++ b/src/server/templates/dart.ts @@ -508,138 +508,6 @@ const PGTYPE_TO_DARTTYPE_MAP: Record = { regclass: new BuiltinDartType('String'), } -class ClassDartConstructForJsonschema implements DartType, Declarable { - name: string - props: PostgresToDartMap - - constructor(name: string, props: PostgresToDartMap) { - this.name = `${formatForDartClassName(name)}` - this.props = props; - } - - generateType(): string { - return this.name - } - - generateDeclaration(): string { - return `class ${this.name} implements JsonSerializable {${Object.entries(this.props).map(([name, [pgType, dartType]]) => ` - final ${dartType.generateType()} ${formatForDartPropertyName(name)};` - ).join('')} - - const ${this.name}({${Object.entries(this.props).map(([name, [pgType, dartType]]) => { - return ` - ${dartType instanceof NullDartType ? '' : 'required '}this.${formatForDartPropertyName(name)}` - }) - .join(',')} - }); - - static Map _generateMap({${Object.entries(this.props).map(([name, [pgType, dartType]]) => { - return ` - ${new NullDartType(dartType).generateType()} ${formatForDartPropertyName(name)}` - }) - .join(',')} - }) => {${Object.entries(this.props).map(([name, [pgType, dartType]]) => { - return ` - if (${formatForDartPropertyName(name)} != null) '${name}': ${formatForDartPropertyName(name)}${dartType.generateJsonEncoding()}` - }) - .join(',')} - }; - - @override - Map toJson() => _generateMap(${Object.entries(this.props).map(([name, [pgType, dartType]]) => { - return ` - ${formatForDartPropertyName(name)}: ${formatForDartPropertyName(name)}` - }) - .join(',')} - ); - - @override - factory ${this.name}.fromJson(Map jsonObject) { - return ${this.name}(${Object.entries(this.props).map(([name, [pgType, dartType]]) => { - return ` - ${formatForDartPropertyName(name)}: ${dartType.generateJsonDecoding(`jsonObject['${name}']`)}` - }) - .join(',')} - ); - } - - ${this.name} copyWith({${Object.entries(this.props).map(([name, [pgType, dartType]]) => { - return ` - ${new NullDartType(dartType).generateType()} ${formatForDartPropertyName(name)}` - }) - .join(',')} - }) { - return ${this.name}(${Object.entries(this.props).map(([name, [pgType, dartType]]) => { - return ` - ${formatForDartPropertyName(name)}: ${formatForDartPropertyName(name)} ?? this.${formatForDartPropertyName(name)}` - }) - .join(',')} - ); - } -}` - } - - generateJsonEncoding(): string { - return '.toJson()' - } - - generateJsonDecoding(inputParameter: string): string { - return `${this.name}.fromJson(${inputParameter})` - } -} - -function buildDartTypeFromJsonschema(schema: any, name: string | undefined = undefined, ptdMap: PostgresToDartMap): DartType { - if (!schema || typeof schema !== 'object') { - return new MapDartType(new BuiltinDartType('String'), new BuiltinDartType('dynamic')); - } - - // Handle array type - if (schema.type === 'array' && schema.items) { - let newTypeName = `${name}_element` - let newType = buildDartTypeFromJsonschema(schema.items, newTypeName, ptdMap) - ptdMap[newTypeName] = [undefined, newType] - return new ListDartType(newType); - } - - // Handle object type - if (schema.type === 'object' && schema.properties) { - if(name === undefined) { - console.log(`No name (${name}) provided for ${JSON.stringify(schema)}`) - return new MapDartType(new BuiltinDartType('String'), new BuiltinDartType('dynamic')); - } - let classProps: PostgresToDartMap = {} - for(var p in schema.properties) { - let newTypeName = `${p}` - let newType = buildDartTypeFromJsonschema(schema.properties[p], newTypeName, ptdMap) - if('required' in schema.properties && !(p in schema.properties.required)) { - newType = new NullDartType(newType) - } - ptdMap[newTypeName] = [undefined, newType] - classProps[newTypeName] = [undefined, newType] - } - return new ClassDartConstructForJsonschema( - name, - classProps - ); - } - - // Handle primitive types - switch (schema.type) { - case 'string': - return new BuiltinDartType('String'); - case 'integer': - return new BuiltinDartType('int'); - case 'number': - return new BuiltinDartType('double'); - case 'boolean': - return new BuiltinDartType('bool'); - case 'null': - return new NullDartType(new BuiltinDartType('dynamic')); - default: - return new BuiltinDartType('dynamic'); - } -} - function buildDartTypeFromPostgresColumn( postgresColumn: PostgresColumn, ptdMap: PostgresToDartMap, @@ -859,20 +727,6 @@ export const apply = ({ schemas, tables, views, columns, types }: GeneratorMetad } } - for(const c of columns) { - if(c.format === 'jsonb' && c.check && c.check.startsWith('jsonb_matches_schema')) { - let jsonschemaDartType = buildDartTypeFromJsonschema(JSON.parse(c.check.substring('jsonb_matches_schema(\''.length, c.check.lastIndexOf('}')+1)), c.name, ptdMap) - if(c.is_nullable) jsonschemaDartType = new NullDartType(jsonschemaDartType) - ptdMap[c.name] = [undefined, jsonschemaDartType] - } - } - for(const k in ptdMap) { - const t = ptdMap[k][1] - if(t instanceof ClassDartConstructForJsonschema) { - declarableTypes.push(t) - } - } - const tableClassConstructs = tables .filter((table) => schemas.some((schema) => schema.name === table.schema)) .map( From 1c19648b0a4af6e4416e4d01225b5624a15056c0 Mon Sep 17 00:00:00 2001 From: Leonardo Mosciatti Date: Sun, 2 Nov 2025 18:03:53 +0100 Subject: [PATCH 03/12] add comment for getRequiredTypes --- src/server/templates/dart.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/server/templates/dart.ts b/src/server/templates/dart.ts index 0f81de20..1195c359 100644 --- a/src/server/templates/dart.ts +++ b/src/server/templates/dart.ts @@ -642,6 +642,13 @@ export function sortTypesByDependency(types: PostgresType[]): PostgresType[] { ) } +/** + * Find the subset of all the types that are actually used by the columns. + * + * @param types Array of all the existing PostgreSQL types to choose from + * @param types Array of the columns we are going to get the types from + * @returns Array of the types that are used by the columns + */ export function getRequiredTypes( allTypes: PostgresType[], columns: PostgresColumn[] From df33a84cffd360f97ce8258474625d65323f9946 Mon Sep 17 00:00:00 2001 From: Leonardo Mosciatti Date: Sun, 2 Nov 2025 18:08:51 +0100 Subject: [PATCH 04/12] prepend schema to dart class name --- src/server/templates/dart.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/server/templates/dart.ts b/src/server/templates/dart.ts index 1195c359..da031523 100644 --- a/src/server/templates/dart.ts +++ b/src/server/templates/dart.ts @@ -311,13 +311,14 @@ class ClassDartConstruct implements Declarable { constructor( rowableName: string, + schema: string, operations: Operation[], columns: PostgresColumn[], ptdMap: PostgresToDartMap ) { this.rowableName = rowableName - this.rowClassName = `${formatForDartClassName(rowableName)}Row` - this.entityClassName = `${formatForDartClassName(rowableName)}Entity` + this.rowClassName = `${formatForDartClassName(schema)}${formatForDartClassName(rowableName)}Row` + this.entityClassName = `${formatForDartClassName(schema)}${formatForDartClassName(rowableName)}Entity` this.operations = operations this.columns = columns this.ptdMap = ptdMap @@ -740,6 +741,7 @@ export const apply = ({ schemas, tables, views, columns, types }: GeneratorMetad (table) => new ClassDartConstruct( table.name, + table.schema, ['Select', 'Insert', 'Update'], columnsByTableId[table.id], ptdMap @@ -748,7 +750,7 @@ export const apply = ({ schemas, tables, views, columns, types }: GeneratorMetad const viewClassConstructs = views .filter((view) => schemas.some((schema) => schema.name === view.schema)) - .map((view) => new ClassDartConstruct(view.name, ['Select'], columnsByTableId[view.id], ptdMap)) + .map((view) => new ClassDartConstruct(view.name, view.schema, ['Select'], columnsByTableId[view.id], ptdMap)) let result = ` Duration parsePostgresInterval(String interval) { From 898da1bba280097aed2e4d592ce3e63aa3cc0cb8 Mon Sep 17 00:00:00 2001 From: Leonardo Mosciatti Date: Sun, 2 Nov 2025 18:30:38 +0100 Subject: [PATCH 05/12] prepend schema to enum definitions --- src/server/templates/dart.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/server/templates/dart.ts b/src/server/templates/dart.ts index da031523..81be38c7 100644 --- a/src/server/templates/dart.ts +++ b/src/server/templates/dart.ts @@ -158,16 +158,16 @@ class MapDartType implements DartType { } class EnumDartConstruct implements DartType, Declarable { - originalName: string + name: string values: string[] - constructor(name: string, values: string[], comment: string | null) { - this.originalName = name + constructor(name: string, schema: string, values: string[], comment: string | null) { + this.name = `${formatForDartClassName(schema)}${formatForDartClassName(name)}` this.values = values } generateType(): string { - return formatForDartClassName(this.originalName) + return this.name } generateJsonEncoding(): string { @@ -175,11 +175,11 @@ class EnumDartConstruct implements DartType, Declarable { } generateJsonDecoding(inputParameter: string): string { - return `${formatForDartClassName(this.originalName)}.fromJson(${inputParameter})` + return `${this.name}.fromJson(${inputParameter})` } generateDeclaration(): string { - return `enum ${formatForDartClassName(this.originalName)} { + return `enum ${this.name} { ${this.values.map((v) => ` ${formatForDartPropertyName(v)}`).join(',\n')}; String toJson() { @@ -187,20 +187,20 @@ ${this.values.map((v) => ` ${formatForDartPropertyName(v)}`).join(',\n')}; ${this.values .map( (v) => - ` case ${formatForDartClassName(this.originalName)}.${formatForDartPropertyName(v)}: + ` case ${this.name}.${formatForDartPropertyName(v)}: return '${v}';` ) .join('\n')} } } - factory ${formatForDartClassName(this.originalName)}.fromJson(String name) { + factory ${this.name}.fromJson(String name) { switch(name) { ${this.values .map( (v) => ` case '${v}': - return ${formatForDartClassName(this.originalName)}.${formatForDartPropertyName(v)};` + return ${this.name}.${formatForDartPropertyName(v)};` ) .join('\n')} } @@ -219,7 +219,7 @@ class ClassDartConstructForCompositeType implements DartType, Declarable { constructor(postgresType: PostgresType, ptdMap: PostgresToDartMap) { this.postgresType = postgresType this.ptdMap = ptdMap - this.name = `${formatForDartClassName(this.postgresType.name)}` + this.name = `${formatForDartClassName(this.postgresType.schema)}${formatForDartClassName(this.postgresType.name)}` } generateType(): string { @@ -551,6 +551,7 @@ function buildDartTypeFromPostgresType( if (postgresType.enums.length > 0) { const enumConstruct = new EnumDartConstruct( postgresType.name, + postgresType.schema, postgresType.enums, postgresType.comment ) From d1e09edb5cfada3dd3f9bc7877eee2071bafe27a Mon Sep 17 00:00:00 2001 From: Leonardo Mosciatti Date: Sun, 2 Nov 2025 18:32:33 +0100 Subject: [PATCH 06/12] remove composite types to simplify code --- src/server/templates/dart.ts | 98 +----------------------------------- 1 file changed, 1 insertion(+), 97 deletions(-) diff --git a/src/server/templates/dart.ts b/src/server/templates/dart.ts index 81be38c7..8d941e4a 100644 --- a/src/server/templates/dart.ts +++ b/src/server/templates/dart.ts @@ -211,96 +211,6 @@ ${this.values } } -class ClassDartConstructForCompositeType implements DartType, Declarable { - postgresType: PostgresType - ptdMap: PostgresToDartMap - name: string - - constructor(postgresType: PostgresType, ptdMap: PostgresToDartMap) { - this.postgresType = postgresType - this.ptdMap = ptdMap - this.name = `${formatForDartClassName(this.postgresType.schema)}${formatForDartClassName(this.postgresType.name)}` - } - - generateType(): string { - return this.name - } - - generateDeclaration(): string { - return `class ${this.name} implements JsonSerializable {${this.postgresType.attributes.map( - (attr) => ` - final ${new NullDartType(this.ptdMap[attr.type_id][1]).generateType()} ${formatForDartPropertyName(attr.name)};` - ).join('')} - - const ${this.name}({${this.postgresType.attributes.map( - attr => { - return ` - this.${formatForDartPropertyName(attr.name)}` - }) - .join(',')} - }); - - static Map _generateMap({${this.postgresType.attributes - .map(attr => { - return ` - ${new NullDartType(this.ptdMap[attr.type_id][1]).generateType()} ${formatForDartPropertyName(attr.name)}` - }) - .join(',')} - }) => {${this.postgresType.attributes - .map(attr => { - return ` - if (${formatForDartPropertyName(attr.name)} != null) '${attr.name}': ${formatForDartPropertyName(attr.name)}${this.ptdMap[attr.type_id][1].generateJsonEncoding()}` - }) - .join(',')} - }; - - @override - Map toJson() => _generateMap(${this.postgresType.attributes - .map((attr) => { - return ` - ${formatForDartPropertyName(attr.name)}: ${formatForDartPropertyName(attr.name)}` - }) - .join(',')} - ); - - ${this.name} copyWith({${this.postgresType.attributes - .map((attr) => { - return ` - ${new NullDartType(this.ptdMap[attr.type_id][1]).generateType()} ${formatForDartPropertyName(attr.name)}` - }) - .join(',')} - }) { - return ${this.name}(${this.postgresType.attributes - .map((attr) => { - return ` - ${formatForDartPropertyName(attr.name)}: ${formatForDartPropertyName(attr.name)} ?? this.${formatForDartPropertyName(attr.name)}` - }) - .join(',')} - ); - } - - @override - factory ${this.name}.fromJson(Map jsonObject) { - return ${this.name}(${this.postgresType.attributes - .map((attr) => { - return ` - ${formatForDartPropertyName(attr.name)}: ${new NullDartType(this.ptdMap[attr.type_id][1]).generateJsonDecoding(`jsonObject['${attr.name}']`)}` - }) - .join(',')} - ); - } -}` - } - - generateJsonEncoding(): string { - return '.toJson()' - } - - generateJsonDecoding(inputParameter: string): string { - return `${this.name}.fromJson(${inputParameter})` - } -} - class ClassDartConstruct implements Declarable { rowableName: string rowClassName: string @@ -558,12 +468,6 @@ function buildDartTypeFromPostgresType( return postgresType.name.startsWith('_') ? new ListDartType(enumConstruct) : enumConstruct } - // Composite type - if (postgresType.attributes.length > 0) { - const compositeType = new ClassDartConstructForCompositeType(postgresType, ptdMap) - return postgresType.name.startsWith('_') ? new ListDartType(compositeType) : compositeType - } - console.log(`Could not find matching type for: ${JSON.stringify(postgresType)}`) return new BuiltinDartType('dynamic') } @@ -731,7 +635,7 @@ export const apply = ({ schemas, tables, views, columns, types }: GeneratorMetad const newDartType = buildDartTypeFromPostgresType(t, ptdMap) ptdMap[t.id] = [t, newDartType] ptdMap[t.name] = [t, newDartType] - if(newDartType instanceof EnumDartConstruct || newDartType instanceof ClassDartConstructForCompositeType) { + if(newDartType instanceof EnumDartConstruct) { declarableTypes.push(newDartType) } } From 3def08924d0d5466088faceba778747b3dc950d2 Mon Sep 17 00:00:00 2001 From: Leonardo Mosciatti Date: Sun, 2 Nov 2025 18:35:33 +0100 Subject: [PATCH 07/12] remove entity class generation --- src/server/templates/dart.ts | 68 +----------------------------------- 1 file changed, 1 insertion(+), 67 deletions(-) diff --git a/src/server/templates/dart.ts b/src/server/templates/dart.ts index 8d941e4a..c5318384 100644 --- a/src/server/templates/dart.ts +++ b/src/server/templates/dart.ts @@ -214,7 +214,6 @@ ${this.values class ClassDartConstruct implements Declarable { rowableName: string rowClassName: string - entityClassName: string operations: Operation[] columns: PostgresColumn[] ptdMap: PostgresToDartMap @@ -228,7 +227,6 @@ class ClassDartConstruct implements Declarable { ) { this.rowableName = rowableName this.rowClassName = `${formatForDartClassName(schema)}${formatForDartClassName(rowableName)}Row` - this.entityClassName = `${formatForDartClassName(schema)}${formatForDartClassName(rowableName)}Entity` this.operations = operations this.columns = columns this.ptdMap = ptdMap @@ -321,71 +319,7 @@ ${ : '' } } - -class ${this.entityClassName} implements JsonSerializable { - static const tableName = '${this.rowableName}'; - ${this.columns - .map((column) => { - return ` - ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap)).generateType()} ${formatForDartPropertyName(column.name)};` - }) - .join('')} - - ${this.entityClassName}({${this.columns - .map((column) => { - return ` - this.${formatForDartPropertyName(column.name)}` - }) - .join(',')} - }); - - ${this.entityClassName} copyWith({${this.columns - .map((column) => { - return ` - ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap)).generateType()} ${formatForDartPropertyName(column.name)}` - }) - .join(',')} - }) { - return ${this.entityClassName}(${this.columns - .map((column) => { - return ` - ${formatForDartPropertyName(column.name)}: ${formatForDartPropertyName(column.name)} ?? this.${formatForDartPropertyName(column.name)}` - }) - .join(',')} - ); - } - - static Map _generateMap({${this.columns - .map((column) => { - return ` - ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap)).generateType()} ${formatForDartPropertyName(column.name)}` - }) - .join(',')} - }) => {${this.columns - .map((column) => { - return ` - if (${formatForDartPropertyName(column.name)} != null) '${column.name}': ${formatForDartPropertyName(column.name)}${buildDartTypeFromPostgresColumn(column, this.ptdMap).generateJsonEncoding()}` - }) - .join(',')} - }; - - @override - Map toJson() => _generateMap(${this.columns - .map((column) => { - return ` - ${formatForDartPropertyName(column.name)}: ${formatForDartPropertyName(column.name)}` - }) - .join(',')} - ); - - factory ${this.entityClassName}.fromRow(${this.rowClassName} ${this.rowClassName[0].toLowerCase()}${this.rowClassName.slice(1)}) => ${this.entityClassName}(${this.columns - .map((column) => { - return ` - ${formatForDartPropertyName(column.name)}: ${this.rowClassName[0].toLowerCase()}${this.rowClassName.slice(1)}.${formatForDartPropertyName(column.name)}` - }) - .join(',')} - ); -}` +` } } From 36d603f7dde3405f442655d7e95d1daa87f33bc0 Mon Sep 17 00:00:00 2001 From: Leonardo Mosciatti Date: Sun, 2 Nov 2025 19:13:55 +0100 Subject: [PATCH 08/12] generate classes appending operation names --- src/server/templates/dart.ts | 166 +++++++++++++++++++++++++++++++---- 1 file changed, 150 insertions(+), 16 deletions(-) diff --git a/src/server/templates/dart.ts b/src/server/templates/dart.ts index c5318384..8190e95e 100644 --- a/src/server/templates/dart.ts +++ b/src/server/templates/dart.ts @@ -226,14 +226,15 @@ class ClassDartConstruct implements Declarable { ptdMap: PostgresToDartMap ) { this.rowableName = rowableName - this.rowClassName = `${formatForDartClassName(schema)}${formatForDartClassName(rowableName)}Row` + this.rowClassName = `${formatForDartClassName(schema)}${formatForDartClassName(rowableName)}` this.operations = operations this.columns = columns this.ptdMap = ptdMap } - generateDeclaration(): string { - return `class ${this.rowClassName} implements JsonSerializable { + generateSelectDeclaration(): string { + const className = `${this.rowClassName}Select` + return `class ${className} implements JsonSerializable { static const tableName = '${this.rowableName}'; ${this.columns .map((column) => { @@ -242,7 +243,7 @@ class ClassDartConstruct implements Declarable { }) .join('')} - const ${this.rowClassName}({${this.columns + const ${className}({${this.columns .map((column) => { return ` ${buildDartTypeFromPostgresColumn(column, this.ptdMap) instanceof NullDartType ? '' : 'required '}this.${formatForDartPropertyName(column.name)}` @@ -274,8 +275,8 @@ class ClassDartConstruct implements Declarable { ); @override - factory ${this.rowClassName}.fromJson(Map jsonObject) { - return ${this.rowClassName}(${this.columns + factory ${className}.fromJson(Map jsonObject) { + return ${className}(${this.columns .map((column) => { return ` ${formatForDartPropertyName(column.name)}: ${buildDartTypeFromPostgresColumn(column, this.ptdMap).generateJsonDecoding(`jsonObject['${column.name}']`)}` @@ -284,14 +285,14 @@ class ClassDartConstruct implements Declarable { ); } - ${this.rowClassName} copyWith({${this.columns + ${className} copyWith({${this.columns .map((column) => { return ` ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap)).generateType()} ${formatForDartPropertyName(column.name)}` }) .join(',')} }) { - return ${this.rowClassName}(${this.columns + return ${className}(${this.columns .map((column) => { return ` ${formatForDartPropertyName(column.name)}: ${formatForDartPropertyName(column.name)} ?? this.${formatForDartPropertyName(column.name)}` @@ -299,28 +300,161 @@ class ClassDartConstruct implements Declarable { .join(',')} ); } -${ - this.operations.indexOf('Insert') !== -1 - ? ` - static Map forInsert({${this.columns +} +` + } + + generateInsertDeclaration(): string { + const className = `${this.rowClassName}Insert` + return `class ${className} implements JsonSerializable { + static const tableName = '${this.rowableName}'; + ${this.columns + .map((column) => { + return ` + final ${buildDartTypeFromPostgresColumn(column, this.ptdMap, true).generateType()} ${formatForDartPropertyName(column.name)};` + }) + .join('')} + + const ${className}({${this.columns + .map((column) => { + return ` + ${buildDartTypeFromPostgresColumn(column, this.ptdMap, true) instanceof NullDartType ? '' : 'required '}this.${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }); + + static Map _generateMap({${this.columns + .map((column) => { + return ` + ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap, true)).generateType()} ${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }) => {${this.columns .map((column) => { return ` - ${(buildDartTypeFromPostgresColumn(column, this.ptdMap, true) instanceof NullDartType) ? '' : 'required '}${buildDartTypeFromPostgresColumn(column, this.ptdMap, true).generateType()} ${formatForDartPropertyName(column.name)}` + if (${formatForDartPropertyName(column.name)} != null) '${column.name}': ${formatForDartPropertyName(column.name)}${buildDartTypeFromPostgresColumn(column, this.ptdMap, true).generateJsonEncoding()}` }) .join(',')} - }) => _generateMap(${this.columns + }; + + @override + Map toJson() => _generateMap(${this.columns .map((column) => { return ` ${formatForDartPropertyName(column.name)}: ${formatForDartPropertyName(column.name)}` }) .join(',')} ); -` - : '' + + @override + factory ${className}.fromJson(Map jsonObject) { + return ${className}(${this.columns + .map((column) => { + return ` + ${formatForDartPropertyName(column.name)}: ${buildDartTypeFromPostgresColumn(column, this.ptdMap, true).generateJsonDecoding(`jsonObject['${column.name}']`)}` + }) + .join(',')} + ); + } + + ${className} copyWith({${this.columns + .map((column) => { + return ` + ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap, true)).generateType()} ${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }) { + return ${className}(${this.columns + .map((column) => { + return ` + ${formatForDartPropertyName(column.name)}: ${formatForDartPropertyName(column.name)} ?? this.${formatForDartPropertyName(column.name)}` + }) + .join(',')} + ); + } } +` + } + + generateUpdateDeclaration(): string { + const className = `${this.rowClassName}Update` + return `class ${className} implements JsonSerializable { + static const tableName = '${this.rowableName}'; + ${this.columns + .map((column) => { + return ` + final ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap, true)).generateType()} ${formatForDartPropertyName(column.name)};` + }) + .join('')} + + const ${className}({${this.columns + .map((column) => { + return ` + this.${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }); + + static Map _generateMap({${this.columns + .map((column) => { + return ` + ${new NullDartType(new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap, true))).generateType()} ${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }) => {${this.columns + .map((column) => { + return ` + if (${formatForDartPropertyName(column.name)} != null) '${column.name}': ${formatForDartPropertyName(column.name)}${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap, true)).generateJsonEncoding()}` + }) + .join(',')} + }; + + @override + Map toJson() => _generateMap(${this.columns + .map((column) => { + return ` + ${formatForDartPropertyName(column.name)}: ${formatForDartPropertyName(column.name)}` + }) + .join(',')} + ); + + @override + factory ${className}.fromJson(Map jsonObject) { + return ${className}(${this.columns + .map((column) => { + return ` + ${formatForDartPropertyName(column.name)}: ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap, true)).generateJsonDecoding(`jsonObject['${column.name}']`)}` + }) + .join(',')} + ); + } + + ${className} copyWith({${this.columns + .map((column) => { + return ` + ${new NullDartType(new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap, true))).generateType()} ${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }) { + return ${className}(${this.columns + .map((column) => { + return ` + ${formatForDartPropertyName(column.name)}: ${formatForDartPropertyName(column.name)} ?? this.${formatForDartPropertyName(column.name)}` + }) + .join(',')} + ); + } } ` } + + generateDeclaration(): string { + return `${this.operations.indexOf('Select') !== -1 ? this.generateSelectDeclaration() : ''} + +${this.operations.indexOf('Insert') !== -1 ? this.generateInsertDeclaration() : ''} + +${this.operations.indexOf('Update') !== -1 ? this.generateUpdateDeclaration() : ''}` + } } type PostgresToDartMap = Record From a9f702615c7f302e8172cb8509f9ed2634b2408e Mon Sep 17 00:00:00 2001 From: Leonardo Mosciatti Date: Sun, 2 Nov 2025 19:15:32 +0100 Subject: [PATCH 09/12] add dart generator route --- src/server/routes/generators/dart.ts | 34 ++++++++++++++++++++++++++++ src/server/routes/index.ts | 3 ++- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/server/routes/generators/dart.ts diff --git a/src/server/routes/generators/dart.ts b/src/server/routes/generators/dart.ts new file mode 100644 index 00000000..620bab3e --- /dev/null +++ b/src/server/routes/generators/dart.ts @@ -0,0 +1,34 @@ +import type { FastifyInstance } from 'fastify' +import { PostgresMeta } from '../../../lib/index.js' +import { createConnectionConfig, extractRequestForLogging } from '../../utils.js' +import { apply as applyDartTemplate } from '../../templates/dart.js' +import { getGeneratorMetadata } from '../../../lib/generators.js' + +export default async (fastify: FastifyInstance) => { + fastify.get<{ + Headers: { pg: string; 'x-pg-application-name'?: string } + Querystring: { + excluded_schemas?: string + included_schemas?: string + } + }>('/', async (request, reply) => { + const config = createConnectionConfig(request) + const excludedSchemas = + request.query.excluded_schemas?.split(',').map((schema) => schema.trim()) ?? [] + const includedSchemas = + request.query.included_schemas?.split(',').map((schema) => schema.trim()) ?? [] + + const pgMeta: PostgresMeta = new PostgresMeta(config) + const { data: generatorMeta, error: generatorMetaError } = await getGeneratorMetadata(pgMeta, { + includedSchemas, + excludedSchemas, + }) + if (generatorMetaError) { + request.log.error({ error: generatorMetaError, request: extractRequestForLogging(request) }) + reply.code(500) + return { error: generatorMetaError.message } + } + + return applyDartTemplate(generatorMeta) + }) +} \ No newline at end of file diff --git a/src/server/routes/index.ts b/src/server/routes/index.ts index 46ffba0f..bcb76f80 100644 --- a/src/server/routes/index.ts +++ b/src/server/routes/index.ts @@ -22,6 +22,7 @@ import TypeScriptTypeGenRoute from './generators/typescript.js' import GoTypeGenRoute from './generators/go.js' import SwiftTypeGenRoute from './generators/swift.js' import PythonTypeGenRoute from './generators/python.js' +import DartTypeGenRoute from './generators/dart.js' import { PG_CONNECTION, CRYPTO_KEY } from '../constants.js' export default async (fastify: FastifyInstance) => { @@ -83,5 +84,5 @@ export default async (fastify: FastifyInstance) => { fastify.register(TypeScriptTypeGenRoute, { prefix: '/generators/typescript' }) fastify.register(GoTypeGenRoute, { prefix: '/generators/go' }) fastify.register(SwiftTypeGenRoute, { prefix: '/generators/swift' }) - fastify.register(PythonTypeGenRoute, { prefix: '/generators/python' }) + fastify.register(DartTypeGenRoute, { prefix: '/generators/dart' }) } From 9015de644bdf30f08871633d118d54aeb573860c Mon Sep 17 00:00:00 2001 From: Leonardo Mosciatti Date: Mon, 3 Nov 2025 11:53:21 +0100 Subject: [PATCH 10/12] fixes and test --- src/server/templates/dart.ts | 365 ++++-- test/server/typegen.ts | 2217 ++++++++++++++++++++++++++++++++++ 2 files changed, 2449 insertions(+), 133 deletions(-) diff --git a/src/server/templates/dart.ts b/src/server/templates/dart.ts index 8190e95e..bc442360 100644 --- a/src/server/templates/dart.ts +++ b/src/server/templates/dart.ts @@ -1,14 +1,5 @@ -import type { - PostgresColumn, - PostgresMaterializedView, - PostgresSchema, - PostgresTable, - PostgresType, - PostgresView, -} from '../../lib/index.js' +import type { PostgresColumn, PostgresType } from '../../lib/index.js' import type { GeneratorMetadata } from '../../lib/generators.js' -import { Console } from 'console' -const console = new Console(process.stderr) type Operation = 'Select' | 'Insert' | 'Update' @@ -24,6 +15,80 @@ function formatForDartPropertyName(name: string): string { return className[0].toLowerCase() + className.slice(1) } +function escapeDartKeyword(str: string): string { + const dartKeywords = new Set([ + 'abstract', + 'as', + 'assert', + 'async', + 'await', + 'break', + 'case', + 'catch', + 'class', + 'const', + 'continue', + 'covariant', + 'default', + 'deferred', + 'do', + 'dynamic', + 'else', + 'enum', + 'export', + 'extends', + 'extension', + 'external', + 'factory', + 'false', + 'final', + 'finally', + 'for', + 'Function', + 'get', + 'hide', + 'if', + 'implements', + 'import', + 'in', + 'interface', + 'is', + 'late', + 'library', + 'mixin', + 'new', + 'null', + 'on', + 'operator', + 'part', + 'required', + 'rethrow', + 'return', + 'set', + 'show', + 'static', + 'super', + 'switch', + 'sync', + 'this', + 'throw', + 'true', + 'try', + 'typedef', + 'var', + 'void', + 'while', + 'with', + 'yield', + ]) + + return dartKeywords.has(str) ? `_${str}` : str +} + +function formatForDartEnum(name: string): string { + return escapeDartKeyword(formatForDartClassName(name).toLowerCase()) +} + interface Typeable { generateType(): string } @@ -180,14 +245,14 @@ class EnumDartConstruct implements DartType, Declarable { generateDeclaration(): string { return `enum ${this.name} { -${this.values.map((v) => ` ${formatForDartPropertyName(v)}`).join(',\n')}; +${this.values.map((v) => ` ${formatForDartEnum(v)}`).join(',\n')}; String toJson() { switch(this) { ${this.values .map( (v) => - ` case ${this.name}.${formatForDartPropertyName(v)}: + ` case ${this.name}.${formatForDartEnum(v)}: return '${v}';` ) .join('\n')} @@ -200,7 +265,7 @@ ${this.values .map( (v) => ` case '${v}': - return ${this.name}.${formatForDartPropertyName(v)};` + return ${this.name}.${formatForDartEnum(v)};` ) .join('\n')} } @@ -219,6 +284,7 @@ class ClassDartConstruct implements Declarable { ptdMap: PostgresToDartMap constructor( + id: number, rowableName: string, schema: string, operations: Operation[], @@ -230,6 +296,8 @@ class ClassDartConstruct implements Declarable { this.operations = operations this.columns = columns this.ptdMap = ptdMap + this.ptdMap[id] = [undefined, new BuiltinDartType('dynamic')] + this.ptdMap[rowableName] = [undefined, new BuiltinDartType('dynamic')] } generateSelectDeclaration(): string { @@ -237,26 +305,34 @@ class ClassDartConstruct implements Declarable { return `class ${className} implements JsonSerializable { static const tableName = '${this.rowableName}'; ${this.columns - .map((column) => { - return ` - final ${buildDartTypeFromPostgresColumn(column, this.ptdMap).generateType()} ${formatForDartPropertyName(column.name)};` - }) - .join('')} - - const ${className}({${this.columns .map((column) => { return ` - ${buildDartTypeFromPostgresColumn(column, this.ptdMap) instanceof NullDartType ? '' : 'required '}this.${formatForDartPropertyName(column.name)}` + final ${buildDartTypeFromPostgresColumn(column, this.ptdMap).generateType()} ${formatForDartPropertyName(column.name)};` }) - .join(',')} + .join('')} + + const ${className}(${ + this.columns.length > 0 + ? `{${this.columns + .map((column) => { + return ` + ${buildDartTypeFromPostgresColumn(column, this.ptdMap) instanceof NullDartType ? '' : 'required '}this.${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }` + : '' }); - static Map _generateMap({${this.columns - .map((column) => { - return ` + static Map _generateMap(${ + this.columns.length > 0 + ? `{${this.columns + .map((column) => { + return ` ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap)).generateType()} ${formatForDartPropertyName(column.name)}` - }) - .join(',')} + }) + .join(',')} + }` + : '' }) => {${this.columns .map((column) => { return ` @@ -285,23 +361,30 @@ class ClassDartConstruct implements Declarable { ); } - ${className} copyWith({${this.columns - .map((column) => { - return ` + ${className} copyWith(${ + this.columns.length > 0 + ? `{${this.columns + .map((column) => { + return ` ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap)).generateType()} ${formatForDartPropertyName(column.name)}` - }) - .join(',')} + }) + .join(',')} + }` + : '' }) { - return ${className}(${this.columns - .map((column) => { - return ` + return ${className}(${ + this.columns.length > 0 + ? `${this.columns + .map((column) => { + return ` ${formatForDartPropertyName(column.name)}: ${formatForDartPropertyName(column.name)} ?? this.${formatForDartPropertyName(column.name)}` - }) - .join(',')} + }) + .join(',')}` + : '' + } ); } -} -` +}` } generateInsertDeclaration(): string { @@ -309,26 +392,34 @@ class ClassDartConstruct implements Declarable { return `class ${className} implements JsonSerializable { static const tableName = '${this.rowableName}'; ${this.columns - .map((column) => { - return ` - final ${buildDartTypeFromPostgresColumn(column, this.ptdMap, true).generateType()} ${formatForDartPropertyName(column.name)};` - }) - .join('')} - - const ${className}({${this.columns .map((column) => { return ` - ${buildDartTypeFromPostgresColumn(column, this.ptdMap, true) instanceof NullDartType ? '' : 'required '}this.${formatForDartPropertyName(column.name)}` + final ${buildDartTypeFromPostgresColumn(column, this.ptdMap, true).generateType()} ${formatForDartPropertyName(column.name)};` }) - .join(',')} + .join('')} + + const ${className}(${ + this.columns.length > 0 + ? `{${this.columns + .map((column) => { + return ` + ${buildDartTypeFromPostgresColumn(column, this.ptdMap, true) instanceof NullDartType ? '' : 'required '}this.${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }` + : '' }); - static Map _generateMap({${this.columns - .map((column) => { - return ` + static Map _generateMap(${ + this.columns.length > 0 + ? `{${this.columns + .map((column) => { + return ` ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap, true)).generateType()} ${formatForDartPropertyName(column.name)}` - }) - .join(',')} + }) + .join(',')} + }` + : '' }) => {${this.columns .map((column) => { return ` @@ -357,23 +448,30 @@ class ClassDartConstruct implements Declarable { ); } - ${className} copyWith({${this.columns - .map((column) => { - return ` + ${className} copyWith(${ + this.columns.length > 0 + ? `{${this.columns + .map((column) => { + return ` ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap, true)).generateType()} ${formatForDartPropertyName(column.name)}` - }) - .join(',')} + }) + .join(',')} + }` + : '' }) { - return ${className}(${this.columns - .map((column) => { - return ` + return ${className}(${ + this.columns.length > 0 + ? `${this.columns + .map((column) => { + return ` ${formatForDartPropertyName(column.name)}: ${formatForDartPropertyName(column.name)} ?? this.${formatForDartPropertyName(column.name)}` - }) - .join(',')} + }) + .join(',')}` + : '' + } ); } -} -` +}` } generateUpdateDeclaration(): string { @@ -381,26 +479,34 @@ class ClassDartConstruct implements Declarable { return `class ${className} implements JsonSerializable { static const tableName = '${this.rowableName}'; ${this.columns - .map((column) => { - return ` - final ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap, true)).generateType()} ${formatForDartPropertyName(column.name)};` - }) - .join('')} - - const ${className}({${this.columns .map((column) => { return ` - this.${formatForDartPropertyName(column.name)}` + final ${new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap, true)).generateType()} ${formatForDartPropertyName(column.name)};` }) - .join(',')} + .join('')} + + const ${className}(${ + this.columns.length > 0 + ? `{${this.columns + .map((column) => { + return ` + this.${formatForDartPropertyName(column.name)}` + }) + .join(',')} + }` + : '' }); - static Map _generateMap({${this.columns - .map((column) => { - return ` + static Map _generateMap(${ + this.columns.length > 0 + ? `{${this.columns + .map((column) => { + return ` ${new NullDartType(new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap, true))).generateType()} ${formatForDartPropertyName(column.name)}` - }) - .join(',')} + }) + .join(',')} + }` + : '' }) => {${this.columns .map((column) => { return ` @@ -429,30 +535,35 @@ class ClassDartConstruct implements Declarable { ); } - ${className} copyWith({${this.columns - .map((column) => { - return ` + ${className} copyWith(${ + this.columns.length > 0 + ? `{${this.columns + .map((column) => { + return ` ${new NullDartType(new NullDartType(buildDartTypeFromPostgresColumn(column, this.ptdMap, true))).generateType()} ${formatForDartPropertyName(column.name)}` - }) - .join(',')} + }) + .join(',')} + }` + : '' }) { - return ${className}(${this.columns - .map((column) => { - return ` + return ${className}(${ + this.columns.length > 0 + ? `${this.columns + .map((column) => { + return ` ${formatForDartPropertyName(column.name)}: ${formatForDartPropertyName(column.name)} ?? this.${formatForDartPropertyName(column.name)}` - }) - .join(',')} + }) + .join(',')}` + : '' + } ); } -} -` +}` } generateDeclaration(): string { return `${this.operations.indexOf('Select') !== -1 ? this.generateSelectDeclaration() : ''} - ${this.operations.indexOf('Insert') !== -1 ? this.generateInsertDeclaration() : ''} - ${this.operations.indexOf('Update') !== -1 ? this.generateUpdateDeclaration() : ''}` } } @@ -490,13 +601,19 @@ const PGTYPE_TO_DARTTYPE_MAP: Record = { function buildDartTypeFromPostgresColumn( postgresColumn: PostgresColumn, ptdMap: PostgresToDartMap, - forInsert: boolean = false, + forInsert: boolean = false ): DartType { let dartType = ptdMap[postgresColumn.format][1] - if(postgresColumn.format === 'jsonb' && postgresColumn.name in ptdMap) { + if (postgresColumn.format === 'jsonb' && postgresColumn.name in ptdMap) { dartType = ptdMap[postgresColumn.name][1] } - if(postgresColumn.is_nullable || (forInsert && (postgresColumn.is_generated || postgresColumn.is_identity || postgresColumn.default_value !== null))) { + if ( + postgresColumn.is_nullable || + (forInsert && + (postgresColumn.is_generated || + postgresColumn.is_identity || + postgresColumn.default_value !== null)) + ) { return new NullDartType(dartType) } return dartType @@ -536,7 +653,6 @@ function buildDartTypeFromPostgresType( return postgresType.name.startsWith('_') ? new ListDartType(enumConstruct) : enumConstruct } - console.log(`Could not find matching type for: ${JSON.stringify(postgresType)}`) return new BuiltinDartType('dynamic') } @@ -638,10 +754,6 @@ export function getRequiredTypes( const type = typesByName.get(column.format) if (type) { directTypeIds.add(type.id) - } else { - console.log( - `Type not found for column ${column.name}: format: ${column.format}\tdata_type: ${column.data_type}` - ) } const nonArrayType = column.format.startsWith('_') ? typesByName.get(column.format.slice(1)) @@ -665,9 +777,9 @@ export function getRequiredTypes( for (const attr of type.attributes) { collectDependencies(attr.type_id) } - if(type.name.startsWith('_')) { + if (type.name.startsWith('_')) { const nonArrayType = typesByName.get(type.name.slice(1)) - if(nonArrayType) { + if (nonArrayType) { collectDependencies(nonArrayType.id) } } @@ -703,7 +815,7 @@ export const apply = ({ schemas, tables, views, columns, types }: GeneratorMetad const newDartType = buildDartTypeFromPostgresType(t, ptdMap) ptdMap[t.id] = [t, newDartType] ptdMap[t.name] = [t, newDartType] - if(newDartType instanceof EnumDartConstruct) { + if (newDartType instanceof EnumDartConstruct) { declarableTypes.push(newDartType) } } @@ -713,40 +825,30 @@ export const apply = ({ schemas, tables, views, columns, types }: GeneratorMetad .map( (table) => new ClassDartConstruct( + table.id, table.name, table.schema, ['Select', 'Insert', 'Update'], - columnsByTableId[table.id], + columnsByTableId[table.id] ?? [], ptdMap ) ) const viewClassConstructs = views .filter((view) => schemas.some((schema) => schema.name === view.schema)) - .map((view) => new ClassDartConstruct(view.name, view.schema, ['Select'], columnsByTableId[view.id], ptdMap)) - - let result = ` -Duration parsePostgresInterval(String interval) { - // Regular expression to match HH:MM:SS format - final regex = RegExp(r'^([0-9]{2}):([0-5][0-9]):([0-5][0-9])$'); - final match = regex.firstMatch(interval); - - if (match == null) { - throw FormatException('Invalid interval format. Expected HH:MM:SS'); - } - - final hours = int.parse(match.group(1)!); - final minutes = int.parse(match.group(2)!); - final seconds = int.parse(match.group(3)!); - - return Duration( - hours: hours, - minutes: minutes, - seconds: seconds, - ); -} + .map( + (view) => + new ClassDartConstruct( + view.id, + view.name, + view.schema, + ['Select'], + columnsByTableId[view.id], + ptdMap + ) + ) -abstract class JsonSerializable { + let result = `abstract class JsonSerializable { Map toJson(); // We can't declare a constructor in an interface, but we can declare @@ -756,13 +858,10 @@ abstract class JsonSerializable { } } -${declarableTypes - .map((t) => t.generateDeclaration()) - .join('\n\n')} +${declarableTypes.map((t) => t.generateDeclaration()).join('\n\n')} ${tableClassConstructs.map((classConstruct) => classConstruct.generateDeclaration()).join('\n\n')} -${viewClassConstructs.map((classConstruct) => classConstruct.generateDeclaration()).join('\n\n')} -` +${viewClassConstructs.map((classConstruct) => classConstruct.generateDeclaration()).join('\n\n')}` return result } diff --git a/test/server/typegen.ts b/test/server/typegen.ts index 852eddce..cefaa4b3 100644 --- a/test/server/typegen.ts +++ b/test/server/typegen.ts @@ -6694,3 +6694,2220 @@ test('typegen: python w/ excluded/included schemas', async () => { }) } }) + +test('typegen: dart', async () => { + const { body } = await app.inject({ method: 'GET', path: '/generators/dart' }) + expect(body).toMatchInlineSnapshot(`"abstract class JsonSerializable { + Map toJson(); + + // We can't declare a constructor in an interface, but we can declare + // a factory constructor that implementing classes must provide + factory JsonSerializable.fromJson(Map json) { + throw UnimplementedError(); + } + } + + enum PublicUserStatus { + active, + inactive; + + String toJson() { + switch(this) { + case PublicUserStatus.active: + return 'ACTIVE'; + case PublicUserStatus.inactive: + return 'INACTIVE'; + } + } + + factory PublicUserStatus.fromJson(String name) { + switch(name) { + case 'ACTIVE': + return PublicUserStatus.active; + case 'INACTIVE': + return PublicUserStatus.inactive; + } + throw ArgumentError.value(name, "name", "No enum value with that name"); + } + } + + + enum PublicMemeStatus { + _new, + old, + retired; + + String toJson() { + switch(this) { + case PublicMemeStatus._new: + return 'new'; + case PublicMemeStatus.old: + return 'old'; + case PublicMemeStatus.retired: + return 'retired'; + } + } + + factory PublicMemeStatus.fromJson(String name) { + switch(name) { + case 'new': + return PublicMemeStatus._new; + case 'old': + return PublicMemeStatus.old; + case 'retired': + return PublicMemeStatus.retired; + } + throw ArgumentError.value(name, "name", "No enum value with that name"); + } + } + + + class PublicUsersSelect implements JsonSerializable { + static const tableName = 'users'; + + final double? decimal; + final int id; + final String? name; + final PublicUserStatus? status; + + const PublicUsersSelect({ + this.decimal, + required this.id, + this.name, + this.status + }); + + static Map _generateMap({ + double? decimal, + int? id, + String? name, + PublicUserStatus? status + }) => { + if (decimal != null) 'decimal': decimal, + if (id != null) 'id': id, + if (name != null) 'name': name, + if (status != null) 'status': status.toJson() + }; + + @override + Map toJson() => _generateMap( + decimal: decimal, + id: id, + name: name, + status: status + ); + + @override + factory PublicUsersSelect.fromJson(Map jsonObject) { + return PublicUsersSelect( + decimal: jsonObject['decimal'] == null ? null : jsonObject['decimal'] as double, + id: jsonObject['id'] as int, + name: jsonObject['name'] == null ? null : jsonObject['name'] as String, + status: jsonObject['status'] == null ? null : PublicUserStatus.fromJson(jsonObject['status']) + ); + } + + PublicUsersSelect copyWith({ + double? decimal, + int? id, + String? name, + PublicUserStatus? status + }) { + return PublicUsersSelect( + decimal: decimal ?? this.decimal, + id: id ?? this.id, + name: name ?? this.name, + status: status ?? this.status + ); + } + } + class PublicUsersInsert implements JsonSerializable { + static const tableName = 'users'; + + final double? decimal; + final int? id; + final String? name; + final PublicUserStatus? status; + + const PublicUsersInsert({ + this.decimal, + this.id, + this.name, + this.status + }); + + static Map _generateMap({ + double? decimal, + int? id, + String? name, + PublicUserStatus? status + }) => { + if (decimal != null) 'decimal': decimal, + if (id != null) 'id': id, + if (name != null) 'name': name, + if (status != null) 'status': status.toJson() + }; + + @override + Map toJson() => _generateMap( + decimal: decimal, + id: id, + name: name, + status: status + ); + + @override + factory PublicUsersInsert.fromJson(Map jsonObject) { + return PublicUsersInsert( + decimal: jsonObject['decimal'] == null ? null : jsonObject['decimal'] as double, + id: jsonObject['id'] == null ? null : jsonObject['id'] as int, + name: jsonObject['name'] == null ? null : jsonObject['name'] as String, + status: jsonObject['status'] == null ? null : PublicUserStatus.fromJson(jsonObject['status']) + ); + } + + PublicUsersInsert copyWith({ + double? decimal, + int? id, + String? name, + PublicUserStatus? status + }) { + return PublicUsersInsert( + decimal: decimal ?? this.decimal, + id: id ?? this.id, + name: name ?? this.name, + status: status ?? this.status + ); + } + } + class PublicUsersUpdate implements JsonSerializable { + static const tableName = 'users'; + + final double? decimal; + final int? id; + final String? name; + final PublicUserStatus? status; + + const PublicUsersUpdate({ + this.decimal, + this.id, + this.name, + this.status + }); + + static Map _generateMap({ + double? decimal, + int? id, + String? name, + PublicUserStatus? status + }) => { + if (decimal != null) 'decimal': decimal, + if (id != null) 'id': id, + if (name != null) 'name': name, + if (status != null) 'status': status.toJson() + }; + + @override + Map toJson() => _generateMap( + decimal: decimal, + id: id, + name: name, + status: status + ); + + @override + factory PublicUsersUpdate.fromJson(Map jsonObject) { + return PublicUsersUpdate( + decimal: jsonObject['decimal'] == null ? null : jsonObject['decimal'] as double, + id: jsonObject['id'] == null ? null : jsonObject['id'] as int, + name: jsonObject['name'] == null ? null : jsonObject['name'] as String, + status: jsonObject['status'] == null ? null : PublicUserStatus.fromJson(jsonObject['status']) + ); + } + + PublicUsersUpdate copyWith({ + double? decimal, + int? id, + String? name, + PublicUserStatus? status + }) { + return PublicUsersUpdate( + decimal: decimal ?? this.decimal, + id: id ?? this.id, + name: name ?? this.name, + status: status ?? this.status + ); + } + } + + class PublicTodosSelect implements JsonSerializable { + static const tableName = 'todos'; + + final String? details; + final int id; + final int userId; + + const PublicTodosSelect({ + this.details, + required this.id, + required this.userId + }); + + static Map _generateMap({ + String? details, + int? id, + int? userId + }) => { + if (details != null) 'details': details, + if (id != null) 'id': id, + if (userId != null) 'user-id': userId + }; + + @override + Map toJson() => _generateMap( + details: details, + id: id, + userId: userId + ); + + @override + factory PublicTodosSelect.fromJson(Map jsonObject) { + return PublicTodosSelect( + details: jsonObject['details'] == null ? null : jsonObject['details'] as String, + id: jsonObject['id'] as int, + userId: jsonObject['user-id'] as int + ); + } + + PublicTodosSelect copyWith({ + String? details, + int? id, + int? userId + }) { + return PublicTodosSelect( + details: details ?? this.details, + id: id ?? this.id, + userId: userId ?? this.userId + ); + } + } + class PublicTodosInsert implements JsonSerializable { + static const tableName = 'todos'; + + final String? details; + final int? id; + final int userId; + + const PublicTodosInsert({ + this.details, + this.id, + required this.userId + }); + + static Map _generateMap({ + String? details, + int? id, + int? userId + }) => { + if (details != null) 'details': details, + if (id != null) 'id': id, + if (userId != null) 'user-id': userId + }; + + @override + Map toJson() => _generateMap( + details: details, + id: id, + userId: userId + ); + + @override + factory PublicTodosInsert.fromJson(Map jsonObject) { + return PublicTodosInsert( + details: jsonObject['details'] == null ? null : jsonObject['details'] as String, + id: jsonObject['id'] == null ? null : jsonObject['id'] as int, + userId: jsonObject['user-id'] as int + ); + } + + PublicTodosInsert copyWith({ + String? details, + int? id, + int? userId + }) { + return PublicTodosInsert( + details: details ?? this.details, + id: id ?? this.id, + userId: userId ?? this.userId + ); + } + } + class PublicTodosUpdate implements JsonSerializable { + static const tableName = 'todos'; + + final String? details; + final int? id; + final int? userId; + + const PublicTodosUpdate({ + this.details, + this.id, + this.userId + }); + + static Map _generateMap({ + String? details, + int? id, + int? userId + }) => { + if (details != null) 'details': details, + if (id != null) 'id': id, + if (userId != null) 'user-id': userId + }; + + @override + Map toJson() => _generateMap( + details: details, + id: id, + userId: userId + ); + + @override + factory PublicTodosUpdate.fromJson(Map jsonObject) { + return PublicTodosUpdate( + details: jsonObject['details'] == null ? null : jsonObject['details'] as String, + id: jsonObject['id'] == null ? null : jsonObject['id'] as int, + userId: jsonObject['user-id'] == null ? null : jsonObject['user-id'] as int + ); + } + + PublicTodosUpdate copyWith({ + String? details, + int? id, + int? userId + }) { + return PublicTodosUpdate( + details: details ?? this.details, + id: id ?? this.id, + userId: userId ?? this.userId + ); + } + } + + class PublicUsersAuditSelect implements JsonSerializable { + static const tableName = 'users_audit'; + + final DateTime? createdAt; + final int id; + final Map? previousValue; + final int? userId; + + const PublicUsersAuditSelect({ + this.createdAt, + required this.id, + this.previousValue, + this.userId + }); + + static Map _generateMap({ + DateTime? createdAt, + int? id, + Map? previousValue, + int? userId + }) => { + if (createdAt != null) 'created_at': createdAt.toIso8601String(), + if (id != null) 'id': id, + if (previousValue != null) 'previous_value': previousValue, + if (userId != null) 'user_id': userId + }; + + @override + Map toJson() => _generateMap( + createdAt: createdAt, + id: id, + previousValue: previousValue, + userId: userId + ); + + @override + factory PublicUsersAuditSelect.fromJson(Map jsonObject) { + return PublicUsersAuditSelect( + createdAt: jsonObject['created_at'] == null ? null : DateTime.parse(jsonObject['created_at']), + id: jsonObject['id'] as int, + previousValue: jsonObject['previous_value'] == null ? null : jsonObject['previous_value'] as Map, + userId: jsonObject['user_id'] == null ? null : jsonObject['user_id'] as int + ); + } + + PublicUsersAuditSelect copyWith({ + DateTime? createdAt, + int? id, + Map? previousValue, + int? userId + }) { + return PublicUsersAuditSelect( + createdAt: createdAt ?? this.createdAt, + id: id ?? this.id, + previousValue: previousValue ?? this.previousValue, + userId: userId ?? this.userId + ); + } + } + class PublicUsersAuditInsert implements JsonSerializable { + static const tableName = 'users_audit'; + + final DateTime? createdAt; + final int? id; + final Map? previousValue; + final int? userId; + + const PublicUsersAuditInsert({ + this.createdAt, + this.id, + this.previousValue, + this.userId + }); + + static Map _generateMap({ + DateTime? createdAt, + int? id, + Map? previousValue, + int? userId + }) => { + if (createdAt != null) 'created_at': createdAt.toIso8601String(), + if (id != null) 'id': id, + if (previousValue != null) 'previous_value': previousValue, + if (userId != null) 'user_id': userId + }; + + @override + Map toJson() => _generateMap( + createdAt: createdAt, + id: id, + previousValue: previousValue, + userId: userId + ); + + @override + factory PublicUsersAuditInsert.fromJson(Map jsonObject) { + return PublicUsersAuditInsert( + createdAt: jsonObject['created_at'] == null ? null : DateTime.parse(jsonObject['created_at']), + id: jsonObject['id'] == null ? null : jsonObject['id'] as int, + previousValue: jsonObject['previous_value'] == null ? null : jsonObject['previous_value'] as Map, + userId: jsonObject['user_id'] == null ? null : jsonObject['user_id'] as int + ); + } + + PublicUsersAuditInsert copyWith({ + DateTime? createdAt, + int? id, + Map? previousValue, + int? userId + }) { + return PublicUsersAuditInsert( + createdAt: createdAt ?? this.createdAt, + id: id ?? this.id, + previousValue: previousValue ?? this.previousValue, + userId: userId ?? this.userId + ); + } + } + class PublicUsersAuditUpdate implements JsonSerializable { + static const tableName = 'users_audit'; + + final DateTime? createdAt; + final int? id; + final Map? previousValue; + final int? userId; + + const PublicUsersAuditUpdate({ + this.createdAt, + this.id, + this.previousValue, + this.userId + }); + + static Map _generateMap({ + DateTime? createdAt, + int? id, + Map? previousValue, + int? userId + }) => { + if (createdAt != null) 'created_at': createdAt.toIso8601String(), + if (id != null) 'id': id, + if (previousValue != null) 'previous_value': previousValue, + if (userId != null) 'user_id': userId + }; + + @override + Map toJson() => _generateMap( + createdAt: createdAt, + id: id, + previousValue: previousValue, + userId: userId + ); + + @override + factory PublicUsersAuditUpdate.fromJson(Map jsonObject) { + return PublicUsersAuditUpdate( + createdAt: jsonObject['created_at'] == null ? null : DateTime.parse(jsonObject['created_at']), + id: jsonObject['id'] == null ? null : jsonObject['id'] as int, + previousValue: jsonObject['previous_value'] == null ? null : jsonObject['previous_value'] as Map, + userId: jsonObject['user_id'] == null ? null : jsonObject['user_id'] as int + ); + } + + PublicUsersAuditUpdate copyWith({ + DateTime? createdAt, + int? id, + Map? previousValue, + int? userId + }) { + return PublicUsersAuditUpdate( + createdAt: createdAt ?? this.createdAt, + id: id ?? this.id, + previousValue: previousValue ?? this.previousValue, + userId: userId ?? this.userId + ); + } + } + + class PublicUserDetailsSelect implements JsonSerializable { + static const tableName = 'user_details'; + + final String? details; + final int userId; + + const PublicUserDetailsSelect({ + this.details, + required this.userId + }); + + static Map _generateMap({ + String? details, + int? userId + }) => { + if (details != null) 'details': details, + if (userId != null) 'user_id': userId + }; + + @override + Map toJson() => _generateMap( + details: details, + userId: userId + ); + + @override + factory PublicUserDetailsSelect.fromJson(Map jsonObject) { + return PublicUserDetailsSelect( + details: jsonObject['details'] == null ? null : jsonObject['details'] as String, + userId: jsonObject['user_id'] as int + ); + } + + PublicUserDetailsSelect copyWith({ + String? details, + int? userId + }) { + return PublicUserDetailsSelect( + details: details ?? this.details, + userId: userId ?? this.userId + ); + } + } + class PublicUserDetailsInsert implements JsonSerializable { + static const tableName = 'user_details'; + + final String? details; + final int userId; + + const PublicUserDetailsInsert({ + this.details, + required this.userId + }); + + static Map _generateMap({ + String? details, + int? userId + }) => { + if (details != null) 'details': details, + if (userId != null) 'user_id': userId + }; + + @override + Map toJson() => _generateMap( + details: details, + userId: userId + ); + + @override + factory PublicUserDetailsInsert.fromJson(Map jsonObject) { + return PublicUserDetailsInsert( + details: jsonObject['details'] == null ? null : jsonObject['details'] as String, + userId: jsonObject['user_id'] as int + ); + } + + PublicUserDetailsInsert copyWith({ + String? details, + int? userId + }) { + return PublicUserDetailsInsert( + details: details ?? this.details, + userId: userId ?? this.userId + ); + } + } + class PublicUserDetailsUpdate implements JsonSerializable { + static const tableName = 'user_details'; + + final String? details; + final int? userId; + + const PublicUserDetailsUpdate({ + this.details, + this.userId + }); + + static Map _generateMap({ + String? details, + int? userId + }) => { + if (details != null) 'details': details, + if (userId != null) 'user_id': userId + }; + + @override + Map toJson() => _generateMap( + details: details, + userId: userId + ); + + @override + factory PublicUserDetailsUpdate.fromJson(Map jsonObject) { + return PublicUserDetailsUpdate( + details: jsonObject['details'] == null ? null : jsonObject['details'] as String, + userId: jsonObject['user_id'] == null ? null : jsonObject['user_id'] as int + ); + } + + PublicUserDetailsUpdate copyWith({ + String? details, + int? userId + }) { + return PublicUserDetailsUpdate( + details: details ?? this.details, + userId: userId ?? this.userId + ); + } + } + + class PublicEmptySelect implements JsonSerializable { + static const tableName = 'empty'; + + + const PublicEmptySelect(); + + static Map _generateMap() => { + }; + + @override + Map toJson() => _generateMap( + ); + + @override + factory PublicEmptySelect.fromJson(Map jsonObject) { + return PublicEmptySelect( + ); + } + + PublicEmptySelect copyWith() { + return PublicEmptySelect( + ); + } + } + class PublicEmptyInsert implements JsonSerializable { + static const tableName = 'empty'; + + + const PublicEmptyInsert(); + + static Map _generateMap() => { + }; + + @override + Map toJson() => _generateMap( + ); + + @override + factory PublicEmptyInsert.fromJson(Map jsonObject) { + return PublicEmptyInsert( + ); + } + + PublicEmptyInsert copyWith() { + return PublicEmptyInsert( + ); + } + } + class PublicEmptyUpdate implements JsonSerializable { + static const tableName = 'empty'; + + + const PublicEmptyUpdate(); + + static Map _generateMap() => { + }; + + @override + Map toJson() => _generateMap( + ); + + @override + factory PublicEmptyUpdate.fromJson(Map jsonObject) { + return PublicEmptyUpdate( + ); + } + + PublicEmptyUpdate copyWith() { + return PublicEmptyUpdate( + ); + } + } + + class PublicTableWithOtherTablesRowTypeSelect implements JsonSerializable { + static const tableName = 'table_with_other_tables_row_type'; + + final dynamic? col1; + final dynamic? col2; + + const PublicTableWithOtherTablesRowTypeSelect({ + this.col1, + this.col2 + }); + + static Map _generateMap({ + dynamic? col1, + dynamic? col2 + }) => { + if (col1 != null) 'col1': col1, + if (col2 != null) 'col2': col2 + }; + + @override + Map toJson() => _generateMap( + col1: col1, + col2: col2 + ); + + @override + factory PublicTableWithOtherTablesRowTypeSelect.fromJson(Map jsonObject) { + return PublicTableWithOtherTablesRowTypeSelect( + col1: jsonObject['col1'] == null ? null : jsonObject['col1'] as dynamic, + col2: jsonObject['col2'] == null ? null : jsonObject['col2'] as dynamic + ); + } + + PublicTableWithOtherTablesRowTypeSelect copyWith({ + dynamic? col1, + dynamic? col2 + }) { + return PublicTableWithOtherTablesRowTypeSelect( + col1: col1 ?? this.col1, + col2: col2 ?? this.col2 + ); + } + } + class PublicTableWithOtherTablesRowTypeInsert implements JsonSerializable { + static const tableName = 'table_with_other_tables_row_type'; + + final dynamic? col1; + final dynamic? col2; + + const PublicTableWithOtherTablesRowTypeInsert({ + this.col1, + this.col2 + }); + + static Map _generateMap({ + dynamic? col1, + dynamic? col2 + }) => { + if (col1 != null) 'col1': col1, + if (col2 != null) 'col2': col2 + }; + + @override + Map toJson() => _generateMap( + col1: col1, + col2: col2 + ); + + @override + factory PublicTableWithOtherTablesRowTypeInsert.fromJson(Map jsonObject) { + return PublicTableWithOtherTablesRowTypeInsert( + col1: jsonObject['col1'] == null ? null : jsonObject['col1'] as dynamic, + col2: jsonObject['col2'] == null ? null : jsonObject['col2'] as dynamic + ); + } + + PublicTableWithOtherTablesRowTypeInsert copyWith({ + dynamic? col1, + dynamic? col2 + }) { + return PublicTableWithOtherTablesRowTypeInsert( + col1: col1 ?? this.col1, + col2: col2 ?? this.col2 + ); + } + } + class PublicTableWithOtherTablesRowTypeUpdate implements JsonSerializable { + static const tableName = 'table_with_other_tables_row_type'; + + final dynamic? col1; + final dynamic? col2; + + const PublicTableWithOtherTablesRowTypeUpdate({ + this.col1, + this.col2 + }); + + static Map _generateMap({ + dynamic? col1, + dynamic? col2 + }) => { + if (col1 != null) 'col1': col1, + if (col2 != null) 'col2': col2 + }; + + @override + Map toJson() => _generateMap( + col1: col1, + col2: col2 + ); + + @override + factory PublicTableWithOtherTablesRowTypeUpdate.fromJson(Map jsonObject) { + return PublicTableWithOtherTablesRowTypeUpdate( + col1: jsonObject['col1'] == null ? null : jsonObject['col1'] as dynamic, + col2: jsonObject['col2'] == null ? null : jsonObject['col2'] as dynamic + ); + } + + PublicTableWithOtherTablesRowTypeUpdate copyWith({ + dynamic? col1, + dynamic? col2 + }) { + return PublicTableWithOtherTablesRowTypeUpdate( + col1: col1 ?? this.col1, + col2: col2 ?? this.col2 + ); + } + } + + class PublicTableWithPrimaryKeyOtherThanIdSelect implements JsonSerializable { + static const tableName = 'table_with_primary_key_other_than_id'; + + final String? name; + final int otherId; + + const PublicTableWithPrimaryKeyOtherThanIdSelect({ + this.name, + required this.otherId + }); + + static Map _generateMap({ + String? name, + int? otherId + }) => { + if (name != null) 'name': name, + if (otherId != null) 'other_id': otherId + }; + + @override + Map toJson() => _generateMap( + name: name, + otherId: otherId + ); + + @override + factory PublicTableWithPrimaryKeyOtherThanIdSelect.fromJson(Map jsonObject) { + return PublicTableWithPrimaryKeyOtherThanIdSelect( + name: jsonObject['name'] == null ? null : jsonObject['name'] as String, + otherId: jsonObject['other_id'] as int + ); + } + + PublicTableWithPrimaryKeyOtherThanIdSelect copyWith({ + String? name, + int? otherId + }) { + return PublicTableWithPrimaryKeyOtherThanIdSelect( + name: name ?? this.name, + otherId: otherId ?? this.otherId + ); + } + } + class PublicTableWithPrimaryKeyOtherThanIdInsert implements JsonSerializable { + static const tableName = 'table_with_primary_key_other_than_id'; + + final String? name; + final int? otherId; + + const PublicTableWithPrimaryKeyOtherThanIdInsert({ + this.name, + this.otherId + }); + + static Map _generateMap({ + String? name, + int? otherId + }) => { + if (name != null) 'name': name, + if (otherId != null) 'other_id': otherId + }; + + @override + Map toJson() => _generateMap( + name: name, + otherId: otherId + ); + + @override + factory PublicTableWithPrimaryKeyOtherThanIdInsert.fromJson(Map jsonObject) { + return PublicTableWithPrimaryKeyOtherThanIdInsert( + name: jsonObject['name'] == null ? null : jsonObject['name'] as String, + otherId: jsonObject['other_id'] == null ? null : jsonObject['other_id'] as int + ); + } + + PublicTableWithPrimaryKeyOtherThanIdInsert copyWith({ + String? name, + int? otherId + }) { + return PublicTableWithPrimaryKeyOtherThanIdInsert( + name: name ?? this.name, + otherId: otherId ?? this.otherId + ); + } + } + class PublicTableWithPrimaryKeyOtherThanIdUpdate implements JsonSerializable { + static const tableName = 'table_with_primary_key_other_than_id'; + + final String? name; + final int? otherId; + + const PublicTableWithPrimaryKeyOtherThanIdUpdate({ + this.name, + this.otherId + }); + + static Map _generateMap({ + String? name, + int? otherId + }) => { + if (name != null) 'name': name, + if (otherId != null) 'other_id': otherId + }; + + @override + Map toJson() => _generateMap( + name: name, + otherId: otherId + ); + + @override + factory PublicTableWithPrimaryKeyOtherThanIdUpdate.fromJson(Map jsonObject) { + return PublicTableWithPrimaryKeyOtherThanIdUpdate( + name: jsonObject['name'] == null ? null : jsonObject['name'] as String, + otherId: jsonObject['other_id'] == null ? null : jsonObject['other_id'] as int + ); + } + + PublicTableWithPrimaryKeyOtherThanIdUpdate copyWith({ + String? name, + int? otherId + }) { + return PublicTableWithPrimaryKeyOtherThanIdUpdate( + name: name ?? this.name, + otherId: otherId ?? this.otherId + ); + } + } + + class PublicEventsSelect implements JsonSerializable { + static const tableName = 'events'; + + final DateTime createdAt; + final Map? data; + final String? eventType; + final int id; + + const PublicEventsSelect({ + required this.createdAt, + this.data, + this.eventType, + required this.id + }); + + static Map _generateMap({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) => { + if (createdAt != null) 'created_at': createdAt.toIso8601String(), + if (data != null) 'data': data, + if (eventType != null) 'event_type': eventType, + if (id != null) 'id': id + }; + + @override + Map toJson() => _generateMap( + createdAt: createdAt, + data: data, + eventType: eventType, + id: id + ); + + @override + factory PublicEventsSelect.fromJson(Map jsonObject) { + return PublicEventsSelect( + createdAt: DateTime.parse(jsonObject['created_at']), + data: jsonObject['data'] == null ? null : jsonObject['data'] as Map, + eventType: jsonObject['event_type'] == null ? null : jsonObject['event_type'] as String, + id: jsonObject['id'] as int + ); + } + + PublicEventsSelect copyWith({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) { + return PublicEventsSelect( + createdAt: createdAt ?? this.createdAt, + data: data ?? this.data, + eventType: eventType ?? this.eventType, + id: id ?? this.id + ); + } + } + class PublicEventsInsert implements JsonSerializable { + static const tableName = 'events'; + + final DateTime? createdAt; + final Map? data; + final String? eventType; + final int? id; + + const PublicEventsInsert({ + this.createdAt, + this.data, + this.eventType, + this.id + }); + + static Map _generateMap({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) => { + if (createdAt != null) 'created_at': createdAt.toIso8601String(), + if (data != null) 'data': data, + if (eventType != null) 'event_type': eventType, + if (id != null) 'id': id + }; + + @override + Map toJson() => _generateMap( + createdAt: createdAt, + data: data, + eventType: eventType, + id: id + ); + + @override + factory PublicEventsInsert.fromJson(Map jsonObject) { + return PublicEventsInsert( + createdAt: jsonObject['created_at'] == null ? null : DateTime.parse(jsonObject['created_at']), + data: jsonObject['data'] == null ? null : jsonObject['data'] as Map, + eventType: jsonObject['event_type'] == null ? null : jsonObject['event_type'] as String, + id: jsonObject['id'] == null ? null : jsonObject['id'] as int + ); + } + + PublicEventsInsert copyWith({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) { + return PublicEventsInsert( + createdAt: createdAt ?? this.createdAt, + data: data ?? this.data, + eventType: eventType ?? this.eventType, + id: id ?? this.id + ); + } + } + class PublicEventsUpdate implements JsonSerializable { + static const tableName = 'events'; + + final DateTime? createdAt; + final Map? data; + final String? eventType; + final int? id; + + const PublicEventsUpdate({ + this.createdAt, + this.data, + this.eventType, + this.id + }); + + static Map _generateMap({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) => { + if (createdAt != null) 'created_at': createdAt.toIso8601String(), + if (data != null) 'data': data, + if (eventType != null) 'event_type': eventType, + if (id != null) 'id': id + }; + + @override + Map toJson() => _generateMap( + createdAt: createdAt, + data: data, + eventType: eventType, + id: id + ); + + @override + factory PublicEventsUpdate.fromJson(Map jsonObject) { + return PublicEventsUpdate( + createdAt: jsonObject['created_at'] == null ? null : DateTime.parse(jsonObject['created_at']), + data: jsonObject['data'] == null ? null : jsonObject['data'] as Map, + eventType: jsonObject['event_type'] == null ? null : jsonObject['event_type'] as String, + id: jsonObject['id'] == null ? null : jsonObject['id'] as int + ); + } + + PublicEventsUpdate copyWith({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) { + return PublicEventsUpdate( + createdAt: createdAt ?? this.createdAt, + data: data ?? this.data, + eventType: eventType ?? this.eventType, + id: id ?? this.id + ); + } + } + + class PublicEvents2024Select implements JsonSerializable { + static const tableName = 'events_2024'; + + final DateTime createdAt; + final Map? data; + final String? eventType; + final int id; + + const PublicEvents2024Select({ + required this.createdAt, + this.data, + this.eventType, + required this.id + }); + + static Map _generateMap({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) => { + if (createdAt != null) 'created_at': createdAt.toIso8601String(), + if (data != null) 'data': data, + if (eventType != null) 'event_type': eventType, + if (id != null) 'id': id + }; + + @override + Map toJson() => _generateMap( + createdAt: createdAt, + data: data, + eventType: eventType, + id: id + ); + + @override + factory PublicEvents2024Select.fromJson(Map jsonObject) { + return PublicEvents2024Select( + createdAt: DateTime.parse(jsonObject['created_at']), + data: jsonObject['data'] == null ? null : jsonObject['data'] as Map, + eventType: jsonObject['event_type'] == null ? null : jsonObject['event_type'] as String, + id: jsonObject['id'] as int + ); + } + + PublicEvents2024Select copyWith({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) { + return PublicEvents2024Select( + createdAt: createdAt ?? this.createdAt, + data: data ?? this.data, + eventType: eventType ?? this.eventType, + id: id ?? this.id + ); + } + } + class PublicEvents2024Insert implements JsonSerializable { + static const tableName = 'events_2024'; + + final DateTime? createdAt; + final Map? data; + final String? eventType; + final int id; + + const PublicEvents2024Insert({ + this.createdAt, + this.data, + this.eventType, + required this.id + }); + + static Map _generateMap({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) => { + if (createdAt != null) 'created_at': createdAt.toIso8601String(), + if (data != null) 'data': data, + if (eventType != null) 'event_type': eventType, + if (id != null) 'id': id + }; + + @override + Map toJson() => _generateMap( + createdAt: createdAt, + data: data, + eventType: eventType, + id: id + ); + + @override + factory PublicEvents2024Insert.fromJson(Map jsonObject) { + return PublicEvents2024Insert( + createdAt: jsonObject['created_at'] == null ? null : DateTime.parse(jsonObject['created_at']), + data: jsonObject['data'] == null ? null : jsonObject['data'] as Map, + eventType: jsonObject['event_type'] == null ? null : jsonObject['event_type'] as String, + id: jsonObject['id'] as int + ); + } + + PublicEvents2024Insert copyWith({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) { + return PublicEvents2024Insert( + createdAt: createdAt ?? this.createdAt, + data: data ?? this.data, + eventType: eventType ?? this.eventType, + id: id ?? this.id + ); + } + } + class PublicEvents2024Update implements JsonSerializable { + static const tableName = 'events_2024'; + + final DateTime? createdAt; + final Map? data; + final String? eventType; + final int? id; + + const PublicEvents2024Update({ + this.createdAt, + this.data, + this.eventType, + this.id + }); + + static Map _generateMap({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) => { + if (createdAt != null) 'created_at': createdAt.toIso8601String(), + if (data != null) 'data': data, + if (eventType != null) 'event_type': eventType, + if (id != null) 'id': id + }; + + @override + Map toJson() => _generateMap( + createdAt: createdAt, + data: data, + eventType: eventType, + id: id + ); + + @override + factory PublicEvents2024Update.fromJson(Map jsonObject) { + return PublicEvents2024Update( + createdAt: jsonObject['created_at'] == null ? null : DateTime.parse(jsonObject['created_at']), + data: jsonObject['data'] == null ? null : jsonObject['data'] as Map, + eventType: jsonObject['event_type'] == null ? null : jsonObject['event_type'] as String, + id: jsonObject['id'] == null ? null : jsonObject['id'] as int + ); + } + + PublicEvents2024Update copyWith({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) { + return PublicEvents2024Update( + createdAt: createdAt ?? this.createdAt, + data: data ?? this.data, + eventType: eventType ?? this.eventType, + id: id ?? this.id + ); + } + } + + class PublicEvents2025Select implements JsonSerializable { + static const tableName = 'events_2025'; + + final DateTime createdAt; + final Map? data; + final String? eventType; + final int id; + + const PublicEvents2025Select({ + required this.createdAt, + this.data, + this.eventType, + required this.id + }); + + static Map _generateMap({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) => { + if (createdAt != null) 'created_at': createdAt.toIso8601String(), + if (data != null) 'data': data, + if (eventType != null) 'event_type': eventType, + if (id != null) 'id': id + }; + + @override + Map toJson() => _generateMap( + createdAt: createdAt, + data: data, + eventType: eventType, + id: id + ); + + @override + factory PublicEvents2025Select.fromJson(Map jsonObject) { + return PublicEvents2025Select( + createdAt: DateTime.parse(jsonObject['created_at']), + data: jsonObject['data'] == null ? null : jsonObject['data'] as Map, + eventType: jsonObject['event_type'] == null ? null : jsonObject['event_type'] as String, + id: jsonObject['id'] as int + ); + } + + PublicEvents2025Select copyWith({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) { + return PublicEvents2025Select( + createdAt: createdAt ?? this.createdAt, + data: data ?? this.data, + eventType: eventType ?? this.eventType, + id: id ?? this.id + ); + } + } + class PublicEvents2025Insert implements JsonSerializable { + static const tableName = 'events_2025'; + + final DateTime? createdAt; + final Map? data; + final String? eventType; + final int id; + + const PublicEvents2025Insert({ + this.createdAt, + this.data, + this.eventType, + required this.id + }); + + static Map _generateMap({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) => { + if (createdAt != null) 'created_at': createdAt.toIso8601String(), + if (data != null) 'data': data, + if (eventType != null) 'event_type': eventType, + if (id != null) 'id': id + }; + + @override + Map toJson() => _generateMap( + createdAt: createdAt, + data: data, + eventType: eventType, + id: id + ); + + @override + factory PublicEvents2025Insert.fromJson(Map jsonObject) { + return PublicEvents2025Insert( + createdAt: jsonObject['created_at'] == null ? null : DateTime.parse(jsonObject['created_at']), + data: jsonObject['data'] == null ? null : jsonObject['data'] as Map, + eventType: jsonObject['event_type'] == null ? null : jsonObject['event_type'] as String, + id: jsonObject['id'] as int + ); + } + + PublicEvents2025Insert copyWith({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) { + return PublicEvents2025Insert( + createdAt: createdAt ?? this.createdAt, + data: data ?? this.data, + eventType: eventType ?? this.eventType, + id: id ?? this.id + ); + } + } + class PublicEvents2025Update implements JsonSerializable { + static const tableName = 'events_2025'; + + final DateTime? createdAt; + final Map? data; + final String? eventType; + final int? id; + + const PublicEvents2025Update({ + this.createdAt, + this.data, + this.eventType, + this.id + }); + + static Map _generateMap({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) => { + if (createdAt != null) 'created_at': createdAt.toIso8601String(), + if (data != null) 'data': data, + if (eventType != null) 'event_type': eventType, + if (id != null) 'id': id + }; + + @override + Map toJson() => _generateMap( + createdAt: createdAt, + data: data, + eventType: eventType, + id: id + ); + + @override + factory PublicEvents2025Update.fromJson(Map jsonObject) { + return PublicEvents2025Update( + createdAt: jsonObject['created_at'] == null ? null : DateTime.parse(jsonObject['created_at']), + data: jsonObject['data'] == null ? null : jsonObject['data'] as Map, + eventType: jsonObject['event_type'] == null ? null : jsonObject['event_type'] as String, + id: jsonObject['id'] == null ? null : jsonObject['id'] as int + ); + } + + PublicEvents2025Update copyWith({ + DateTime? createdAt, + Map? data, + String? eventType, + int? id + }) { + return PublicEvents2025Update( + createdAt: createdAt ?? this.createdAt, + data: data ?? this.data, + eventType: eventType ?? this.eventType, + id: id ?? this.id + ); + } + } + + class PublicCategorySelect implements JsonSerializable { + static const tableName = 'category'; + + final int id; + final String name; + + const PublicCategorySelect({ + required this.id, + required this.name + }); + + static Map _generateMap({ + int? id, + String? name + }) => { + if (id != null) 'id': id, + if (name != null) 'name': name + }; + + @override + Map toJson() => _generateMap( + id: id, + name: name + ); + + @override + factory PublicCategorySelect.fromJson(Map jsonObject) { + return PublicCategorySelect( + id: jsonObject['id'] as int, + name: jsonObject['name'] as String + ); + } + + PublicCategorySelect copyWith({ + int? id, + String? name + }) { + return PublicCategorySelect( + id: id ?? this.id, + name: name ?? this.name + ); + } + } + class PublicCategoryInsert implements JsonSerializable { + static const tableName = 'category'; + + final int? id; + final String name; + + const PublicCategoryInsert({ + this.id, + required this.name + }); + + static Map _generateMap({ + int? id, + String? name + }) => { + if (id != null) 'id': id, + if (name != null) 'name': name + }; + + @override + Map toJson() => _generateMap( + id: id, + name: name + ); + + @override + factory PublicCategoryInsert.fromJson(Map jsonObject) { + return PublicCategoryInsert( + id: jsonObject['id'] == null ? null : jsonObject['id'] as int, + name: jsonObject['name'] as String + ); + } + + PublicCategoryInsert copyWith({ + int? id, + String? name + }) { + return PublicCategoryInsert( + id: id ?? this.id, + name: name ?? this.name + ); + } + } + class PublicCategoryUpdate implements JsonSerializable { + static const tableName = 'category'; + + final int? id; + final String? name; + + const PublicCategoryUpdate({ + this.id, + this.name + }); + + static Map _generateMap({ + int? id, + String? name + }) => { + if (id != null) 'id': id, + if (name != null) 'name': name + }; + + @override + Map toJson() => _generateMap( + id: id, + name: name + ); + + @override + factory PublicCategoryUpdate.fromJson(Map jsonObject) { + return PublicCategoryUpdate( + id: jsonObject['id'] == null ? null : jsonObject['id'] as int, + name: jsonObject['name'] == null ? null : jsonObject['name'] as String + ); + } + + PublicCategoryUpdate copyWith({ + int? id, + String? name + }) { + return PublicCategoryUpdate( + id: id ?? this.id, + name: name ?? this.name + ); + } + } + + class PublicMemesSelect implements JsonSerializable { + static const tableName = 'memes'; + + final int? category; + final DateTime createdAt; + final int id; + final Map? metadata; + final String name; + final PublicMemeStatus? status; + + const PublicMemesSelect({ + this.category, + required this.createdAt, + required this.id, + this.metadata, + required this.name, + this.status + }); + + static Map _generateMap({ + int? category, + DateTime? createdAt, + int? id, + Map? metadata, + String? name, + PublicMemeStatus? status + }) => { + if (category != null) 'category': category, + if (createdAt != null) 'created_at': createdAt.toIso8601String(), + if (id != null) 'id': id, + if (metadata != null) 'metadata': metadata, + if (name != null) 'name': name, + if (status != null) 'status': status.toJson() + }; + + @override + Map toJson() => _generateMap( + category: category, + createdAt: createdAt, + id: id, + metadata: metadata, + name: name, + status: status + ); + + @override + factory PublicMemesSelect.fromJson(Map jsonObject) { + return PublicMemesSelect( + category: jsonObject['category'] == null ? null : jsonObject['category'] as int, + createdAt: DateTime.parse(jsonObject['created_at']), + id: jsonObject['id'] as int, + metadata: jsonObject['metadata'] == null ? null : jsonObject['metadata'] as Map, + name: jsonObject['name'] as String, + status: jsonObject['status'] == null ? null : PublicMemeStatus.fromJson(jsonObject['status']) + ); + } + + PublicMemesSelect copyWith({ + int? category, + DateTime? createdAt, + int? id, + Map? metadata, + String? name, + PublicMemeStatus? status + }) { + return PublicMemesSelect( + category: category ?? this.category, + createdAt: createdAt ?? this.createdAt, + id: id ?? this.id, + metadata: metadata ?? this.metadata, + name: name ?? this.name, + status: status ?? this.status + ); + } + } + class PublicMemesInsert implements JsonSerializable { + static const tableName = 'memes'; + + final int? category; + final DateTime createdAt; + final int? id; + final Map? metadata; + final String name; + final PublicMemeStatus? status; + + const PublicMemesInsert({ + this.category, + required this.createdAt, + this.id, + this.metadata, + required this.name, + this.status + }); + + static Map _generateMap({ + int? category, + DateTime? createdAt, + int? id, + Map? metadata, + String? name, + PublicMemeStatus? status + }) => { + if (category != null) 'category': category, + if (createdAt != null) 'created_at': createdAt.toIso8601String(), + if (id != null) 'id': id, + if (metadata != null) 'metadata': metadata, + if (name != null) 'name': name, + if (status != null) 'status': status.toJson() + }; + + @override + Map toJson() => _generateMap( + category: category, + createdAt: createdAt, + id: id, + metadata: metadata, + name: name, + status: status + ); + + @override + factory PublicMemesInsert.fromJson(Map jsonObject) { + return PublicMemesInsert( + category: jsonObject['category'] == null ? null : jsonObject['category'] as int, + createdAt: DateTime.parse(jsonObject['created_at']), + id: jsonObject['id'] == null ? null : jsonObject['id'] as int, + metadata: jsonObject['metadata'] == null ? null : jsonObject['metadata'] as Map, + name: jsonObject['name'] as String, + status: jsonObject['status'] == null ? null : PublicMemeStatus.fromJson(jsonObject['status']) + ); + } + + PublicMemesInsert copyWith({ + int? category, + DateTime? createdAt, + int? id, + Map? metadata, + String? name, + PublicMemeStatus? status + }) { + return PublicMemesInsert( + category: category ?? this.category, + createdAt: createdAt ?? this.createdAt, + id: id ?? this.id, + metadata: metadata ?? this.metadata, + name: name ?? this.name, + status: status ?? this.status + ); + } + } + class PublicMemesUpdate implements JsonSerializable { + static const tableName = 'memes'; + + final int? category; + final DateTime? createdAt; + final int? id; + final Map? metadata; + final String? name; + final PublicMemeStatus? status; + + const PublicMemesUpdate({ + this.category, + this.createdAt, + this.id, + this.metadata, + this.name, + this.status + }); + + static Map _generateMap({ + int? category, + DateTime? createdAt, + int? id, + Map? metadata, + String? name, + PublicMemeStatus? status + }) => { + if (category != null) 'category': category, + if (createdAt != null) 'created_at': createdAt.toIso8601String(), + if (id != null) 'id': id, + if (metadata != null) 'metadata': metadata, + if (name != null) 'name': name, + if (status != null) 'status': status.toJson() + }; + + @override + Map toJson() => _generateMap( + category: category, + createdAt: createdAt, + id: id, + metadata: metadata, + name: name, + status: status + ); + + @override + factory PublicMemesUpdate.fromJson(Map jsonObject) { + return PublicMemesUpdate( + category: jsonObject['category'] == null ? null : jsonObject['category'] as int, + createdAt: jsonObject['created_at'] == null ? null : DateTime.parse(jsonObject['created_at']), + id: jsonObject['id'] == null ? null : jsonObject['id'] as int, + metadata: jsonObject['metadata'] == null ? null : jsonObject['metadata'] as Map, + name: jsonObject['name'] == null ? null : jsonObject['name'] as String, + status: jsonObject['status'] == null ? null : PublicMemeStatus.fromJson(jsonObject['status']) + ); + } + + PublicMemesUpdate copyWith({ + int? category, + DateTime? createdAt, + int? id, + Map? metadata, + String? name, + PublicMemeStatus? status + }) { + return PublicMemesUpdate( + category: category ?? this.category, + createdAt: createdAt ?? this.createdAt, + id: id ?? this.id, + metadata: metadata ?? this.metadata, + name: name ?? this.name, + status: status ?? this.status + ); + } + } + + class PublicAViewSelect implements JsonSerializable { + static const tableName = 'a_view'; + + final int? id; + + const PublicAViewSelect({ + this.id + }); + + static Map _generateMap({ + int? id + }) => { + if (id != null) 'id': id + }; + + @override + Map toJson() => _generateMap( + id: id + ); + + @override + factory PublicAViewSelect.fromJson(Map jsonObject) { + return PublicAViewSelect( + id: jsonObject['id'] == null ? null : jsonObject['id'] as int + ); + } + + PublicAViewSelect copyWith({ + int? id + }) { + return PublicAViewSelect( + id: id ?? this.id + ); + } + } + + + + class PublicTodosViewSelect implements JsonSerializable { + static const tableName = 'todos_view'; + + final String? details; + final int? id; + final int? userId; + + const PublicTodosViewSelect({ + this.details, + this.id, + this.userId + }); + + static Map _generateMap({ + String? details, + int? id, + int? userId + }) => { + if (details != null) 'details': details, + if (id != null) 'id': id, + if (userId != null) 'user-id': userId + }; + + @override + Map toJson() => _generateMap( + details: details, + id: id, + userId: userId + ); + + @override + factory PublicTodosViewSelect.fromJson(Map jsonObject) { + return PublicTodosViewSelect( + details: jsonObject['details'] == null ? null : jsonObject['details'] as String, + id: jsonObject['id'] == null ? null : jsonObject['id'] as int, + userId: jsonObject['user-id'] == null ? null : jsonObject['user-id'] as int + ); + } + + PublicTodosViewSelect copyWith({ + String? details, + int? id, + int? userId + }) { + return PublicTodosViewSelect( + details: details ?? this.details, + id: id ?? this.id, + userId: userId ?? this.userId + ); + } + } + + + + class PublicUsersViewSelect implements JsonSerializable { + static const tableName = 'users_view'; + + final double? decimal; + final int? id; + final String? name; + final PublicUserStatus? status; + + const PublicUsersViewSelect({ + this.decimal, + this.id, + this.name, + this.status + }); + + static Map _generateMap({ + double? decimal, + int? id, + String? name, + PublicUserStatus? status + }) => { + if (decimal != null) 'decimal': decimal, + if (id != null) 'id': id, + if (name != null) 'name': name, + if (status != null) 'status': status.toJson() + }; + + @override + Map toJson() => _generateMap( + decimal: decimal, + id: id, + name: name, + status: status + ); + + @override + factory PublicUsersViewSelect.fromJson(Map jsonObject) { + return PublicUsersViewSelect( + decimal: jsonObject['decimal'] == null ? null : jsonObject['decimal'] as double, + id: jsonObject['id'] == null ? null : jsonObject['id'] as int, + name: jsonObject['name'] == null ? null : jsonObject['name'] as String, + status: jsonObject['status'] == null ? null : PublicUserStatus.fromJson(jsonObject['status']) + ); + } + + PublicUsersViewSelect copyWith({ + double? decimal, + int? id, + String? name, + PublicUserStatus? status + }) { + return PublicUsersViewSelect( + decimal: decimal ?? this.decimal, + id: id ?? this.id, + name: name ?? this.name, + status: status ?? this.status + ); + } + } + + + + class PublicUserTodosSummaryViewSelect implements JsonSerializable { + static const tableName = 'user_todos_summary_view'; + + final int? todoCount; + final List? todoDetails; + final int? userId; + final String? userName; + final PublicUserStatus? userStatus; + + const PublicUserTodosSummaryViewSelect({ + this.todoCount, + this.todoDetails, + this.userId, + this.userName, + this.userStatus + }); + + static Map _generateMap({ + int? todoCount, + List? todoDetails, + int? userId, + String? userName, + PublicUserStatus? userStatus + }) => { + if (todoCount != null) 'todo_count': todoCount, + if (todoDetails != null) 'todo_details': todoDetails.map((v) => v).toList(), + if (userId != null) 'user_id': userId, + if (userName != null) 'user_name': userName, + if (userStatus != null) 'user_status': userStatus.toJson() + }; + + @override + Map toJson() => _generateMap( + todoCount: todoCount, + todoDetails: todoDetails, + userId: userId, + userName: userName, + userStatus: userStatus + ); + + @override + factory PublicUserTodosSummaryViewSelect.fromJson(Map jsonObject) { + return PublicUserTodosSummaryViewSelect( + todoCount: jsonObject['todo_count'] == null ? null : jsonObject['todo_count'] as int, + todoDetails: jsonObject['todo_details'] == null ? null : (jsonObject['todo_details'] as List).map((v) => v as String).toList(), + userId: jsonObject['user_id'] == null ? null : jsonObject['user_id'] as int, + userName: jsonObject['user_name'] == null ? null : jsonObject['user_name'] as String, + userStatus: jsonObject['user_status'] == null ? null : PublicUserStatus.fromJson(jsonObject['user_status']) + ); + } + + PublicUserTodosSummaryViewSelect copyWith({ + int? todoCount, + List? todoDetails, + int? userId, + String? userName, + PublicUserStatus? userStatus + }) { + return PublicUserTodosSummaryViewSelect( + todoCount: todoCount ?? this.todoCount, + todoDetails: todoDetails ?? this.todoDetails, + userId: userId ?? this.userId, + userName: userName ?? this.userName, + userStatus: userStatus ?? this.userStatus + ); + } + } + + + + class PublicUsersViewWithMultipleRefsToUsersSelect implements JsonSerializable { + static const tableName = 'users_view_with_multiple_refs_to_users'; + + final int? initialId; + final String? initialName; + final int? secondId; + final String? secondName; + + const PublicUsersViewWithMultipleRefsToUsersSelect({ + this.initialId, + this.initialName, + this.secondId, + this.secondName + }); + + static Map _generateMap({ + int? initialId, + String? initialName, + int? secondId, + String? secondName + }) => { + if (initialId != null) 'initial_id': initialId, + if (initialName != null) 'initial_name': initialName, + if (secondId != null) 'second_id': secondId, + if (secondName != null) 'second_name': secondName + }; + + @override + Map toJson() => _generateMap( + initialId: initialId, + initialName: initialName, + secondId: secondId, + secondName: secondName + ); + + @override + factory PublicUsersViewWithMultipleRefsToUsersSelect.fromJson(Map jsonObject) { + return PublicUsersViewWithMultipleRefsToUsersSelect( + initialId: jsonObject['initial_id'] == null ? null : jsonObject['initial_id'] as int, + initialName: jsonObject['initial_name'] == null ? null : jsonObject['initial_name'] as String, + secondId: jsonObject['second_id'] == null ? null : jsonObject['second_id'] as int, + secondName: jsonObject['second_name'] == null ? null : jsonObject['second_name'] as String + ); + } + + PublicUsersViewWithMultipleRefsToUsersSelect copyWith({ + int? initialId, + String? initialName, + int? secondId, + String? secondName + }) { + return PublicUsersViewWithMultipleRefsToUsersSelect( + initialId: initialId ?? this.initialId, + initialName: initialName ?? this.initialName, + secondId: secondId ?? this.secondId, + secondName: secondName ?? this.secondName + ); + } + } + + "`) +}) From 0db73b72d6863d95caafbb74d2cab8d3ee480fc7 Mon Sep 17 00:00:00 2001 From: Leonardo Mosciatti Date: Sun, 2 Nov 2025 18:32:33 +0100 Subject: [PATCH 11/12] add class for composite types --- src/server/templates/dart.ts | 103 ++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/src/server/templates/dart.ts b/src/server/templates/dart.ts index bc442360..76e45546 100644 --- a/src/server/templates/dart.ts +++ b/src/server/templates/dart.ts @@ -568,6 +568,98 @@ ${this.operations.indexOf('Update') !== -1 ? this.generateUpdateDeclaration() : } } +class ClassDartConstructForCompositeType implements DartType, Declarable { + postgresType: PostgresType + ptdMap: PostgresToDartMap + name: string + + constructor(postgresType: PostgresType, ptdMap: PostgresToDartMap) { + this.postgresType = postgresType + this.ptdMap = ptdMap + this.name = `${formatForDartClassName(this.postgresType.schema)}${formatForDartClassName(this.postgresType.name)}` + } + + generateType(): string { + return this.name + } + + generateDeclaration(): string { + return `class ${this.name} implements JsonSerializable {${this.postgresType.attributes + .map( + (attr) => ` + final ${new NullDartType(this.ptdMap[attr.type_id][1]).generateType()} ${formatForDartPropertyName(attr.name)};` + ) + .join('')} + + const ${this.name}({${this.postgresType.attributes + .map((attr) => { + return ` + this.${formatForDartPropertyName(attr.name)}` + }) + .join(',')} + }); + + static Map _generateMap({${this.postgresType.attributes + .map((attr) => { + return ` + ${new NullDartType(this.ptdMap[attr.type_id][1]).generateType()} ${formatForDartPropertyName(attr.name)}` + }) + .join(',')} + }) => {${this.postgresType.attributes + .map((attr) => { + return ` + if (${formatForDartPropertyName(attr.name)} != null) '${attr.name}': ${formatForDartPropertyName(attr.name)}${this.ptdMap[attr.type_id][1].generateJsonEncoding()}` + }) + .join(',')} + }; + + @override + Map toJson() => _generateMap(${this.postgresType.attributes + .map((attr) => { + return ` + ${formatForDartPropertyName(attr.name)}: ${formatForDartPropertyName(attr.name)}` + }) + .join(',')} + ); + + ${this.name} copyWith({${this.postgresType.attributes + .map((attr) => { + return ` + ${new NullDartType(this.ptdMap[attr.type_id][1]).generateType()} ${formatForDartPropertyName(attr.name)}` + }) + .join(',')} + }) { + return ${this.name}(${this.postgresType.attributes + .map((attr) => { + return ` + ${formatForDartPropertyName(attr.name)}: ${formatForDartPropertyName(attr.name)} ?? this.${formatForDartPropertyName(attr.name)}` + }) + .join(',')} + ); + } + + @override + factory ${this.name}.fromJson(Map jsonObject) { + return ${this.name}(${this.postgresType.attributes + .map((attr) => { + return ` + ${formatForDartPropertyName(attr.name)}: ${new NullDartType(this.ptdMap[attr.type_id][1]).generateJsonDecoding(`jsonObject['${attr.name}']`)}` + }) + .join(',')} + ); + } +}` + } + + generateJsonEncoding(): string { + return '.toJson()' + } + + generateJsonDecoding(inputParameter: string): string { + return `${this.name}.fromJson(${inputParameter})` + } +} + type PostgresToDartMap = Record const PGTYPE_TO_DARTTYPE_MAP: Record = { @@ -653,6 +745,12 @@ function buildDartTypeFromPostgresType( return postgresType.name.startsWith('_') ? new ListDartType(enumConstruct) : enumConstruct } + // Composite type + if (postgresType.attributes.length > 0) { + const compositeType = new ClassDartConstructForCompositeType(postgresType, ptdMap) + return postgresType.name.startsWith('_') ? new ListDartType(compositeType) : compositeType + } + return new BuiltinDartType('dynamic') } @@ -815,7 +913,10 @@ export const apply = ({ schemas, tables, views, columns, types }: GeneratorMetad const newDartType = buildDartTypeFromPostgresType(t, ptdMap) ptdMap[t.id] = [t, newDartType] ptdMap[t.name] = [t, newDartType] - if (newDartType instanceof EnumDartConstruct) { + if ( + newDartType instanceof EnumDartConstruct || + newDartType instanceof ClassDartConstructForCompositeType + ) { declarableTypes.push(newDartType) } } From 2cf959f02753f44875ebd27ab1a96e06c55c4d29 Mon Sep 17 00:00:00 2001 From: Leonardo Mosciatti Date: Fri, 5 Dec 2025 18:07:53 +0100 Subject: [PATCH 12/12] add back wrongly removed python generation route line --- src/server/routes/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/routes/index.ts b/src/server/routes/index.ts index bcb76f80..2956791a 100644 --- a/src/server/routes/index.ts +++ b/src/server/routes/index.ts @@ -84,5 +84,6 @@ export default async (fastify: FastifyInstance) => { fastify.register(TypeScriptTypeGenRoute, { prefix: '/generators/typescript' }) fastify.register(GoTypeGenRoute, { prefix: '/generators/go' }) fastify.register(SwiftTypeGenRoute, { prefix: '/generators/swift' }) + fastify.register(PythonTypeGenRoute, { prefix: '/generators/python' }) fastify.register(DartTypeGenRoute, { prefix: '/generators/dart' }) }