@syncular/typegen 0.0.1-60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/render.js ADDED
@@ -0,0 +1,140 @@
1
+ /**
2
+ * @syncular/typegen - TypeScript code generation
3
+ */
4
+ /**
5
+ * Convert a snake_case table/column name to PascalCase.
6
+ */
7
+ function toPascalCase(str) {
8
+ return str
9
+ .split(/[_-]/)
10
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
11
+ .join('');
12
+ }
13
+ /**
14
+ * Render a single column definition.
15
+ */
16
+ function renderColumn(column) {
17
+ // Use optional modifier for nullable columns with defaults (Generated type pattern)
18
+ const optional = column.hasDefault || column.nullable ? '?' : '';
19
+ return ` ${column.name}${optional}: ${column.tsType};`;
20
+ }
21
+ /**
22
+ * Render a table interface.
23
+ */
24
+ function renderTableInterface(table, interfaceName) {
25
+ const columns = table.columns.map(renderColumn).join('\n');
26
+ return `export interface ${interfaceName} {\n${columns}\n}`;
27
+ }
28
+ /**
29
+ * Render a database interface containing all tables.
30
+ */
31
+ function renderDbInterface(schema, interfaceName, extendsType) {
32
+ const extendsClause = extendsType ? ` extends ${extendsType}` : '';
33
+ const tableEntries = schema.tables
34
+ .map((t) => ` ${t.name}: ${toPascalCase(t.name)}Table;`)
35
+ .join('\n');
36
+ return `export interface ${interfaceName}${extendsClause} {\n${tableEntries}\n}`;
37
+ }
38
+ /**
39
+ * Render complete TypeScript type definitions.
40
+ */
41
+ export function renderTypes(options) {
42
+ const { schemas, extendsSyncClientDb, includeVersionHistory, customImports } = options;
43
+ const lines = [];
44
+ // Header
45
+ lines.push('/**');
46
+ lines.push(' * Auto-generated database types from migrations.');
47
+ lines.push(' * DO NOT EDIT - regenerate with @syncular/typegen');
48
+ lines.push(' */');
49
+ lines.push('');
50
+ // Import SyncClientDb if extending
51
+ if (extendsSyncClientDb) {
52
+ lines.push("import type { SyncClientDb } from '@syncular/client';");
53
+ lines.push('');
54
+ }
55
+ // Render custom imports from resolver
56
+ if (customImports && customImports.length > 0) {
57
+ // Group imports by source module
58
+ const byModule = new Map();
59
+ for (const imp of customImports) {
60
+ let names = byModule.get(imp.from);
61
+ if (!names) {
62
+ names = new Set();
63
+ byModule.set(imp.from, names);
64
+ }
65
+ names.add(imp.name);
66
+ }
67
+ for (const [from, names] of byModule) {
68
+ const sorted = [...names].sort();
69
+ lines.push(`import type { ${sorted.join(', ')} } from '${from}';`);
70
+ }
71
+ lines.push('');
72
+ }
73
+ // Get the latest schema
74
+ const latestSchema = schemas[schemas.length - 1];
75
+ if (!latestSchema) {
76
+ lines.push('// No migrations defined');
77
+ return lines.join('\n');
78
+ }
79
+ // Generate table interfaces for latest version
80
+ for (const table of latestSchema.tables) {
81
+ lines.push(renderTableInterface(table, `${toPascalCase(table.name)}Table`));
82
+ lines.push('');
83
+ }
84
+ // Generate versioned DB interfaces if requested
85
+ if (includeVersionHistory && schemas.length > 0) {
86
+ for (const schema of schemas) {
87
+ // For each version, generate table interfaces with version suffix
88
+ // if they differ from the latest
89
+ const versionSuffix = `V${schema.version}`;
90
+ // Generate versioned table interfaces
91
+ for (const table of schema.tables) {
92
+ const latestTable = latestSchema.tables.find((t) => t.name === table.name);
93
+ // Only generate versioned interface if different from latest
94
+ if (latestTable && !tablesEqual(table, latestTable)) {
95
+ lines.push(renderTableInterface(table, `${toPascalCase(table.name)}Table${versionSuffix}`));
96
+ lines.push('');
97
+ }
98
+ }
99
+ // Generate versioned DB interface
100
+ const tableEntries = schema.tables
101
+ .map((t) => {
102
+ const latestTable = latestSchema.tables.find((lt) => lt.name === t.name);
103
+ const useVersioned = latestTable && !tablesEqual(t, latestTable);
104
+ const typeName = useVersioned
105
+ ? `${toPascalCase(t.name)}Table${versionSuffix}`
106
+ : `${toPascalCase(t.name)}Table`;
107
+ return ` ${t.name}: ${typeName};`;
108
+ })
109
+ .join('\n');
110
+ lines.push(`export interface ClientDb${versionSuffix} {`);
111
+ lines.push(tableEntries);
112
+ lines.push('}');
113
+ lines.push('');
114
+ }
115
+ }
116
+ // Generate main DB interface (latest version)
117
+ const extendsType = extendsSyncClientDb ? 'SyncClientDb' : undefined;
118
+ lines.push(renderDbInterface(latestSchema, 'ClientDb', extendsType));
119
+ lines.push('');
120
+ return lines.join('\n');
121
+ }
122
+ /**
123
+ * Check if two table schemas are equal.
124
+ */
125
+ function tablesEqual(a, b) {
126
+ if (a.columns.length !== b.columns.length)
127
+ return false;
128
+ for (let i = 0; i < a.columns.length; i++) {
129
+ const colA = a.columns[i];
130
+ const colB = b.columns[i];
131
+ if (colA.name !== colB.name ||
132
+ colA.tsType !== colB.tsType ||
133
+ colA.nullable !== colB.nullable ||
134
+ colA.hasDefault !== colB.hasDefault) {
135
+ return false;
136
+ }
137
+ }
138
+ return true;
139
+ }
140
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;GAEG;AACH,SAAS,YAAY,CAAC,GAAW,EAAU;IACzC,OAAO,GAAG;SACP,KAAK,CAAC,MAAM,CAAC;SACb,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SACzE,IAAI,CAAC,EAAE,CAAC,CAAC;AAAA,CACb;AAED;;GAEG;AACH,SAAS,YAAY,CAAC,MAAoB,EAAU;IAClD,oFAAoF;IACpF,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACjE,OAAO,KAAK,MAAM,CAAC,IAAI,GAAG,QAAQ,KAAK,MAAM,CAAC,MAAM,GAAG,CAAC;AAAA,CACzD;AAED;;GAEG;AACH,SAAS,oBAAoB,CAC3B,KAAkB,EAClB,aAAqB,EACb;IACR,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3D,OAAO,oBAAoB,aAAa,OAAO,OAAO,KAAK,CAAC;AAAA,CAC7D;AAED;;GAEG;AACH,SAAS,iBAAiB,CACxB,MAAuB,EACvB,aAAqB,EACrB,WAAoB,EACZ;IACR,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,CAAC,YAAY,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACnE,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM;SAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC;SACxD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO,oBAAoB,aAAa,GAAG,aAAa,OAAO,YAAY,KAAK,CAAC;AAAA,CAClF;AAgBD;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,OAAsB,EAAU;IAC1D,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,aAAa,EAAE,GAC1E,OAAO,CAAC;IACV,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,SAAS;IACT,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,KAAK,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;IAChE,KAAK,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;IACjE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,mCAAmC;IACnC,IAAI,mBAAmB,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;QACpE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,sCAAsC;IACtC,IAAI,aAAa,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9C,iCAAiC;QACjC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;QAChD,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;YAChC,IAAI,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACnC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,KAAK,GAAG,IAAI,GAAG,EAAE,CAAC;gBAClB,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAChC,CAAC;YACD,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,QAAQ,EAAE,CAAC;YACrC,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;YACjC,KAAK,CAAC,IAAI,CAAC,iBAAiB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC;QACrE,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,wBAAwB;IACxB,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACjD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,KAAK,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QACvC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,+CAA+C;IAC/C,KAAK,MAAM,KAAK,IAAI,YAAY,CAAC,MAAM,EAAE,CAAC;QACxC,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,KAAK,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5E,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,gDAAgD;IAChD,IAAI,qBAAqB,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,kEAAkE;YAClE,iCAAiC;YACjC,MAAM,aAAa,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YAE3C,sCAAsC;YACtC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClC,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,CAC1C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAC7B,CAAC;gBAEF,6DAA6D;gBAC7D,IAAI,WAAW,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC;oBACpD,KAAK,CAAC,IAAI,CACR,oBAAoB,CAClB,KAAK,EACL,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,aAAa,EAAE,CACnD,CACF,CAAC;oBACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACjB,CAAC;YACH,CAAC;YAED,kCAAkC;YAClC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM;iBAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBACV,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,CAC1C,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAC3B,CAAC;gBACF,MAAM,YAAY,GAAG,WAAW,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;gBACjE,MAAM,QAAQ,GAAG,YAAY;oBAC3B,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,aAAa,EAAE;oBAChD,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;gBACnC,OAAO,KAAK,CAAC,CAAC,IAAI,KAAK,QAAQ,GAAG,CAAC;YAAA,CACpC,CAAC;iBACD,IAAI,CAAC,IAAI,CAAC,CAAC;YAEd,KAAK,CAAC,IAAI,CAAC,4BAA4B,aAAa,IAAI,CAAC,CAAC;YAC1D,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACzB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAChB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;IAED,8CAA8C;IAC9C,MAAM,WAAW,GAAG,mBAAmB,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC;IACrE,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,YAAY,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC;IACrE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACzB;AAED;;GAEG;AACH,SAAS,WAAW,CAAC,CAAc,EAAE,CAAc,EAAW;IAC5D,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAExD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC;QAE3B,IACE,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;YACvB,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;YAC3B,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ;YAC/B,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC,UAAU,EACnC,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AAAA,CACb"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * @syncular/typegen - Type definitions
3
+ */
4
+ import type { DefinedMigrations } from '@syncular/migrations';
5
+ export type TypegenDialect = 'sqlite' | 'postgres';
6
+ /**
7
+ * Column information passed to the resolver function.
8
+ */
9
+ export interface ColumnInfo {
10
+ table: string;
11
+ column: string;
12
+ sqlType: string;
13
+ nullable: boolean;
14
+ isPrimaryKey: boolean;
15
+ hasDefault: boolean;
16
+ dialect: TypegenDialect;
17
+ }
18
+ /**
19
+ * Return type from a resolver function.
20
+ */
21
+ export type TypeOverride = string | {
22
+ type: string;
23
+ import?: {
24
+ name: string;
25
+ from: string;
26
+ };
27
+ };
28
+ /**
29
+ * User-provided function to override default type mapping.
30
+ */
31
+ export type ResolveTypeFn = (col: ColumnInfo) => TypeOverride | undefined;
32
+ /**
33
+ * Parsed table schema.
34
+ */
35
+ export interface TableSchema {
36
+ name: string;
37
+ columns: ColumnSchema[];
38
+ }
39
+ /**
40
+ * Parsed column schema.
41
+ */
42
+ export interface ColumnSchema {
43
+ name: string;
44
+ sqlType: string;
45
+ tsType: string;
46
+ nullable: boolean;
47
+ isPrimaryKey: boolean;
48
+ hasDefault: boolean;
49
+ }
50
+ /**
51
+ * Schema snapshot at a specific version.
52
+ */
53
+ export interface VersionedSchema {
54
+ version: number;
55
+ tables: TableSchema[];
56
+ }
57
+ /**
58
+ * Options for generateTypes().
59
+ */
60
+ export interface GenerateTypesOptions<DB = unknown> {
61
+ /** Defined migrations from defineMigrations() */
62
+ migrations: DefinedMigrations<DB>;
63
+ /** Output file path for generated types */
64
+ output: string;
65
+ /** Database dialect to use for introspection (default: 'sqlite') */
66
+ dialect?: TypegenDialect;
67
+ /** Whether to extend SyncClientDb interface (adds sync infrastructure types) */
68
+ extendsSyncClientDb?: boolean;
69
+ /** Generate versioned interfaces (ClientDbV1, ClientDbV2, etc.) */
70
+ includeVersionHistory?: boolean;
71
+ /** Only generate types for these tables (default: all tables) */
72
+ tables?: string[];
73
+ /** Custom type resolver for overriding default type mapping */
74
+ resolveType?: ResolveTypeFn;
75
+ }
76
+ /**
77
+ * Result of type generation.
78
+ */
79
+ export interface GenerateTypesResult {
80
+ /** Path to the generated file */
81
+ outputPath: string;
82
+ /** Current schema version */
83
+ currentVersion: number;
84
+ /** Number of tables generated */
85
+ tableCount: number;
86
+ /** Generated TypeScript code */
87
+ code: string;
88
+ }
89
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEnD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,cAAc,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,MAAM,GACN;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAE9D;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,UAAU,KAAK,YAAY,GAAG,SAAS,CAAC;AAE1E;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB,CAAC,EAAE,GAAG,OAAO;IAChD,iDAAiD;IACjD,UAAU,EAAE,iBAAiB,CAAC,EAAE,CAAC,CAAC;IAClC,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,gFAAgF;IAChF,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,mEAAmE;IACnE,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,aAAa,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,6BAA6B;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd"}
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @syncular/typegen - Type definitions
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@syncular/typegen",
3
+ "version": "0.0.1-60",
4
+ "description": "TypeScript type generator for Syncular schemas",
5
+ "license": "MIT",
6
+ "author": "Benjamin Kniffler",
7
+ "homepage": "https://syncular.dev",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/syncular/syncular.git",
11
+ "directory": "packages/typegen"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/syncular/syncular/issues"
15
+ },
16
+ "keywords": [
17
+ "sync",
18
+ "offline-first",
19
+ "realtime",
20
+ "database",
21
+ "typescript",
22
+ "codegen",
23
+ "typescript"
24
+ ],
25
+ "private": false,
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "type": "module",
30
+ "exports": {
31
+ ".": {
32
+ "bun": "./src/index.ts",
33
+ "import": {
34
+ "types": "./dist/index.d.ts",
35
+ "default": "./dist/index.js"
36
+ }
37
+ }
38
+ },
39
+ "scripts": {
40
+ "test": "bun test --pass-with-no-tests",
41
+ "tsgo": "tsgo --noEmit",
42
+ "build": "rm -rf dist && tsgo",
43
+ "release": "bun pm pack --destination . && npm publish ./*.tgz --tag latest && rm -f ./*.tgz"
44
+ },
45
+ "dependencies": {
46
+ "@electric-sql/pglite": "^0.3.15",
47
+ "@syncular/migrations": "0.0.1",
48
+ "better-sqlite3": "^12.6.2",
49
+ "kysely-pglite-dialect": "^1.2.0"
50
+ },
51
+ "peerDependencies": {
52
+ "kysely": "^0.28.0"
53
+ },
54
+ "devDependencies": {
55
+ "@syncular/config": "0.0.0",
56
+ "@types/better-sqlite3": "^7.6.13",
57
+ "kysely": "*"
58
+ },
59
+ "files": [
60
+ "dist",
61
+ "src"
62
+ ]
63
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @syncular/typegen - Type generation entry point
3
+ */
4
+
5
+ import { mkdir, writeFile } from 'node:fs/promises';
6
+ import { dirname } from 'node:path';
7
+ import { introspectAllVersions, introspectCurrentSchema } from './introspect';
8
+ import { resolveColumnType } from './map-types';
9
+ import { renderTypes } from './render';
10
+ import type {
11
+ GenerateTypesOptions,
12
+ GenerateTypesResult,
13
+ ResolveTypeFn,
14
+ TypegenDialect,
15
+ VersionedSchema,
16
+ } from './types';
17
+
18
+ /**
19
+ * Apply type mapping to all columns in the schemas.
20
+ * Returns the schemas with tsType filled in and any custom imports collected.
21
+ */
22
+ function applyTypeMappings(
23
+ schemas: VersionedSchema[],
24
+ dialect: TypegenDialect,
25
+ resolveType?: ResolveTypeFn
26
+ ): {
27
+ schemas: VersionedSchema[];
28
+ customImports: Array<{ name: string; from: string }>;
29
+ } {
30
+ const allImports: Array<{ name: string; from: string }> = [];
31
+
32
+ const mapped = schemas.map((schema) => ({
33
+ ...schema,
34
+ tables: schema.tables.map((table) => ({
35
+ ...table,
36
+ columns: table.columns.map((col) => {
37
+ const resolved = resolveColumnType(
38
+ {
39
+ table: table.name,
40
+ column: col.name,
41
+ sqlType: col.sqlType,
42
+ nullable: col.nullable,
43
+ isPrimaryKey: col.isPrimaryKey,
44
+ hasDefault: col.hasDefault,
45
+ dialect,
46
+ },
47
+ resolveType
48
+ );
49
+ allImports.push(...resolved.imports);
50
+ return {
51
+ ...col,
52
+ tsType: resolved.tsType,
53
+ };
54
+ }),
55
+ })),
56
+ }));
57
+
58
+ return { schemas: mapped, customImports: allImports };
59
+ }
60
+
61
+ /**
62
+ * Generate TypeScript types from migrations.
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * import { generateTypes } from '@syncular/typegen';
67
+ * import { migrations } from './migrations';
68
+ *
69
+ * await generateTypes({
70
+ * migrations,
71
+ * output: './src/db.generated.ts',
72
+ * extendsSyncClientDb: true,
73
+ * });
74
+ * ```
75
+ */
76
+ export async function generateTypes<DB>(
77
+ options: GenerateTypesOptions<DB>
78
+ ): Promise<GenerateTypesResult> {
79
+ const {
80
+ migrations,
81
+ output,
82
+ extendsSyncClientDb,
83
+ includeVersionHistory,
84
+ tables,
85
+ dialect = 'sqlite',
86
+ resolveType,
87
+ } = options;
88
+
89
+ // Introspect schemas (raw SQL types, no TS mapping yet)
90
+ let rawSchemas: VersionedSchema[];
91
+ if (includeVersionHistory) {
92
+ rawSchemas = await introspectAllVersions(migrations, dialect, tables);
93
+ } else {
94
+ const current = await introspectCurrentSchema(migrations, dialect, tables);
95
+ rawSchemas = [current];
96
+ }
97
+
98
+ // Apply type mapping (default + user resolver)
99
+ const { schemas, customImports } = applyTypeMappings(
100
+ rawSchemas,
101
+ dialect,
102
+ resolveType
103
+ );
104
+
105
+ // Render TypeScript code
106
+ const code = renderTypes({
107
+ schemas,
108
+ extendsSyncClientDb,
109
+ includeVersionHistory,
110
+ customImports,
111
+ });
112
+
113
+ // Ensure output directory exists
114
+ await mkdir(dirname(output), { recursive: true });
115
+
116
+ // Write the file
117
+ await writeFile(output, code, 'utf-8');
118
+
119
+ const latestSchema = schemas[schemas.length - 1];
120
+
121
+ return {
122
+ outputPath: output,
123
+ currentVersion: migrations.currentVersion,
124
+ tableCount: latestSchema?.tables.length ?? 0,
125
+ code,
126
+ };
127
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @syncular/typegen - Generate TypeScript types from migrations
3
+ *
4
+ * Creates type definitions by:
5
+ * 1. Applying migrations to an in-memory database (SQLite or PostgreSQL)
6
+ * 2. Introspecting the resulting schema
7
+ * 3. Generating TypeScript interfaces
8
+ */
9
+
10
+ export * from './generate';
11
+ export * from './introspect';
12
+ export * from './map-types';
13
+ export * from './render';
14
+ export * from './types';
@@ -0,0 +1,149 @@
1
+ /**
2
+ * @syncular/typegen - PostgreSQL schema introspection via PGlite
3
+ */
4
+
5
+ import { PGlite } from '@electric-sql/pglite';
6
+ import type { DefinedMigrations } from '@syncular/migrations';
7
+ import { Kysely } from 'kysely';
8
+ import { PGliteDialect } from 'kysely-pglite-dialect';
9
+ import type { TableSchema, VersionedSchema } from './types';
10
+
11
+ interface PgColumn {
12
+ table_name: string;
13
+ column_name: string;
14
+ data_type: string;
15
+ udt_name: string;
16
+ is_nullable: string;
17
+ column_default: string | null;
18
+ ordinal_position: number;
19
+ }
20
+
21
+ interface PgPrimaryKey {
22
+ table_name: string;
23
+ column_name: string;
24
+ }
25
+
26
+ async function introspectPg(pglite: PGlite): Promise<TableSchema[]> {
27
+ const colResult = await pglite.query<PgColumn>(
28
+ `SELECT table_name, column_name, data_type, udt_name,
29
+ is_nullable, column_default, ordinal_position
30
+ FROM information_schema.columns
31
+ WHERE table_schema = 'public'
32
+ ORDER BY table_name, ordinal_position`
33
+ );
34
+
35
+ const pkResult = await pglite.query<PgPrimaryKey>(
36
+ `SELECT kcu.column_name, tc.table_name
37
+ FROM information_schema.table_constraints tc
38
+ JOIN information_schema.key_column_usage kcu
39
+ ON tc.constraint_name = kcu.constraint_name
40
+ AND tc.table_schema = kcu.table_schema
41
+ WHERE tc.constraint_type = 'PRIMARY KEY' AND tc.table_schema = 'public'`
42
+ );
43
+
44
+ const pkSet = new Set(
45
+ pkResult.rows.map((r) => `${r.table_name}.${r.column_name}`)
46
+ );
47
+
48
+ const tableMap = new Map<string, TableSchema>();
49
+
50
+ for (const col of colResult.rows) {
51
+ let table = tableMap.get(col.table_name);
52
+ if (!table) {
53
+ table = { name: col.table_name, columns: [] };
54
+ tableMap.set(col.table_name, table);
55
+ }
56
+
57
+ const isPrimaryKey = pkSet.has(`${col.table_name}.${col.column_name}`);
58
+ const nullable = col.is_nullable === 'YES' && !isPrimaryKey;
59
+ const hasDefault = col.column_default !== null;
60
+
61
+ // Use udt_name for more precise type info (e.g. int4 instead of "integer")
62
+ // For arrays, data_type is "ARRAY" and udt_name starts with "_"
63
+ let sqlType: string;
64
+ if (col.data_type === 'ARRAY' && col.udt_name.startsWith('_')) {
65
+ // Convert _int4 → int4[], _text → text[], etc.
66
+ sqlType = `${col.udt_name.slice(1)}[]`;
67
+ } else if (col.data_type === 'USER-DEFINED') {
68
+ sqlType = col.udt_name;
69
+ } else {
70
+ sqlType = col.udt_name;
71
+ }
72
+
73
+ table.columns.push({
74
+ name: col.column_name,
75
+ sqlType,
76
+ tsType: '', // resolved later by map-types
77
+ nullable,
78
+ isPrimaryKey,
79
+ hasDefault,
80
+ });
81
+ }
82
+
83
+ // Sort tables by name for deterministic output
84
+ return [...tableMap.values()].sort((a, b) => a.name.localeCompare(b.name));
85
+ }
86
+
87
+ async function introspectAtVersion<DB = unknown>(
88
+ migrations: DefinedMigrations<DB>,
89
+ targetVersion: number,
90
+ filterTables?: string[]
91
+ ): Promise<VersionedSchema> {
92
+ const pglite = await PGlite.create();
93
+
94
+ try {
95
+ const db = new Kysely<DB>({
96
+ dialect: new PGliteDialect(pglite),
97
+ });
98
+
99
+ for (const migration of migrations.migrations) {
100
+ if (migration.version > targetVersion) break;
101
+ await migration.fn(db);
102
+ }
103
+
104
+ await db.destroy();
105
+
106
+ let tables = await introspectPg(pglite);
107
+
108
+ if (filterTables && filterTables.length > 0) {
109
+ const filterSet = new Set(filterTables);
110
+ tables = tables.filter((t) => filterSet.has(t.name));
111
+ }
112
+
113
+ return {
114
+ version: targetVersion,
115
+ tables,
116
+ };
117
+ } finally {
118
+ await pglite.close();
119
+ }
120
+ }
121
+
122
+ export async function introspectPostgresAllVersions<DB = unknown>(
123
+ migrations: DefinedMigrations<DB>,
124
+ filterTables?: string[]
125
+ ): Promise<VersionedSchema[]> {
126
+ const schemas: VersionedSchema[] = [];
127
+
128
+ for (const migration of migrations.migrations) {
129
+ const schema = await introspectAtVersion(
130
+ migrations,
131
+ migration.version,
132
+ filterTables
133
+ );
134
+ schemas.push(schema);
135
+ }
136
+
137
+ return schemas;
138
+ }
139
+
140
+ export async function introspectPostgresCurrentSchema<DB = unknown>(
141
+ migrations: DefinedMigrations<DB>,
142
+ filterTables?: string[]
143
+ ): Promise<VersionedSchema> {
144
+ return introspectAtVersion(
145
+ migrations,
146
+ migrations.currentVersion,
147
+ filterTables
148
+ );
149
+ }