@syncular/server 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/dist/d1-storage.js +14 -1
- package/dist/postgres-storage.js +8 -1
- package/dist/relational-rows.d.ts +8 -0
- package/dist/relational-rows.js +14 -0
- package/dist/sqlite-storage.js +8 -1
- package/package.json +2 -2
- package/src/d1-storage.ts +17 -0
- package/src/postgres-storage.ts +9 -0
- package/src/relational-rows.ts +19 -0
- package/src/sqlite-storage.ts +9 -0
package/dist/d1-storage.js
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
* rather than a lock D1 does not expose.
|
|
41
41
|
*/
|
|
42
42
|
import { syncError } from './errors.js';
|
|
43
|
-
import { commitWindowPageSql, deleteRowSql, layoutsOf, migratePayload, parseLayouts, rewritePlan, rewriteRowSql, rewriteValues, SCHEMA_META_DDL_SQLITE, scanRowPageSql, schemaDdl, selectRowScopesSql, selectRowSql, selectRowsForRewriteSql, tableColumnNames, upsertSql, upsertValues, } from './relational-rows.js';
|
|
43
|
+
import { commitWindowPageSql, deleteRowSql, dropTableDdl, layoutsOf, migratePayload, parseLayouts, retiredTableNames, rewritePlan, rewriteRowSql, rewriteValues, SCHEMA_META_DDL_SQLITE, scanRowPageSql, schemaDdl, selectRowScopesSql, selectRowSql, selectRowsForRewriteSql, tableColumnNames, upsertSql, upsertValues, } from './relational-rows.js';
|
|
44
44
|
import { matchesEffective } from './scopes.js';
|
|
45
45
|
import { asUint8Array, collectCommitWindowPage, deserializePushResult, serializePushResult, sqliteDdlStatements, toStoredRow, } from './sqlite-dialect.js';
|
|
46
46
|
class D1Transaction {
|
|
@@ -307,6 +307,7 @@ export class D1ServerStorage {
|
|
|
307
307
|
if (marker === null || marker.schema_version < schema.version) {
|
|
308
308
|
await this.migrate();
|
|
309
309
|
const layouts = parseLayouts(marker?.layouts);
|
|
310
|
+
const retiredTables = retiredTableNames(schema, layouts);
|
|
310
311
|
const existing = new Map();
|
|
311
312
|
for (const table of schema.tables.values()) {
|
|
312
313
|
const { results } = await this.#db
|
|
@@ -331,6 +332,18 @@ export class D1ServerStorage {
|
|
|
331
332
|
continue;
|
|
332
333
|
await this.#rewriteRows(table, plan.migrate ? oldLayout : undefined);
|
|
333
334
|
}
|
|
335
|
+
// Retire tables only after the additive DDL and rewrites succeed. D1
|
|
336
|
+
// cannot wrap the whole bump in an interactive transaction, but this
|
|
337
|
+
// ordering avoids destructive work before every fallible preparatory
|
|
338
|
+
// step and the batch keeps table + live-scope cleanup atomic.
|
|
339
|
+
if (retiredTables.length > 0) {
|
|
340
|
+
await this.#db.batch(retiredTables.flatMap((tableName) => [
|
|
341
|
+
this.#db
|
|
342
|
+
.prepare('DELETE FROM sync_row_scopes WHERE tbl=?')
|
|
343
|
+
.bind(tableName),
|
|
344
|
+
this.#db.prepare(dropTableDdl(tableName)),
|
|
345
|
+
]));
|
|
346
|
+
}
|
|
334
347
|
await this.#db
|
|
335
348
|
.prepare('INSERT INTO sync_schema_meta(id, schema_version, layouts) VALUES (1, ?, ?) ON CONFLICT(id) DO UPDATE SET schema_version=excluded.schema_version, layouts=excluded.layouts')
|
|
336
349
|
.bind(schema.version, layoutsOf(schema))
|
package/dist/postgres-storage.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { syncError } from './errors.js';
|
|
2
2
|
import { asBytes, asNumber, } from './pg-executor.js';
|
|
3
|
-
import { commitWindowPageSql, deleteRowSql, layoutsOf, migratePayload, parseLayouts, rewritePlan, rewriteRowSql, rewriteValues, SCHEMA_META_DDL_POSTGRES, scanRowPageSql, schemaDdl, selectRowScopesSql, selectRowSql, selectRowsForRewriteSql, upsertSql, upsertValues, } from './relational-rows.js';
|
|
3
|
+
import { commitWindowPageSql, deleteRowSql, dropTableDdl, layoutsOf, migratePayload, parseLayouts, retiredTableNames, rewritePlan, rewriteRowSql, rewriteValues, SCHEMA_META_DDL_POSTGRES, scanRowPageSql, schemaDdl, selectRowScopesSql, selectRowSql, selectRowsForRewriteSql, upsertSql, upsertValues, } from './relational-rows.js';
|
|
4
4
|
import { matchesEffective } from './scopes.js';
|
|
5
5
|
/**
|
|
6
6
|
* Schema DDL. Applied by `PostgresServerStorage.migrate()` (idempotent).
|
|
@@ -457,6 +457,7 @@ export class PostgresServerStorage {
|
|
|
457
457
|
const layouts = parseLayouts(typeof marker.rows[0]?.layouts === 'string'
|
|
458
458
|
? marker.rows[0].layouts
|
|
459
459
|
: undefined);
|
|
460
|
+
const retiredTables = retiredTableNames(schema, layouts);
|
|
460
461
|
await this.#exec.transaction(async (client) => {
|
|
461
462
|
const existing = new Map();
|
|
462
463
|
for (const table of schema.tables.values()) {
|
|
@@ -466,6 +467,12 @@ export class PostgresServerStorage {
|
|
|
466
467
|
existing.set(table.name, new Set(rows.map((r) => r.column_name)));
|
|
467
468
|
}
|
|
468
469
|
}
|
|
470
|
+
for (const tableName of retiredTables) {
|
|
471
|
+
await client.query('DELETE FROM sync_row_scopes WHERE tbl=$1', [
|
|
472
|
+
tableName,
|
|
473
|
+
]);
|
|
474
|
+
await client.query(dropTableDdl(tableName));
|
|
475
|
+
}
|
|
469
476
|
for (const statement of schemaDdl(schema, existing, 'postgres')) {
|
|
470
477
|
await client.query(statement);
|
|
471
478
|
}
|
|
@@ -178,6 +178,14 @@ export type StoredLayouts = Record<string, readonly StoredColumnLayout[]>;
|
|
|
178
178
|
/** The layouts JSON persisted alongside the schema version marker. */
|
|
179
179
|
export declare function layoutsOf(schema: CompiledSchema): string;
|
|
180
180
|
export declare function parseLayouts(json: string | null | undefined): StoredLayouts;
|
|
181
|
+
/**
|
|
182
|
+
* Tables present in the stored layout but absent from the configured head
|
|
183
|
+
* schema. A schema-version bump retires their relational current-row tables;
|
|
184
|
+
* append-only commit history remains governed by the normal retention policy.
|
|
185
|
+
*/
|
|
186
|
+
export declare function retiredTableNames(schema: CompiledSchema, storedLayouts: StoredLayouts): string[];
|
|
187
|
+
/** Idempotent DDL for one retired relational current-row table. */
|
|
188
|
+
export declare function dropTableDdl(tableName: string): string;
|
|
181
189
|
/**
|
|
182
190
|
* Enforce the migration subset on a table's column list: the old layout
|
|
183
191
|
* must be an exact prefix (same name, type, nullability) of the new one,
|
package/dist/relational-rows.js
CHANGED
|
@@ -381,6 +381,20 @@ export function parseLayouts(json) {
|
|
|
381
381
|
return {};
|
|
382
382
|
return JSON.parse(json);
|
|
383
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Tables present in the stored layout but absent from the configured head
|
|
386
|
+
* schema. A schema-version bump retires their relational current-row tables;
|
|
387
|
+
* append-only commit history remains governed by the normal retention policy.
|
|
388
|
+
*/
|
|
389
|
+
export function retiredTableNames(schema, storedLayouts) {
|
|
390
|
+
return Object.keys(storedLayouts)
|
|
391
|
+
.filter((name) => !schema.tables.has(name))
|
|
392
|
+
.sort();
|
|
393
|
+
}
|
|
394
|
+
/** Idempotent DDL for one retired relational current-row table. */
|
|
395
|
+
export function dropTableDdl(tableName) {
|
|
396
|
+
return `DROP TABLE IF EXISTS ${quoteIdent(tableName)}`;
|
|
397
|
+
}
|
|
384
398
|
/**
|
|
385
399
|
* Enforce the migration subset on a table's column list: the old layout
|
|
386
400
|
* must be an exact prefix (same name, type, nullability) of the new one,
|
package/dist/sqlite-storage.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Database } from 'bun:sqlite';
|
|
10
10
|
import { syncError } from './errors.js';
|
|
11
|
-
import { commitWindowPageSql, deleteRowSql, layoutsOf, migratePayload, parseLayouts, rewritePlan, rewriteRowSql, rewriteValues, SCHEMA_META_DDL_SQLITE, scanRowPageSql, schemaDdl, selectRowScopesSql, selectRowSql, selectRowsForRewriteSql, upsertSql, upsertValues, } from './relational-rows.js';
|
|
11
|
+
import { commitWindowPageSql, deleteRowSql, dropTableDdl, layoutsOf, migratePayload, parseLayouts, retiredTableNames, rewritePlan, rewriteRowSql, rewriteValues, SCHEMA_META_DDL_SQLITE, scanRowPageSql, schemaDdl, selectRowScopesSql, selectRowSql, selectRowsForRewriteSql, upsertSql, upsertValues, } from './relational-rows.js';
|
|
12
12
|
import { matchesEffective } from './scopes.js';
|
|
13
13
|
import { collectCommitWindowPage, deserializePushResult, SQLITE_DDL, serializePushResult, toStoredRow, } from './sqlite-dialect.js';
|
|
14
14
|
class SqliteTransaction {
|
|
@@ -143,6 +143,7 @@ export class SqliteServerStorage {
|
|
|
143
143
|
// projection backfill for flipped-on materialization). One
|
|
144
144
|
// transaction: a failed bump leaves no half-state.
|
|
145
145
|
const layouts = parseLayouts(marker?.layouts);
|
|
146
|
+
const retiredTables = retiredTableNames(schema, layouts);
|
|
146
147
|
const existing = new Map();
|
|
147
148
|
for (const table of schema.tables.values()) {
|
|
148
149
|
const columns = this.db
|
|
@@ -154,6 +155,12 @@ export class SqliteServerStorage {
|
|
|
154
155
|
}
|
|
155
156
|
this.db.exec('BEGIN IMMEDIATE');
|
|
156
157
|
try {
|
|
158
|
+
for (const tableName of retiredTables) {
|
|
159
|
+
this.db
|
|
160
|
+
.query('DELETE FROM sync_row_scopes WHERE tbl=?')
|
|
161
|
+
.run(tableName);
|
|
162
|
+
this.db.exec(dropTableDdl(tableName));
|
|
163
|
+
}
|
|
157
164
|
for (const statement of schemaDdl(schema, existing, 'sqlite')) {
|
|
158
165
|
this.db.exec(statement);
|
|
159
166
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Syncular server: handleSyncRequest + storage/auth interfaces for the sync protocol",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"!dist/**/*.test.d.ts"
|
|
54
54
|
],
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@syncular/core": "0.
|
|
56
|
+
"@syncular/core": "0.12.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@electric-sql/pglite": "^0.5.4"
|
package/src/d1-storage.ts
CHANGED
|
@@ -43,9 +43,11 @@ import { syncError } from './errors';
|
|
|
43
43
|
import {
|
|
44
44
|
commitWindowPageSql,
|
|
45
45
|
deleteRowSql,
|
|
46
|
+
dropTableDdl,
|
|
46
47
|
layoutsOf,
|
|
47
48
|
migratePayload,
|
|
48
49
|
parseLayouts,
|
|
50
|
+
retiredTableNames,
|
|
49
51
|
rewritePlan,
|
|
50
52
|
rewriteRowSql,
|
|
51
53
|
rewriteValues,
|
|
@@ -489,6 +491,7 @@ export class D1ServerStorage implements ServerStorage {
|
|
|
489
491
|
if (marker === null || marker.schema_version < schema.version) {
|
|
490
492
|
await this.migrate();
|
|
491
493
|
const layouts = parseLayouts(marker?.layouts);
|
|
494
|
+
const retiredTables = retiredTableNames(schema, layouts);
|
|
492
495
|
const existing = new Map<string, ReadonlySet<string>>();
|
|
493
496
|
for (const table of schema.tables.values()) {
|
|
494
497
|
const { results } = await this.#db
|
|
@@ -512,6 +515,20 @@ export class D1ServerStorage implements ServerStorage {
|
|
|
512
515
|
if (!plan.migrate && !plan.backfill) continue;
|
|
513
516
|
await this.#rewriteRows(table, plan.migrate ? oldLayout : undefined);
|
|
514
517
|
}
|
|
518
|
+
// Retire tables only after the additive DDL and rewrites succeed. D1
|
|
519
|
+
// cannot wrap the whole bump in an interactive transaction, but this
|
|
520
|
+
// ordering avoids destructive work before every fallible preparatory
|
|
521
|
+
// step and the batch keeps table + live-scope cleanup atomic.
|
|
522
|
+
if (retiredTables.length > 0) {
|
|
523
|
+
await this.#db.batch(
|
|
524
|
+
retiredTables.flatMap((tableName) => [
|
|
525
|
+
this.#db
|
|
526
|
+
.prepare('DELETE FROM sync_row_scopes WHERE tbl=?')
|
|
527
|
+
.bind(tableName),
|
|
528
|
+
this.#db.prepare(dropTableDdl(tableName)),
|
|
529
|
+
]),
|
|
530
|
+
);
|
|
531
|
+
}
|
|
515
532
|
await this.#db
|
|
516
533
|
.prepare(
|
|
517
534
|
'INSERT INTO sync_schema_meta(id, schema_version, layouts) VALUES (1, ?, ?) ON CONFLICT(id) DO UPDATE SET schema_version=excluded.schema_version, layouts=excluded.layouts',
|
package/src/postgres-storage.ts
CHANGED
|
@@ -46,9 +46,11 @@ import {
|
|
|
46
46
|
import {
|
|
47
47
|
commitWindowPageSql,
|
|
48
48
|
deleteRowSql,
|
|
49
|
+
dropTableDdl,
|
|
49
50
|
layoutsOf,
|
|
50
51
|
migratePayload,
|
|
51
52
|
parseLayouts,
|
|
53
|
+
retiredTableNames,
|
|
52
54
|
rewritePlan,
|
|
53
55
|
rewriteRowSql,
|
|
54
56
|
rewriteValues,
|
|
@@ -721,6 +723,7 @@ export class PostgresServerStorage implements ServerStorage {
|
|
|
721
723
|
? marker.rows[0].layouts
|
|
722
724
|
: undefined,
|
|
723
725
|
);
|
|
726
|
+
const retiredTables = retiredTableNames(schema, layouts);
|
|
724
727
|
await this.#exec.transaction(async (client) => {
|
|
725
728
|
const existing = new Map<string, ReadonlySet<string>>();
|
|
726
729
|
for (const table of schema.tables.values()) {
|
|
@@ -733,6 +736,12 @@ export class PostgresServerStorage implements ServerStorage {
|
|
|
733
736
|
existing.set(table.name, new Set(rows.map((r) => r.column_name)));
|
|
734
737
|
}
|
|
735
738
|
}
|
|
739
|
+
for (const tableName of retiredTables) {
|
|
740
|
+
await client.query('DELETE FROM sync_row_scopes WHERE tbl=$1', [
|
|
741
|
+
tableName,
|
|
742
|
+
]);
|
|
743
|
+
await client.query(dropTableDdl(tableName));
|
|
744
|
+
}
|
|
736
745
|
for (const statement of schemaDdl(schema, existing, 'postgres')) {
|
|
737
746
|
await client.query(statement);
|
|
738
747
|
}
|
package/src/relational-rows.ts
CHANGED
|
@@ -453,6 +453,25 @@ export function parseLayouts(json: string | null | undefined): StoredLayouts {
|
|
|
453
453
|
return JSON.parse(json) as StoredLayouts;
|
|
454
454
|
}
|
|
455
455
|
|
|
456
|
+
/**
|
|
457
|
+
* Tables present in the stored layout but absent from the configured head
|
|
458
|
+
* schema. A schema-version bump retires their relational current-row tables;
|
|
459
|
+
* append-only commit history remains governed by the normal retention policy.
|
|
460
|
+
*/
|
|
461
|
+
export function retiredTableNames(
|
|
462
|
+
schema: CompiledSchema,
|
|
463
|
+
storedLayouts: StoredLayouts,
|
|
464
|
+
): string[] {
|
|
465
|
+
return Object.keys(storedLayouts)
|
|
466
|
+
.filter((name) => !schema.tables.has(name))
|
|
467
|
+
.sort();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** Idempotent DDL for one retired relational current-row table. */
|
|
471
|
+
export function dropTableDdl(tableName: string): string {
|
|
472
|
+
return `DROP TABLE IF EXISTS ${quoteIdent(tableName)}`;
|
|
473
|
+
}
|
|
474
|
+
|
|
456
475
|
/**
|
|
457
476
|
* Enforce the migration subset on a table's column list: the old layout
|
|
458
477
|
* must be an exact prefix (same name, type, nullability) of the new one,
|
package/src/sqlite-storage.ts
CHANGED
|
@@ -11,9 +11,11 @@ import { syncError } from './errors';
|
|
|
11
11
|
import {
|
|
12
12
|
commitWindowPageSql,
|
|
13
13
|
deleteRowSql,
|
|
14
|
+
dropTableDdl,
|
|
14
15
|
layoutsOf,
|
|
15
16
|
migratePayload,
|
|
16
17
|
parseLayouts,
|
|
18
|
+
retiredTableNames,
|
|
17
19
|
rewritePlan,
|
|
18
20
|
rewriteRowSql,
|
|
19
21
|
rewriteValues,
|
|
@@ -273,6 +275,7 @@ export class SqliteServerStorage implements ServerStorage {
|
|
|
273
275
|
// projection backfill for flipped-on materialization). One
|
|
274
276
|
// transaction: a failed bump leaves no half-state.
|
|
275
277
|
const layouts = parseLayouts(marker?.layouts);
|
|
278
|
+
const retiredTables = retiredTableNames(schema, layouts);
|
|
276
279
|
const existing = new Map<string, ReadonlySet<string>>();
|
|
277
280
|
for (const table of schema.tables.values()) {
|
|
278
281
|
const columns = this.db
|
|
@@ -286,6 +289,12 @@ export class SqliteServerStorage implements ServerStorage {
|
|
|
286
289
|
}
|
|
287
290
|
this.db.exec('BEGIN IMMEDIATE');
|
|
288
291
|
try {
|
|
292
|
+
for (const tableName of retiredTables) {
|
|
293
|
+
this.db
|
|
294
|
+
.query('DELETE FROM sync_row_scopes WHERE tbl=?')
|
|
295
|
+
.run(tableName);
|
|
296
|
+
this.db.exec(dropTableDdl(tableName));
|
|
297
|
+
}
|
|
289
298
|
for (const statement of schemaDdl(schema, existing, 'sqlite')) {
|
|
290
299
|
this.db.exec(statement);
|
|
291
300
|
}
|