orez 0.4.40 → 0.4.41

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.
@@ -1,29 +1,39 @@
1
1
  /**
2
2
  * neutralize zero-cache's litestream replica restore for orez.
3
3
  *
4
- * zero 1.5's dedicated change-streamer worker unconditionally calls
5
- * `restoreReplica()` on startup whenever the change-log already has rows
6
- * (i.e. on every restart after the first replication). it assumes a litestream
7
- * backup exists to restore the SQLite replica from. orez has no litestream
8
- * backup it owns the replica on disk directly — so the restore throws
9
- * "Missing --litestream-executable" (or "Missing required value" for the backup
10
- * URL), which zero logs as an error and "recovers" from by wastefully
11
- * re-syncing the entire replica from scratch.
4
+ * zero's dedicated change-streamer worker unconditionally calls
5
+ * `restoreReplica()` on startup whenever the change-log already has rows.
6
+ * it assumes a litestream backup exists to restore the SQLite replica from.
7
+ * orez has no litestream backup it owns the replica on disk directly so
8
+ * without a guard the restore throws "Missing --litestream-executable" (or
9
+ * "Missing required value" for the backup URL) after a retry, which zero
10
+ * logs at error level on every warm restart.
12
11
  *
13
12
  * the correct config gate doesn't exist for the dedicated change-streamer:
14
- * - leaving litestream unconfigured -> the error above + resync.
13
+ * - leaving litestream unconfigured -> the noisy double-attempt error above.
15
14
  * - setting ZERO_LITESTREAM_BACKUP_URL to satisfy the restore -> switches the
16
15
  * replicator into "serving-copy" mode (VACUUM INTO a copy on every start)
17
16
  * and spawns a backup worker + litestream `replicate` subprocess. wrong for
18
17
  * an ephemeral dev backend.
19
18
  *
20
- * so we patch the compiled `restoreReplica` to be a no-op when no litestream
21
- * backup URL is configured. zero's own dispatcher (`main.js`) already gates all
22
- * backup/restore work behind `litestream.backupURL`; this brings the dedicated
23
- * change-streamer in line. a no-op restore leaves orez's existing local replica
24
- * in place with the purge-lock held identical to the path where a real
25
- * restore found a backup matching the current replica so the change-streamer
26
- * resumes from the local replica and releases the lock on ownership assumption.
19
+ * so we patch the compiled `restoreReplica` for the no-backup case. the guard
20
+ * must preserve upstream's LOCK-RELEASE semantics, not just silence the error:
21
+ * the change-streamer acquires a changeLog purge lock before restoring — an
22
+ * open `SELECT ... FOR SHARE` transaction, which holds a real xid — and its
23
+ * caller only releases that lock when restoreReplica THROWS. a plain
24
+ * `return` here (the original v1 guard) kept the lock held into
25
+ * `initializePostgresChangeSource`; whenever the local replica was missing
26
+ * (unclean shutdown, cache-only reset), initial sync then ran with the lock
27
+ * still open and `CREATE_REPLICATION_SLOT` waited on our own transaction's
28
+ * xid — a deterministic self-deadlock, crash-looping the change-streamer on
29
+ * `ensureSchemaMigrated`/55P03 lock timeouts until the change db was dropped.
30
+ *
31
+ * throwing `BackupNotFoundException` mirrors what unpatched zero does when
32
+ * litestream config is absent (its `must(backupURL)` throws and the caller
33
+ * releases the lock, logs at warn, and proceeds): a valid local replica is
34
+ * resumed exactly as before, an invalid/missing one initial-syncs lock-free.
35
+ * the purge lock only exists to fence multi-node purge races during a real
36
+ * backup restore; single-node orez has neither.
27
37
  *
28
38
  * this mirrors orez's existing in-place patching of @rocicorp/zero-sqlite3
29
39
  * (see sqlite-mode/apply-mode.ts). it is idempotent and only touches orez's
@@ -33,7 +43,7 @@
33
43
  /**
34
44
  * apply the restore-guard to a specific compiled litestream commands.js —
35
45
  * shared by the in-place node path and the CF overlay (cf-patches.ts).
36
- * idempotent.
46
+ * idempotent; upgrades a v1 guard in place.
37
47
  */
38
48
  export declare function applyLitestreamRestoreGuard(commandsPath: string): void;
39
49
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"zero-litestream-patch.d.ts","sourceRoot":"","sources":["../src/zero-litestream-patch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAwCH;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAgBtE;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,IAAI,IAAI,CAInD"}
1
+ {"version":3,"file":"zero-litestream-patch.d.ts","sourceRoot":"","sources":["../src/zero-litestream-patch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAmDH;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CA8BtE;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,IAAI,IAAI,CAInD"}
@@ -1,29 +1,39 @@
1
1
  /**
2
2
  * neutralize zero-cache's litestream replica restore for orez.
3
3
  *
4
- * zero 1.5's dedicated change-streamer worker unconditionally calls
5
- * `restoreReplica()` on startup whenever the change-log already has rows
6
- * (i.e. on every restart after the first replication). it assumes a litestream
7
- * backup exists to restore the SQLite replica from. orez has no litestream
8
- * backup it owns the replica on disk directly — so the restore throws
9
- * "Missing --litestream-executable" (or "Missing required value" for the backup
10
- * URL), which zero logs as an error and "recovers" from by wastefully
11
- * re-syncing the entire replica from scratch.
4
+ * zero's dedicated change-streamer worker unconditionally calls
5
+ * `restoreReplica()` on startup whenever the change-log already has rows.
6
+ * it assumes a litestream backup exists to restore the SQLite replica from.
7
+ * orez has no litestream backup it owns the replica on disk directly so
8
+ * without a guard the restore throws "Missing --litestream-executable" (or
9
+ * "Missing required value" for the backup URL) after a retry, which zero
10
+ * logs at error level on every warm restart.
12
11
  *
13
12
  * the correct config gate doesn't exist for the dedicated change-streamer:
14
- * - leaving litestream unconfigured -> the error above + resync.
13
+ * - leaving litestream unconfigured -> the noisy double-attempt error above.
15
14
  * - setting ZERO_LITESTREAM_BACKUP_URL to satisfy the restore -> switches the
16
15
  * replicator into "serving-copy" mode (VACUUM INTO a copy on every start)
17
16
  * and spawns a backup worker + litestream `replicate` subprocess. wrong for
18
17
  * an ephemeral dev backend.
19
18
  *
20
- * so we patch the compiled `restoreReplica` to be a no-op when no litestream
21
- * backup URL is configured. zero's own dispatcher (`main.js`) already gates all
22
- * backup/restore work behind `litestream.backupURL`; this brings the dedicated
23
- * change-streamer in line. a no-op restore leaves orez's existing local replica
24
- * in place with the purge-lock held identical to the path where a real
25
- * restore found a backup matching the current replica so the change-streamer
26
- * resumes from the local replica and releases the lock on ownership assumption.
19
+ * so we patch the compiled `restoreReplica` for the no-backup case. the guard
20
+ * must preserve upstream's LOCK-RELEASE semantics, not just silence the error:
21
+ * the change-streamer acquires a changeLog purge lock before restoring — an
22
+ * open `SELECT ... FOR SHARE` transaction, which holds a real xid — and its
23
+ * caller only releases that lock when restoreReplica THROWS. a plain
24
+ * `return` here (the original v1 guard) kept the lock held into
25
+ * `initializePostgresChangeSource`; whenever the local replica was missing
26
+ * (unclean shutdown, cache-only reset), initial sync then ran with the lock
27
+ * still open and `CREATE_REPLICATION_SLOT` waited on our own transaction's
28
+ * xid — a deterministic self-deadlock, crash-looping the change-streamer on
29
+ * `ensureSchemaMigrated`/55P03 lock timeouts until the change db was dropped.
30
+ *
31
+ * throwing `BackupNotFoundException` mirrors what unpatched zero does when
32
+ * litestream config is absent (its `must(backupURL)` throws and the caller
33
+ * releases the lock, logs at warn, and proceeds): a valid local replica is
34
+ * resumed exactly as before, an invalid/missing one initial-syncs lock-free.
35
+ * the purge lock only exists to fence multi-node purge races during a real
36
+ * backup restore; single-node orez has neither.
27
37
  *
28
38
  * this mirrors orez's existing in-place patching of @rocicorp/zero-sqlite3
29
39
  * (see sqlite-mode/apply-mode.ts). it is idempotent and only touches orez's
@@ -33,11 +43,21 @@
33
43
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
34
44
  import { dirname, resolve } from 'node:path';
35
45
  import { resolvePackage } from './sqlite-mode/package-resolve.js';
36
- const OREZ_MARKER = '/* orez: litestream restore disabled (no backup configured) */';
37
- // the early-return guard injected at the top of restoreReplica's body. when
38
- // no backup URL is configured there is nothing to restore, so the local
39
- // replica is used as-is and the change-streamer resumes from it.
40
- const GUARD = `${OREZ_MARKER}\n\tif (!config.litestream?.backupURL) return;`;
46
+ const OREZ_MARKER = '/* orez: litestream restore disabled (no backup configured) — v2 */';
47
+ // the previous guard generation: silenced the error but swallowed the throw
48
+ // the caller relies on to release the changeLog purge lock (see header).
49
+ // stripped on sight so upgrades replace it instead of stacking guards.
50
+ const OREZ_MARKER_V1 = '/* orez: litestream restore disabled (no backup configured) */';
51
+ const GUARD_V1 = `${OREZ_MARKER_V1}\n\tif (!config.litestream?.backupURL) return;`;
52
+ // the guard injected at the top of restoreReplica's body. when no backup URL
53
+ // is configured there is nothing to restore; surfacing not-found makes the
54
+ // caller release the purge lock before any initial sync (the deadlock fix),
55
+ // while callers without a lock (replicaConstraints == null) just proceed.
56
+ const GUARD = `${OREZ_MARKER}
57
+ if (!config.litestream?.backupURL) {
58
+ if (replicaConstraints) throw new BackupNotFoundException("(litestream disabled)");
59
+ return;
60
+ }`;
41
61
  /** locate the compiled litestream commands.js inside the resolved @rocicorp/zero */
42
62
  function findLitestreamCommands() {
43
63
  const zeroEntry = resolvePackage('@rocicorp/zero');
@@ -58,12 +78,24 @@ function findLitestreamCommands() {
58
78
  /**
59
79
  * apply the restore-guard to a specific compiled litestream commands.js —
60
80
  * shared by the in-place node path and the CF overlay (cf-patches.ts).
61
- * idempotent.
81
+ * idempotent; upgrades a v1 guard in place.
62
82
  */
63
83
  export function applyLitestreamRestoreGuard(commandsPath) {
64
- const content = readFileSync(commandsPath, 'utf-8');
65
- if (content.includes(OREZ_MARKER))
84
+ let content = readFileSync(commandsPath, 'utf-8');
85
+ // strip the v1 guard BEFORE the already-patched check: a v1-era orez run
86
+ // against an already-v2-patched file re-injects v1 above the v2 guard
87
+ // (its marker check doesn't recognize v2), and v1's early `return` then
88
+ // shadows the fix. checking the v2 marker first would return early here
89
+ // and leave that stacked v1 in place. observed live on 2026-07-07.
90
+ const hadV1 = content.includes(GUARD_V1);
91
+ if (hadV1) {
92
+ content = content.replace(`\n\t${GUARD_V1}`, '');
93
+ }
94
+ if (content.includes(OREZ_MARKER)) {
95
+ if (hadV1)
96
+ writeFileSync(commandsPath, content);
66
97
  return; // already patched
98
+ }
67
99
  const anchor = 'async function restoreReplica(lc, config, replicaConstraints) {';
68
100
  if (!content.includes(anchor)) {
69
101
  // upstream shape changed — fail loudly rather than silently leaving the
@@ -1 +1 @@
1
- {"version":3,"file":"zero-litestream-patch.js","sourceRoot":"","sources":["../src/zero-litestream-patch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACjE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAE5C,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AAEjE,MAAM,WAAW,GAAG,gEAAgE,CAAA;AAEpF,4EAA4E;AAC5E,wEAAwE;AACxE,iEAAiE;AACjE,MAAM,KAAK,GAAG,GAAG,WAAW,gDAAgD,CAAA;AAE5E,oFAAoF;AACpF,SAAS,sBAAsB;IAC7B,MAAM,SAAS,GAAG,cAAc,CAAC,gBAAgB,CAAC,CAAA;IAClD,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAA;IAE3B,8EAA8E;IAC9E,sEAAsE;IACtE,IAAI,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IAC5B,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;QACxD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QACjC,IAAI,MAAM,KAAK,GAAG;YAAE,MAAK;QACzB,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAC1B,GAAG,EACH,KAAK,EACL,YAAY,EACZ,KAAK,EACL,UAAU,EACV,YAAY,EACZ,aAAa,CACd,CAAA;IACD,OAAO,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAA;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAAC,YAAoB;IAC9D,MAAM,OAAO,GAAG,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;IACnD,IAAI,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,OAAM,CAAC,kBAAkB;IAE5D,MAAM,MAAM,GAAG,iEAAiE,CAAA;IAChF,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,wEAAwE;QACxE,yDAAyD;QACzD,MAAM,IAAI,KAAK,CACb,+FAA+F,YAAY,IAAI;YAC7G,mEAAmE,CACtE,CAAA;IACH,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,MAAM,OAAO,KAAK,EAAE,CAAC,CAAA;IAChE,aAAa,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,4BAA4B;IAC1C,MAAM,YAAY,GAAG,sBAAsB,EAAE,CAAA;IAC7C,IAAI,CAAC,YAAY;QAAE,OAAM;IACzB,2BAA2B,CAAC,YAAY,CAAC,CAAA;AAC3C,CAAC"}
1
+ {"version":3,"file":"zero-litestream-patch.js","sourceRoot":"","sources":["../src/zero-litestream-patch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACjE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAE5C,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AAEjE,MAAM,WAAW,GAAG,qEAAqE,CAAA;AAEzF,4EAA4E;AAC5E,yEAAyE;AACzE,uEAAuE;AACvE,MAAM,cAAc,GAAG,gEAAgE,CAAA;AACvF,MAAM,QAAQ,GAAG,GAAG,cAAc,gDAAgD,CAAA;AAElF,6EAA6E;AAC7E,2EAA2E;AAC3E,4EAA4E;AAC5E,0EAA0E;AAC1E,MAAM,KAAK,GAAG,GAAG,WAAW;;;;GAIzB,CAAA;AAEH,oFAAoF;AACpF,SAAS,sBAAsB;IAC7B,MAAM,SAAS,GAAG,cAAc,CAAC,gBAAgB,CAAC,CAAA;IAClD,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAA;IAE3B,8EAA8E;IAC9E,sEAAsE;IACtE,IAAI,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IAC5B,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;QACxD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QACjC,IAAI,MAAM,KAAK,GAAG;YAAE,MAAK;QACzB,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAC1B,GAAG,EACH,KAAK,EACL,YAAY,EACZ,KAAK,EACL,UAAU,EACV,YAAY,EACZ,aAAa,CACd,CAAA;IACD,OAAO,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAA;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAAC,YAAoB;IAC9D,IAAI,OAAO,GAAG,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;IAEjD,yEAAyE;IACzE,sEAAsE;IACtE,wEAAwE;IACxE,wEAAwE;IACxE,mEAAmE;IACnE,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACxC,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAA;IAClD,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAClC,IAAI,KAAK;YAAE,aAAa,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;QAC/C,OAAM,CAAC,kBAAkB;IAC3B,CAAC;IAED,MAAM,MAAM,GAAG,iEAAiE,CAAA;IAChF,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,wEAAwE;QACxE,yDAAyD;QACzD,MAAM,IAAI,KAAK,CACb,+FAA+F,YAAY,IAAI;YAC7G,mEAAmE,CACtE,CAAA;IACH,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,MAAM,OAAO,KAAK,EAAE,CAAC,CAAA;IAChE,aAAa,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,4BAA4B;IAC1C,MAAM,YAAY,GAAG,sBAAsB,EAAE,CAAA;IAC7C,IAAI,CAAC,YAAY;QAAE,OAAM;IACzB,2BAA2B,CAAC,YAAY,CAAC,CAAA;AAC3C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orez",
3
- "version": "0.4.40",
3
+ "version": "0.4.41",
4
4
  "description": "PGlite-powered zero-sync development backend. No Docker required.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -87,7 +87,7 @@
87
87
  "@electric-sql/pglite": "0.4.1",
88
88
  "@electric-sql/pglite-tools": "^0.3.1",
89
89
  "@pgsql/traverse": "17.2.6",
90
- "bedrock-sqlite": "0.4.40",
90
+ "bedrock-sqlite": "0.4.41",
91
91
  "citty": "^0.2.0",
92
92
  "pg-gateway": "0.3.0-beta.4",
93
93
  "pgsql-parser": "^17.9.11",