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.
- package/dist/cf-do/test-protocol.d.ts +11 -0
- package/dist/cf-do/test-protocol.d.ts.map +1 -0
- package/dist/cf-do/test-protocol.js +137 -0
- package/dist/cf-do/test-protocol.js.map +1 -0
- package/dist/cf-do/watermark.d.ts +21 -0
- package/dist/cf-do/watermark.d.ts.map +1 -0
- package/dist/cf-do/watermark.js +93 -0
- package/dist/cf-do/watermark.js.map +1 -0
- package/dist/cf-do/worker.d.ts +91 -0
- package/dist/cf-do/worker.d.ts.map +1 -0
- package/dist/cf-do/worker.js +813 -0
- package/dist/cf-do/worker.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/do-sql-tracking.d.ts +6 -0
- package/dist/do-sql-tracking.d.ts.map +1 -0
- package/dist/do-sql-tracking.js +14 -0
- package/dist/do-sql-tracking.js.map +1 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +69 -23
- package/dist/index.js.map +1 -1
- package/dist/pg-proxy-browser.js +6 -6
- package/dist/pg-proxy-browser.js.map +1 -1
- package/dist/pg-proxy-do-backend.d.ts +128 -0
- package/dist/pg-proxy-do-backend.d.ts.map +1 -0
- package/dist/pg-proxy-do-backend.js +6292 -0
- package/dist/pg-proxy-do-backend.js.map +1 -0
- package/dist/pglite-ipc.d.ts +3 -0
- package/dist/pglite-ipc.d.ts.map +1 -1
- package/dist/pglite-ipc.js +34 -12
- package/dist/pglite-ipc.js.map +1 -1
- package/dist/pglite-web-proxy.d.ts +3 -0
- package/dist/pglite-web-proxy.d.ts.map +1 -1
- package/dist/pglite-web-proxy.js +50 -7
- package/dist/pglite-web-proxy.js.map +1 -1
- package/dist/query-rewrites.d.ts +2 -0
- package/dist/query-rewrites.d.ts.map +1 -0
- package/dist/query-rewrites.js +140 -0
- package/dist/query-rewrites.js.map +1 -0
- package/dist/replication/change-tracker.d.ts.map +1 -1
- package/dist/replication/change-tracker.js +18 -1
- package/dist/replication/change-tracker.js.map +1 -1
- package/dist/replication/handler.d.ts.map +1 -1
- package/dist/replication/handler.js +7 -2
- package/dist/replication/handler.js.map +1 -1
- package/dist/replication/pgoutput-encoder.d.ts.map +1 -1
- package/dist/replication/pgoutput-encoder.js +72 -30
- package/dist/replication/pgoutput-encoder.js.map +1 -1
- package/dist/worker/browser-build-config.d.ts.map +1 -1
- package/dist/worker/browser-build-config.js +2 -1
- package/dist/worker/browser-build-config.js.map +1 -1
- package/dist/worker/cf-patches.d.ts +5 -2
- package/dist/worker/cf-patches.d.ts.map +1 -1
- package/dist/worker/cf-patches.js +238 -4
- package/dist/worker/cf-patches.js.map +1 -1
- package/dist/worker/shims/node-stub.d.ts +35 -0
- package/dist/worker/shims/node-stub.d.ts.map +1 -1
- package/dist/worker/shims/node-stub.js +53 -1
- package/dist/worker/shims/node-stub.js.map +1 -1
- package/dist/worker/shims/oxfmt.d.ts +4 -0
- package/dist/worker/shims/oxfmt.d.ts.map +1 -0
- package/dist/worker/shims/oxfmt.js +4 -0
- package/dist/worker/shims/oxfmt.js.map +1 -0
- package/dist/worker/shims/postgres-socket.js +1 -1
- package/dist/worker/shims/postgres-socket.js.map +1 -1
- package/dist/worker/shims/sqlite.d.ts +1 -0
- package/dist/worker/shims/sqlite.d.ts.map +1 -1
- package/dist/worker/shims/sqlite.js +229 -9
- package/dist/worker/shims/sqlite.js.map +1 -1
- package/dist/worker/shims/ws.d.ts.map +1 -1
- package/dist/worker/shims/ws.js +45 -0
- package/dist/worker/shims/ws.js.map +1 -1
- package/dist/worker/shims/zero-process-env.d.ts +2 -0
- package/dist/worker/shims/zero-process-env.d.ts.map +1 -0
- package/dist/worker/shims/zero-process-env.js +9 -0
- package/dist/worker/shims/zero-process-env.js.map +1 -0
- package/dist/worker/zero-cache-embed-cf.d.ts +29 -12
- package/dist/worker/zero-cache-embed-cf.d.ts.map +1 -1
- package/dist/worker/zero-cache-embed-cf.js +83 -14
- package/dist/worker/zero-cache-embed-cf.js.map +1 -1
- package/package.json +6 -2
- package/src/cf-do/.wrangler/cache/cf.json +1 -0
- 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 +83 -0
- package/src/cf-do/watermark.test.ts +103 -0
- package/src/cf-do/watermark.ts +118 -0
- package/src/cf-do/worker.ts +1033 -0
- package/src/cf-do/wrangler.toml +11 -0
- package/src/config.ts +5 -0
- package/src/do-sql-tracking.test.ts +19 -0
- package/src/do-sql-tracking.ts +19 -0
- package/src/index.ts +76 -28
- package/src/pg-proxy-browser.ts +6 -6
- package/src/pg-proxy-do-backend.test.ts +3890 -0
- package/src/pg-proxy-do-backend.ts +7157 -0
- package/src/pglite-ipc.test.ts +17 -0
- package/src/pglite-ipc.ts +31 -12
- package/src/pglite-web-proxy.test.ts +57 -0
- package/src/pglite-web-proxy.ts +48 -7
- package/src/replication/change-tracker.ts +16 -1
- package/src/replication/handler.test.ts +35 -0
- package/src/replication/handler.ts +7 -2
- package/src/replication/pgoutput-encoder.test.ts +71 -2
- package/src/replication/pgoutput-encoder.ts +65 -30
- package/src/worker/browser-build-config.test.ts +12 -0
- package/src/worker/browser-build-config.ts +2 -1
- package/src/worker/cf-patches.ts +274 -4
- package/src/worker/shims/node-stub.ts +53 -1
- package/src/worker/shims/oxfmt.ts +3 -0
- package/src/worker/shims/postgres-socket.ts +1 -1
- package/src/worker/shims/sqlite.test.ts +145 -0
- package/src/worker/shims/sqlite.ts +256 -9
- package/src/worker/shims/ws.ts +45 -0
- package/src/worker/shims/zero-process-env.ts +11 -0
- package/src/worker/zero-cache-embed-cf.ts +114 -18
package/src/worker/cf-patches.ts
CHANGED
|
@@ -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
|
-
*
|
|
8
|
+
* five patches:
|
|
9
9
|
* 1. worker-urls.js — replace file:// URLs with zero-worker:// identifiers
|
|
10
|
-
* 2.
|
|
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
|
-
|
|
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,
|
|
@@ -151,7 +151,7 @@ class MessagePortSocket extends EventEmitter {
|
|
|
151
151
|
copy.set(bytes)
|
|
152
152
|
|
|
153
153
|
try {
|
|
154
|
-
this.port.postMessage(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
|