orez 0.2.27 → 0.2.30

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 (157) hide show
  1. package/dist/cf-do/worker.d.ts +3 -0
  2. package/dist/cf-do/worker.d.ts.map +1 -1
  3. package/dist/cf-do/worker.js +37 -15
  4. package/dist/cf-do/worker.js.map +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +8 -0
  7. package/dist/index.js.map +1 -1
  8. package/package.json +3 -4
  9. package/src/admin/admin-data.test.ts +0 -348
  10. package/src/admin/http-proxy.ts +0 -252
  11. package/src/admin/log-store.ts +0 -192
  12. package/src/admin/server.ts +0 -471
  13. package/src/admin/ui.ts +0 -1322
  14. package/src/bench/proxy-throughput.bench.ts +0 -343
  15. package/src/bench/serial-mutations.bench.ts +0 -270
  16. package/src/browser.ts +0 -203
  17. package/src/cf-do/.wrangler/cache/cf.json +0 -1
  18. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
  19. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
  20. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
  21. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0ffaabee41a60e04dd0eb7db3073f0a40139e6a97ccd26823967acb652b89a7b.sqlite +0 -0
  22. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
  23. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
  24. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
  25. package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-insertion-facade.js +0 -11
  26. package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-loader.entry.ts +0 -134
  27. package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-insertion-facade.js +0 -11
  28. package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-loader.entry.ts +0 -134
  29. package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js +0 -1059
  30. package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js.map +0 -8
  31. package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js +0 -1059
  32. package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js.map +0 -8
  33. package/src/cf-do/ARCHITECTURE.md +0 -93
  34. package/src/cf-do/CHAT_E2E.md +0 -213
  35. package/src/cf-do/watermark.test.ts +0 -103
  36. package/src/cf-do/watermark.ts +0 -118
  37. package/src/cf-do/worker.ts +0 -1041
  38. package/src/cf-do/wrangler.toml +0 -11
  39. package/src/cf-pglite/README.md +0 -19
  40. package/src/change-tracking.ts +0 -25
  41. package/src/child-process.test.ts +0 -147
  42. package/src/child-process.ts +0 -90
  43. package/src/cli-entry.ts +0 -72
  44. package/src/cli.test.ts +0 -40
  45. package/src/cli.ts +0 -1214
  46. package/src/config.ts +0 -150
  47. package/src/do-sql-tracking.test.ts +0 -19
  48. package/src/do-sql-tracking.ts +0 -19
  49. package/src/index.ts +0 -1215
  50. package/src/integration/integration.test.ts +0 -517
  51. package/src/integration/native-binary.guard.test.ts +0 -13
  52. package/src/integration/native-startup.test.ts +0 -44
  53. package/src/integration/replication-latency.test.ts +0 -428
  54. package/src/integration/restore-live-stress.test.ts +0 -433
  55. package/src/integration/restore-reset.test.ts +0 -400
  56. package/src/integration/restore.test.ts +0 -274
  57. package/src/integration/test-permissions.ts +0 -147
  58. package/src/load-config.ts +0 -46
  59. package/src/log.ts +0 -96
  60. package/src/mutex.ts +0 -47
  61. package/src/pg-proxy-browser.singledb.test.ts +0 -233
  62. package/src/pg-proxy-browser.ts +0 -2022
  63. package/src/pg-proxy-do-backend.test.ts +0 -3890
  64. package/src/pg-proxy-do-backend.ts +0 -7191
  65. package/src/pg-proxy.ts +0 -1087
  66. package/src/pg-sqlite-compiler/README.md +0 -53
  67. package/src/pg-sqlite-compiler/catalog/seed.ts +0 -524
  68. package/src/pg-sqlite-compiler/fixtures/pgsqlite/arithmetic.json +0 -307
  69. package/src/pg-sqlite-compiler/fixtures/pgsqlite/array.json +0 -377
  70. package/src/pg-sqlite-compiler/fixtures/pgsqlite/cast.json +0 -12
  71. package/src/pg-sqlite-compiler/fixtures/pgsqlite/catalog.json +0 -447
  72. package/src/pg-sqlite-compiler/fixtures/pgsqlite/create-table.json +0 -32
  73. package/src/pg-sqlite-compiler/fixtures/pgsqlite/datetime.json +0 -397
  74. package/src/pg-sqlite-compiler/fixtures/pgsqlite/enum.json +0 -337
  75. package/src/pg-sqlite-compiler/fixtures/pgsqlite/insert.json +0 -337
  76. package/src/pg-sqlite-compiler/fixtures/pgsqlite/json.json +0 -537
  77. package/src/pg-sqlite-compiler/fixtures/pgsqlite/misc.json +0 -1837
  78. package/src/pg-sqlite-compiler/index.ts +0 -73
  79. package/src/pg-sqlite-compiler/integration.test.ts +0 -136
  80. package/src/pg-sqlite-compiler/passes/ast-utils.ts +0 -113
  81. package/src/pg-sqlite-compiler/passes/catalog.ts +0 -65
  82. package/src/pg-sqlite-compiler/passes/datetime.ts +0 -74
  83. package/src/pg-sqlite-compiler/passes/index.ts +0 -49
  84. package/src/pg-sqlite-compiler/passes/types.ts +0 -156
  85. package/src/pg-sqlite-compiler/smoke.test.ts +0 -69
  86. package/src/pg-sqlite-compiler/test/catalog.test.ts +0 -171
  87. package/src/pg-sqlite-compiler/test/corpus.test.ts +0 -161
  88. package/src/pg-sqlite-compiler/test/datetime.oracle.test.ts +0 -102
  89. package/src/pg-sqlite-compiler/test/oracle.ts +0 -237
  90. package/src/pg-sqlite-compiler/test/types.test.ts +0 -109
  91. package/src/pg-sqlite-compiler/types.ts +0 -63
  92. package/src/pglite-ipc.test.ts +0 -116
  93. package/src/pglite-ipc.ts +0 -266
  94. package/src/pglite-manager.ts +0 -557
  95. package/src/pglite-web-proxy.test.ts +0 -57
  96. package/src/pglite-web-proxy.ts +0 -221
  97. package/src/pglite-web-worker.ts +0 -152
  98. package/src/pglite-worker-thread.ts +0 -253
  99. package/src/port.ts +0 -25
  100. package/src/process-title.ts +0 -9
  101. package/src/recovery.ts +0 -155
  102. package/src/replication/change-tracker.test.ts +0 -357
  103. package/src/replication/change-tracker.ts +0 -279
  104. package/src/replication/handler.test.ts +0 -511
  105. package/src/replication/handler.ts +0 -1190
  106. package/src/replication/pgoutput-encoder.test.ts +0 -697
  107. package/src/replication/pgoutput-encoder.ts +0 -373
  108. package/src/replication/tcp-replication.test.ts +0 -876
  109. package/src/replication/zero-compat.test.ts +0 -1150
  110. package/src/restore-stress.test.ts +0 -188
  111. package/src/s3-local.ts +0 -203
  112. package/src/shim/hooks.mjs +0 -120
  113. package/src/shim/register.mjs +0 -4
  114. package/src/sqlite-mode/apply-mode.ts +0 -224
  115. package/src/sqlite-mode/index.ts +0 -15
  116. package/src/sqlite-mode/native-binary.ts +0 -89
  117. package/src/sqlite-mode/package-resolve.ts +0 -17
  118. package/src/sqlite-mode/resolve-mode.ts +0 -80
  119. package/src/sqlite-mode/shim-template.ts +0 -159
  120. package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
  121. package/src/sqlite-mode/types.ts +0 -30
  122. package/src/vite-plugin.ts +0 -67
  123. package/src/wasm-sqlite.test.ts +0 -537
  124. package/src/worker/browser-admin.ts +0 -52
  125. package/src/worker/browser-build-config.test.ts +0 -71
  126. package/src/worker/browser-build-config.ts +0 -109
  127. package/src/worker/browser-embed-admin.test.ts +0 -75
  128. package/src/worker/browser-embed.ts +0 -345
  129. package/src/worker/cf-patches.ts +0 -384
  130. package/src/worker/embed-integration.test.ts +0 -321
  131. package/src/worker/index.ts +0 -138
  132. package/src/worker/shims/fastify.test.ts +0 -255
  133. package/src/worker/shims/fastify.ts +0 -306
  134. package/src/worker/shims/http-service.test.ts +0 -355
  135. package/src/worker/shims/http-service.ts +0 -293
  136. package/src/worker/shims/node-stub.ts +0 -290
  137. package/src/worker/shims/oxfmt.ts +0 -3
  138. package/src/worker/shims/postgres-browser.ts +0 -59
  139. package/src/worker/shims/postgres-socket.test.ts +0 -576
  140. package/src/worker/shims/postgres-socket.ts +0 -310
  141. package/src/worker/shims/postgres.test.ts +0 -364
  142. package/src/worker/shims/postgres.ts +0 -1454
  143. package/src/worker/shims/sqlite-browser.test.ts +0 -233
  144. package/src/worker/shims/sqlite-browser.ts +0 -175
  145. package/src/worker/shims/sqlite.test.ts +0 -786
  146. package/src/worker/shims/sqlite.ts +0 -978
  147. package/src/worker/shims/stream-browser.ts +0 -15
  148. package/src/worker/shims/ws-browser.test.ts +0 -205
  149. package/src/worker/shims/ws-browser.ts +0 -248
  150. package/src/worker/shims/ws.test.ts +0 -288
  151. package/src/worker/shims/ws.ts +0 -467
  152. package/src/worker/shims/zero-process-env.ts +0 -11
  153. package/src/worker/types.ts +0 -75
  154. package/src/worker/worker-integration.test.ts +0 -223
  155. package/src/worker/worker.test.ts +0 -136
  156. package/src/worker/zero-cache-embed-cf.ts +0 -463
  157. package/src/worker/zero-cache-embed.ts +0 -277
@@ -1,384 +0,0 @@
1
- /**
2
- * zero-cache CF Workers patches.
3
- *
4
- * applies patches to @rocicorp/zero's internal files so zero-cache
5
- * can run in SINGLE_PROCESS mode on CF Workers where dynamic import()
6
- * doesn't work.
7
- *
8
- * five patches:
9
- * 1. worker-urls.js — replace file:// URLs with zero-worker:// identifiers
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
14
- *
15
- * usage in a post-build script:
16
- *
17
- * import { patchZeroCacheForCF } from 'orez/worker/cf-patches'
18
- * patchZeroCacheForCF('./node_modules')
19
- *
20
- * idempotent: safe to run multiple times.
21
- */
22
-
23
- import { readFileSync, writeFileSync, existsSync } from 'node:fs'
24
- import { resolve } from 'node:path'
25
-
26
- export function patchZeroCacheForCF(nodeModulesPath: string): void {
27
- const zcBase = resolve(nodeModulesPath, '@rocicorp', 'zero', 'out', 'zero-cache', 'src')
28
-
29
- patchWorkerUrls(zcBase)
30
- patchWorkerEntrypoints(zcBase)
31
- patchProcesses(zcBase)
32
- patchWriteWorkerClient(zcBase)
33
- patchPgsqlParserWasm(nodeModulesPath)
34
- }
35
-
36
- function patchWorkerUrls(zcBase: string): void {
37
- const workerUrlsPath = resolve(zcBase, 'server', 'worker-urls.js')
38
- if (!existsSync(workerUrlsPath)) {
39
- console.warn('[orez] worker-urls.js not found at', workerUrlsPath)
40
- return
41
- }
42
-
43
- const content = readFileSync(workerUrlsPath, 'utf-8')
44
-
45
- // skip if already patched
46
- if (content.includes('zero-worker://')) {
47
- return
48
- }
49
-
50
- writeFileSync(
51
- workerUrlsPath,
52
- `// patched by orez for CF Workers (replaces file:// URLs with identifiers)
53
- const u = (n) => new URL("zero-worker://" + n);
54
- export const MAIN_URL = u("main");
55
- export const CHANGE_STREAMER_URL = u("change-streamer");
56
- export const REAPER_URL = u("reaper");
57
- export const REPLICATOR_URL = u("replicator");
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");
63
- `
64
- )
65
- console.log('[orez] patched zero-cache worker-urls.js')
66
- }
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
-
102
- function patchProcesses(zcBase: string): void {
103
- const processesPath = resolve(zcBase, 'types', 'processes.js')
104
- if (!existsSync(processesPath)) {
105
- console.warn('[orez] processes.js not found at', processesPath)
106
- return
107
- }
108
-
109
- let code = readFileSync(processesPath, 'utf-8')
110
-
111
- // skip if already patched
112
- if (code.includes('__zc_workers')) {
113
- return
114
- }
115
-
116
- // add static imports of all zero-cache worker modules at the top.
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.
121
- const workerImports = `\
122
- // patched by orez for CF Workers (static imports replace dynamic import())
123
- import { default as __zc_main } from "../server/main.js";
124
- import { default as __zc_change_streamer } from "../server/change-streamer.js";
125
- import { default as __zc_reaper } from "../server/reaper.js";
126
- import { default as __zc_replicator } from "../server/replicator.js";
127
- import { default as __zc_syncer } from "../server/syncer.js";
128
- const __zc_workers = {
129
- "main": __zc_main,
130
- "change-streamer": __zc_change_streamer,
131
- "reaper": __zc_reaper,
132
- "replicator": __zc_replicator,
133
- "syncer": __zc_syncer,
134
- };
135
- `
136
-
137
- // replace the dynamic import in childWorker with a synchronous lookup.
138
- // original: import(moduleUrl.href).then(async ({ default: runWorker }) => ...
139
- // patched: lookup __zc_workers by name, then continue as before
140
- const dynamicImportPattern =
141
- 'import(moduleUrl.href).then(async ({ default: runWorker })'
142
- const staticLookup =
143
- '((async () => { ' +
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); ' +
146
- 'const runWorker = __zc_workers[_name]; ' +
147
- 'if (!runWorker) throw new Error("orez: unknown zero-cache worker: " + _name + " (available: " + Object.keys(__zc_workers).join(", ") + ")"); ' +
148
- 'return { default: runWorker, name: _name }; ' +
149
- '})()).then(async ({ default: runWorker, name })'
150
-
151
- if (!code.includes(dynamicImportPattern)) {
152
- console.warn(
153
- '[orez] could not find dynamic import pattern in processes.js. ' +
154
- 'zero-cache version may have changed — check compatibility.'
155
- )
156
- return
157
- }
158
-
159
- code = workerImports + code.replace(dynamicImportPattern, staticLookup)
160
- writeFileSync(processesPath, code)
161
- console.log('[orez] patched zero-cache processes.js (static worker imports)')
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
- }
@@ -1,321 +0,0 @@
1
- /**
2
- * integration test for zero-cache embedded mode.
3
- *
4
- * validates that zero-cache can run in-process with SINGLE_PROCESS=1,
5
- * connected to PGlite via the TCP proxy. this is the same pipeline as
6
- * the full integration test but without child_process.fork().
7
- *
8
- * test flow:
9
- * 1. create PGlite instances (postgres, cvr, cdb)
10
- * 2. start TCP proxy
11
- * 3. start zero-cache in-process via startZeroCacheEmbed()
12
- * 4. connect WebSocket client and verify sync
13
- */
14
-
15
- import { mkdirSync } from 'node:fs'
16
- import { resolve } from 'node:path'
17
-
18
- import { describe, test, expect, beforeAll, afterAll } from 'vitest'
19
- import WebSocket from 'ws'
20
-
21
- import { getConfig, getConnectionString } from '../config.js'
22
- import {
23
- ensureTablesInPublications,
24
- installAllowAllPermissions,
25
- } from '../integration/test-permissions.js'
26
- import { startPgProxy } from '../pg-proxy.js'
27
- import { createPGliteInstances, type PGliteInstances } from '../pglite-manager.js'
28
- import { installChangeTracking } from '../replication/change-tracker.js'
29
- import { startZeroCacheEmbed, type ZeroCacheEmbed } from './zero-cache-embed.js'
30
-
31
- import type { PGlite } from '@electric-sql/pglite'
32
-
33
- const SYNC_PROTOCOL_VERSION = 49
34
-
35
- function encodeSecProtocols(
36
- initConnectionMessage: unknown,
37
- authToken: string | undefined
38
- ): string {
39
- const payload = JSON.stringify({ initConnectionMessage, authToken })
40
- return encodeURIComponent(Buffer.from(payload, 'utf-8').toString('base64'))
41
- }
42
-
43
- describe('zero-cache embed integration', { timeout: 120000 }, () => {
44
- let db: PGlite
45
- let instances: PGliteInstances
46
- let pgServer: ReturnType<Awaited<ReturnType<typeof startPgProxy>>>
47
- let embed: ZeroCacheEmbed
48
- let zeroPort: number
49
- let pgPort: number
50
- let dataDir: string
51
-
52
- beforeAll(async () => {
53
- // use random ports to avoid conflicts with other tests
54
- pgPort = 24000 + Math.floor(Math.random() * 1000)
55
- zeroPort = pgPort + 100
56
-
57
- dataDir = `.orez-embed-test-${Date.now()}`
58
-
59
- const config = getConfig({
60
- pgPort,
61
- zeroPort,
62
- dataDir,
63
- logLevel: 'info',
64
- useWorkerThreads: false,
65
- singleDb: false,
66
- })
67
-
68
- mkdirSync(dataDir, { recursive: true })
69
-
70
- // create PGlite instances
71
- instances = await createPGliteInstances(config)
72
- db = instances.postgres
73
-
74
- // create test table
75
- await db.exec(`
76
- CREATE TABLE IF NOT EXISTS foo (
77
- id TEXT PRIMARY KEY,
78
- value TEXT,
79
- num INTEGER
80
- )
81
- `)
82
-
83
- // set up publications
84
- const pubName = `orez_zero_public`
85
- process.env.ZERO_APP_PUBLICATIONS = pubName
86
- await db.exec(`CREATE PUBLICATION "${pubName}"`).catch(() => {})
87
- await db
88
- .exec(`ALTER PUBLICATION "${pubName}" ADD TABLE "public"."foo"`)
89
- .catch(() => {})
90
-
91
- // install change tracking
92
- await installChangeTracking(db)
93
-
94
- // install allow-all permissions for test
95
- await installAllowAllPermissions(db, ['foo'])
96
- await ensureTablesInPublications(db, ['foo'])
97
-
98
- // start TCP proxy
99
- pgServer = await startPgProxy(instances, config)
100
-
101
- // start zero-cache in-process
102
- const upstreamDb = getConnectionString(config, 'postgres')
103
- const cvrDb = getConnectionString(config, 'zero_cvr')
104
- const changeDb = getConnectionString(config, 'zero_cdb')
105
- const replicaFile = resolve(dataDir, 'zero-replica.db')
106
-
107
- console.log(`[embed-test] starting in-process zero-cache on port ${zeroPort}`)
108
- console.log(`[embed-test] upstream: ${upstreamDb}`)
109
-
110
- embed = await startZeroCacheEmbed({
111
- pglite: db,
112
- upstreamDb,
113
- cvrDb,
114
- changeDb,
115
- replicaFile,
116
- port: zeroPort,
117
- publications: [pubName],
118
- env: {
119
- ZERO_LOG_LEVEL: 'info',
120
- },
121
- })
122
-
123
- console.log(`[embed-test] zero-cache ready on port ${embed.port}`)
124
-
125
- // wait for HTTP health check
126
- await waitForZero(zeroPort, 30000)
127
- console.log(`[embed-test] health check passed`)
128
- }, 120000)
129
-
130
- afterAll(async () => {
131
- if (embed) await embed.stop()
132
- if (pgServer) pgServer.close()
133
- if (instances) {
134
- await instances.postgres.close().catch(() => {})
135
- await instances.cvr.close().catch(() => {})
136
- await instances.cdb.close().catch(() => {})
137
- }
138
- if (dataDir) {
139
- const { rmSync } = await import('node:fs')
140
- try {
141
- rmSync(dataDir, { recursive: true, force: true })
142
- } catch {}
143
- }
144
- })
145
-
146
- test('zero-cache is ready', () => {
147
- expect(embed.ready).toBe(true)
148
- expect(embed.port).toBe(zeroPort)
149
- })
150
-
151
- test('accepts WebSocket connections', async () => {
152
- const cg = `test-cg-${Date.now()}`
153
- const cid = `test-client-${Date.now()}`
154
- const secProtocol = encodeSecProtocols(
155
- [
156
- 'initConnection',
157
- {
158
- desiredQueriesPatch: [],
159
- clientSchema: {
160
- tables: {
161
- foo: {
162
- columns: {
163
- id: { type: 'string' },
164
- value: { type: 'string' },
165
- num: { type: 'number' },
166
- },
167
- primaryKey: ['id'],
168
- },
169
- },
170
- },
171
- },
172
- ],
173
- undefined
174
- )
175
- const ws = new WebSocket(
176
- `ws://localhost:${zeroPort}/sync/v${SYNC_PROTOCOL_VERSION}/connect` +
177
- `?clientGroupID=${cg}&clientID=${cid}&wsid=ws1&schemaVersion=1&baseCookie=&ts=${Date.now()}&lmid=0`,
178
- secProtocol
179
- )
180
-
181
- // collect messages — attach listener before open to catch everything
182
- const messages: unknown[] = []
183
- ws.on('message', (data) => {
184
- messages.push(JSON.parse(data.toString()))
185
- })
186
-
187
- const connected = new Promise<void>((resolve, reject) => {
188
- ws.on('open', resolve)
189
- ws.on('error', reject)
190
- setTimeout(() => reject(new Error('ws connect timeout')), 10000)
191
- })
192
-
193
- await connected
194
-
195
- // wait for messages to arrive
196
- const deadline = Date.now() + 10000
197
- while (
198
- Date.now() < deadline &&
199
- !messages.some((m) => Array.isArray(m) && m[0] === 'connected')
200
- ) {
201
- await new Promise((r) => setTimeout(r, 100))
202
- }
203
-
204
- const connectedMsg = messages.find((m) => Array.isArray(m) && m[0] === 'connected')
205
- expect(connectedMsg).toMatchObject(['connected', { wsid: 'ws1' }])
206
- ws.close()
207
- })
208
-
209
- test('live replication: insert triggers poke', async () => {
210
- // insert data
211
- await db.query(`INSERT INTO foo (id, value, num) VALUES ($1, $2, $3)`, [
212
- 'embed-row',
213
- 'hello-embed',
214
- 42,
215
- ])
216
-
217
- // wait for replication to deliver
218
- await waitForReplicationCatchup(db)
219
- await new Promise((r) => setTimeout(r, 1000))
220
-
221
- // connect and subscribe
222
- const downstream: unknown[] = []
223
- const ws = connectAndSubscribe(zeroPort, downstream)
224
-
225
- // wait for poke with our data
226
- const deadline = Date.now() + 30000
227
- let found = false
228
- while (Date.now() < deadline && !found) {
229
- await new Promise((r) => setTimeout(r, 500))
230
- for (const msg of downstream) {
231
- if (Array.isArray(msg) && msg[0] === 'pokePart' && msg[1]?.rowsPatch) {
232
- for (const patch of msg[1].rowsPatch) {
233
- if (
234
- patch.op === 'put' &&
235
- patch.tableName === 'foo' &&
236
- patch.value?.id === 'embed-row'
237
- ) {
238
- found = true
239
- break
240
- }
241
- }
242
- }
243
- }
244
- }
245
-
246
- ws.close()
247
- expect(found).toBe(true)
248
- })
249
- })
250
-
251
- function connectAndSubscribe(port: number, downstream: unknown[]): WebSocket {
252
- const cg = `test-cg-${Date.now()}`
253
- const cid = `test-client-${Date.now()}`
254
- const secProtocol = encodeSecProtocols(
255
- [
256
- 'initConnection',
257
- {
258
- desiredQueriesPatch: [
259
- {
260
- op: 'put',
261
- hash: 'q1',
262
- ast: {
263
- table: 'foo',
264
- orderBy: [['id', 'asc']],
265
- },
266
- },
267
- ],
268
- clientSchema: {
269
- tables: {
270
- foo: {
271
- columns: {
272
- id: { type: 'string' },
273
- value: { type: 'string' },
274
- num: { type: 'number' },
275
- },
276
- primaryKey: ['id'],
277
- },
278
- },
279
- },
280
- },
281
- ],
282
- undefined
283
- )
284
- const ws = new WebSocket(
285
- `ws://localhost:${port}/sync/v${SYNC_PROTOCOL_VERSION}/connect` +
286
- `?clientGroupID=${cg}&clientID=${cid}&wsid=ws1&schemaVersion=1&baseCookie=&ts=${Date.now()}&lmid=0`,
287
- secProtocol
288
- )
289
-
290
- ws.on('message', (data) => {
291
- downstream.push(JSON.parse(data.toString()))
292
- })
293
-
294
- return ws
295
- }
296
-
297
- async function waitForReplicationCatchup(
298
- pglite: PGlite,
299
- timeoutMs = 15000
300
- ): Promise<void> {
301
- const deadline = Date.now() + timeoutMs
302
- while (Date.now() < deadline) {
303
- const result = await pglite.query<{ count: string }>(
304
- `SELECT count(*)::text as count FROM _orez._zero_changes`
305
- )
306
- if (Number(result.rows[0]?.count) === 0) return
307
- await new Promise((r) => setTimeout(r, 100))
308
- }
309
- }
310
-
311
- async function waitForZero(port: number, timeoutMs = 30000) {
312
- const deadline = Date.now() + timeoutMs
313
- while (Date.now() < deadline) {
314
- try {
315
- const res = await fetch(`http://localhost:${port}/`)
316
- if (res.ok || res.status === 404) return
317
- } catch {}
318
- await new Promise((r) => setTimeout(r, 500))
319
- }
320
- throw new Error(`zero-cache not ready on port ${port} after ${timeoutMs}ms`)
321
- }