@syncular/typegen 0.1.3 → 0.2.1
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 +436 -94
- package/dist/cli.d.ts +1 -2
- package/dist/cli.js +159 -73
- package/dist/emit-dart.d.ts +17 -0
- package/dist/emit-dart.js +201 -0
- package/dist/emit-kotlin.d.ts +16 -0
- package/dist/emit-kotlin.js +201 -0
- package/dist/emit-queries-dart.d.ts +2 -0
- package/dist/emit-queries-dart.js +160 -0
- package/dist/emit-queries-kotlin.d.ts +2 -0
- package/dist/emit-queries-kotlin.js +162 -0
- package/dist/emit-queries-swift.d.ts +2 -0
- package/dist/emit-queries-swift.js +146 -0
- package/dist/emit-queries.d.ts +2 -0
- package/dist/emit-queries.js +129 -0
- package/dist/emit-swift.d.ts +18 -0
- package/dist/emit-swift.js +235 -0
- package/dist/emit.d.ts +16 -0
- package/dist/emit.js +191 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +10 -0
- package/dist/generate.d.ts +53 -18
- package/dist/generate.js +391 -77
- package/dist/index.d.ts +17 -14
- package/dist/index.js +16 -13
- package/dist/init.d.ts +12 -0
- package/dist/init.js +85 -0
- package/dist/ir.d.ts +90 -0
- package/dist/ir.js +94 -0
- package/dist/manifest.d.ts +76 -0
- package/dist/manifest.js +303 -0
- package/dist/query.d.ts +96 -0
- package/dist/query.js +719 -0
- package/dist/sql.d.ts +13 -0
- package/dist/sql.js +440 -0
- package/package.json +14 -34
- package/src/cli.ts +161 -82
- package/src/emit-dart.ts +245 -0
- package/src/emit-kotlin.ts +257 -0
- package/src/emit-queries-dart.ts +203 -0
- package/src/emit-queries-kotlin.ts +209 -0
- package/src/emit-queries-swift.ts +198 -0
- package/src/emit-queries.ts +183 -0
- package/src/emit-swift.ts +295 -0
- package/src/emit.ts +239 -0
- package/src/errors.ts +11 -0
- package/src/generate.ts +574 -105
- package/src/index.ts +16 -13
- package/src/init.ts +100 -0
- package/src/ir.ts +197 -0
- package/src/manifest.ts +445 -0
- package/src/query.ts +918 -0
- package/src/sql.ts +533 -0
- package/dist/app-contract.d.ts +0 -154
- package/dist/app-contract.d.ts.map +0 -1
- package/dist/app-contract.js +0 -250
- package/dist/app-contract.js.map +0 -1
- package/dist/checksums.d.ts +0 -6
- package/dist/checksums.d.ts.map +0 -1
- package/dist/checksums.js +0 -173
- package/dist/checksums.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/generate.d.ts.map +0 -1
- package/dist/generate.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/introspect-postgres.d.ts +0 -8
- package/dist/introspect-postgres.d.ts.map +0 -1
- package/dist/introspect-postgres.js +0 -94
- package/dist/introspect-postgres.js.map +0 -1
- package/dist/introspect-sqlite.d.ts +0 -10
- package/dist/introspect-sqlite.d.ts.map +0 -1
- package/dist/introspect-sqlite.js +0 -97
- package/dist/introspect-sqlite.js.map +0 -1
- package/dist/introspect.d.ts +0 -8
- package/dist/introspect.d.ts.map +0 -1
- package/dist/introspect.js +0 -18
- package/dist/introspect.js.map +0 -1
- package/dist/map-types.d.ts +0 -20
- package/dist/map-types.d.ts.map +0 -1
- package/dist/map-types.js +0 -151
- package/dist/map-types.js.map +0 -1
- package/dist/render.d.ts +0 -23
- package/dist/render.d.ts.map +0 -1
- package/dist/render.js +0 -140
- package/dist/render.js.map +0 -1
- package/dist/types.d.ts +0 -122
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -5
- package/dist/types.js.map +0 -1
- package/src/app-contract.ts +0 -531
- package/src/checksums.ts +0 -257
- package/src/introspect-postgres.ts +0 -149
- package/src/introspect-sqlite.ts +0 -156
- package/src/introspect.ts +0 -36
- package/src/map-types.ts +0 -189
- package/src/render.ts +0 -196
- package/src/types.ts +0 -137
package/src/checksums.ts
DELETED
|
@@ -1,257 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
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
|
-
const db = new Kysely<DB>({
|
|
94
|
-
dialect: new PGliteDialect(pglite),
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
for (const migration of migrations.migrations) {
|
|
99
|
-
if (migration.version > targetVersion) break;
|
|
100
|
-
await migration.up(db);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
let tables = await introspectPg(pglite);
|
|
104
|
-
|
|
105
|
-
if (filterTables && filterTables.length > 0) {
|
|
106
|
-
const filterSet = new Set(filterTables);
|
|
107
|
-
tables = tables.filter((t) => filterSet.has(t.name));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
version: targetVersion,
|
|
112
|
-
tables,
|
|
113
|
-
};
|
|
114
|
-
} finally {
|
|
115
|
-
await db.destroy();
|
|
116
|
-
if (!pglite.closed) {
|
|
117
|
-
await pglite.close();
|
|
118
|
-
}
|
|
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
|
-
}
|
package/src/introspect-sqlite.ts
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @syncular/typegen - SQLite schema introspection
|
|
3
|
-
*
|
|
4
|
-
* Works with both better-sqlite3 (Node.js) and bun:sqlite (Bun runtime).
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { DefinedMigrations } from '@syncular/migrations';
|
|
8
|
-
import { Kysely, SqliteDialect } from 'kysely';
|
|
9
|
-
import type { TableSchema, VersionedSchema } from './types';
|
|
10
|
-
|
|
11
|
-
interface SqliteColumnInfo {
|
|
12
|
-
cid: number;
|
|
13
|
-
name: string;
|
|
14
|
-
type: string;
|
|
15
|
-
notnull: 0 | 1;
|
|
16
|
-
dflt_value: string | null;
|
|
17
|
-
pk: 0 | 1;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Minimal interface shared by better-sqlite3 and bun:sqlite */
|
|
21
|
-
interface SqliteDb {
|
|
22
|
-
prepare(sql: string): { all(...params: unknown[]): unknown[] };
|
|
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
|
-
async function createSqliteDb(): Promise<SqliteDb> {
|
|
34
|
-
if (isBun) {
|
|
35
|
-
const { Database } = await import('bun:sqlite');
|
|
36
|
-
return new Database(':memory:');
|
|
37
|
-
}
|
|
38
|
-
const { default: Database } = await import('better-sqlite3');
|
|
39
|
-
return new Database(':memory:');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function createKysely<DB>(sqliteDb: SqliteDb): Promise<Kysely<DB>> {
|
|
43
|
-
if (isBun) {
|
|
44
|
-
const { BunSqliteDialect } = await import('kysely-bun-sqlite');
|
|
45
|
-
return new Kysely<DB>({
|
|
46
|
-
dialect: new BunSqliteDialect({
|
|
47
|
-
database: sqliteDb as never,
|
|
48
|
-
}),
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return new Kysely<DB>({
|
|
53
|
-
dialect: new SqliteDialect({
|
|
54
|
-
database: sqliteDb as never,
|
|
55
|
-
}),
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function introspectTable(sqliteDb: SqliteDb, tableName: string): TableSchema {
|
|
60
|
-
const columns = sqliteDb
|
|
61
|
-
.prepare(`PRAGMA table_info("${tableName}")`)
|
|
62
|
-
.all() as SqliteColumnInfo[];
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
name: tableName,
|
|
66
|
-
columns: columns.map((col) => {
|
|
67
|
-
const nullable = col.notnull === 0 && col.pk === 0;
|
|
68
|
-
const hasDefault = col.dflt_value !== null;
|
|
69
|
-
return {
|
|
70
|
-
name: col.name,
|
|
71
|
-
sqlType: col.type,
|
|
72
|
-
tsType: '', // resolved later by map-types
|
|
73
|
-
nullable,
|
|
74
|
-
isPrimaryKey: col.pk === 1,
|
|
75
|
-
hasDefault,
|
|
76
|
-
};
|
|
77
|
-
}),
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function getAllTables(sqliteDb: SqliteDb): string[] {
|
|
82
|
-
const rows = sqliteDb
|
|
83
|
-
.prepare(
|
|
84
|
-
`SELECT name FROM sqlite_master
|
|
85
|
-
WHERE type='table'
|
|
86
|
-
AND name NOT LIKE 'sqlite_%'
|
|
87
|
-
ORDER BY name`
|
|
88
|
-
)
|
|
89
|
-
.all() as { name: string }[];
|
|
90
|
-
|
|
91
|
-
return rows.map((r) => r.name);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function introspectAtVersion<DB = unknown>(
|
|
95
|
-
migrations: DefinedMigrations<DB>,
|
|
96
|
-
targetVersion: number,
|
|
97
|
-
filterTables?: string[]
|
|
98
|
-
): Promise<VersionedSchema> {
|
|
99
|
-
const sqliteDb = await createSqliteDb();
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
const db = await createKysely<DB>(sqliteDb);
|
|
103
|
-
|
|
104
|
-
for (const migration of migrations.migrations) {
|
|
105
|
-
if (migration.version > targetVersion) break;
|
|
106
|
-
await migration.up(db);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
let tableNames = getAllTables(sqliteDb);
|
|
110
|
-
|
|
111
|
-
if (filterTables && filterTables.length > 0) {
|
|
112
|
-
const filterSet = new Set(filterTables);
|
|
113
|
-
tableNames = tableNames.filter((t) => filterSet.has(t));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const tables = tableNames.map((name) => introspectTable(sqliteDb, name));
|
|
117
|
-
|
|
118
|
-
await db.destroy();
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
version: targetVersion,
|
|
122
|
-
tables,
|
|
123
|
-
};
|
|
124
|
-
} finally {
|
|
125
|
-
sqliteDb.close();
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export async function introspectSqliteAllVersions<DB = unknown>(
|
|
130
|
-
migrations: DefinedMigrations<DB>,
|
|
131
|
-
filterTables?: string[]
|
|
132
|
-
): Promise<VersionedSchema[]> {
|
|
133
|
-
const schemas: VersionedSchema[] = [];
|
|
134
|
-
|
|
135
|
-
for (const migration of migrations.migrations) {
|
|
136
|
-
const schema = await introspectAtVersion(
|
|
137
|
-
migrations,
|
|
138
|
-
migration.version,
|
|
139
|
-
filterTables
|
|
140
|
-
);
|
|
141
|
-
schemas.push(schema);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return schemas;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export async function introspectSqliteCurrentSchema<DB = unknown>(
|
|
148
|
-
migrations: DefinedMigrations<DB>,
|
|
149
|
-
filterTables?: string[]
|
|
150
|
-
): Promise<VersionedSchema> {
|
|
151
|
-
return introspectAtVersion(
|
|
152
|
-
migrations,
|
|
153
|
-
migrations.currentVersion,
|
|
154
|
-
filterTables
|
|
155
|
-
);
|
|
156
|
-
}
|
package/src/introspect.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @syncular/typegen - Schema introspection dispatcher
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { DefinedMigrations } from '@syncular/migrations';
|
|
6
|
-
import {
|
|
7
|
-
introspectPostgresAllVersions,
|
|
8
|
-
introspectPostgresCurrentSchema,
|
|
9
|
-
} from './introspect-postgres';
|
|
10
|
-
import {
|
|
11
|
-
introspectSqliteAllVersions,
|
|
12
|
-
introspectSqliteCurrentSchema,
|
|
13
|
-
} from './introspect-sqlite';
|
|
14
|
-
import type { TypegenDialect, VersionedSchema } from './types';
|
|
15
|
-
|
|
16
|
-
export async function introspectAllVersions<DB = unknown>(
|
|
17
|
-
migrations: DefinedMigrations<DB>,
|
|
18
|
-
dialect: TypegenDialect,
|
|
19
|
-
filterTables?: string[]
|
|
20
|
-
): Promise<VersionedSchema[]> {
|
|
21
|
-
if (dialect === 'postgres') {
|
|
22
|
-
return introspectPostgresAllVersions(migrations, filterTables);
|
|
23
|
-
}
|
|
24
|
-
return introspectSqliteAllVersions(migrations, filterTables);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function introspectCurrentSchema<DB = unknown>(
|
|
28
|
-
migrations: DefinedMigrations<DB>,
|
|
29
|
-
dialect: TypegenDialect,
|
|
30
|
-
filterTables?: string[]
|
|
31
|
-
): Promise<VersionedSchema> {
|
|
32
|
-
if (dialect === 'postgres') {
|
|
33
|
-
return introspectPostgresCurrentSchema(migrations, filterTables);
|
|
34
|
-
}
|
|
35
|
-
return introspectSqliteCurrentSchema(migrations, filterTables);
|
|
36
|
-
}
|