@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.
Files changed (99) hide show
  1. package/README.md +436 -94
  2. package/dist/cli.d.ts +1 -2
  3. package/dist/cli.js +159 -73
  4. package/dist/emit-dart.d.ts +17 -0
  5. package/dist/emit-dart.js +201 -0
  6. package/dist/emit-kotlin.d.ts +16 -0
  7. package/dist/emit-kotlin.js +201 -0
  8. package/dist/emit-queries-dart.d.ts +2 -0
  9. package/dist/emit-queries-dart.js +160 -0
  10. package/dist/emit-queries-kotlin.d.ts +2 -0
  11. package/dist/emit-queries-kotlin.js +162 -0
  12. package/dist/emit-queries-swift.d.ts +2 -0
  13. package/dist/emit-queries-swift.js +146 -0
  14. package/dist/emit-queries.d.ts +2 -0
  15. package/dist/emit-queries.js +129 -0
  16. package/dist/emit-swift.d.ts +18 -0
  17. package/dist/emit-swift.js +235 -0
  18. package/dist/emit.d.ts +16 -0
  19. package/dist/emit.js +191 -0
  20. package/dist/errors.d.ts +6 -0
  21. package/dist/errors.js +10 -0
  22. package/dist/generate.d.ts +53 -18
  23. package/dist/generate.js +391 -77
  24. package/dist/index.d.ts +17 -14
  25. package/dist/index.js +16 -13
  26. package/dist/init.d.ts +12 -0
  27. package/dist/init.js +85 -0
  28. package/dist/ir.d.ts +90 -0
  29. package/dist/ir.js +94 -0
  30. package/dist/manifest.d.ts +76 -0
  31. package/dist/manifest.js +303 -0
  32. package/dist/query.d.ts +96 -0
  33. package/dist/query.js +719 -0
  34. package/dist/sql.d.ts +13 -0
  35. package/dist/sql.js +440 -0
  36. package/package.json +14 -34
  37. package/src/cli.ts +161 -82
  38. package/src/emit-dart.ts +245 -0
  39. package/src/emit-kotlin.ts +257 -0
  40. package/src/emit-queries-dart.ts +203 -0
  41. package/src/emit-queries-kotlin.ts +209 -0
  42. package/src/emit-queries-swift.ts +198 -0
  43. package/src/emit-queries.ts +183 -0
  44. package/src/emit-swift.ts +295 -0
  45. package/src/emit.ts +239 -0
  46. package/src/errors.ts +11 -0
  47. package/src/generate.ts +574 -105
  48. package/src/index.ts +16 -13
  49. package/src/init.ts +100 -0
  50. package/src/ir.ts +197 -0
  51. package/src/manifest.ts +445 -0
  52. package/src/query.ts +918 -0
  53. package/src/sql.ts +533 -0
  54. package/dist/app-contract.d.ts +0 -154
  55. package/dist/app-contract.d.ts.map +0 -1
  56. package/dist/app-contract.js +0 -250
  57. package/dist/app-contract.js.map +0 -1
  58. package/dist/checksums.d.ts +0 -6
  59. package/dist/checksums.d.ts.map +0 -1
  60. package/dist/checksums.js +0 -173
  61. package/dist/checksums.js.map +0 -1
  62. package/dist/cli.d.ts.map +0 -1
  63. package/dist/cli.js.map +0 -1
  64. package/dist/generate.d.ts.map +0 -1
  65. package/dist/generate.js.map +0 -1
  66. package/dist/index.d.ts.map +0 -1
  67. package/dist/index.js.map +0 -1
  68. package/dist/introspect-postgres.d.ts +0 -8
  69. package/dist/introspect-postgres.d.ts.map +0 -1
  70. package/dist/introspect-postgres.js +0 -94
  71. package/dist/introspect-postgres.js.map +0 -1
  72. package/dist/introspect-sqlite.d.ts +0 -10
  73. package/dist/introspect-sqlite.d.ts.map +0 -1
  74. package/dist/introspect-sqlite.js +0 -97
  75. package/dist/introspect-sqlite.js.map +0 -1
  76. package/dist/introspect.d.ts +0 -8
  77. package/dist/introspect.d.ts.map +0 -1
  78. package/dist/introspect.js +0 -18
  79. package/dist/introspect.js.map +0 -1
  80. package/dist/map-types.d.ts +0 -20
  81. package/dist/map-types.d.ts.map +0 -1
  82. package/dist/map-types.js +0 -151
  83. package/dist/map-types.js.map +0 -1
  84. package/dist/render.d.ts +0 -23
  85. package/dist/render.d.ts.map +0 -1
  86. package/dist/render.js +0 -140
  87. package/dist/render.js.map +0 -1
  88. package/dist/types.d.ts +0 -122
  89. package/dist/types.d.ts.map +0 -1
  90. package/dist/types.js +0 -5
  91. package/dist/types.js.map +0 -1
  92. package/src/app-contract.ts +0 -531
  93. package/src/checksums.ts +0 -257
  94. package/src/introspect-postgres.ts +0 -149
  95. package/src/introspect-sqlite.ts +0 -156
  96. package/src/introspect.ts +0 -36
  97. package/src/map-types.ts +0 -189
  98. package/src/render.ts +0 -196
  99. package/src/types.ts +0 -137
package/dist/sql.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { IrColumn, IrIndex } from './ir.js';
2
+ export interface ParsedTable {
3
+ readonly name: string;
4
+ primaryKey: string;
5
+ readonly columns: IrColumn[];
6
+ /** Local secondary indexes, in declaration order (CREATE INDEX subset). */
7
+ readonly indexes: IrIndex[];
8
+ }
9
+ /**
10
+ * Apply one migration file's SQL to the accumulated table map. Columns
11
+ * accumulate in declaration order — the §2.4 row-codec positional order.
12
+ */
13
+ export declare function applyMigrationSql(tables: Map<string, ParsedTable>, sql: string, source: string): void;
package/dist/sql.js ADDED
@@ -0,0 +1,440 @@
1
+ /**
2
+ * SQL migration subset parser (REVISE B5).
3
+ *
4
+ * Parses exactly the subset v1-style migrations need:
5
+ *
6
+ * - `CREATE TABLE [IF NOT EXISTS] name (columns…, [PRIMARY KEY (col)])
7
+ * [WITHOUT ROWID]`
8
+ * - `ALTER TABLE name ADD [COLUMN] coldef`
9
+ * - `CREATE [UNIQUE] INDEX [IF NOT EXISTS] name ON table (col [, col…])`
10
+ * - column defs: `name TYPE [PRIMARY KEY] [NOT NULL] [NULL]
11
+ * [DEFAULT literal]`
12
+ * - `--` and C-style comments
13
+ *
14
+ * Anything else — other statements, table constraints, parameterized or
15
+ * unknown types, DEFAULT expressions, quoted identifiers, composite
16
+ * primary keys, ASC/DESC or expression index columns, partial (`WHERE`)
17
+ * indexes — is a hard error naming the unsupported construct.
18
+ */
19
+ import { TypegenError } from './errors.js';
20
+ /** SQL type keyword → the §2.4 column types. Case-insensitive. */
21
+ const TYPE_MAP = {
22
+ TEXT: 'string',
23
+ INTEGER: 'integer',
24
+ INT: 'integer',
25
+ BIGINT: 'integer',
26
+ SMALLINT: 'integer',
27
+ REAL: 'float',
28
+ FLOAT: 'float',
29
+ DOUBLE: 'float',
30
+ BOOLEAN: 'boolean',
31
+ BOOL: 'boolean',
32
+ JSON: 'json',
33
+ JSONB: 'json',
34
+ BLOB: 'bytes',
35
+ BYTEA: 'bytes',
36
+ // §5.9 tag 7: a stored-as-TEXT column carrying a canonical BlobRef
37
+ // document. The synthetic keyword designates the semantic type; the local
38
+ // column is TEXT-shaped like `json`.
39
+ BLOB_REF: 'blob_ref',
40
+ BLOBREF: 'blob_ref',
41
+ // §5.10 tag 8: a stored-as-BLOB column carrying opaque server-merged CRDT
42
+ // bytes. The synthetic keyword designates the semantic type; crdtType
43
+ // defaults to the one built-in merger (`yjs-doc`, §5.10.1).
44
+ CRDT: 'crdt',
45
+ };
46
+ /** Default `crdtType` for a bare `CRDT` keyword (§5.10.1). */
47
+ const DEFAULT_CRDT_TYPE = 'yjs-doc';
48
+ const WORD_START = /[A-Za-z_]/;
49
+ const WORD_PART = /[A-Za-z0-9_]/;
50
+ const DIGIT = /[0-9]/;
51
+ function tokenizeStatements(sql, source) {
52
+ const statements = [];
53
+ let current = [];
54
+ let i = 0;
55
+ while (i < sql.length) {
56
+ const ch = sql[i];
57
+ if (/\s/.test(ch)) {
58
+ i += 1;
59
+ }
60
+ else if (ch === '-' && sql[i + 1] === '-') {
61
+ const end = sql.indexOf('\n', i);
62
+ i = end === -1 ? sql.length : end + 1;
63
+ }
64
+ else if (ch === '/' && sql[i + 1] === '*') {
65
+ const end = sql.indexOf('*/', i + 2);
66
+ if (end === -1)
67
+ throw new TypegenError(source, 'unterminated comment');
68
+ i = end + 2;
69
+ }
70
+ else if (ch === ';') {
71
+ if (current.length > 0)
72
+ statements.push(current);
73
+ current = [];
74
+ i += 1;
75
+ }
76
+ else if (ch === "'") {
77
+ let text = '';
78
+ i += 1;
79
+ for (;;) {
80
+ if (i >= sql.length) {
81
+ throw new TypegenError(source, 'unterminated string literal');
82
+ }
83
+ if (sql[i] === "'") {
84
+ if (sql[i + 1] === "'") {
85
+ text += "'";
86
+ i += 2;
87
+ }
88
+ else {
89
+ i += 1;
90
+ break;
91
+ }
92
+ }
93
+ else {
94
+ text += sql[i];
95
+ i += 1;
96
+ }
97
+ }
98
+ current.push({ kind: 'string', text });
99
+ }
100
+ else if (ch === '"' || ch === '`' || ch === '[') {
101
+ throw new TypegenError(source, `quoted identifiers are unsupported (found ${JSON.stringify(ch)})`);
102
+ }
103
+ else if (WORD_START.test(ch)) {
104
+ let end = i + 1;
105
+ while (end < sql.length && WORD_PART.test(sql[end]))
106
+ end += 1;
107
+ current.push({ kind: 'word', text: sql.slice(i, end) });
108
+ i = end;
109
+ }
110
+ else if (DIGIT.test(ch)) {
111
+ let end = i + 1;
112
+ while (end < sql.length && /[0-9.]/.test(sql[end]))
113
+ end += 1;
114
+ current.push({ kind: 'number', text: sql.slice(i, end) });
115
+ i = end;
116
+ }
117
+ else if (ch === '(' || ch === ')' || ch === ',') {
118
+ current.push({ kind: 'punct', text: ch });
119
+ i += 1;
120
+ }
121
+ else {
122
+ throw new TypegenError(source, `unsupported SQL syntax: unexpected character ${JSON.stringify(ch)}`);
123
+ }
124
+ }
125
+ if (current.length > 0)
126
+ statements.push(current);
127
+ return statements;
128
+ }
129
+ class Cursor {
130
+ tokens;
131
+ source;
132
+ index = 0;
133
+ constructor(tokens, source) {
134
+ this.tokens = tokens;
135
+ this.source = source;
136
+ }
137
+ peek() {
138
+ return this.tokens[this.index];
139
+ }
140
+ next() {
141
+ const token = this.tokens[this.index];
142
+ if (token === undefined) {
143
+ throw new TypegenError(this.source, 'unexpected end of SQL statement');
144
+ }
145
+ this.index += 1;
146
+ return token;
147
+ }
148
+ /** Consume a word token if it matches (case-insensitive). */
149
+ eatWord(word) {
150
+ const token = this.peek();
151
+ if (token?.kind === 'word' && token.text.toUpperCase() === word) {
152
+ this.index += 1;
153
+ return true;
154
+ }
155
+ return false;
156
+ }
157
+ expectWord(word, context) {
158
+ const token = this.next();
159
+ if (token.kind !== 'word' || token.text.toUpperCase() !== word) {
160
+ throw new TypegenError(this.source, `expected ${word} ${context}, found ${JSON.stringify(token.text)}`);
161
+ }
162
+ }
163
+ expectPunct(punct, context) {
164
+ const token = this.next();
165
+ if (token.kind !== 'punct' || token.text !== punct) {
166
+ throw new TypegenError(this.source, `expected ${JSON.stringify(punct)} ${context}, found ${JSON.stringify(token.text)}`);
167
+ }
168
+ }
169
+ identifier(context) {
170
+ const token = this.next();
171
+ if (token.kind !== 'word') {
172
+ throw new TypegenError(this.source, `expected ${context}, found ${JSON.stringify(token.text)}`);
173
+ }
174
+ return token.text;
175
+ }
176
+ expectEnd() {
177
+ const token = this.peek();
178
+ if (token !== undefined) {
179
+ throw new TypegenError(this.source, `unsupported trailing SQL ${JSON.stringify(token.text)}`);
180
+ }
181
+ }
182
+ fail(message) {
183
+ throw new TypegenError(this.source, message);
184
+ }
185
+ }
186
+ function parseColumnType(cursor, columnName) {
187
+ const typeWord = cursor.identifier(`a column type for ${columnName}`);
188
+ if (cursor.peek()?.text === '(') {
189
+ cursor.fail(`column ${columnName}: parameterized column types are unsupported (${typeWord}(…))`);
190
+ }
191
+ const mapped = TYPE_MAP[typeWord.toUpperCase()];
192
+ if (mapped === undefined) {
193
+ cursor.fail(`column ${columnName}: unsupported column type ${JSON.stringify(typeWord)}`);
194
+ }
195
+ return mapped;
196
+ }
197
+ function parseColumnDef(cursor, allowPrimaryKey) {
198
+ const name = cursor.identifier('a column name');
199
+ const type = parseColumnType(cursor, name);
200
+ let nullable = true;
201
+ let primaryKey = false;
202
+ for (;;) {
203
+ const token = cursor.peek();
204
+ if (token === undefined || token.kind === 'punct')
205
+ break;
206
+ const word = token.text.toUpperCase();
207
+ if (word === 'PRIMARY') {
208
+ cursor.next();
209
+ cursor.expectWord('KEY', 'after PRIMARY');
210
+ if (!allowPrimaryKey) {
211
+ cursor.fail(`column ${name}: PRIMARY KEY is not supported on ADD COLUMN`);
212
+ }
213
+ primaryKey = true;
214
+ nullable = false;
215
+ }
216
+ else if (word === 'NOT') {
217
+ cursor.next();
218
+ cursor.expectWord('NULL', 'after NOT');
219
+ nullable = false;
220
+ }
221
+ else if (word === 'NULL') {
222
+ cursor.next();
223
+ }
224
+ else if (word === 'DEFAULT') {
225
+ cursor.next();
226
+ const value = cursor.next();
227
+ if (value.kind === 'punct') {
228
+ cursor.fail(`column ${name}: DEFAULT expressions are unsupported (only literals)`);
229
+ }
230
+ // Literal defaults are accepted and ignored: typegen extracts the
231
+ // schema shape; running migrations is the host's job.
232
+ }
233
+ else {
234
+ cursor.fail(`column ${name}: unsupported column constraint ${JSON.stringify(token.text)}`);
235
+ }
236
+ }
237
+ if (primaryKey)
238
+ nullable = false;
239
+ // §5.10.1: a crdt column carries a crdtType (default `yjs-doc`).
240
+ const column = type === 'crdt'
241
+ ? { name, type, nullable, crdtType: DEFAULT_CRDT_TYPE }
242
+ : { name, type, nullable };
243
+ return { column, primaryKey };
244
+ }
245
+ /**
246
+ * Dispatch a `CREATE …` statement: `CREATE TABLE`, `CREATE INDEX`, or
247
+ * `CREATE UNIQUE INDEX`. Anything else is a hard error naming the construct.
248
+ */
249
+ function parseCreate(cursor, tables, source) {
250
+ if (cursor.eatWord('TABLE')) {
251
+ parseCreateTable(cursor, tables, source);
252
+ return;
253
+ }
254
+ if (cursor.eatWord('UNIQUE')) {
255
+ cursor.expectWord('INDEX', 'after CREATE UNIQUE');
256
+ parseCreateIndex(cursor, tables, true);
257
+ return;
258
+ }
259
+ if (cursor.eatWord('INDEX')) {
260
+ parseCreateIndex(cursor, tables, false);
261
+ return;
262
+ }
263
+ const token = cursor.peek();
264
+ cursor.fail(`unsupported CREATE statement (only CREATE TABLE and CREATE [UNIQUE] INDEX), found ${JSON.stringify(token?.text ?? '')}`);
265
+ }
266
+ function parseCreateTable(cursor, tables, source) {
267
+ if (cursor.eatWord('IF')) {
268
+ cursor.expectWord('NOT', 'after IF');
269
+ cursor.expectWord('EXISTS', 'after IF NOT');
270
+ }
271
+ const name = cursor.identifier('a table name');
272
+ if (tables.has(name)) {
273
+ cursor.fail(`table ${name} is created twice`);
274
+ }
275
+ cursor.expectPunct('(', `after CREATE TABLE ${name}`);
276
+ const columns = [];
277
+ const primaryKeys = [];
278
+ for (;;) {
279
+ const head = cursor.peek();
280
+ if (head?.kind === 'word') {
281
+ const word = head.text.toUpperCase();
282
+ if (word === 'PRIMARY') {
283
+ cursor.next();
284
+ cursor.expectWord('KEY', 'after PRIMARY');
285
+ cursor.expectPunct('(', 'after PRIMARY KEY');
286
+ primaryKeys.push(cursor.identifier('a primary-key column name'));
287
+ if (cursor.peek()?.text === ',') {
288
+ cursor.fail(`table ${name}: composite primary keys are unsupported`);
289
+ }
290
+ cursor.expectPunct(')', 'after the PRIMARY KEY column');
291
+ }
292
+ else if (word === 'FOREIGN' ||
293
+ word === 'UNIQUE' ||
294
+ word === 'CHECK' ||
295
+ word === 'CONSTRAINT') {
296
+ cursor.fail(`table ${name}: unsupported table constraint ${JSON.stringify(head.text)}`);
297
+ }
298
+ else {
299
+ const def = parseColumnDef(cursor, true);
300
+ if (columns.some((c) => c.name === def.column.name)) {
301
+ cursor.fail(`table ${name}: duplicate column ${JSON.stringify(def.column.name)}`);
302
+ }
303
+ columns.push(def.column);
304
+ if (def.primaryKey)
305
+ primaryKeys.push(def.column.name);
306
+ }
307
+ }
308
+ else {
309
+ cursor.fail(`table ${name}: expected a column definition`);
310
+ }
311
+ const sep = cursor.next();
312
+ if (sep.kind === 'punct' && sep.text === ',')
313
+ continue;
314
+ if (sep.kind === 'punct' && sep.text === ')')
315
+ break;
316
+ cursor.fail(`table ${name}: expected "," or ")", found ${JSON.stringify(sep.text)}`);
317
+ }
318
+ if (cursor.eatWord('WITHOUT')) {
319
+ cursor.expectWord('ROWID', 'after WITHOUT');
320
+ }
321
+ cursor.expectEnd();
322
+ if (primaryKeys.length === 0) {
323
+ throw new TypegenError(source, `table ${name} declares no primary key`);
324
+ }
325
+ if (primaryKeys.length > 1) {
326
+ throw new TypegenError(source, `table ${name}: composite primary keys are unsupported (${primaryKeys.join(', ')})`);
327
+ }
328
+ const primaryKey = primaryKeys[0];
329
+ const pkColumn = columns.find((c) => c.name === primaryKey);
330
+ if (pkColumn === undefined) {
331
+ throw new TypegenError(source, `table ${name}: primary key ${JSON.stringify(primaryKey)} is not a column`);
332
+ }
333
+ const finalColumns = columns.map((c) => c.name === primaryKey ? { ...c, nullable: false } : c);
334
+ tables.set(name, { name, primaryKey, columns: finalColumns, indexes: [] });
335
+ }
336
+ /**
337
+ * `CREATE [UNIQUE] INDEX [IF NOT EXISTS] name ON table (col [, col…])`.
338
+ * The `UNIQUE` keyword is already consumed by the dispatcher (it precedes
339
+ * `INDEX`), so `unique` is passed in. The subset keeps the column list to
340
+ * bare column names of the target table: ASC/DESC, expressions, and a `WHERE`
341
+ * (partial index) clause are hard errors naming the construct. Index names
342
+ * are unique per accumulated schema.
343
+ */
344
+ function parseCreateIndex(cursor, tables, unique) {
345
+ // `INDEX` already matched by the dispatcher.
346
+ if (cursor.eatWord('IF')) {
347
+ cursor.expectWord('NOT', 'after IF');
348
+ cursor.expectWord('EXISTS', 'after IF NOT');
349
+ }
350
+ const name = cursor.identifier('an index name');
351
+ for (const table of tables.values()) {
352
+ if (table.indexes.some((i) => i.name === name)) {
353
+ cursor.fail(`index ${name} is created twice`);
354
+ }
355
+ }
356
+ cursor.expectWord('ON', `after CREATE INDEX ${name}`);
357
+ const tableName = cursor.identifier('a table name');
358
+ const table = tables.get(tableName);
359
+ if (table === undefined) {
360
+ cursor.fail(`CREATE INDEX ${name}: table ${tableName} does not exist at this point`);
361
+ }
362
+ cursor.expectPunct('(', `after CREATE INDEX ${name} ON ${tableName}`);
363
+ const columns = [];
364
+ for (;;) {
365
+ const col = cursor.identifier(`an index column for ${name}`);
366
+ // Reject ASC/DESC and any expression tail: the IR stores bare column names
367
+ // only, so accepting a direction would silently drop it. A following word
368
+ // (not "," or ")") is such an unsupported construct.
369
+ const after = cursor.peek();
370
+ if (after !== undefined &&
371
+ !(after.kind === 'punct' && (after.text === ',' || after.text === ')'))) {
372
+ const dir = after.text.toUpperCase();
373
+ if (dir === 'ASC' || dir === 'DESC') {
374
+ cursor.fail(`index ${name}: ASC/DESC index columns are unsupported (found ${JSON.stringify(after.text)} after ${JSON.stringify(col)})`);
375
+ }
376
+ cursor.fail(`index ${name}: expression index columns are unsupported (found ${JSON.stringify(after.text)} after ${JSON.stringify(col)})`);
377
+ }
378
+ if (table.columns.every((c) => c.name !== col)) {
379
+ cursor.fail(`index ${name}: column ${JSON.stringify(col)} does not exist on table ${tableName}`);
380
+ }
381
+ if (columns.includes(col)) {
382
+ cursor.fail(`index ${name}: column ${JSON.stringify(col)} appears twice`);
383
+ }
384
+ columns.push(col);
385
+ const sep = cursor.next();
386
+ if (sep.kind === 'punct' && sep.text === ',')
387
+ continue;
388
+ if (sep.kind === 'punct' && sep.text === ')')
389
+ break;
390
+ cursor.fail(`index ${name}: expected "," or ")", found ${JSON.stringify(sep.text)}`);
391
+ }
392
+ // A trailing token here is a partial-index `WHERE`, `COLLATE`, etc.
393
+ const trailing = cursor.peek();
394
+ if (trailing !== undefined) {
395
+ if (trailing.kind === 'word' && trailing.text.toUpperCase() === 'WHERE') {
396
+ cursor.fail(`index ${name}: partial indexes (WHERE …) are unsupported`);
397
+ }
398
+ cursor.fail(`index ${name}: unsupported trailing SQL ${JSON.stringify(trailing.text)}`);
399
+ }
400
+ table.indexes.push({ name, columns, unique });
401
+ }
402
+ function parseAlterTable(cursor, tables) {
403
+ cursor.expectWord('TABLE', 'after ALTER');
404
+ const name = cursor.identifier('a table name');
405
+ const table = tables.get(name);
406
+ if (table === undefined) {
407
+ cursor.fail(`ALTER TABLE ${name}: table does not exist at this point`);
408
+ }
409
+ const action = cursor.identifier('an ALTER TABLE action');
410
+ if (action.toUpperCase() !== 'ADD') {
411
+ cursor.fail(`ALTER TABLE ${name}: unsupported action ${JSON.stringify(action)} (only ADD COLUMN)`);
412
+ }
413
+ cursor.eatWord('COLUMN');
414
+ const def = parseColumnDef(cursor, false);
415
+ cursor.expectEnd();
416
+ if (table.columns.some((c) => c.name === def.column.name)) {
417
+ cursor.fail(`ALTER TABLE ${name}: duplicate column ${JSON.stringify(def.column.name)}`);
418
+ }
419
+ table.columns.push(def.column);
420
+ }
421
+ /**
422
+ * Apply one migration file's SQL to the accumulated table map. Columns
423
+ * accumulate in declaration order — the §2.4 row-codec positional order.
424
+ */
425
+ export function applyMigrationSql(tables, sql, source) {
426
+ for (const tokens of tokenizeStatements(sql, source)) {
427
+ const cursor = new Cursor(tokens, source);
428
+ const head = cursor.next();
429
+ const word = head.kind === 'word' ? head.text.toUpperCase() : '';
430
+ if (word === 'CREATE') {
431
+ parseCreate(cursor, tables, source);
432
+ }
433
+ else if (word === 'ALTER') {
434
+ parseAlterTable(cursor, tables);
435
+ }
436
+ else {
437
+ throw new TypegenError(source, `unsupported SQL statement starting with ${JSON.stringify(head.text)} (only CREATE TABLE, CREATE [UNIQUE] INDEX, and ALTER TABLE … ADD COLUMN)`);
438
+ }
439
+ }
440
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@syncular/typegen",
3
- "version": "0.1.3",
4
- "description": "TypeScript type generator for Syncular schemas",
3
+ "version": "0.2.1",
4
+ "description": "Syncular schema-to-TypeScript type generator and the `syncular` CLI",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
7
7
  "homepage": "https://syncular.dev",
@@ -18,57 +18,37 @@
18
18
  "offline-first",
19
19
  "realtime",
20
20
  "database",
21
- "typescript",
22
- "codegen",
23
21
  "typescript"
24
22
  ],
25
- "private": false,
23
+ "type": "module",
24
+ "sideEffects": false,
26
25
  "publishConfig": {
27
26
  "access": "public"
28
27
  },
29
- "type": "module",
30
28
  "bin": {
31
- "syncular-typegen": "./src/cli.ts"
29
+ "syncular": "./dist/cli.js"
32
30
  },
33
31
  "exports": {
34
32
  ".": {
35
33
  "bun": "./src/index.ts",
34
+ "browser": "./src/index.ts",
36
35
  "import": {
37
36
  "types": "./dist/index.d.ts",
38
37
  "default": "./dist/index.js"
39
38
  }
40
39
  }
41
40
  },
42
- "scripts": {
43
- "test": "bun test --pass-with-no-tests",
44
- "tsgo": "tsgo --noEmit",
45
- "build": "tsgo",
46
- "release": "syncular-publish"
47
- },
48
- "dependencies": {
49
- "@electric-sql/pglite": "^0.3.15",
50
- "@syncular/migrations": "0.1.3",
51
- "better-sqlite3": "^12.8.0",
52
- "kysely-bun-sqlite": "^0.4.0",
53
- "kysely-pglite-dialect": "^1.2.0"
54
- },
55
- "peerDependencies": {
56
- "kysely": "^0.28.0"
57
- },
58
- "devDependencies": {
59
- "@syncular/config": "0.0.0",
60
- "@types/better-sqlite3": "^7.6.13",
61
- "kysely": "*"
62
- },
63
41
  "files": [
64
42
  "dist",
65
43
  "src",
44
+ "README.md",
66
45
  "!src/**/*.test.ts",
67
46
  "!src/**/*.test.tsx",
68
- "!src/**/__tests__/**",
69
- "!dist/**/*.test.*",
70
- "!dist/**/__tests__/**",
71
- "!test/**",
72
- "!tests/**"
73
- ]
47
+ "!dist/**/*.test.js",
48
+ "!dist/**/*.test.d.ts"
49
+ ],
50
+ "devDependencies": {
51
+ "@syncular/core": "0.2.1",
52
+ "@syncular/server": "0.2.1"
53
+ }
74
54
  }