@syncular/typegen 0.11.0 → 0.12.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 +20 -13
- package/dist/generate.js +2 -1
- package/dist/sql.d.ts +1 -1
- package/dist/sql.js +31 -6
- package/package.json +3 -3
- package/src/generate.ts +7 -1
- package/src/sql.ts +36 -3
package/README.md
CHANGED
|
@@ -184,6 +184,7 @@ duplicate ordinals are errors). The parser accepts exactly:
|
|
|
184
184
|
- `CREATE TABLE [IF NOT EXISTS] name ( column-defs…, [PRIMARY KEY (col)] ) [WITHOUT ROWID]`
|
|
185
185
|
- `ALTER TABLE name ADD [COLUMN] column-def`
|
|
186
186
|
- `CREATE [UNIQUE] INDEX [IF NOT EXISTS] name ON table ( col [, col…] )`
|
|
187
|
+
- `DROP TABLE [IF EXISTS] name`
|
|
187
188
|
- column-def: `name TYPE [PRIMARY KEY] [NOT NULL] [NULL] [DEFAULT literal]`
|
|
188
189
|
- `--` line comments and `/* … */` block comments
|
|
189
190
|
|
|
@@ -206,21 +207,27 @@ inline or table-level.
|
|
|
206
207
|
secondary indexes on an already-created table: `CREATE INDEX name ON table
|
|
207
208
|
(col)`, a compound `… (a, b)` (order preserved), the `UNIQUE` variant, and
|
|
208
209
|
`IF NOT EXISTS`. Each index becomes an `IrTable.indexes` entry
|
|
209
|
-
(`{ name, columns, unique }`, declaration order) and is materialized
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
columns must exist on the table; index names must be unique
|
|
213
|
-
a column must not repeat within one index.
|
|
214
|
-
|
|
215
|
-
(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
210
|
+
(`{ name, columns, unique }`, declaration order) and is materialized by the
|
|
211
|
+
clients (the TS web-client mirror + the Rust core's base/visible table pair),
|
|
212
|
+
typegen's named-query type-check DB, and the server's relational app-table
|
|
213
|
+
projection. Index columns must exist on the table; index names must be unique
|
|
214
|
+
across the schema; a column must not repeat within one index. The column list
|
|
215
|
+
is bare column names: **ASC/DESC**, **expression** columns (`lower(a)`), and
|
|
216
|
+
**partial** (`WHERE …`) indexes are hard errors (the IR models column names
|
|
217
|
+
only, so accepting a direction/expression would silently drop it).
|
|
218
|
+
|
|
219
|
+
**Table retirement** (`DROP TABLE`). A table created earlier in migration
|
|
220
|
+
history and dropped before the head version is absent from the IR and must be
|
|
221
|
+
omitted from the manifest's `tables` list. `IF EXISTS` is supported. Reusing a
|
|
222
|
+
dropped table name is rejected: the head-only IR cannot distinguish a new
|
|
223
|
+
table from an incompatible in-place rewrite on an upgrading server. On a
|
|
224
|
+
schema-version bump, clients wipe and re-bootstrap their synced tables; the
|
|
225
|
+
reference relational server drops the retired current-row table and its live
|
|
226
|
+
scope index. Append-only commit history follows the configured retention
|
|
227
|
+
policy—`DROP TABLE` is schema retirement, not a compliance erasure API.
|
|
221
228
|
|
|
222
229
|
**Hard errors** (each names the construct and source file): any other
|
|
223
|
-
statement (`CREATE TRIGGER/VIEW`, `DROP`, DML, `ALTER … RENAME`, …); unknown
|
|
230
|
+
statement (`CREATE TRIGGER/VIEW`, `DROP INDEX`, DML, `ALTER … RENAME`, …); unknown
|
|
224
231
|
or parameterized types (`VARCHAR(36)`); quoted identifiers
|
|
225
232
|
(`"t"`, `` `t` ``, `[t]`); table constraints (`FOREIGN KEY`, `UNIQUE`,
|
|
226
233
|
`CHECK`, `CONSTRAINT`); column constraints beyond the list above
|
package/dist/generate.js
CHANGED
|
@@ -152,8 +152,9 @@ export function buildIr(manifest, migrations) {
|
|
|
152
152
|
throw new TypegenError(MANIFEST_FILENAME, 'no migrations found');
|
|
153
153
|
}
|
|
154
154
|
const parsedTables = new Map();
|
|
155
|
+
const droppedTables = new Set();
|
|
155
156
|
for (const migration of migrations) {
|
|
156
|
-
applyMigrationSql(parsedTables, migration.sql, `${migration.name}/up.sql
|
|
157
|
+
applyMigrationSql(parsedTables, migration.sql, `${migration.name}/up.sql`, droppedTables);
|
|
157
158
|
}
|
|
158
159
|
const schemaVersions = buildSchemaVersions(manifest, migrations);
|
|
159
160
|
const tables = manifest.tables.map((manifestTable) => {
|
package/dist/sql.d.ts
CHANGED
|
@@ -10,4 +10,4 @@ export interface ParsedTable {
|
|
|
10
10
|
* Apply one migration file's SQL to the accumulated table map. Columns
|
|
11
11
|
* accumulate in declaration order — the §2.4 row-codec positional order.
|
|
12
12
|
*/
|
|
13
|
-
export declare function applyMigrationSql(tables: Map<string, ParsedTable>, sql: string, source: string): void;
|
|
13
|
+
export declare function applyMigrationSql(tables: Map<string, ParsedTable>, sql: string, source: string, droppedTables?: Set<string>): void;
|
package/dist/sql.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* [WITHOUT ROWID]`
|
|
8
8
|
* - `ALTER TABLE name ADD [COLUMN] coldef`
|
|
9
9
|
* - `CREATE [UNIQUE] INDEX [IF NOT EXISTS] name ON table (col [, col…])`
|
|
10
|
+
* - `DROP TABLE [IF EXISTS] name`
|
|
10
11
|
* - column defs: `name TYPE [PRIMARY KEY] [NOT NULL] [NULL]
|
|
11
12
|
* [DEFAULT literal]`
|
|
12
13
|
* - `--` and C-style comments
|
|
@@ -246,9 +247,9 @@ function parseColumnDef(cursor, allowPrimaryKey) {
|
|
|
246
247
|
* Dispatch a `CREATE …` statement: `CREATE TABLE`, `CREATE INDEX`, or
|
|
247
248
|
* `CREATE UNIQUE INDEX`. Anything else is a hard error naming the construct.
|
|
248
249
|
*/
|
|
249
|
-
function parseCreate(cursor, tables, source) {
|
|
250
|
+
function parseCreate(cursor, tables, droppedTables, source) {
|
|
250
251
|
if (cursor.eatWord('TABLE')) {
|
|
251
|
-
parseCreateTable(cursor, tables, source);
|
|
252
|
+
parseCreateTable(cursor, tables, droppedTables, source);
|
|
252
253
|
return;
|
|
253
254
|
}
|
|
254
255
|
if (cursor.eatWord('UNIQUE')) {
|
|
@@ -263,12 +264,15 @@ function parseCreate(cursor, tables, source) {
|
|
|
263
264
|
const token = cursor.peek();
|
|
264
265
|
cursor.fail(`unsupported CREATE statement (only CREATE TABLE and CREATE [UNIQUE] INDEX), found ${JSON.stringify(token?.text ?? '')}`);
|
|
265
266
|
}
|
|
266
|
-
function parseCreateTable(cursor, tables, source) {
|
|
267
|
+
function parseCreateTable(cursor, tables, droppedTables, source) {
|
|
267
268
|
if (cursor.eatWord('IF')) {
|
|
268
269
|
cursor.expectWord('NOT', 'after IF');
|
|
269
270
|
cursor.expectWord('EXISTS', 'after IF NOT');
|
|
270
271
|
}
|
|
271
272
|
const name = cursor.identifier('a table name');
|
|
273
|
+
if (droppedTables.has(name)) {
|
|
274
|
+
cursor.fail(`table ${name} cannot be re-created after DROP TABLE — table-name reuse is unsupported`);
|
|
275
|
+
}
|
|
272
276
|
if (tables.has(name)) {
|
|
273
277
|
cursor.fail(`table ${name} is created twice`);
|
|
274
278
|
}
|
|
@@ -418,23 +422,44 @@ function parseAlterTable(cursor, tables) {
|
|
|
418
422
|
}
|
|
419
423
|
table.columns.push(def.column);
|
|
420
424
|
}
|
|
425
|
+
/** `DROP TABLE [IF EXISTS] name`; table-name reuse remains unsupported. */
|
|
426
|
+
function parseDropTable(cursor, tables, droppedTables) {
|
|
427
|
+
cursor.expectWord('TABLE', 'after DROP');
|
|
428
|
+
let ifExists = false;
|
|
429
|
+
if (cursor.eatWord('IF')) {
|
|
430
|
+
cursor.expectWord('EXISTS', 'after IF');
|
|
431
|
+
ifExists = true;
|
|
432
|
+
}
|
|
433
|
+
const name = cursor.identifier('a table name');
|
|
434
|
+
cursor.expectEnd();
|
|
435
|
+
if (!tables.has(name)) {
|
|
436
|
+
if (ifExists)
|
|
437
|
+
return;
|
|
438
|
+
cursor.fail(`DROP TABLE ${name}: table does not exist at this point`);
|
|
439
|
+
}
|
|
440
|
+
tables.delete(name);
|
|
441
|
+
droppedTables.add(name);
|
|
442
|
+
}
|
|
421
443
|
/**
|
|
422
444
|
* Apply one migration file's SQL to the accumulated table map. Columns
|
|
423
445
|
* accumulate in declaration order — the §2.4 row-codec positional order.
|
|
424
446
|
*/
|
|
425
|
-
export function applyMigrationSql(tables, sql, source) {
|
|
447
|
+
export function applyMigrationSql(tables, sql, source, droppedTables = new Set()) {
|
|
426
448
|
for (const tokens of tokenizeStatements(sql, source)) {
|
|
427
449
|
const cursor = new Cursor(tokens, source);
|
|
428
450
|
const head = cursor.next();
|
|
429
451
|
const word = head.kind === 'word' ? head.text.toUpperCase() : '';
|
|
430
452
|
if (word === 'CREATE') {
|
|
431
|
-
parseCreate(cursor, tables, source);
|
|
453
|
+
parseCreate(cursor, tables, droppedTables, source);
|
|
432
454
|
}
|
|
433
455
|
else if (word === 'ALTER') {
|
|
434
456
|
parseAlterTable(cursor, tables);
|
|
435
457
|
}
|
|
458
|
+
else if (word === 'DROP') {
|
|
459
|
+
parseDropTable(cursor, tables, droppedTables);
|
|
460
|
+
}
|
|
436
461
|
else {
|
|
437
|
-
throw new TypegenError(source, `unsupported SQL statement starting with ${JSON.stringify(head.text)} (only CREATE TABLE, CREATE [UNIQUE] INDEX,
|
|
462
|
+
throw new TypegenError(source, `unsupported SQL statement starting with ${JSON.stringify(head.text)} (only CREATE TABLE, CREATE [UNIQUE] INDEX, ALTER TABLE … ADD COLUMN, and DROP TABLE)`);
|
|
438
463
|
}
|
|
439
464
|
}
|
|
440
465
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/typegen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Syncular schema-to-TypeScript type generator and the `syncular` CLI",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"!dist/**/*.test.d.ts"
|
|
49
49
|
],
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@syncular/core": "0.
|
|
52
|
-
"@syncular/server": "0.
|
|
51
|
+
"@syncular/core": "0.12.0",
|
|
52
|
+
"@syncular/server": "0.12.0"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/src/generate.ts
CHANGED
|
@@ -263,8 +263,14 @@ export function buildIr(
|
|
|
263
263
|
throw new TypegenError(MANIFEST_FILENAME, 'no migrations found');
|
|
264
264
|
}
|
|
265
265
|
const parsedTables = new Map<string, ParsedTable>();
|
|
266
|
+
const droppedTables = new Set<string>();
|
|
266
267
|
for (const migration of migrations) {
|
|
267
|
-
applyMigrationSql(
|
|
268
|
+
applyMigrationSql(
|
|
269
|
+
parsedTables,
|
|
270
|
+
migration.sql,
|
|
271
|
+
`${migration.name}/up.sql`,
|
|
272
|
+
droppedTables,
|
|
273
|
+
);
|
|
268
274
|
}
|
|
269
275
|
const schemaVersions = buildSchemaVersions(manifest, migrations);
|
|
270
276
|
const tables = manifest.tables.map((manifestTable) => {
|
package/src/sql.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* [WITHOUT ROWID]`
|
|
8
8
|
* - `ALTER TABLE name ADD [COLUMN] coldef`
|
|
9
9
|
* - `CREATE [UNIQUE] INDEX [IF NOT EXISTS] name ON table (col [, col…])`
|
|
10
|
+
* - `DROP TABLE [IF EXISTS] name`
|
|
10
11
|
* - column defs: `name TYPE [PRIMARY KEY] [NOT NULL] [NULL]
|
|
11
12
|
* [DEFAULT literal]`
|
|
12
13
|
* - `--` and C-style comments
|
|
@@ -290,10 +291,11 @@ function parseColumnDef(cursor: Cursor, allowPrimaryKey: boolean): ColumnDef {
|
|
|
290
291
|
function parseCreate(
|
|
291
292
|
cursor: Cursor,
|
|
292
293
|
tables: Map<string, ParsedTable>,
|
|
294
|
+
droppedTables: ReadonlySet<string>,
|
|
293
295
|
source: string,
|
|
294
296
|
): void {
|
|
295
297
|
if (cursor.eatWord('TABLE')) {
|
|
296
|
-
parseCreateTable(cursor, tables, source);
|
|
298
|
+
parseCreateTable(cursor, tables, droppedTables, source);
|
|
297
299
|
return;
|
|
298
300
|
}
|
|
299
301
|
if (cursor.eatWord('UNIQUE')) {
|
|
@@ -314,6 +316,7 @@ function parseCreate(
|
|
|
314
316
|
function parseCreateTable(
|
|
315
317
|
cursor: Cursor,
|
|
316
318
|
tables: Map<string, ParsedTable>,
|
|
319
|
+
droppedTables: ReadonlySet<string>,
|
|
317
320
|
source: string,
|
|
318
321
|
): void {
|
|
319
322
|
if (cursor.eatWord('IF')) {
|
|
@@ -321,6 +324,11 @@ function parseCreateTable(
|
|
|
321
324
|
cursor.expectWord('EXISTS', 'after IF NOT');
|
|
322
325
|
}
|
|
323
326
|
const name = cursor.identifier('a table name');
|
|
327
|
+
if (droppedTables.has(name)) {
|
|
328
|
+
cursor.fail(
|
|
329
|
+
`table ${name} cannot be re-created after DROP TABLE — table-name reuse is unsupported`,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
324
332
|
if (tables.has(name)) {
|
|
325
333
|
cursor.fail(`table ${name} is created twice`);
|
|
326
334
|
}
|
|
@@ -506,6 +514,28 @@ function parseAlterTable(
|
|
|
506
514
|
table.columns.push(def.column);
|
|
507
515
|
}
|
|
508
516
|
|
|
517
|
+
/** `DROP TABLE [IF EXISTS] name`; table-name reuse remains unsupported. */
|
|
518
|
+
function parseDropTable(
|
|
519
|
+
cursor: Cursor,
|
|
520
|
+
tables: Map<string, ParsedTable>,
|
|
521
|
+
droppedTables: Set<string>,
|
|
522
|
+
): void {
|
|
523
|
+
cursor.expectWord('TABLE', 'after DROP');
|
|
524
|
+
let ifExists = false;
|
|
525
|
+
if (cursor.eatWord('IF')) {
|
|
526
|
+
cursor.expectWord('EXISTS', 'after IF');
|
|
527
|
+
ifExists = true;
|
|
528
|
+
}
|
|
529
|
+
const name = cursor.identifier('a table name');
|
|
530
|
+
cursor.expectEnd();
|
|
531
|
+
if (!tables.has(name)) {
|
|
532
|
+
if (ifExists) return;
|
|
533
|
+
cursor.fail(`DROP TABLE ${name}: table does not exist at this point`);
|
|
534
|
+
}
|
|
535
|
+
tables.delete(name);
|
|
536
|
+
droppedTables.add(name);
|
|
537
|
+
}
|
|
538
|
+
|
|
509
539
|
/**
|
|
510
540
|
* Apply one migration file's SQL to the accumulated table map. Columns
|
|
511
541
|
* accumulate in declaration order — the §2.4 row-codec positional order.
|
|
@@ -514,19 +544,22 @@ export function applyMigrationSql(
|
|
|
514
544
|
tables: Map<string, ParsedTable>,
|
|
515
545
|
sql: string,
|
|
516
546
|
source: string,
|
|
547
|
+
droppedTables: Set<string> = new Set<string>(),
|
|
517
548
|
): void {
|
|
518
549
|
for (const tokens of tokenizeStatements(sql, source)) {
|
|
519
550
|
const cursor = new Cursor(tokens, source);
|
|
520
551
|
const head = cursor.next();
|
|
521
552
|
const word = head.kind === 'word' ? head.text.toUpperCase() : '';
|
|
522
553
|
if (word === 'CREATE') {
|
|
523
|
-
parseCreate(cursor, tables, source);
|
|
554
|
+
parseCreate(cursor, tables, droppedTables, source);
|
|
524
555
|
} else if (word === 'ALTER') {
|
|
525
556
|
parseAlterTable(cursor, tables);
|
|
557
|
+
} else if (word === 'DROP') {
|
|
558
|
+
parseDropTable(cursor, tables, droppedTables);
|
|
526
559
|
} else {
|
|
527
560
|
throw new TypegenError(
|
|
528
561
|
source,
|
|
529
|
-
`unsupported SQL statement starting with ${JSON.stringify(head.text)} (only CREATE TABLE, CREATE [UNIQUE] INDEX,
|
|
562
|
+
`unsupported SQL statement starting with ${JSON.stringify(head.text)} (only CREATE TABLE, CREATE [UNIQUE] INDEX, ALTER TABLE … ADD COLUMN, and DROP TABLE)`,
|
|
530
563
|
);
|
|
531
564
|
}
|
|
532
565
|
}
|