@syncular/typegen 0.0.6-95 → 0.1.0
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/README.md +70 -1
- package/dist/app-contract.d.ts +154 -0
- package/dist/app-contract.d.ts.map +1 -0
- package/dist/app-contract.js +250 -0
- package/dist/app-contract.js.map +1 -0
- package/dist/checksums.d.ts +6 -0
- package/dist/checksums.d.ts.map +1 -0
- package/dist/checksums.js +173 -0
- package/dist/checksums.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +88 -0
- package/dist/cli.js.map +1 -0
- package/dist/generate.d.ts +0 -1
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +4 -7
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/introspect-postgres.js +7 -5
- package/dist/introspect-postgres.js.map +1 -1
- package/dist/introspect-sqlite.d.ts.map +1 -1
- package/dist/introspect-sqlite.js +2 -1
- package/dist/introspect-sqlite.js.map +1 -1
- package/dist/introspect.js +2 -2
- package/dist/introspect.js.map +1 -1
- package/dist/map-types.js.map +1 -1
- package/dist/render.d.ts +1 -5
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +2 -21
- package/dist/render.js.map +1 -1
- package/dist/types.d.ts +18 -13
- package/dist/types.d.ts.map +1 -1
- package/package.json +16 -6
- package/src/app-contract.ts +531 -0
- package/src/checksums.ts +257 -0
- package/src/cli.ts +104 -0
- package/src/generate.ts +0 -5
- package/src/index.ts +2 -0
- package/src/introspect-postgres.ts +7 -7
- package/src/introspect-sqlite.ts +6 -1
- package/src/render.ts +3 -43
- package/src/types.ts +20 -17
package/src/checksums.ts
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
4
|
+
import type {
|
|
5
|
+
DefinedMigrations,
|
|
6
|
+
MigrationChecksums,
|
|
7
|
+
ParsedMigration,
|
|
8
|
+
} from '@syncular/migrations';
|
|
9
|
+
import { Kysely, SqliteDialect } from 'kysely';
|
|
10
|
+
import { PGliteDialect } from 'kysely-pglite-dialect';
|
|
11
|
+
import type {
|
|
12
|
+
GenerateMigrationChecksumsOptions,
|
|
13
|
+
GenerateMigrationChecksumsResult,
|
|
14
|
+
TypegenDialect,
|
|
15
|
+
} from './types';
|
|
16
|
+
|
|
17
|
+
interface TraceableQuery {
|
|
18
|
+
sql: string;
|
|
19
|
+
parameters: readonly unknown[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface SqliteDb {
|
|
23
|
+
close(): void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type BunAwareGlobals = typeof globalThis & {
|
|
27
|
+
Bun?: object;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const runtimeGlobals = globalThis as BunAwareGlobals;
|
|
31
|
+
const isBun = typeof runtimeGlobals.Bun !== 'undefined';
|
|
32
|
+
|
|
33
|
+
function hashString(value: string): string {
|
|
34
|
+
let hash = 0;
|
|
35
|
+
|
|
36
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
37
|
+
hash = (hash * 31 + value.charCodeAt(index)) >>> 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return hash.toString(16).padStart(8, '0');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeParameterValue(value: unknown): unknown {
|
|
44
|
+
if (typeof value === 'bigint') {
|
|
45
|
+
return { type: 'bigint', value: value.toString() };
|
|
46
|
+
}
|
|
47
|
+
if (value instanceof Date) {
|
|
48
|
+
return { type: 'date', value: value.toISOString() };
|
|
49
|
+
}
|
|
50
|
+
if (value instanceof Uint8Array) {
|
|
51
|
+
return { type: 'bytes', value: Array.from(value) };
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(value)) {
|
|
54
|
+
return value.map((entry) => normalizeParameterValue(entry));
|
|
55
|
+
}
|
|
56
|
+
if (value && typeof value === 'object') {
|
|
57
|
+
return Object.fromEntries(
|
|
58
|
+
Object.entries(value)
|
|
59
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
60
|
+
.map(([key, entry]) => [key, normalizeParameterValue(entry)])
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function serializeQuery(query: TraceableQuery): string {
|
|
67
|
+
return JSON.stringify({
|
|
68
|
+
sql: query.sql,
|
|
69
|
+
parameters: query.parameters.map((value) => normalizeParameterValue(value)),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function hashTrace(entries: string[]): string {
|
|
74
|
+
return hashString(entries.join('\n'));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function quoteTsString(value: string): string {
|
|
78
|
+
return `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'")}'`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function createSqliteTraceDb<DB>(traceEntries: string[]): Promise<{
|
|
82
|
+
db: Kysely<DB>;
|
|
83
|
+
sqliteDb: SqliteDb;
|
|
84
|
+
}> {
|
|
85
|
+
if (isBun) {
|
|
86
|
+
const bunSqliteSpecifier = 'bun:sqlite';
|
|
87
|
+
const sqliteModule = await import(bunSqliteSpecifier);
|
|
88
|
+
const dialectModule = await import('kysely-bun-sqlite');
|
|
89
|
+
const sqliteDb = new sqliteModule.Database(':memory:');
|
|
90
|
+
const db = new Kysely<DB>({
|
|
91
|
+
dialect: new dialectModule.BunSqliteDialect({
|
|
92
|
+
database: sqliteDb as never,
|
|
93
|
+
}),
|
|
94
|
+
log(event) {
|
|
95
|
+
if (event.level === 'query') {
|
|
96
|
+
traceEntries.push(serializeQuery(event.query));
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return { db, sqliteDb };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const { default: Database } = await import('better-sqlite3');
|
|
105
|
+
const sqliteDb = new Database(':memory:');
|
|
106
|
+
const db = new Kysely<DB>({
|
|
107
|
+
dialect: new SqliteDialect({
|
|
108
|
+
database: sqliteDb as never,
|
|
109
|
+
}),
|
|
110
|
+
log(event) {
|
|
111
|
+
if (event.level === 'query') {
|
|
112
|
+
traceEntries.push(serializeQuery(event.query));
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return { db, sqliteDb };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function createPostgresTraceDb<DB>(traceEntries: string[]): Promise<{
|
|
121
|
+
db: Kysely<DB>;
|
|
122
|
+
dispose: () => Promise<void>;
|
|
123
|
+
}> {
|
|
124
|
+
const pglite = await PGlite.create();
|
|
125
|
+
const db = new Kysely<DB>({
|
|
126
|
+
dialect: new PGliteDialect(pglite),
|
|
127
|
+
log(event) {
|
|
128
|
+
if (event.level === 'query') {
|
|
129
|
+
traceEntries.push(serializeQuery(event.query));
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
db,
|
|
136
|
+
dispose: async () => {
|
|
137
|
+
if (!pglite.closed) {
|
|
138
|
+
await pglite.close();
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function createTraceDb<DB>(
|
|
145
|
+
dialect: TypegenDialect,
|
|
146
|
+
traceEntries: string[]
|
|
147
|
+
): Promise<{
|
|
148
|
+
db: Kysely<DB>;
|
|
149
|
+
dispose: () => Promise<void>;
|
|
150
|
+
}> {
|
|
151
|
+
if (dialect === 'postgres') {
|
|
152
|
+
return createPostgresTraceDb<DB>(traceEntries);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const { db, sqliteDb } = await createSqliteTraceDb<DB>(traceEntries);
|
|
156
|
+
return {
|
|
157
|
+
db,
|
|
158
|
+
dispose: async () => {
|
|
159
|
+
sqliteDb.close();
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function computeMigrationChecksum<DB>(
|
|
165
|
+
migrations: DefinedMigrations<DB>,
|
|
166
|
+
targetMigration: ParsedMigration<DB>,
|
|
167
|
+
dialect: TypegenDialect
|
|
168
|
+
): Promise<string> {
|
|
169
|
+
const traceEntries: string[] = [];
|
|
170
|
+
const { db, dispose } = await createTraceDb<DB>(dialect, traceEntries);
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
for (const migration of migrations.migrations) {
|
|
174
|
+
if (migration.version > targetMigration.version) {
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (migration.version === targetMigration.version) {
|
|
179
|
+
traceEntries.length = 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
await migration.up(db);
|
|
183
|
+
|
|
184
|
+
if (migration.version === targetMigration.version) {
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return hashTrace(traceEntries);
|
|
190
|
+
} finally {
|
|
191
|
+
await db.destroy();
|
|
192
|
+
await dispose();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function createMigrationChecksums<DB>(
|
|
197
|
+
migrations: DefinedMigrations<DB>,
|
|
198
|
+
dialect: TypegenDialect = 'sqlite'
|
|
199
|
+
): Promise<MigrationChecksums> {
|
|
200
|
+
const checksums: Record<string, string> = {};
|
|
201
|
+
|
|
202
|
+
for (const migration of migrations.migrations) {
|
|
203
|
+
if (migration.checksum === 'disabled') {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
checksums[String(migration.version)] = await computeMigrationChecksum(
|
|
208
|
+
migrations,
|
|
209
|
+
migration,
|
|
210
|
+
dialect
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return checksums;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function renderMigrationChecksums(
|
|
218
|
+
checksums: MigrationChecksums
|
|
219
|
+
): string {
|
|
220
|
+
const entries = Object.entries(checksums)
|
|
221
|
+
.sort(([left], [right]) => Number(left) - Number(right))
|
|
222
|
+
.map(
|
|
223
|
+
([version, checksum]) =>
|
|
224
|
+
` ${quoteTsString(version)}: ${quoteTsString(checksum)},`
|
|
225
|
+
)
|
|
226
|
+
.join('\n');
|
|
227
|
+
|
|
228
|
+
return [
|
|
229
|
+
'/**',
|
|
230
|
+
' * Generated by @syncular/typegen.',
|
|
231
|
+
' * Do not edit by hand.',
|
|
232
|
+
' */',
|
|
233
|
+
'',
|
|
234
|
+
'export const migrationChecksums = {',
|
|
235
|
+
entries,
|
|
236
|
+
'} as const;',
|
|
237
|
+
'',
|
|
238
|
+
].join('\n');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export async function generateMigrationChecksums<DB>(
|
|
242
|
+
options: GenerateMigrationChecksumsOptions<DB>
|
|
243
|
+
): Promise<GenerateMigrationChecksumsResult> {
|
|
244
|
+
const { migrations, output, dialect = 'sqlite' } = options;
|
|
245
|
+
const checksums = await createMigrationChecksums(migrations, dialect);
|
|
246
|
+
const code = renderMigrationChecksums(checksums);
|
|
247
|
+
|
|
248
|
+
await mkdir(dirname(output), { recursive: true });
|
|
249
|
+
await writeFile(output, code, 'utf-8');
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
outputPath: output,
|
|
253
|
+
currentVersion: migrations.currentVersion,
|
|
254
|
+
checksumCount: Object.keys(checksums).length,
|
|
255
|
+
code,
|
|
256
|
+
};
|
|
257
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import {
|
|
7
|
+
loadSyncularClientContract,
|
|
8
|
+
toSyncularCodegenJson,
|
|
9
|
+
writeSyncularCodegenJsonFromModule,
|
|
10
|
+
} from './app-contract';
|
|
11
|
+
|
|
12
|
+
interface CodegenConfigCommand {
|
|
13
|
+
app: string;
|
|
14
|
+
out: string;
|
|
15
|
+
exportName?: string;
|
|
16
|
+
check: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function main(argv: string[]): Promise<void> {
|
|
20
|
+
const command = argv[0];
|
|
21
|
+
if (command !== 'codegen-config') {
|
|
22
|
+
printUsageAndExit(command ? `Unknown command ${command}` : undefined);
|
|
23
|
+
}
|
|
24
|
+
const options = parseCodegenConfigArgs(argv.slice(1));
|
|
25
|
+
if (options.check) {
|
|
26
|
+
const contract = await loadSyncularClientContract({
|
|
27
|
+
modulePath: options.app,
|
|
28
|
+
exportName: options.exportName,
|
|
29
|
+
});
|
|
30
|
+
const expected = toSyncularCodegenJson(contract);
|
|
31
|
+
const actual = await readFile(options.out, 'utf8').catch((error) => {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Cannot read ${options.out}; run without --check to generate it first: ${error.message}`
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
if (actual !== expected) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`${options.out} does not match ${options.app}; run syncular generate --app ${options.app}`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
await writeSyncularCodegenJsonFromModule({
|
|
44
|
+
modulePath: options.app,
|
|
45
|
+
exportName: options.exportName,
|
|
46
|
+
outputPath: options.out,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseCodegenConfigArgs(args: string[]): CodegenConfigCommand {
|
|
51
|
+
let app: string | undefined;
|
|
52
|
+
let out: string | undefined;
|
|
53
|
+
let exportName: string | undefined;
|
|
54
|
+
let check = false;
|
|
55
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
56
|
+
const arg = args[index];
|
|
57
|
+
if (arg === '--app') {
|
|
58
|
+
app = requireValue(args, (index += 1), '--app');
|
|
59
|
+
} else if (arg === '--out') {
|
|
60
|
+
out = requireValue(args, (index += 1), '--out');
|
|
61
|
+
} else if (arg === '--export') {
|
|
62
|
+
exportName = requireValue(args, (index += 1), '--export');
|
|
63
|
+
} else if (arg === '--check') {
|
|
64
|
+
check = true;
|
|
65
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
66
|
+
printUsageAndExit();
|
|
67
|
+
} else {
|
|
68
|
+
printUsageAndExit(`Unknown option ${arg}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (!app) printUsageAndExit('Missing --app');
|
|
72
|
+
return {
|
|
73
|
+
app,
|
|
74
|
+
out: out ?? defaultCodegenConfigPath(app),
|
|
75
|
+
exportName,
|
|
76
|
+
check,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function defaultCodegenConfigPath(app: string): string {
|
|
81
|
+
const appPath = app.startsWith('file:') ? fileURLToPath(app) : app;
|
|
82
|
+
return join(dirname(appPath), 'generated', 'syncular.codegen.json');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function requireValue(args: string[], index: number, option: string): string {
|
|
86
|
+
const value = args[index];
|
|
87
|
+
if (!value || value.startsWith('--')) {
|
|
88
|
+
printUsageAndExit(`${option} requires a value`);
|
|
89
|
+
}
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function printUsageAndExit(message?: string): never {
|
|
94
|
+
if (message) console.error(message);
|
|
95
|
+
console.error(
|
|
96
|
+
[
|
|
97
|
+
'Usage:',
|
|
98
|
+
' syncular-typegen codegen-config --app ./syncular.app.ts [--out ./generated/syncular.codegen.json] [--export app] [--check]',
|
|
99
|
+
].join('\n')
|
|
100
|
+
);
|
|
101
|
+
process.exit(message ? 1 : 0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await main(process.argv.slice(2));
|
package/src/generate.ts
CHANGED
|
@@ -68,7 +68,6 @@ function applyTypeMappings(
|
|
|
68
68
|
* await generateTypes({
|
|
69
69
|
* migrations,
|
|
70
70
|
* output: './src/db.generated.ts',
|
|
71
|
-
* extendsSyncClientDb: true,
|
|
72
71
|
* });
|
|
73
72
|
* ```
|
|
74
73
|
*/
|
|
@@ -78,8 +77,6 @@ export async function generateTypes<DB>(
|
|
|
78
77
|
const {
|
|
79
78
|
migrations,
|
|
80
79
|
output,
|
|
81
|
-
extendsSyncClientDb,
|
|
82
|
-
syncularImportType,
|
|
83
80
|
includeVersionHistory,
|
|
84
81
|
tables,
|
|
85
82
|
dialect = 'sqlite',
|
|
@@ -105,8 +102,6 @@ export async function generateTypes<DB>(
|
|
|
105
102
|
// Render TypeScript code
|
|
106
103
|
const code = renderTypes({
|
|
107
104
|
schemas,
|
|
108
|
-
extendsSyncClientDb,
|
|
109
|
-
syncularImportType,
|
|
110
105
|
includeVersionHistory,
|
|
111
106
|
customImports,
|
|
112
107
|
});
|
package/src/index.ts
CHANGED
|
@@ -90,19 +90,16 @@ async function introspectAtVersion<DB = unknown>(
|
|
|
90
90
|
filterTables?: string[]
|
|
91
91
|
): Promise<VersionedSchema> {
|
|
92
92
|
const pglite = await PGlite.create();
|
|
93
|
+
const db = new Kysely<DB>({
|
|
94
|
+
dialect: new PGliteDialect(pglite),
|
|
95
|
+
});
|
|
93
96
|
|
|
94
97
|
try {
|
|
95
|
-
const db = new Kysely<DB>({
|
|
96
|
-
dialect: new PGliteDialect(pglite),
|
|
97
|
-
});
|
|
98
|
-
|
|
99
98
|
for (const migration of migrations.migrations) {
|
|
100
99
|
if (migration.version > targetVersion) break;
|
|
101
100
|
await migration.up(db);
|
|
102
101
|
}
|
|
103
102
|
|
|
104
|
-
await db.destroy();
|
|
105
|
-
|
|
106
103
|
let tables = await introspectPg(pglite);
|
|
107
104
|
|
|
108
105
|
if (filterTables && filterTables.length > 0) {
|
|
@@ -115,7 +112,10 @@ async function introspectAtVersion<DB = unknown>(
|
|
|
115
112
|
tables,
|
|
116
113
|
};
|
|
117
114
|
} finally {
|
|
118
|
-
await
|
|
115
|
+
await db.destroy();
|
|
116
|
+
if (!pglite.closed) {
|
|
117
|
+
await pglite.close();
|
|
118
|
+
}
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
|
package/src/introspect-sqlite.ts
CHANGED
|
@@ -23,7 +23,12 @@ interface SqliteDb {
|
|
|
23
23
|
close(): void;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
type BunAwareGlobals = typeof globalThis & {
|
|
27
|
+
Bun?: object;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const runtimeGlobals = globalThis as BunAwareGlobals;
|
|
31
|
+
const isBun = typeof runtimeGlobals.Bun !== 'undefined';
|
|
27
32
|
|
|
28
33
|
async function createSqliteDb(): Promise<SqliteDb> {
|
|
29
34
|
if (isBun) {
|
package/src/render.ts
CHANGED
|
@@ -2,12 +2,7 @@
|
|
|
2
2
|
* @syncular/typegen - TypeScript code generation
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type {
|
|
6
|
-
ColumnSchema,
|
|
7
|
-
SyncularImportType,
|
|
8
|
-
TableSchema,
|
|
9
|
-
VersionedSchema,
|
|
10
|
-
} from './types';
|
|
5
|
+
import type { ColumnSchema, TableSchema, VersionedSchema } from './types';
|
|
11
6
|
|
|
12
7
|
/**
|
|
13
8
|
* Convert a snake_case table/column name to PascalCase.
|
|
@@ -62,43 +57,17 @@ function renderDbInterface(
|
|
|
62
57
|
export interface RenderOptions {
|
|
63
58
|
/** Schemas at each version (for version history) */
|
|
64
59
|
schemas: VersionedSchema[];
|
|
65
|
-
/** Whether to extend SyncClientDb */
|
|
66
|
-
extendsSyncClientDb?: boolean;
|
|
67
|
-
/** Controls package import style for SyncClientDb (default: 'scoped') */
|
|
68
|
-
syncularImportType?: SyncularImportType;
|
|
69
60
|
/** Generate versioned interfaces */
|
|
70
61
|
includeVersionHistory?: boolean;
|
|
71
62
|
/** Custom imports collected from resolver results */
|
|
72
63
|
customImports?: Array<{ name: string; from: string }>;
|
|
73
64
|
}
|
|
74
65
|
|
|
75
|
-
function resolveSyncClientImportPath(importType: SyncularImportType): string {
|
|
76
|
-
if (importType === 'umbrella') {
|
|
77
|
-
return 'syncular/client';
|
|
78
|
-
}
|
|
79
|
-
if (importType === 'scoped') {
|
|
80
|
-
return '@syncular/client';
|
|
81
|
-
}
|
|
82
|
-
const clientImportPath = importType.client.trim();
|
|
83
|
-
if (clientImportPath.length === 0) {
|
|
84
|
-
throw new Error(
|
|
85
|
-
'syncularImportType.client must be a non-empty package import path'
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
return clientImportPath;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
66
|
/**
|
|
92
67
|
* Render complete TypeScript type definitions.
|
|
93
68
|
*/
|
|
94
69
|
export function renderTypes(options: RenderOptions): string {
|
|
95
|
-
const {
|
|
96
|
-
schemas,
|
|
97
|
-
extendsSyncClientDb,
|
|
98
|
-
syncularImportType = 'scoped',
|
|
99
|
-
includeVersionHistory,
|
|
100
|
-
customImports,
|
|
101
|
-
} = options;
|
|
70
|
+
const { schemas, includeVersionHistory, customImports } = options;
|
|
102
71
|
const lines: string[] = [];
|
|
103
72
|
|
|
104
73
|
// Header
|
|
@@ -108,14 +77,6 @@ export function renderTypes(options: RenderOptions): string {
|
|
|
108
77
|
lines.push(' */');
|
|
109
78
|
lines.push('');
|
|
110
79
|
|
|
111
|
-
// Import SyncClientDb if extending
|
|
112
|
-
if (extendsSyncClientDb) {
|
|
113
|
-
lines.push(
|
|
114
|
-
`import type { SyncClientDb } from '${resolveSyncClientImportPath(syncularImportType)}';`
|
|
115
|
-
);
|
|
116
|
-
lines.push('');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
80
|
const usesGenerated = schemas.some((schema) =>
|
|
120
81
|
schema.tables.some((table) =>
|
|
121
82
|
table.columns.some((column) => column.hasDefault)
|
|
@@ -205,8 +166,7 @@ export function renderTypes(options: RenderOptions): string {
|
|
|
205
166
|
}
|
|
206
167
|
|
|
207
168
|
// Generate main DB interface (latest version)
|
|
208
|
-
|
|
209
|
-
lines.push(renderDbInterface(latestSchema, 'ClientDb', extendsType));
|
|
169
|
+
lines.push(renderDbInterface(latestSchema, 'ClientDb'));
|
|
210
170
|
lines.push('');
|
|
211
171
|
|
|
212
172
|
return lines.join('\n');
|
package/src/types.ts
CHANGED
|
@@ -6,14 +6,6 @@ import type { DefinedMigrations } from '@syncular/migrations';
|
|
|
6
6
|
|
|
7
7
|
export type TypegenDialect = 'sqlite' | 'postgres';
|
|
8
8
|
|
|
9
|
-
export type SyncularImportType =
|
|
10
|
-
| 'scoped'
|
|
11
|
-
| 'umbrella'
|
|
12
|
-
| {
|
|
13
|
-
client: string;
|
|
14
|
-
[packageName: string]: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
9
|
/**
|
|
18
10
|
* Column information for a schema column.
|
|
19
11
|
*/
|
|
@@ -99,15 +91,6 @@ export interface GenerateTypesOptions<DB = unknown> {
|
|
|
99
91
|
output: string;
|
|
100
92
|
/** Database dialect to use for introspection (default: 'sqlite') */
|
|
101
93
|
dialect?: TypegenDialect;
|
|
102
|
-
/** Whether to extend SyncClientDb interface (adds sync infrastructure types) */
|
|
103
|
-
extendsSyncClientDb?: boolean;
|
|
104
|
-
/**
|
|
105
|
-
* Controls how syncular package imports are rendered in generated output.
|
|
106
|
-
* - 'scoped' (default): '@syncular/client'
|
|
107
|
-
* - 'umbrella': 'syncular/client'
|
|
108
|
-
* - object: explicit package mapping (must include `client`)
|
|
109
|
-
*/
|
|
110
|
-
syncularImportType?: SyncularImportType;
|
|
111
94
|
/** Generate versioned interfaces (ClientDbV1, ClientDbV2, etc.) */
|
|
112
95
|
includeVersionHistory?: boolean;
|
|
113
96
|
/** Only generate types for these tables (default: all tables) */
|
|
@@ -132,3 +115,23 @@ export interface GenerateTypesResult {
|
|
|
132
115
|
/** Generated TypeScript code */
|
|
133
116
|
code: string;
|
|
134
117
|
}
|
|
118
|
+
|
|
119
|
+
export interface GenerateMigrationChecksumsOptions<DB = unknown> {
|
|
120
|
+
/** Defined migrations from defineMigrations() */
|
|
121
|
+
migrations: DefinedMigrations<DB>;
|
|
122
|
+
/** Output file path for generated checksums */
|
|
123
|
+
output: string;
|
|
124
|
+
/** Database dialect to use for replay (default: 'sqlite') */
|
|
125
|
+
dialect?: TypegenDialect;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface GenerateMigrationChecksumsResult {
|
|
129
|
+
/** Path to the generated file */
|
|
130
|
+
outputPath: string;
|
|
131
|
+
/** Current schema version */
|
|
132
|
+
currentVersion: number;
|
|
133
|
+
/** Number of checksums generated */
|
|
134
|
+
checksumCount: number;
|
|
135
|
+
/** Generated TypeScript code */
|
|
136
|
+
code: string;
|
|
137
|
+
}
|