@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/generate.d.ts +21 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +86 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/introspect-postgres.d.ts +8 -0
- package/dist/introspect-postgres.d.ts.map +1 -0
- package/dist/introspect-postgres.js +92 -0
- package/dist/introspect-postgres.js.map +1 -0
- package/dist/introspect-sqlite.d.ts +10 -0
- package/dist/introspect-sqlite.d.ts.map +1 -0
- package/dist/introspect-sqlite.js +88 -0
- package/dist/introspect-sqlite.js.map +1 -0
- package/dist/introspect.d.ts +8 -0
- package/dist/introspect.d.ts.map +1 -0
- package/dist/introspect.js +18 -0
- package/dist/introspect.js.map +1 -0
- package/dist/map-types.d.ts +20 -0
- package/dist/map-types.d.ts.map +1 -0
- package/dist/map-types.js +154 -0
- package/dist/map-types.js.map +1 -0
- package/dist/render.d.ts +25 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +140 -0
- package/dist/render.js.map +1 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +63 -0
- package/src/generate.ts +127 -0
- package/src/index.ts +14 -0
- package/src/introspect-postgres.ts +149 -0
- package/src/introspect-sqlite.ts +142 -0
- package/src/introspect.ts +36 -0
- package/src/map-types.ts +192 -0
- package/src/render.ts +195 -0
- package/src/types.ts +94 -0
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":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|
package/src/generate.ts
ADDED
|
@@ -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
|
+
}
|