orez 0.4.44 → 0.4.46
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/cf-do/cursor-pull.d.ts +27 -0
- package/dist/cf-do/cursor-pull.d.ts.map +1 -0
- package/dist/cf-do/cursor-pull.js +78 -0
- package/dist/cf-do/cursor-pull.js.map +1 -0
- package/dist/pg-proxy-do-backend.d.ts +1 -0
- package/dist/pg-proxy-do-backend.d.ts.map +1 -1
- package/dist/pg-proxy-do-backend.js +32 -4
- package/dist/pg-proxy-do-backend.js.map +1 -1
- package/dist/sync-server/sync-server.d.ts +77 -0
- package/dist/sync-server/sync-server.d.ts.map +1 -0
- package/dist/sync-server/sync-server.js +385 -0
- package/dist/sync-server/sync-server.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type CursorPullTables = Record<string, // EXACT _zero_changes.table_name identity, e.g. 'public.file'
|
|
2
|
+
{
|
|
3
|
+
clientName: string;
|
|
4
|
+
primaryKey: string[];
|
|
5
|
+
}>;
|
|
6
|
+
export type ChangeLogRow = {
|
|
7
|
+
tableName: string;
|
|
8
|
+
op: 'INSERT' | 'UPDATE' | 'DELETE';
|
|
9
|
+
rowData: Record<string, unknown> | null;
|
|
10
|
+
oldData: Record<string, unknown> | null;
|
|
11
|
+
};
|
|
12
|
+
export type CursorDiffOp = {
|
|
13
|
+
op: 'put';
|
|
14
|
+
tableName: string;
|
|
15
|
+
row: Record<string, unknown>;
|
|
16
|
+
} | {
|
|
17
|
+
op: 'del';
|
|
18
|
+
tableName: string;
|
|
19
|
+
pk: Record<string, unknown>;
|
|
20
|
+
};
|
|
21
|
+
export declare function cursorDiffPatch(opts: {
|
|
22
|
+
changes: ChangeLogRow[];
|
|
23
|
+
tables: CursorPullTables;
|
|
24
|
+
skip: (logTableName: string) => boolean;
|
|
25
|
+
readRow: (clientTableName: string, pk: Record<string, unknown>) => Record<string, unknown> | undefined;
|
|
26
|
+
}): CursorDiffOp[];
|
|
27
|
+
//# sourceMappingURL=cursor-pull.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor-pull.d.ts","sourceRoot":"","sources":["../../src/cf-do/cursor-pull.ts"],"names":[],"mappings":"AAoCA,MAAM,MAAM,gBAAgB,GAAG,MAAM,CACnC,MAAM,EAAE,8DAA8D;AACtE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAC7C,CAAA;AAID,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,EAAE,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;IAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACvC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CACxC,CAAA;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC9D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAA;AAgBjE,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,MAAM,EAAE,gBAAgB,CAAA;IAIxB,IAAI,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAA;IAGvC,OAAO,EAAE,CACP,eAAe,EAAE,MAAM,EACvB,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACxB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;CACzC,GAAG,YAAY,EAAE,CA2BjB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// cursor-pull: the phase-2 delta primitive over the PRODUCTION change log
|
|
2
|
+
// (plans/zero-server-rewrite.md "build plan for the production composition").
|
|
3
|
+
// maps `_zero_changes` rows since a client's cookie into put/del row patches
|
|
4
|
+
// for an http-pull diff response.
|
|
5
|
+
//
|
|
6
|
+
// semantics follow the reference core (src/sync-server/sync-server.ts + its
|
|
7
|
+
// delta suite) exactly: collect the pks TOUCHED since the cookie, dedup,
|
|
8
|
+
// then resolve each against LIVE table state inside the pull transaction —
|
|
9
|
+
// row exists → put (current values), row gone → del. old row images in the
|
|
10
|
+
// log are used only to find pks (DELETE + pk-changing UPDATE), never as
|
|
11
|
+
// patch values, so op-coalescing bugs and serializer fidelity questions
|
|
12
|
+
// can't exist.
|
|
13
|
+
//
|
|
14
|
+
// table identity is EXPLICIT (2026-07-09 design review, blocker 1):
|
|
15
|
+
// production `_zero_changes.table_name` is schema-qualified
|
|
16
|
+
// (`public.file`, `soot_0.clients` — pg-proxy trackingRequest emits
|
|
17
|
+
// `<schema>.<table>`), so `tables` is keyed by the EXACT log identity and
|
|
18
|
+
// carries the zero/client table name to emit. rows matching neither the
|
|
19
|
+
// synced spec nor the host's `skip` classifier THROW — a silently dropped
|
|
20
|
+
// synced change is permanent client divergence, the one failure mode this
|
|
21
|
+
// module must never have.
|
|
22
|
+
//
|
|
23
|
+
// completeness contract (review, high 4): the caller must pass EVERY change
|
|
24
|
+
// row with `watermark > cookie` up to the watermark it returns as the next
|
|
25
|
+
// cookie, read atomically with the live point-reads. if the host caps the
|
|
26
|
+
// change read, it must return the last INCLUDED watermark as the cookie —
|
|
27
|
+
// never the global current watermark — so the remainder ships on the next
|
|
28
|
+
// pull. pk mutability (review, high 5): the main pg-proxy tracking path
|
|
29
|
+
// records old_data=null for UPDATEs, so a pk-changing UPDATE through it
|
|
30
|
+
// only surfaces the NEW pk. published-table pks must be immutable (enforce
|
|
31
|
+
// host-side) unless the tracker learns to capture old pk images.
|
|
32
|
+
//
|
|
33
|
+
// the host (soot's project pull endpoint) owns everything else: cookie
|
|
34
|
+
// compare against the watermark, floor/epoch snapshot fallback, LMID rows,
|
|
35
|
+
// and wire value conversion (its toZeroRow).
|
|
36
|
+
// pks touched by a change: NEW row for INSERT/UPDATE, OLD row for
|
|
37
|
+
// UPDATE/DELETE (when the tracker provides old images, a pk-changing
|
|
38
|
+
// UPDATE touches both, deleting the old pk)
|
|
39
|
+
function pksOf(change, primaryKey) {
|
|
40
|
+
const out = [];
|
|
41
|
+
for (const source of [change.rowData, change.oldData]) {
|
|
42
|
+
if (!source)
|
|
43
|
+
continue;
|
|
44
|
+
const pk = {};
|
|
45
|
+
for (const col of primaryKey)
|
|
46
|
+
pk[col] = source[col];
|
|
47
|
+
out.push(pk);
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
export function cursorDiffPatch(opts) {
|
|
52
|
+
const { changes, tables, skip, readRow } = opts;
|
|
53
|
+
const touched = new Map();
|
|
54
|
+
for (const change of changes) {
|
|
55
|
+
const spec = tables[change.tableName];
|
|
56
|
+
if (!spec) {
|
|
57
|
+
if (skip(change.tableName))
|
|
58
|
+
continue;
|
|
59
|
+
throw new Error(`cursor-pull: change log row for unmapped table '${change.tableName}' — ` +
|
|
60
|
+
`synced tables must be in the spec and internal tables must be classified by skip(); ` +
|
|
61
|
+
`silently dropping it would permanently diverge clients`);
|
|
62
|
+
}
|
|
63
|
+
for (const pk of pksOf(change, spec.primaryKey)) {
|
|
64
|
+
const key = `${spec.clientName} ${JSON.stringify(spec.primaryKey.map((c) => pk[c]))}`;
|
|
65
|
+
touched.set(key, { clientName: spec.clientName, pk });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const patch = [];
|
|
69
|
+
for (const { clientName, pk } of touched.values()) {
|
|
70
|
+
const row = readRow(clientName, pk);
|
|
71
|
+
if (row === undefined)
|
|
72
|
+
patch.push({ op: 'del', tableName: clientName, pk });
|
|
73
|
+
else
|
|
74
|
+
patch.push({ op: 'put', tableName: clientName, row });
|
|
75
|
+
}
|
|
76
|
+
return patch;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=cursor-pull.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor-pull.js","sourceRoot":"","sources":["../../src/cf-do/cursor-pull.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,6 +36,7 @@ export declare class DoBackend {
|
|
|
36
36
|
private txChangeTablesSnapshotted;
|
|
37
37
|
private txMetadataDirty;
|
|
38
38
|
private txHasTrackedWrite;
|
|
39
|
+
private lastPersistedMetadata;
|
|
39
40
|
private txOwner;
|
|
40
41
|
constructor(doUrl: string, dbName?: string, namespace?: string, opts?: {
|
|
41
42
|
fetch?: typeof fetch;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pg-proxy-do-backend.d.ts","sourceRoot":"","sources":["../src/pg-proxy-do-backend.ts"],"names":[],"mappings":"AAq/HA,wBAAsB,+BAA+B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CACzE,KAAK,CAAC;IACJ,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,kBAAkB,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACtD,mBAAmB,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CACxD,CAAC,CACH,CAkDA;AAwkCD,qBAAa,SAAS;IACpB,KAAK,UAAQ;IACb,MAAM,UAAQ;IACd,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,UAAU,CAAY;IAC9B,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,oBAAoB,CAAa;IACzC,OAAO,CAAC,gBAAgB,CAAwC;IAChE,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,YAAY,CAAoC;IAGxD,OAAO,CAAC,4BAA4B,CAAoB;IACxD,OAAO,CAAC,YAAY,CAAmC;IACvD,OAAO,CAAC,kBAAkB,CAAuC;IACjE,OAAO,CAAC,OAAO,CAAiC;IAOhD,OAAO,CAAC,yBAAyB,CAAsC;IACvE,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,cAAc,CAAc;IAQpC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,IAAI,CAAsB;IAClC,OAAO,CAAC,UAAU,CAA2C;IAC7D,OAAO,CAAC,eAAe,CAAmC;IAC1D,OAAO,CAAC,iBAAiB,CAAI;IAM7B,OAAO,CAAC,yBAAyB,CAAQ;IAGzC,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,iBAAiB,CAAQ;
|
|
1
|
+
{"version":3,"file":"pg-proxy-do-backend.d.ts","sourceRoot":"","sources":["../src/pg-proxy-do-backend.ts"],"names":[],"mappings":"AAq/HA,wBAAsB,+BAA+B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CACzE,KAAK,CAAC;IACJ,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,kBAAkB,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACtD,mBAAmB,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CACxD,CAAC,CACH,CAkDA;AAwkCD,qBAAa,SAAS;IACpB,KAAK,UAAQ;IACb,MAAM,UAAQ;IACd,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,UAAU,CAAY;IAC9B,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,oBAAoB,CAAa;IACzC,OAAO,CAAC,gBAAgB,CAAwC;IAChE,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,YAAY,CAAoC;IAGxD,OAAO,CAAC,4BAA4B,CAAoB;IACxD,OAAO,CAAC,YAAY,CAAmC;IACvD,OAAO,CAAC,kBAAkB,CAAuC;IACjE,OAAO,CAAC,OAAO,CAAiC;IAOhD,OAAO,CAAC,yBAAyB,CAAsC;IACvE,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,cAAc,CAAc;IAQpC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,IAAI,CAAsB;IAClC,OAAO,CAAC,UAAU,CAA2C;IAC7D,OAAO,CAAC,eAAe,CAAmC;IAC1D,OAAO,CAAC,iBAAiB,CAAI;IAM7B,OAAO,CAAC,yBAAyB,CAAQ;IAGzC,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,iBAAiB,CAAQ;IAQjC,OAAO,CAAC,qBAAqB,CAAmC;IAOhE,OAAO,CAAC,OAAO,CAAQ;gBAGrB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,MAAmB,EAC3B,SAAS,SAAY,EACrB,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE;IAenD,IAAI,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAE7B;IAED,OAAO,CAAC,WAAW;YAWL,IAAI;YAUJ,0BAA0B;YAwB1B,mBAAmB;IAIjC,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,mBAAmB;YAyBb,mBAAmB;YA+CnB,yBAAyB;YAsBzB,+BAA+B;YA8B/B,sBAAsB;YAgEtB,YAAY;IASpB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5B,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,iBAAiB;IAmBzB,OAAO,CAAC,qBAAqB;IAQ7B,OAAO,CAAC,kCAAkC;IAS1C,OAAO,CAAC,kCAAkC;IAsB1C,OAAO,CAAC,qBAAqB;IAW7B,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,gBAAgB;YAMV,gBAAgB;YAOhB,iBAAiB;YAuBjB,mBAAmB;IAwB3B,eAAe,CACnB,OAAO,EAAE,UAAU,EACnB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,GACvD,OAAO,CAAC,UAAU,CAAC;YAIR,qBAAqB;IAwBnC,OAAO,CAAC,qBAAqB;IAY7B,OAAO,CAAC,2BAA2B;YAKrB,mBAAmB;YAoCnB,iBAAiB;YAgGjB,sBAAsB;IAsCpC,OAAO,CAAC,yBAAyB;IAgCjC,OAAO,CAAC,sBAAsB;IAkC9B,gBAAgB,IAAI,MAAM,EAAE;IAI5B,OAAO,CAAC,oBAAoB;IAyB5B,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,wBAAwB;YAYlB,sBAAsB;YAQtB,mCAAmC;IAkBjD,OAAO,CAAC,WAAW;IAuFnB,OAAO,CAAC,UAAU;YAWJ,aAAa;IAiI3B,OAAO,CAAC,UAAU;YAIJ,cAAc;IAuB5B,OAAO,CAAC,WAAW;IAgBb,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;YAIzB,UAAU;YAsCV,wBAAwB;IAqBhC,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,GAAG,EAAE,GACb,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,EAAE,CAAA;KAAE,CAAC;YAIX,WAAW;IAoHzB,OAAO,CAAC,GAAG;IAQX,OAAO,CAAC,oBAAoB;IAwB5B,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,yBAAyB;IAkBjC,OAAO,CAAC,qBAAqB;YAkCf,MAAM;YAIN,YAAY;IA4C1B,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,uBAAuB;YAKjB,eAAe;YAQf,wBAAwB;YA2BxB,+BAA+B;YAc/B,wBAAwB;YAOxB,UAAU;IAYxB,qFAAqF;YACvE,cAAc;YAsBd,mBAAmB;YA4BnB,yBAAyB;YAsBzB,0BAA0B;YAiB1B,oBAAoB;YAgCpB,WAAW;YA2EX,YAAY;YAKZ,YAAY;YAOZ,gBAAgB;IAY9B,OAAO,CAAC,sBAAsB;IAyB9B,OAAO,CAAC,kCAAkC;IA0B1C,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,0BAA0B;IAmBlC,OAAO,CAAC,eAAe;IAsFvB,OAAO,CAAC,wBAAwB;IAYhC,OAAO,CAAC,oBAAoB;YAUd,qBAAqB;YAiBrB,yBAAyB;IA8EvC,OAAO,CAAC,iBAAiB;YAcX,cAAc;YAiBd,8BAA8B;YAmD9B,iCAAiC;YAwEjC,yBAAyB;IA0DvC,OAAO,CAAC,oBAAoB;YAyCd,qBAAqB;YAiBrB,sBAAsB;YAatB,qBAAqB;IAoBnC,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,UAAU;IA+ClB,OAAO,CAAC,YAAY;IAqGpB,OAAO,CAAC,gBAAgB;IA8BxB,OAAO,CAAC,cAAc;IA2GtB,OAAO,CAAC,gBAAgB;IA0CxB,OAAO,CAAC,aAAa;IAqCrB,OAAO,CAAC,eAAe;IA6DvB,OAAO,CAAC,4BAA4B,CAAoB;IAExD,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,uBAAuB;IAuB/B,OAAO,CAAC,iBAAiB;IAyBzB,OAAO,CAAC,4BAA4B;IAoDpC,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,oBAAoB;YA0Bd,cAAc;IAoB5B,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,mBAAmB;YAOb,oBAAoB;YAgBpB,kBAAkB;YAIlB,mBAAmB;CAkClC"}
|
|
@@ -4764,6 +4764,14 @@ export class DoBackend {
|
|
|
4764
4764
|
// round-trip to durable storage after every DDL statement in a migration.
|
|
4765
4765
|
txMetadataDirty = false;
|
|
4766
4766
|
txHasTrackedWrite = false;
|
|
4767
|
+
// durable-metadata rows as last read/written (kind\0key\0subkey -> value).
|
|
4768
|
+
// persistDurableMetadata diffs against this and writes only changed rows —
|
|
4769
|
+
// a full INSERT OR REPLACE of the whole set on every dirty commit/rollback
|
|
4770
|
+
// is what let a crash-looping embed boot re-write ~700 identical
|
|
4771
|
+
// _orez_pg_metadata rows per cycle into the SQL DO until the write circuit
|
|
4772
|
+
// tripped (2026-07-09 prod incident). null until the first load/persist,
|
|
4773
|
+
// which falls back to a full write.
|
|
4774
|
+
lastPersistedMetadata = null;
|
|
4767
4775
|
// identifies the client process generation owning this backend's
|
|
4768
4776
|
// transactions in the durable tx journal. a process that knows its previous
|
|
4769
4777
|
// generation is dead (e.g. the zero-cache embed at boot) recovers orphaned
|
|
@@ -4855,11 +4863,13 @@ export class DoBackend {
|
|
|
4855
4863
|
try {
|
|
4856
4864
|
await this.ensureMetadataTable();
|
|
4857
4865
|
const result = await this.doExecResult(`SELECT kind, key, subkey, value FROM ${quoteIdentifier(METADATA_TABLE)}`);
|
|
4866
|
+
this.lastPersistedMetadata = new Map();
|
|
4858
4867
|
for (const row of result.rows) {
|
|
4859
4868
|
const kind = String(row.kind ?? '');
|
|
4860
4869
|
const key = String(row.key ?? '');
|
|
4861
4870
|
const subkey = String(row.subkey ?? '');
|
|
4862
4871
|
const value = String(row.value ?? '');
|
|
4872
|
+
this.lastPersistedMetadata.set(`${kind}\u0000${key}\u0000${subkey}`, value);
|
|
4863
4873
|
if (kind === 'schema-column') {
|
|
4864
4874
|
const metadata = JSON.parse(value);
|
|
4865
4875
|
let table = this.schemaMetadata.get(key);
|
|
@@ -4944,7 +4954,6 @@ export class DoBackend {
|
|
|
4944
4954
|
}
|
|
4945
4955
|
async persistDurableMetadata() {
|
|
4946
4956
|
try {
|
|
4947
|
-
await this.ensureMetadataTable();
|
|
4948
4957
|
const rows = [];
|
|
4949
4958
|
for (const [tableName, columns] of this.schemaMetadata) {
|
|
4950
4959
|
for (const [columnName, metadata] of columns) {
|
|
@@ -4964,21 +4973,40 @@ export class DoBackend {
|
|
|
4964
4973
|
JSON.stringify(child),
|
|
4965
4974
|
]);
|
|
4966
4975
|
}
|
|
4967
|
-
|
|
4976
|
+
// write only rows that differ from what durable storage already holds
|
|
4977
|
+
// (seeded by loadDurableMetadata, maintained below). every persisted row
|
|
4978
|
+
// is a real DO rows-written cost even when the value is identical —
|
|
4979
|
+
// INSERT OR REPLACE always rewrites — and this method fires on every
|
|
4980
|
+
// dirty commit AND every rollback, so a crash-looping embed boot used to
|
|
4981
|
+
// rewrite the entire set (~700 rows on a real app schema) several times
|
|
4982
|
+
// per ~4s cycle into the SQL DO until the write circuit tripped and
|
|
4983
|
+
// blocked auth (2026-07-09 prod incident).
|
|
4984
|
+
const known = this.lastPersistedMetadata;
|
|
4985
|
+
const changed = known === null
|
|
4986
|
+
? rows
|
|
4987
|
+
: rows.filter(([kind, key, subkey, value]) => known.get(`${kind}\u0000${key}\u0000${subkey}`) !== value);
|
|
4988
|
+
if (changed.length === 0)
|
|
4968
4989
|
return;
|
|
4990
|
+
await this.ensureMetadataTable();
|
|
4969
4991
|
// single multi-row INSERT OR REPLACE per chunk. previously this was one
|
|
4970
4992
|
// HTTP roundtrip per row, which dominated boot when migrations touched
|
|
4971
4993
|
// many columns. Cloudflare DO SQLite has a lower host-param cap than
|
|
4972
4994
|
// stock SQLite; 4 cols × 20 rows keeps metadata persistence comfortably
|
|
4973
4995
|
// below that limit.
|
|
4974
4996
|
const CHUNK = 20;
|
|
4975
|
-
|
|
4976
|
-
|
|
4997
|
+
const persisted = this.lastPersistedMetadata ?? new Map();
|
|
4998
|
+
this.lastPersistedMetadata = persisted;
|
|
4999
|
+
for (let i = 0; i < changed.length; i += CHUNK) {
|
|
5000
|
+
const chunk = changed.slice(i, i + CHUNK);
|
|
4977
5001
|
const placeholders = chunk.map(() => '(?, ?, ?, ?)').join(', ');
|
|
4978
5002
|
const params = [];
|
|
4979
5003
|
for (const row of chunk)
|
|
4980
5004
|
params.push(...row);
|
|
4981
5005
|
await this.doExecResult(`INSERT OR REPLACE INTO ${quoteIdentifier(METADATA_TABLE)} (kind, key, subkey, value) VALUES ${placeholders}`, params);
|
|
5006
|
+
// per-chunk so a mid-persist failure never marks unwritten rows as done
|
|
5007
|
+
for (const [kind, key, subkey, value] of chunk) {
|
|
5008
|
+
persisted.set(`${kind}\u0000${key}\u0000${subkey}`, value);
|
|
5009
|
+
}
|
|
4982
5010
|
}
|
|
4983
5011
|
}
|
|
4984
5012
|
catch { }
|