orez 0.2.24 → 0.2.26

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.
Files changed (138) hide show
  1. package/dist/cf-do/test-protocol.d.ts +11 -0
  2. package/dist/cf-do/test-protocol.d.ts.map +1 -0
  3. package/dist/cf-do/test-protocol.js +137 -0
  4. package/dist/cf-do/test-protocol.js.map +1 -0
  5. package/dist/cf-do/watermark.d.ts +21 -0
  6. package/dist/cf-do/watermark.d.ts.map +1 -0
  7. package/dist/cf-do/watermark.js +93 -0
  8. package/dist/cf-do/watermark.js.map +1 -0
  9. package/dist/cf-do/worker.d.ts +91 -0
  10. package/dist/cf-do/worker.d.ts.map +1 -0
  11. package/dist/cf-do/worker.js +813 -0
  12. package/dist/cf-do/worker.js.map +1 -0
  13. package/dist/config.d.ts +4 -0
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +1 -0
  16. package/dist/config.js.map +1 -1
  17. package/dist/do-sql-tracking.d.ts +6 -0
  18. package/dist/do-sql-tracking.d.ts.map +1 -0
  19. package/dist/do-sql-tracking.js +14 -0
  20. package/dist/do-sql-tracking.js.map +1 -0
  21. package/dist/index.d.ts +2 -3
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +69 -23
  24. package/dist/index.js.map +1 -1
  25. package/dist/pg-proxy-browser.js +6 -6
  26. package/dist/pg-proxy-browser.js.map +1 -1
  27. package/dist/pg-proxy-do-backend.d.ts +128 -0
  28. package/dist/pg-proxy-do-backend.d.ts.map +1 -0
  29. package/dist/pg-proxy-do-backend.js +6292 -0
  30. package/dist/pg-proxy-do-backend.js.map +1 -0
  31. package/dist/pglite-ipc.d.ts +3 -0
  32. package/dist/pglite-ipc.d.ts.map +1 -1
  33. package/dist/pglite-ipc.js +34 -12
  34. package/dist/pglite-ipc.js.map +1 -1
  35. package/dist/pglite-web-proxy.d.ts +3 -0
  36. package/dist/pglite-web-proxy.d.ts.map +1 -1
  37. package/dist/pglite-web-proxy.js +50 -7
  38. package/dist/pglite-web-proxy.js.map +1 -1
  39. package/dist/query-rewrites.d.ts +2 -0
  40. package/dist/query-rewrites.d.ts.map +1 -0
  41. package/dist/query-rewrites.js +140 -0
  42. package/dist/query-rewrites.js.map +1 -0
  43. package/dist/replication/change-tracker.d.ts.map +1 -1
  44. package/dist/replication/change-tracker.js +18 -1
  45. package/dist/replication/change-tracker.js.map +1 -1
  46. package/dist/replication/handler.d.ts.map +1 -1
  47. package/dist/replication/handler.js +7 -2
  48. package/dist/replication/handler.js.map +1 -1
  49. package/dist/replication/pgoutput-encoder.d.ts.map +1 -1
  50. package/dist/replication/pgoutput-encoder.js +72 -30
  51. package/dist/replication/pgoutput-encoder.js.map +1 -1
  52. package/dist/worker/browser-build-config.d.ts.map +1 -1
  53. package/dist/worker/browser-build-config.js +2 -1
  54. package/dist/worker/browser-build-config.js.map +1 -1
  55. package/dist/worker/cf-patches.d.ts +5 -2
  56. package/dist/worker/cf-patches.d.ts.map +1 -1
  57. package/dist/worker/cf-patches.js +238 -4
  58. package/dist/worker/cf-patches.js.map +1 -1
  59. package/dist/worker/shims/node-stub.d.ts +35 -0
  60. package/dist/worker/shims/node-stub.d.ts.map +1 -1
  61. package/dist/worker/shims/node-stub.js +53 -1
  62. package/dist/worker/shims/node-stub.js.map +1 -1
  63. package/dist/worker/shims/oxfmt.d.ts +4 -0
  64. package/dist/worker/shims/oxfmt.d.ts.map +1 -0
  65. package/dist/worker/shims/oxfmt.js +4 -0
  66. package/dist/worker/shims/oxfmt.js.map +1 -0
  67. package/dist/worker/shims/postgres-socket.js +1 -1
  68. package/dist/worker/shims/postgres-socket.js.map +1 -1
  69. package/dist/worker/shims/sqlite.d.ts +1 -0
  70. package/dist/worker/shims/sqlite.d.ts.map +1 -1
  71. package/dist/worker/shims/sqlite.js +229 -9
  72. package/dist/worker/shims/sqlite.js.map +1 -1
  73. package/dist/worker/shims/ws.d.ts.map +1 -1
  74. package/dist/worker/shims/ws.js +45 -0
  75. package/dist/worker/shims/ws.js.map +1 -1
  76. package/dist/worker/shims/zero-process-env.d.ts +2 -0
  77. package/dist/worker/shims/zero-process-env.d.ts.map +1 -0
  78. package/dist/worker/shims/zero-process-env.js +9 -0
  79. package/dist/worker/shims/zero-process-env.js.map +1 -0
  80. package/dist/worker/zero-cache-embed-cf.d.ts +29 -12
  81. package/dist/worker/zero-cache-embed-cf.d.ts.map +1 -1
  82. package/dist/worker/zero-cache-embed-cf.js +83 -14
  83. package/dist/worker/zero-cache-embed-cf.js.map +1 -1
  84. package/package.json +6 -2
  85. package/src/cf-do/.wrangler/cache/cf.json +1 -0
  86. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
  87. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
  88. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
  89. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0f0f3bdf0abda097eb6f1246db4657d9fc622081362d894d82c1a1ce067b05b6.sqlite +0 -0
  90. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/1ddd3a4a48a11b51658444f5458a1fb175194b1d5b6a5bda20ef3fe3205b900c.sqlite +0 -0
  91. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/204a39120310d37e972c5914cfd71ad55c151bdb9e8ed289a5f8c5b052dd60e4.sqlite +0 -0
  92. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/3835f242df9728adba3d127a238793fd054ed3e51df3f60749ee744c469bf2a2.sqlite +0 -0
  93. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/4aa9c80eb716cf55b8995ccf7afab0b36c683e6da07d7c37a3f9c570136036df.sqlite +0 -0
  94. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/533e2fd1d6ea46e7a9a0017916ef341802d438d72583462755f2c1f8225e9bf2.sqlite +0 -0
  95. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/5ffa1aced1225ecaeac6366f7586aa3de92761cdff8711d81fbd81f248076abd.sqlite +0 -0
  96. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/686c3a9f0d7e59ed2ab607efd4b76d779c97cafeb3818380033bf7c7eb86c819.sqlite +0 -0
  97. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/6e8214e8dcfadd0deb52d64e5e9ca85c6b329ace11193909845995396914c473.sqlite +0 -0
  98. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/78d9ec9ff873d3fe3507ff53c2a6f6dfc408b4268eb0db3f2a146c0678965366.sqlite +0 -0
  99. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/7eff9f0ed7e27ad0d3f9d923de0682fab1928591172c1ba336c5f79a134a5d85.sqlite +0 -0
  100. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/836cda5b995b25867d722ed4f4c2292167e80351a3c6038db626648eb247dd8b.sqlite +0 -0
  101. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/91ef63b112209ab30172763acd8a0935106c248f7f1bcae5545ce37a9f201551.sqlite +0 -0
  102. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/a66ea4293a5f5938bc6d116edfa2522bb85bc37aea3541fbc09c3b613b9b32c0.sqlite +0 -0
  103. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/ceb2ab26b80590840b65651deb6e948d3bf81565c6751f3a58752cf4bf4aecae.sqlite +0 -0
  104. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
  105. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
  106. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
  107. package/src/cf-do/ARCHITECTURE.md +83 -0
  108. package/src/cf-do/watermark.test.ts +103 -0
  109. package/src/cf-do/watermark.ts +118 -0
  110. package/src/cf-do/worker.ts +1033 -0
  111. package/src/cf-do/wrangler.toml +11 -0
  112. package/src/config.ts +5 -0
  113. package/src/do-sql-tracking.test.ts +19 -0
  114. package/src/do-sql-tracking.ts +19 -0
  115. package/src/index.ts +76 -28
  116. package/src/pg-proxy-browser.ts +6 -6
  117. package/src/pg-proxy-do-backend.test.ts +3890 -0
  118. package/src/pg-proxy-do-backend.ts +7157 -0
  119. package/src/pglite-ipc.test.ts +17 -0
  120. package/src/pglite-ipc.ts +31 -12
  121. package/src/pglite-web-proxy.test.ts +57 -0
  122. package/src/pglite-web-proxy.ts +48 -7
  123. package/src/replication/change-tracker.ts +16 -1
  124. package/src/replication/handler.test.ts +35 -0
  125. package/src/replication/handler.ts +7 -2
  126. package/src/replication/pgoutput-encoder.test.ts +71 -2
  127. package/src/replication/pgoutput-encoder.ts +65 -30
  128. package/src/worker/browser-build-config.test.ts +12 -0
  129. package/src/worker/browser-build-config.ts +2 -1
  130. package/src/worker/cf-patches.ts +274 -4
  131. package/src/worker/shims/node-stub.ts +53 -1
  132. package/src/worker/shims/oxfmt.ts +3 -0
  133. package/src/worker/shims/postgres-socket.ts +1 -1
  134. package/src/worker/shims/sqlite.test.ts +145 -0
  135. package/src/worker/shims/sqlite.ts +256 -9
  136. package/src/worker/shims/ws.ts +45 -0
  137. package/src/worker/shims/zero-process-env.ts +11 -0
  138. package/src/worker/zero-cache-embed-cf.ts +114 -18
@@ -5,9 +5,12 @@
5
5
  * can run in SINGLE_PROCESS mode on CF Workers where dynamic import()
6
6
  * doesn't work.
7
7
  *
8
- * two patches:
8
+ * five patches:
9
9
  * 1. worker-urls.js — replace file:// URLs with zero-worker:// identifiers
10
- * 2. processes.js replace dynamic import() with static worker module lookup
10
+ * 2. server worker entrypoints disable CLI auto-start blocks
11
+ * 3. processes.js — replace dynamic import() with static worker module lookup
12
+ * 4. write-worker-client.js — run zero-cache's replica writer in-process
13
+ * 5. pgsql-parser — embed libpg-query wasm bytes for Workers
11
14
  *
12
15
  * usage in a post-build script:
13
16
  *
@@ -24,7 +27,10 @@ export function patchZeroCacheForCF(nodeModulesPath: string): void {
24
27
  const zcBase = resolve(nodeModulesPath, '@rocicorp', 'zero', 'out', 'zero-cache', 'src')
25
28
 
26
29
  patchWorkerUrls(zcBase)
30
+ patchWorkerEntrypoints(zcBase)
27
31
  patchProcesses(zcBase)
32
+ patchWriteWorkerClient(zcBase)
33
+ patchPgsqlParserWasm(nodeModulesPath)
28
34
  }
29
35
 
30
36
  function patchWorkerUrls(zcBase: string): void {
@@ -50,11 +56,49 @@ export const CHANGE_STREAMER_URL = u("change-streamer");
50
56
  export const REAPER_URL = u("reaper");
51
57
  export const REPLICATOR_URL = u("replicator");
52
58
  export const SYNCER_URL = u("syncer");
59
+ // write-worker is spawned via 'new Worker()' (node:worker_threads), not via
60
+ // childWorker() — it uses its own URL → worker resolution path. we still expose
61
+ // it here so write-worker-client.js can import it without throwing.
62
+ export const WRITE_WORKER_URL = u("write-worker");
53
63
  `
54
64
  )
55
65
  console.log('[orez] patched zero-cache worker-urls.js')
56
66
  }
57
67
 
68
+ function patchWorkerEntrypoints(zcBase: string): void {
69
+ const entrypoints = ['main', 'change-streamer', 'reaper', 'replicator', 'syncer']
70
+
71
+ for (const entrypoint of entrypoints) {
72
+ const entrypointPath = resolve(zcBase, 'server', `${entrypoint}.js`)
73
+ if (!existsSync(entrypointPath)) {
74
+ console.warn('[orez] zero-cache worker entrypoint not found at', entrypointPath)
75
+ continue
76
+ }
77
+
78
+ let code = readFileSync(entrypointPath, 'utf-8')
79
+ if (code.includes('orez-disable-autostart')) {
80
+ continue
81
+ }
82
+
83
+ const next = code.replace(
84
+ /if \(!singleProcessMode\(\)\) exitAfter\(\(\) => runWorker\(must\(parentWorker\), process\.env(?:, \.\.\.process\.argv\.slice\(2\))?\)\);/g,
85
+ '// orez-disable-autostart: childWorker invokes runWorker explicitly in CF embeds.'
86
+ )
87
+
88
+ if (next === code) {
89
+ console.warn(
90
+ `[orez] could not find auto-start guard in ${entrypoint}.js. ` +
91
+ 'zero-cache version may have changed — check compatibility.'
92
+ )
93
+ continue
94
+ }
95
+
96
+ code = next
97
+ writeFileSync(entrypointPath, code)
98
+ console.log(`[orez] patched zero-cache ${entrypoint}.js (disabled auto-start)`)
99
+ }
100
+ }
101
+
58
102
  function patchProcesses(zcBase: string): void {
59
103
  const processesPath = resolve(zcBase, 'types', 'processes.js')
60
104
  if (!existsSync(processesPath)) {
@@ -71,6 +115,9 @@ function patchProcesses(zcBase: string): void {
71
115
 
72
116
  // add static imports of all zero-cache worker modules at the top.
73
117
  // these are relative to processes.js location in @rocicorp/zero.
118
+ // NOTE: mutator.js and write-worker.js don't export a default `runWorker`
119
+ // (mutator runs via auto-run guard, write-worker spawns via node:worker_threads
120
+ // not via childWorker()), so they're not in the lookup table.
74
121
  const workerImports = `\
75
122
  // patched by orez for CF Workers (static imports replace dynamic import())
76
123
  import { default as __zc_main } from "../server/main.js";
@@ -95,10 +142,11 @@ const __zc_workers = {
95
142
  const staticLookup =
96
143
  '((async () => { ' +
97
144
  'const _name = moduleUrl.hostname || moduleUrl.pathname.split("/").pop()?.replace(".js",""); ' +
145
+ 'if (process.env.OREZ_DEBUG_WIRE === "1" || globalThis.__OREZ_DEBUG_WIRE__ === true) console.debug("[orez-zc-worker] start", _name, args); ' +
98
146
  'const runWorker = __zc_workers[_name]; ' +
99
147
  'if (!runWorker) throw new Error("orez: unknown zero-cache worker: " + _name + " (available: " + Object.keys(__zc_workers).join(", ") + ")"); ' +
100
- 'return { default: runWorker }; ' +
101
- '})()).then(async ({ default: runWorker })'
148
+ 'return { default: runWorker, name: _name }; ' +
149
+ '})()).then(async ({ default: runWorker, name })'
102
150
 
103
151
  if (!code.includes(dynamicImportPattern)) {
104
152
  console.warn(
@@ -112,3 +160,225 @@ const __zc_workers = {
112
160
  writeFileSync(processesPath, code)
113
161
  console.log('[orez] patched zero-cache processes.js (static worker imports)')
114
162
  }
163
+
164
+ function patchWriteWorkerClient(zcBase: string): void {
165
+ const clientPath = resolve(zcBase, 'services', 'replicator', 'write-worker-client.js')
166
+ if (!existsSync(clientPath)) {
167
+ console.warn('[orez] write-worker-client.js not found at', clientPath)
168
+ return
169
+ }
170
+
171
+ let code = readFileSync(clientPath, 'utf-8')
172
+ if (
173
+ code.includes('orez-inline-write-worker') &&
174
+ code.includes('__orez_zero_sqlite_role')
175
+ ) {
176
+ return
177
+ }
178
+
179
+ if (
180
+ !code.includes('orez-inline-write-worker') &&
181
+ !code.includes('import { Worker } from "node:worker_threads";')
182
+ ) {
183
+ console.warn(
184
+ '[orez] could not find node:worker_threads import in write-worker-client.js. ' +
185
+ 'zero-cache version may have changed — check compatibility.'
186
+ )
187
+ return
188
+ }
189
+
190
+ writeFileSync(
191
+ clientPath,
192
+ `// patched by orez for CF Workers (orez-inline-write-worker)
193
+ import { must } from "../../../../shared/src/must.js";
194
+ import { Database } from "../../../../zqlite/src/db.js";
195
+ import { createLogContext } from "../../server/logging.js";
196
+ import { StatementRunner } from "../../db/statements.js";
197
+ import { getSubscriptionState } from "./schema/replication-state.js";
198
+ import { ChangeProcessor } from "./change-processor.js";
199
+
200
+ function applyPragmas(db, pragmas) {
201
+ db.pragma(\`busy_timeout = \${pragmas.busyTimeout}\`);
202
+ db.pragma(\`analysis_limit = \${pragmas.analysisLimit}\`);
203
+ if (pragmas.walAutocheckpoint !== void 0) {
204
+ db.pragma(\`wal_autocheckpoint = \${pragmas.walAutocheckpoint}\`);
205
+ }
206
+ }
207
+
208
+ function createAPI(onWriteError) {
209
+ let db;
210
+ let runner;
211
+ let processor;
212
+ let mode;
213
+ let lc;
214
+
215
+ function createProcessor() {
216
+ processor = new ChangeProcessor(must(runner), must(mode), (_lc, err) => {
217
+ onWriteError(err instanceof Error ? err : new Error(String(err)));
218
+ });
219
+ }
220
+
221
+ return {
222
+ init(dbPath, cpMode, pragmas, logConfig) {
223
+ lc = createLogContext({ log: logConfig }, { worker: "write-worker" });
224
+ const previousRole = globalThis.__orez_zero_sqlite_role;
225
+ globalThis.__orez_zero_sqlite_role = "replica-writer";
226
+ try {
227
+ db = new Database(lc, dbPath);
228
+ } finally {
229
+ if (previousRole === void 0) {
230
+ delete globalThis.__orez_zero_sqlite_role;
231
+ } else {
232
+ globalThis.__orez_zero_sqlite_role = previousRole;
233
+ }
234
+ }
235
+ applyPragmas(db, pragmas);
236
+ runner = new StatementRunner(db);
237
+ mode = cpMode;
238
+ createProcessor();
239
+ },
240
+ getSubscriptionState() {
241
+ return getSubscriptionState(must(runner));
242
+ },
243
+ processMessage(downstream) {
244
+ return must(processor).processMessage(must(lc), downstream);
245
+ },
246
+ abort() {
247
+ must(processor).abort(must(lc));
248
+ createProcessor();
249
+ },
250
+ stop() {
251
+ db?.close();
252
+ db = void 0;
253
+ runner = void 0;
254
+ processor = void 0;
255
+ },
256
+ };
257
+ }
258
+
259
+ class ThreadWriteWorkerClient {
260
+ #api;
261
+ #errorHandler = () => {};
262
+ #writeError = null;
263
+
264
+ constructor() {
265
+ this.#api = createAPI((err) => {
266
+ this.#writeError = err;
267
+ this.#errorHandler(err);
268
+ });
269
+ }
270
+
271
+ async #call(method, args) {
272
+ if (this.#writeError) throw this.#writeError;
273
+ try {
274
+ const result = this.#api[method](...args);
275
+ if (this.#writeError) throw this.#writeError;
276
+ return result;
277
+ } catch (err) {
278
+ throw err instanceof Error ? err : new Error(String(err));
279
+ }
280
+ }
281
+
282
+ init(dbPath, mode, pragmas, logConfig) {
283
+ return this.#call("init", [dbPath, mode, pragmas, logConfig]);
284
+ }
285
+
286
+ getSubscriptionState() {
287
+ return this.#call("getSubscriptionState", []);
288
+ }
289
+
290
+ processMessage(downstream) {
291
+ return this.#call("processMessage", [downstream]);
292
+ }
293
+
294
+ abort() {
295
+ if (!this.#writeError) {
296
+ try {
297
+ this.#api.abort();
298
+ } catch {
299
+ }
300
+ }
301
+ }
302
+
303
+ async stop() {
304
+ await this.#call("stop", []);
305
+ }
306
+
307
+ onError(handler) {
308
+ this.#errorHandler = handler;
309
+ }
310
+ }
311
+
312
+ export { ThreadWriteWorkerClient, applyPragmas };
313
+ `
314
+ )
315
+ console.log('[orez] patched zero-cache write-worker-client.js (inline writer)')
316
+ }
317
+
318
+ function patchPgsqlParserWasm(nodeModulesPath: string): void {
319
+ const parserIndexPath = resolve(nodeModulesPath, 'libpg-query', 'wasm', 'index.js')
320
+ const wasmPath = resolve(nodeModulesPath, 'libpg-query', 'wasm', 'libpg-query.wasm')
321
+
322
+ if (!existsSync(parserIndexPath) || !existsSync(wasmPath)) {
323
+ console.warn('[orez] libpg-query wasm files not found under', nodeModulesPath)
324
+ return
325
+ }
326
+
327
+ let code = readFileSync(parserIndexPath, 'utf-8')
328
+ if (
329
+ code.includes('orez-libpg-query-wasm-binary') &&
330
+ code.includes('__orezLibPgQueryInit')
331
+ ) {
332
+ return
333
+ }
334
+ if (code.includes('orez-libpg-query-wasm-binary')) {
335
+ console.warn(
336
+ '[orez] libpg-query wasm loader already patched with an older shape. ' +
337
+ 'Reinstall libpg-query or restore node_modules before re-patching.'
338
+ )
339
+ return
340
+ }
341
+
342
+ const pattern = 'const initPromise = PgQueryModule().then((module) => {'
343
+ if (!code.includes(pattern)) {
344
+ console.warn(
345
+ '[orez] could not find PgQueryModule init in libpg-query wasm index. ' +
346
+ 'pgsql-parser version may have changed — check compatibility.'
347
+ )
348
+ return
349
+ }
350
+
351
+ const wasmBase64 = readFileSync(wasmPath).toString('base64')
352
+ const replacement = `\
353
+ // orez-libpg-query-wasm-binary: embed parser wasm for CF Workers.
354
+ const __orezLibPgQueryWasmBase64 = '${wasmBase64}';
355
+ function __orezLibPgQueryWasmBinary() {
356
+ const decode = globalThis.atob
357
+ ? globalThis.atob(__orezLibPgQueryWasmBase64)
358
+ : Buffer.from(__orezLibPgQueryWasmBase64, 'base64').toString('binary');
359
+ const bytes = new Uint8Array(decode.length);
360
+ for (let i = 0; i < decode.length; i++) bytes[i] = decode.charCodeAt(i);
361
+ return bytes;
362
+ }
363
+ try {
364
+ const g = globalThis;
365
+ if (g.self && !g.self.location) g.self.location = { href: 'https://orez.local/libpg-query.js' };
366
+ if (!g.location) g.location = { href: 'https://orez.local/libpg-query.js' };
367
+ }
368
+ catch {
369
+ }
370
+ const __orezLibPgQueryPreviousProcessType = globalThis.process?.type;
371
+ if (globalThis.process && !globalThis.process.type) globalThis.process.type = 'renderer';
372
+ const __orezLibPgQueryInit = PgQueryModule({ wasmBinary: __orezLibPgQueryWasmBinary() });
373
+ if (globalThis.process && __orezLibPgQueryPreviousProcessType === undefined) {
374
+ delete globalThis.process.type;
375
+ }
376
+ else if (globalThis.process) {
377
+ globalThis.process.type = __orezLibPgQueryPreviousProcessType;
378
+ }
379
+ const initPromise = __orezLibPgQueryInit.then((module) => {`
380
+
381
+ code = code.replace(pattern, replacement)
382
+ writeFileSync(parserIndexPath, code)
383
+ console.log('[orez] patched libpg-query wasm loader (embedded wasm bytes)')
384
+ }
@@ -64,9 +64,38 @@ export function platform() {
64
64
  export function tmpdir() {
65
65
  return '/tmp'
66
66
  }
67
+ export function homedir() {
68
+ return '/tmp'
69
+ }
67
70
  export function availableParallelism() {
68
71
  return 1
69
72
  }
73
+ export function loadavg() {
74
+ return [0, 0, 0]
75
+ }
76
+ export function uptime() {
77
+ return 0
78
+ }
79
+ export function totalmem() {
80
+ return 128 * 1024 * 1024
81
+ }
82
+ export function freemem() {
83
+ return 64 * 1024 * 1024
84
+ }
85
+ export function cpus() {
86
+ return [{ model: 'worker', speed: 0 }]
87
+ }
88
+ export function networkInterfaces() {
89
+ return {
90
+ lo: [
91
+ {
92
+ address: '127.0.0.1',
93
+ family: 'IPv4',
94
+ internal: true,
95
+ },
96
+ ],
97
+ }
98
+ }
70
99
 
71
100
  // stub for node:crypto
72
101
  export function timingSafeEqual(a: unknown, b: unknown) {
@@ -94,7 +123,23 @@ export class Session {
94
123
 
95
124
  // stub for node:v8
96
125
  export function getHeapStatistics() {
97
- return { total_heap_size: 0, used_heap_size: 0 }
126
+ const heapSizeLimit = 128 * 1024 * 1024
127
+ return {
128
+ total_heap_size: 64 * 1024 * 1024,
129
+ total_heap_size_executable: 0,
130
+ total_physical_size: 64 * 1024 * 1024,
131
+ total_available_size: 64 * 1024 * 1024,
132
+ used_heap_size: 32 * 1024 * 1024,
133
+ heap_size_limit: heapSizeLimit,
134
+ malloced_memory: 0,
135
+ peak_malloced_memory: 0,
136
+ does_zap_garbage: 0,
137
+ number_of_native_contexts: 1,
138
+ number_of_detached_contexts: 0,
139
+ total_global_handles_size: 0,
140
+ used_global_handles_size: 0,
141
+ external_memory: 0,
142
+ }
98
143
  }
99
144
 
100
145
  // stub for node:zlib
@@ -197,9 +242,16 @@ export default {
197
242
  hostname,
198
243
  platform,
199
244
  tmpdir,
245
+ homedir,
200
246
  availableParallelism,
247
+ loadavg,
248
+ uptime,
249
+ totalmem,
250
+ freemem,
251
+ cpus,
201
252
  arch,
202
253
  release,
254
+ networkInterfaces,
203
255
  existsSync,
204
256
  readFileSync,
205
257
  writeFileSync,
@@ -0,0 +1,3 @@
1
+ export async function format(_file: string, content: string) {
2
+ return { code: content }
3
+ }
@@ -151,7 +151,7 @@ class MessagePortSocket extends EventEmitter {
151
151
  copy.set(bytes)
152
152
 
153
153
  try {
154
- this.port.postMessage(copy.buffer, [copy.buffer])
154
+ this.port.postMessage(copy.buffer)
155
155
  } catch (err) {
156
156
  queueMicrotask(() => this.emit('error', err))
157
157
  if (typeof encoding === 'function') encoding()
@@ -139,6 +139,7 @@ describe('Database', () => {
139
139
  })
140
140
 
141
141
  afterEach(() => {
142
+ db.close()
142
143
  mock._nativeDb.close()
143
144
  })
144
145
 
@@ -189,6 +190,7 @@ describe('Database.exec', () => {
189
190
  })
190
191
 
191
192
  afterEach(() => {
193
+ db.close()
192
194
  mock._nativeDb.close()
193
195
  })
194
196
 
@@ -229,6 +231,7 @@ describe('Database.prepare / Statement', () => {
229
231
  })
230
232
 
231
233
  afterEach(() => {
234
+ db.close()
232
235
  mock._nativeDb.close()
233
236
  })
234
237
 
@@ -552,6 +555,148 @@ describe('StatementRunner', () => {
552
555
  })
553
556
  })
554
557
 
558
+ describe('DO snapshot transactions', () => {
559
+ let mock: SqlStorageLike & { _nativeDb: any; transactionSync: <T>(fn: () => T) => T }
560
+ let live: Database
561
+ let snapshot: Database
562
+
563
+ beforeEach(() => {
564
+ mock = createMockSqlStorage() as SqlStorageLike & {
565
+ _nativeDb: any
566
+ transactionSync: <T>(fn: () => T) => T
567
+ }
568
+ mock.transactionSync = (fn) => fn()
569
+ live = new Database(mock)
570
+ snapshot = new Database(mock)
571
+ live.exec('CREATE TABLE todo (id TEXT PRIMARY KEY, title TEXT, _0_version TEXT)')
572
+ live.prepare('INSERT INTO todo VALUES (?, ?, ?)').run('1', 'old', '01')
573
+ })
574
+
575
+ afterEach(() => {
576
+ live.close()
577
+ snapshot.close()
578
+ mock._nativeDb.close()
579
+ })
580
+
581
+ it('keeps BEGIN CONCURRENT reads stable until rollback', () => {
582
+ snapshot.prepare('BEGIN CONCURRENT').run()
583
+ expect(snapshot.prepare('SELECT title FROM todo WHERE id = ?').get('1')).toEqual({
584
+ title: 'old',
585
+ })
586
+
587
+ live
588
+ .prepare('UPDATE todo SET title = ?, _0_version = ? WHERE id = ?')
589
+ .run('new', '02', '1')
590
+
591
+ expect(live.prepare('SELECT title FROM todo WHERE id = ?').get('1')).toEqual({
592
+ title: 'new',
593
+ })
594
+ expect(snapshot.prepare('SELECT title FROM todo WHERE id = ?').get('1')).toEqual({
595
+ title: 'old',
596
+ })
597
+
598
+ snapshot.prepare('ROLLBACK').run()
599
+ expect(snapshot.prepare('SELECT title FROM todo WHERE id = ?').get('1')).toEqual({
600
+ title: 'new',
601
+ })
602
+ })
603
+
604
+ it('hides snapshot tables from sqlite catalog queries', () => {
605
+ snapshot.prepare('BEGIN CONCURRENT').run()
606
+
607
+ expect(
608
+ live
609
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name")
610
+ .all()
611
+ ).toEqual([{ name: 'todo' }])
612
+ })
613
+
614
+ it('does not rewrite table names inside string literals during snapshots', () => {
615
+ live.exec('CREATE TABLE log (id TEXT PRIMARY KEY, table_name TEXT, _0_version TEXT)')
616
+ live.prepare('INSERT INTO log VALUES (?, ?, ?)').run('1', 'todo', '01')
617
+ live.prepare('INSERT INTO log VALUES (?, ?, ?)').run('2', '"todo"', '02')
618
+
619
+ snapshot.prepare('BEGIN CONCURRENT').run()
620
+
621
+ expect(
622
+ snapshot.prepare("SELECT table_name FROM log WHERE table_name = 'todo'").get()
623
+ ).toEqual({ table_name: 'todo' })
624
+ expect(
625
+ snapshot.prepare('SELECT table_name FROM log WHERE table_name = ?').get('"todo"')
626
+ ).toEqual({ table_name: '"todo"' })
627
+ })
628
+
629
+ it('cleans inactive snapshot tables when opening a database', () => {
630
+ mock.exec('CREATE TABLE _orez_snapshot_1_todo AS SELECT * FROM todo')
631
+ expect(
632
+ mock
633
+ .exec("SELECT name FROM sqlite_master WHERE name = '_orez_snapshot_1_todo'")
634
+ .toArray()
635
+ ).toEqual([{ name: '_orez_snapshot_1_todo' }])
636
+
637
+ const reopened = new Database(mock)
638
+ try {
639
+ expect(
640
+ mock
641
+ .exec("SELECT name FROM sqlite_master WHERE name = '_orez_snapshot_1_todo'")
642
+ .toArray()
643
+ ).toEqual([])
644
+ } finally {
645
+ reopened.close()
646
+ }
647
+ })
648
+
649
+ it('does not remove another open connection snapshot', () => {
650
+ snapshot.prepare('BEGIN CONCURRENT').run()
651
+ const snapshotTables = mock
652
+ .exec("SELECT name FROM sqlite_master WHERE name LIKE '_orez_snapshot_%'")
653
+ .toArray()
654
+ expect(snapshotTables).toHaveLength(1)
655
+
656
+ const other = new Database(mock)
657
+ try {
658
+ live
659
+ .prepare('UPDATE todo SET title = ?, _0_version = ? WHERE id = ?')
660
+ .run('new', '02', '1')
661
+
662
+ expect(snapshot.prepare('SELECT title FROM todo WHERE id = ?').get('1')).toEqual({
663
+ title: 'old',
664
+ })
665
+ } finally {
666
+ other.close()
667
+ }
668
+ })
669
+
670
+ it('persists BEGIN CONCURRENT writes for the zero-cache replica writer', () => {
671
+ const globalObject = globalThis as any
672
+ const previousRole = globalObject.__orez_zero_sqlite_role
673
+ globalObject.__orez_zero_sqlite_role = 'replica-writer'
674
+ let writer: Database
675
+ try {
676
+ writer = new Database(mock)
677
+ } finally {
678
+ if (previousRole === undefined) {
679
+ delete globalObject.__orez_zero_sqlite_role
680
+ } else {
681
+ globalObject.__orez_zero_sqlite_role = previousRole
682
+ }
683
+ }
684
+
685
+ writer.prepare('BEGIN CONCURRENT').run()
686
+ writer.prepare('INSERT INTO todo VALUES (?, ?, ?)').run('2', 'writer row', '02')
687
+ writer.prepare('COMMIT').run()
688
+
689
+ expect(live.prepare('SELECT title FROM todo WHERE id = ?').get('2')).toEqual({
690
+ title: 'writer row',
691
+ })
692
+ expect(
693
+ live
694
+ .prepare("SELECT name FROM sqlite_master WHERE name LIKE '_orez_snapshot_%'")
695
+ .all()
696
+ ).toEqual([])
697
+ })
698
+ })
699
+
555
700
  describe('StatementRunner: zero-cache replicator pattern', () => {
556
701
  let mock: SqlStorageLike & { _nativeDb: any }
557
702
  let db: Database