@catalyst-cloud/replicate 0.1.3 → 0.1.5
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/package.json +2 -2
- package/src/replicate.ts +27 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catalyst-cloud/replicate",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Runtime-agnostic replica write path — applyDelta / truncateReplica / cursor over a portable ReplicaWriteDb (ADR-0002). Shared by the host-sync bun:sqlite replica and the browser OPFS replica so the two apply paths can't drift.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"build": "tsc -p tsconfig.build.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@catalyst-cloud/schema": "
|
|
28
|
+
"@catalyst-cloud/schema": "0.1.10"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@catalyst-cloud/typescript-config": "workspace:*",
|
package/src/replicate.ts
CHANGED
|
@@ -67,6 +67,12 @@ export interface ApplyOptions {
|
|
|
67
67
|
* because the client's schema lacks them, so the caller can warn-once / surface the cloud→client
|
|
68
68
|
* schema drift. Never fires when nothing was dropped. */
|
|
69
69
|
onDroppedColumns?: (table: string, dropped: readonly string[]) => void;
|
|
70
|
+
/**
|
|
71
|
+
* CTC-393 — reported when `applyDelta` receives a delta for an entity this client's BUNDLED schema
|
|
72
|
+
* does not recognize AT ALL (the whole table is unknown, not merely a missing column). Never fires
|
|
73
|
+
* when the entity resolves. See {@link applyDelta}'s own doc for why this replaces a throw.
|
|
74
|
+
*/
|
|
75
|
+
onUnknownEntity?: (entity: string) => void;
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
function quoteIdent(ident: string): string {
|
|
@@ -94,8 +100,23 @@ const KNOWN_COLUMNS: Record<string, ReadonlySet<string>> = Object.fromEntries(
|
|
|
94
100
|
|
|
95
101
|
/**
|
|
96
102
|
* Apply ONE change-feed record to the replica. Returns true iff a row was actually written (a stale
|
|
97
|
-
* upsert rejected by the updated_at guard, or a delete of an already-absent row, returns false).
|
|
98
|
-
*
|
|
103
|
+
* upsert rejected by the updated_at guard, or a delete of an already-absent row, returns false).
|
|
104
|
+
*
|
|
105
|
+
* ⛔ CTC-393 — NEVER THROWS, including on an unknown entity. It used to: an entity this client's
|
|
106
|
+
* bundled schema does not recognize AT ALL (the mirror started emitting a table this client predates
|
|
107
|
+
* — the exact shape of a NEW replicated entity rolling out to a fleet that upgrades independently)
|
|
108
|
+
* threw, and on the CURRENT SDK caller (catalyst-replica.ts) that throw propagates out of the SAME
|
|
109
|
+
* `engine.transaction(...)` callback that also calls `setCursor` — so the transaction rolls back,
|
|
110
|
+
* the cursor never advances past that seq, and the daemon's transport holds every LATER frame behind
|
|
111
|
+
* the hole (CTL-1402's own contiguity guarantee: "never delivers a frame beyond deliveredSeq+1").
|
|
112
|
+
* One new entity type permanently wedges an ENTIRE replica, silently, with no resync path — the
|
|
113
|
+
* exact review finding (CTC-393 / the closed PR #290) this fix closes.
|
|
114
|
+
*
|
|
115
|
+
* An unknown entity now returns `false` (nothing written — there is nowhere to put it) and reports
|
|
116
|
+
* via `opts.onUnknownEntity`, mirroring `onDroppedColumns`'s existing graceful-degrade posture. NO
|
|
117
|
+
* CALLER CHANGE IS REQUIRED for safety: `onUnknownEntity` is optional, so the EXISTING SDK code path
|
|
118
|
+
* (which doesn't set it) already gets the correct behavior — `setCursor` runs, the cursor advances,
|
|
119
|
+
* `recordApplyResult("skipped", ...)` fires — the moment this package version is on the fleet.
|
|
99
120
|
*/
|
|
100
121
|
export function applyDelta<B>(
|
|
101
122
|
db: ReplicaWriteDb<B>,
|
|
@@ -104,7 +125,10 @@ export function applyDelta<B>(
|
|
|
104
125
|
opts?: ApplyOptions,
|
|
105
126
|
): boolean {
|
|
106
127
|
const meta = metaFor(change.entity);
|
|
107
|
-
if (!meta)
|
|
128
|
+
if (!meta) {
|
|
129
|
+
opts?.onUnknownEntity?.(change.entity);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
108
132
|
// A delete keys on PK columns only (always present in the local schema), so it needs no
|
|
109
133
|
// forward-compat column filtering — only the upsert path binds the full wire row.
|
|
110
134
|
if (change.op === "delete") return applyDelete(db, change, meta, toBindable);
|