orez 0.2.26 → 0.2.29
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/worker.d.ts.map +1 -1
- package/dist/cf-do/worker.js +9 -1
- package/dist/cf-do/worker.js.map +1 -1
- package/dist/pg-proxy-do-backend.d.ts +2 -0
- package/dist/pg-proxy-do-backend.d.ts.map +1 -1
- package/dist/pg-proxy-do-backend.js +49 -7
- package/dist/pg-proxy-do-backend.js.map +1 -1
- package/dist/pg-sqlite-compiler/catalog/seed.d.ts +67 -0
- package/dist/pg-sqlite-compiler/catalog/seed.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/catalog/seed.js +436 -0
- package/dist/pg-sqlite-compiler/catalog/seed.js.map +1 -0
- package/dist/pg-sqlite-compiler/index.d.ts +12 -0
- package/dist/pg-sqlite-compiler/index.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/index.js +59 -0
- package/dist/pg-sqlite-compiler/index.js.map +1 -0
- package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts +48 -0
- package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/passes/ast-utils.js +93 -0
- package/dist/pg-sqlite-compiler/passes/ast-utils.js.map +1 -0
- package/dist/pg-sqlite-compiler/passes/catalog.d.ts +34 -0
- package/dist/pg-sqlite-compiler/passes/catalog.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/passes/catalog.js +30 -0
- package/dist/pg-sqlite-compiler/passes/catalog.js.map +1 -0
- package/dist/pg-sqlite-compiler/passes/datetime.d.ts +21 -0
- package/dist/pg-sqlite-compiler/passes/datetime.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/passes/datetime.js +53 -0
- package/dist/pg-sqlite-compiler/passes/datetime.js.map +1 -0
- package/dist/pg-sqlite-compiler/passes/index.d.ts +21 -0
- package/dist/pg-sqlite-compiler/passes/index.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/passes/index.js +39 -0
- package/dist/pg-sqlite-compiler/passes/index.js.map +1 -0
- package/dist/pg-sqlite-compiler/passes/types.d.ts +41 -0
- package/dist/pg-sqlite-compiler/passes/types.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/passes/types.js +103 -0
- package/dist/pg-sqlite-compiler/passes/types.js.map +1 -0
- package/dist/pg-sqlite-compiler/test/oracle.d.ts +34 -0
- package/dist/pg-sqlite-compiler/test/oracle.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/test/oracle.js +204 -0
- package/dist/pg-sqlite-compiler/test/oracle.js.map +1 -0
- package/dist/pg-sqlite-compiler/types.d.ts +55 -0
- package/dist/pg-sqlite-compiler/types.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/types.js +2 -0
- package/dist/pg-sqlite-compiler/types.js.map +1 -0
- package/package.json +8 -4
- package/src/admin/admin-data.test.ts +0 -348
- package/src/admin/http-proxy.ts +0 -252
- package/src/admin/log-store.ts +0 -192
- package/src/admin/server.ts +0 -471
- package/src/admin/ui.ts +0 -1322
- package/src/bench/proxy-throughput.bench.ts +0 -343
- package/src/bench/serial-mutations.bench.ts +0 -270
- package/src/browser.ts +0 -203
- package/src/cf-do/.wrangler/cache/cf.json +0 -1
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0f0f3bdf0abda097eb6f1246db4657d9fc622081362d894d82c1a1ce067b05b6.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/1ddd3a4a48a11b51658444f5458a1fb175194b1d5b6a5bda20ef3fe3205b900c.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/204a39120310d37e972c5914cfd71ad55c151bdb9e8ed289a5f8c5b052dd60e4.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/3835f242df9728adba3d127a238793fd054ed3e51df3f60749ee744c469bf2a2.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/4aa9c80eb716cf55b8995ccf7afab0b36c683e6da07d7c37a3f9c570136036df.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/533e2fd1d6ea46e7a9a0017916ef341802d438d72583462755f2c1f8225e9bf2.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/5ffa1aced1225ecaeac6366f7586aa3de92761cdff8711d81fbd81f248076abd.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/686c3a9f0d7e59ed2ab607efd4b76d779c97cafeb3818380033bf7c7eb86c819.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/6e8214e8dcfadd0deb52d64e5e9ca85c6b329ace11193909845995396914c473.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/78d9ec9ff873d3fe3507ff53c2a6f6dfc408b4268eb0db3f2a146c0678965366.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/7eff9f0ed7e27ad0d3f9d923de0682fab1928591172c1ba336c5f79a134a5d85.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/836cda5b995b25867d722ed4f4c2292167e80351a3c6038db626648eb247dd8b.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/91ef63b112209ab30172763acd8a0935106c248f7f1bcae5545ce37a9f201551.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/a66ea4293a5f5938bc6d116edfa2522bb85bc37aea3541fbc09c3b613b9b32c0.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/ceb2ab26b80590840b65651deb6e948d3bf81565c6751f3a58752cf4bf4aecae.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
- package/src/cf-do/ARCHITECTURE.md +0 -83
- package/src/cf-do/watermark.test.ts +0 -103
- package/src/cf-do/watermark.ts +0 -118
- package/src/cf-do/worker.ts +0 -1033
- package/src/cf-do/wrangler.toml +0 -11
- package/src/cf-pglite/README.md +0 -19
- package/src/change-tracking.ts +0 -25
- package/src/child-process.test.ts +0 -147
- package/src/child-process.ts +0 -90
- package/src/cli-entry.ts +0 -72
- package/src/cli.test.ts +0 -38
- package/src/cli.ts +0 -1214
- package/src/config.ts +0 -150
- package/src/do-sql-tracking.test.ts +0 -19
- package/src/do-sql-tracking.ts +0 -19
- package/src/index.ts +0 -1215
- package/src/integration/integration.test.ts +0 -517
- package/src/integration/native-binary.guard.test.ts +0 -13
- package/src/integration/native-startup.test.ts +0 -44
- package/src/integration/replication-latency.test.ts +0 -428
- package/src/integration/restore-live-stress.test.ts +0 -433
- package/src/integration/restore-reset.test.ts +0 -400
- package/src/integration/restore.test.ts +0 -274
- package/src/integration/test-permissions.ts +0 -147
- package/src/load-config.ts +0 -46
- package/src/log.ts +0 -96
- package/src/mutex.ts +0 -47
- package/src/pg-proxy-browser.singledb.test.ts +0 -233
- package/src/pg-proxy-browser.ts +0 -2022
- package/src/pg-proxy-do-backend.test.ts +0 -3890
- package/src/pg-proxy-do-backend.ts +0 -7157
- package/src/pg-proxy.ts +0 -1087
- package/src/pglite-ipc.test.ts +0 -116
- package/src/pglite-ipc.ts +0 -266
- package/src/pglite-manager.ts +0 -557
- package/src/pglite-web-proxy.test.ts +0 -57
- package/src/pglite-web-proxy.ts +0 -221
- package/src/pglite-web-worker.ts +0 -152
- package/src/pglite-worker-thread.ts +0 -253
- package/src/port.ts +0 -25
- package/src/process-title.ts +0 -9
- package/src/recovery.ts +0 -155
- package/src/replication/change-tracker.test.ts +0 -357
- package/src/replication/change-tracker.ts +0 -279
- package/src/replication/handler.test.ts +0 -511
- package/src/replication/handler.ts +0 -1190
- package/src/replication/pgoutput-encoder.test.ts +0 -697
- package/src/replication/pgoutput-encoder.ts +0 -373
- package/src/replication/tcp-replication.test.ts +0 -876
- package/src/replication/zero-compat.test.ts +0 -1150
- package/src/restore-stress.test.ts +0 -188
- package/src/s3-local.ts +0 -203
- package/src/shim/hooks.mjs +0 -120
- package/src/shim/register.mjs +0 -4
- package/src/sqlite-mode/apply-mode.ts +0 -224
- package/src/sqlite-mode/index.ts +0 -15
- package/src/sqlite-mode/native-binary.ts +0 -89
- package/src/sqlite-mode/package-resolve.ts +0 -17
- package/src/sqlite-mode/resolve-mode.ts +0 -80
- package/src/sqlite-mode/shim-template.ts +0 -159
- package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
- package/src/sqlite-mode/types.ts +0 -30
- package/src/vite-plugin.ts +0 -67
- package/src/wasm-sqlite.test.ts +0 -537
- package/src/worker/browser-admin.ts +0 -52
- package/src/worker/browser-build-config.test.ts +0 -71
- package/src/worker/browser-build-config.ts +0 -109
- package/src/worker/browser-embed-admin.test.ts +0 -75
- package/src/worker/browser-embed.ts +0 -345
- package/src/worker/cf-patches.ts +0 -384
- package/src/worker/embed-integration.test.ts +0 -321
- package/src/worker/index.ts +0 -138
- package/src/worker/shims/fastify.test.ts +0 -255
- package/src/worker/shims/fastify.ts +0 -306
- package/src/worker/shims/http-service.test.ts +0 -355
- package/src/worker/shims/http-service.ts +0 -293
- package/src/worker/shims/node-stub.ts +0 -290
- package/src/worker/shims/oxfmt.ts +0 -3
- package/src/worker/shims/postgres-browser.ts +0 -59
- package/src/worker/shims/postgres-socket.test.ts +0 -576
- package/src/worker/shims/postgres-socket.ts +0 -310
- package/src/worker/shims/postgres.test.ts +0 -364
- package/src/worker/shims/postgres.ts +0 -1454
- package/src/worker/shims/sqlite-browser.test.ts +0 -233
- package/src/worker/shims/sqlite-browser.ts +0 -175
- package/src/worker/shims/sqlite.test.ts +0 -786
- package/src/worker/shims/sqlite.ts +0 -978
- package/src/worker/shims/stream-browser.ts +0 -15
- package/src/worker/shims/ws-browser.test.ts +0 -205
- package/src/worker/shims/ws-browser.ts +0 -248
- package/src/worker/shims/ws.test.ts +0 -288
- package/src/worker/shims/ws.ts +0 -467
- package/src/worker/shims/zero-process-env.ts +0 -11
- package/src/worker/types.ts +0 -75
- package/src/worker/worker-integration.test.ts +0 -223
- package/src/worker/worker.test.ts +0 -136
- package/src/worker/zero-cache-embed-cf.ts +0 -463
- package/src/worker/zero-cache-embed.ts +0 -277
package/src/worker/cf-patches.ts
DELETED
|
@@ -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
|
-
}
|