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/browser.ts
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* orez browser entry — aligned to orez-node architecture.
|
|
3
|
-
*
|
|
4
|
-
* mirrors index.ts startup sequence:
|
|
5
|
-
* 1. create PGlite Web Workers (3 instances)
|
|
6
|
-
* 2. install change tracking
|
|
7
|
-
* 3. start pg-proxy-browser (wire protocol proxy)
|
|
8
|
-
* 4. start zero-cache (SINGLE_PROCESS=1, connects to proxy)
|
|
9
|
-
*
|
|
10
|
-
* intended to run inside a Web Worker. PGlite instances run in
|
|
11
|
-
* separate Web Workers (like orez-node's worker threads).
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { PGliteWebProxy } from './pglite-web-proxy.js'
|
|
15
|
-
import { installChangeTracking } from './replication/change-tracker.js'
|
|
16
|
-
import { resetReplicationState } from './replication/handler.js'
|
|
17
|
-
|
|
18
|
-
import type { PGlite } from '@electric-sql/pglite'
|
|
19
|
-
|
|
20
|
-
export interface OrezBrowserConfig {
|
|
21
|
-
/** app ID for zero-cache (default: 'zero') */
|
|
22
|
-
appId?: string
|
|
23
|
-
|
|
24
|
-
/** publication names for replication */
|
|
25
|
-
publications?: string[]
|
|
26
|
-
|
|
27
|
-
/** URL for PGlite web worker script */
|
|
28
|
-
pgliteWorkerUrl: string
|
|
29
|
-
|
|
30
|
-
/** URL for bedrock-sqlite WASM */
|
|
31
|
-
bedrockSqliteUrl?: string
|
|
32
|
-
|
|
33
|
-
/** init SQL to run on postgres instance after creation */
|
|
34
|
-
initSql?: string
|
|
35
|
-
|
|
36
|
-
/** log level */
|
|
37
|
-
logLevel?: string
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* intercept browser-mode orez admin routes before they reach zero-cache.
|
|
41
|
-
* default: true.
|
|
42
|
-
*/
|
|
43
|
-
disableAdminApi?: boolean
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface OrezBrowserInstance {
|
|
47
|
-
/** PGlite proxies (for direct queries from project-server) */
|
|
48
|
-
instances: {
|
|
49
|
-
postgres: PGliteWebProxy
|
|
50
|
-
cvr: PGliteWebProxy
|
|
51
|
-
cdb: PGliteWebProxy
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** signal the replication handler that changes are available */
|
|
55
|
-
signalReplication(): void
|
|
56
|
-
|
|
57
|
-
/** handle a WebSocket connection from a Zero client */
|
|
58
|
-
handleWebSocket(ws: any, url?: string, headers?: Record<string, string>): void
|
|
59
|
-
|
|
60
|
-
/** handle an HTTP request (push/pull) */
|
|
61
|
-
handleHttp(request: {
|
|
62
|
-
method: string
|
|
63
|
-
url: string
|
|
64
|
-
headers?: Record<string, string>
|
|
65
|
-
body?: string | null
|
|
66
|
-
}): Promise<{ status: number; headers: Record<string, string>; body: string }>
|
|
67
|
-
|
|
68
|
-
/** stop everything */
|
|
69
|
-
stop(): Promise<void>
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export async function startOrezBrowser(
|
|
73
|
-
config: OrezBrowserConfig
|
|
74
|
-
): Promise<OrezBrowserInstance> {
|
|
75
|
-
const appId = config.appId || 'zero'
|
|
76
|
-
const publications = config.publications?.join(',') || `orez_${appId}_public`
|
|
77
|
-
|
|
78
|
-
// step 1: create PGlite Web Workers (3 instances, like orez-node)
|
|
79
|
-
const pgPostgresWorker = new Worker(config.pgliteWorkerUrl, {
|
|
80
|
-
type: 'module',
|
|
81
|
-
name: 'pglite-postgres',
|
|
82
|
-
})
|
|
83
|
-
const pgCvrWorker = new Worker(config.pgliteWorkerUrl, {
|
|
84
|
-
type: 'module',
|
|
85
|
-
name: 'pglite-cvr',
|
|
86
|
-
})
|
|
87
|
-
const pgCdbWorker = new Worker(config.pgliteWorkerUrl, {
|
|
88
|
-
type: 'module',
|
|
89
|
-
name: 'pglite-cdb',
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
// init each PGlite worker
|
|
93
|
-
pgPostgresWorker.postMessage({
|
|
94
|
-
type: 'init',
|
|
95
|
-
dataDir: 'idb://orez-postgres',
|
|
96
|
-
name: 'postgres',
|
|
97
|
-
})
|
|
98
|
-
pgCvrWorker.postMessage({ type: 'init', dataDir: 'idb://orez-cvr', name: 'cvr' })
|
|
99
|
-
pgCdbWorker.postMessage({ type: 'init', dataDir: 'idb://orez-cdb', name: 'cdb' })
|
|
100
|
-
|
|
101
|
-
// create proxies (like orez-node's PGliteWorkerProxy)
|
|
102
|
-
const pgPostgres = new PGliteWebProxy(pgPostgresWorker, 'postgres')
|
|
103
|
-
const pgCvr = new PGliteWebProxy(pgCvrWorker, 'cvr')
|
|
104
|
-
const pgCdb = new PGliteWebProxy(pgCdbWorker, 'cdb')
|
|
105
|
-
|
|
106
|
-
await Promise.all([pgPostgres.waitReady, pgCvr.waitReady, pgCdb.waitReady])
|
|
107
|
-
console.debug('[orez-browser] all 3 PGlite workers ready')
|
|
108
|
-
|
|
109
|
-
// step 2: install change tracking (like orez-node)
|
|
110
|
-
await installChangeTracking(pgPostgres as unknown as PGlite)
|
|
111
|
-
console.debug('[orez-browser] change tracking installed')
|
|
112
|
-
|
|
113
|
-
// run user init SQL if provided
|
|
114
|
-
if (config.initSql) {
|
|
115
|
-
await pgPostgres.exec(config.initSql)
|
|
116
|
-
console.debug('[orez-browser] init SQL complete')
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// create publication
|
|
120
|
-
try {
|
|
121
|
-
const pubs = await pgPostgres.query<{ count: string }>(
|
|
122
|
-
`SELECT count(*) as count FROM pg_publication WHERE pubname = $1`,
|
|
123
|
-
[publications]
|
|
124
|
-
)
|
|
125
|
-
if (Number(pubs.rows[0]?.count) === 0) {
|
|
126
|
-
await pgPostgres.exec(`CREATE PUBLICATION "${publications}"`)
|
|
127
|
-
}
|
|
128
|
-
} catch {}
|
|
129
|
-
|
|
130
|
-
// step 3: start pg-proxy-browser
|
|
131
|
-
// the proxy handles wire protocol, replication, mutexes — like orez-node's pg-proxy.
|
|
132
|
-
// zero-cache's postgres shim routes queries through this proxy.
|
|
133
|
-
const { createBrowserProxy } = await import('./pg-proxy-browser.js')
|
|
134
|
-
const proxy = await createBrowserProxy(
|
|
135
|
-
{
|
|
136
|
-
postgres: pgPostgres as unknown as PGlite,
|
|
137
|
-
cvr: pgCvr as unknown as PGlite,
|
|
138
|
-
cdb: pgCdb as unknown as PGlite,
|
|
139
|
-
postgresReplicas: [],
|
|
140
|
-
},
|
|
141
|
-
{ pgPassword: '', pgUser: 'user' }
|
|
142
|
-
)
|
|
143
|
-
console.debug('[orez-browser] pg-proxy-browser started')
|
|
144
|
-
|
|
145
|
-
// step 4: start zero-cache (SINGLE_PROCESS=1)
|
|
146
|
-
// zero-cache uses the real postgres package with a MessagePort socket
|
|
147
|
-
// that connects to pg-proxy-browser. identical to orez-node where
|
|
148
|
-
// zero-cache uses real postgres over TCP to pg-proxy.
|
|
149
|
-
//
|
|
150
|
-
// set up the proxy connect function for postgres-browser.ts shim
|
|
151
|
-
;(globalThis as any).__orez_proxy_connect = (port: MessagePort) => {
|
|
152
|
-
proxy.handleConnection(port)
|
|
153
|
-
}
|
|
154
|
-
;(globalThis as any).__orez_proxy_password = ''
|
|
155
|
-
;(globalThis as any).__orez_proxy_user = 'user'
|
|
156
|
-
|
|
157
|
-
// PGlite global still needed for browser-embed's sqlite setup
|
|
158
|
-
;(globalThis as any).__orez_pglite = pgPostgres
|
|
159
|
-
|
|
160
|
-
// start zero-cache via browser-embed's runWorker
|
|
161
|
-
const { startZeroCacheEmbedBrowser } = await import('./worker/browser-embed.js')
|
|
162
|
-
const zc = await startZeroCacheEmbedBrowser({
|
|
163
|
-
pglite: pgPostgres as unknown as PGlite,
|
|
164
|
-
appId,
|
|
165
|
-
publications: config.publications,
|
|
166
|
-
disableAdminApi: config.disableAdminApi ?? true,
|
|
167
|
-
env: {
|
|
168
|
-
ZERO_LOG_LEVEL: config.logLevel || 'info',
|
|
169
|
-
},
|
|
170
|
-
})
|
|
171
|
-
console.debug('[orez-browser] zero-cache started')
|
|
172
|
-
|
|
173
|
-
// step 5: expose API
|
|
174
|
-
const { signalReplicationChange } = await import('./replication/handler.js')
|
|
175
|
-
|
|
176
|
-
return {
|
|
177
|
-
instances: { postgres: pgPostgres, cvr: pgCvr, cdb: pgCdb },
|
|
178
|
-
|
|
179
|
-
signalReplication() {
|
|
180
|
-
signalReplicationChange()
|
|
181
|
-
},
|
|
182
|
-
|
|
183
|
-
handleWebSocket(ws: any, url = '/', headers?: Record<string, string>) {
|
|
184
|
-
zc.handleWebSocket(ws, url)
|
|
185
|
-
},
|
|
186
|
-
|
|
187
|
-
async handleHttp(request: {
|
|
188
|
-
method: string
|
|
189
|
-
url: string
|
|
190
|
-
headers?: Record<string, string>
|
|
191
|
-
body?: string | null
|
|
192
|
-
}) {
|
|
193
|
-
return zc.handleHttp(request)
|
|
194
|
-
},
|
|
195
|
-
|
|
196
|
-
async stop() {
|
|
197
|
-
await zc.stop()
|
|
198
|
-
proxy.close()
|
|
199
|
-
resetReplicationState()
|
|
200
|
-
await Promise.all([pgPostgres.close(), pgCvr.close(), pgCdb.close()])
|
|
201
|
-
},
|
|
202
|
-
}
|
|
203
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"httpProtocol":"HTTP/1.1","clientAcceptEncoding":"gzip, deflate, br","requestPriority":"","edgeRequestKeepAliveStatus":1,"requestHeaderNames":{},"clientTcpRtt":74,"clientQuicRtt":0,"colo":"LAX","asn":21928,"asOrganization":"T-Mobile USA, Inc.","country":"US","isEUCountry":false,"city":"Honolulu","continent":"NA","region":"Hawaii","regionCode":"HI","timezone":"Pacific/Honolulu","longitude":"-157.85833","latitude":"21.30694","postalCode":"96802","metroCode":"744","tlsVersion":"TLSv1.3","tlsCipher":"AEAD-AES256-GCM-SHA384","tlsClientRandom":"8lAacoE+Gg3H5AKn8BQNpAGr3fgr0OV1txs9E+GEKXk=","tlsClientCiphersSha1":"JZtiTn8H/ntxORk+XXvU2EvNoz8=","tlsClientExtensionsSha1":"Y7DIC8A6G0/aXviZ8ie/xDbJb7g=","tlsClientExtensionsSha1Le":"6e+q3vPm88rSgMTN/h7WTTxQ2wQ=","tlsExportedAuthenticator":{"clientHandshake":"88b6a0bf9d7b4c6bc26b9f11255fe3f55b2bae485c86026892687913505e6a337a54221c8ec52b88741035fde8e80b91","serverHandshake":"a611a55711347da40c0167874992b47d75a4de9ed1c0bc731dd1f80051efde3b73a8baff4e484f30b284d662cd503dcb","clientFinished":"81b77db43ca14e8934ebd5f4c6d3656a1ccc726f04a10164cf7d599128d7dfbd3fa15f932068a583f288a0327d69a5f5","serverFinished":"68efa1737e629e56c92cbb680f96730052e8269e6248b62dd26a62d3ea7be55991e9f0ea85e6bebf95dbc6adbb41334e"},"tlsClientHelloLength":"386","tlsClientAuth":{"certPresented":"0","certVerified":"NONE","certRevoked":"0","certIssuerDN":"","certSubjectDN":"","certIssuerDNRFC2253":"","certSubjectDNRFC2253":"","certIssuerDNLegacy":"","certSubjectDNLegacy":"","certSerial":"","certIssuerSerial":"","certSKI":"","certIssuerSKI":"","certFingerprintSHA1":"","certFingerprintSHA256":"","certNotBefore":"","certNotAfter":"","certRFC9440":"","certRFC9440TooLarge":false,"certChainRFC9440":"","certChainRFC9440TooLarge":false},"verifiedBotCategory":"","edgeL4":{"deliveryRate":52536},"botManagement":{"corporateProxy":false,"verifiedBot":false,"jsDetection":{"passed":false},"staticResource":false,"detectionIds":{},"score":99}}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
# orez/cf-do architecture - READ FIRST
|
|
2
|
-
|
|
3
|
-
## The fundamental constraint
|
|
4
|
-
|
|
5
|
-
**Cloudflare Durable Objects have a 128 MB memory budget per instance.**
|
|
6
|
-
|
|
7
|
-
PGlite + WASM Postgres + extension binaries pushes real deployments over that
|
|
8
|
-
budget. That is the dead end. zero-cache is still essential: it owns the sync
|
|
9
|
-
protocol, IVM/CVR machinery, replication handling, and client semantics.
|
|
10
|
-
|
|
11
|
-
The orez Cloudflare path is therefore:
|
|
12
|
-
|
|
13
|
-
- **yes** real `@rocicorp/zero` zero-cache
|
|
14
|
-
- **yes** Durable Object SQLite as the durable storage engine
|
|
15
|
-
- **yes** orez `DoBackend` serving Postgres-protocol semantics to zero-cache
|
|
16
|
-
- **no** PGlite
|
|
17
|
-
- **no** `pglite.wasm`, `pglite.data`, extension `.so` files, or WASM Postgres
|
|
18
|
-
|
|
19
|
-
## Production shape
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
┌────────────────────────────────────────────────────────────────┐
|
|
23
|
-
│ Worker │
|
|
24
|
-
│ │
|
|
25
|
-
│ /sync/v* and /api/zero/* ─────► ZERO_CACHE_DO singleton │
|
|
26
|
-
│ everything else ──────────────► ASSETS │
|
|
27
|
-
└────────────────────────────────────────────────────────────────┘
|
|
28
|
-
|
|
29
|
-
┌────────────────────────────────────────────────────────────────┐
|
|
30
|
-
│ ZERO_CACHE_DO Durable Object │
|
|
31
|
-
│ │
|
|
32
|
-
│ startZeroCacheEmbedCF() runs real zero-cache in-process │
|
|
33
|
-
│ │ │
|
|
34
|
-
│ ├─ zero-cache replica/CVR/CDB SQLite │
|
|
35
|
-
│ │ @rocicorp/zero-sqlite3 -> orez worker SQLite shim │
|
|
36
|
-
│ │ backed by ctx.storage.sql │
|
|
37
|
-
│ │ │
|
|
38
|
-
│ └─ zero-cache upstream Postgres connections │
|
|
39
|
-
│ postgres -> orez postgres browser shim │
|
|
40
|
-
│ DoBackend -> ZERO_SQL_DO /exec and /batch │
|
|
41
|
-
└────────────────────────────────────────────────────────────────┘
|
|
42
|
-
|
|
43
|
-
┌────────────────────────────────────────────────────────────────┐
|
|
44
|
-
│ ZERO_SQL_DO Durable Object │
|
|
45
|
-
│ │
|
|
46
|
-
│ ZeroDO raw SQL endpoints │
|
|
47
|
-
│ /exec, /batch, /changes, /notify, /__orez/* │
|
|
48
|
-
│ ctx.storage.sql │
|
|
49
|
-
│ _orez.changes populated by SQL tracking triggers │
|
|
50
|
-
└────────────────────────────────────────────────────────────────┘
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
All app traffic for a deployed project must use singleton DO IDs for both
|
|
54
|
-
`ZERO_CACHE_DO` and `ZERO_SQL_DO`; otherwise browser sessions will not share the
|
|
55
|
-
same zero-cache process and durable SQLite state.
|
|
56
|
-
|
|
57
|
-
## Important files
|
|
58
|
-
|
|
59
|
-
- `src/worker/zero-cache-embed-cf.ts` - starts real zero-cache inside a Durable
|
|
60
|
-
Object and wires its storage/network dependencies to CF-safe shims.
|
|
61
|
-
- `src/worker/cf-patches.ts` - patches zero-cache internals so its worker
|
|
62
|
-
graph and writer run in the Workers runtime.
|
|
63
|
-
- `src/pg-proxy-do-backend.ts` - translates Postgres protocol operations from
|
|
64
|
-
zero-cache into DO SQL endpoint requests.
|
|
65
|
-
- `src/cf-do/worker.ts` - `ZeroDO`, the generic DO SQL backend. It also still
|
|
66
|
-
contains a bespoke Zero sync protocol handler used for development and
|
|
67
|
-
protocol experiments, but the production Soot deploy path uses real
|
|
68
|
-
zero-cache through `startZeroCacheEmbedCF()`.
|
|
69
|
-
- `src/do-sql-tracking.ts` and `src/replication/*` - change tracking and
|
|
70
|
-
logical replication support over `_orez.changes`.
|
|
71
|
-
|
|
72
|
-
## What not to do
|
|
73
|
-
|
|
74
|
-
- Do not import `@electric-sql/pglite` into the Cloudflare DO deploy path.
|
|
75
|
-
- Do not bundle PGlite WASM/data/extensions into the deploy template.
|
|
76
|
-
- Do not replace zero-cache with the bespoke handler for production sync.
|
|
77
|
-
- Do not add a second fallback path that silently switches between PGlite,
|
|
78
|
-
bespoke sync, and zero-cache. There should be one production path:
|
|
79
|
-
zero-cache -> orez Postgres protocol -> DO SQLite.
|
|
80
|
-
|
|
81
|
-
If a future change needs more Postgres behavior, implement it in
|
|
82
|
-
`DoBackend`/the SQL translator or the DO SQL backend. Do not put PGlite back in
|
|
83
|
-
the request path.
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { DurableWatermarkState } from './watermark.js'
|
|
4
|
-
|
|
5
|
-
class FakeResult {
|
|
6
|
-
constructor(private readonly rows: Array<Record<string, unknown>> = []) {}
|
|
7
|
-
|
|
8
|
-
one() {
|
|
9
|
-
return this.rows[0]
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
toArray() {
|
|
13
|
-
return this.rows
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
class FakeSql {
|
|
18
|
-
changes: Array<{ watermark: number }> = []
|
|
19
|
-
state = 0
|
|
20
|
-
sequence = { name: '_orez___zero_watermark', last_value: 1, is_called: 0 }
|
|
21
|
-
|
|
22
|
-
exec(sql: string, ...params: unknown[]) {
|
|
23
|
-
if (sql.startsWith('CREATE TABLE IF NOT EXISTS')) return new FakeResult()
|
|
24
|
-
|
|
25
|
-
if (sql.startsWith('SELECT last_value FROM "_zero_change_state"')) {
|
|
26
|
-
return new FakeResult([{ last_value: this.state }])
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (sql.startsWith('INSERT OR IGNORE INTO "_zero_change_state"')) {
|
|
30
|
-
return new FakeResult()
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (sql.startsWith('UPDATE "_zero_change_state" SET last_value = ?')) {
|
|
34
|
-
this.state = Number(params[0])
|
|
35
|
-
return new FakeResult()
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
sql.startsWith('SELECT COALESCE(MAX(watermark), 0) AS watermark FROM _zero_changes')
|
|
40
|
-
) {
|
|
41
|
-
const watermark = Math.max(0, ...this.changes.map((change) => change.watermark))
|
|
42
|
-
return new FakeResult([{ watermark }])
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (sql.includes('sqlite_master') && sql.includes('%zero_watermark%')) {
|
|
46
|
-
return new FakeResult([{ name: this.sequence.name }])
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (sql.startsWith('SELECT last_value, is_called FROM "_orez___zero_watermark"')) {
|
|
50
|
-
return new FakeResult([
|
|
51
|
-
{
|
|
52
|
-
last_value: this.sequence.last_value,
|
|
53
|
-
is_called: this.sequence.is_called,
|
|
54
|
-
},
|
|
55
|
-
])
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (sql.startsWith('INSERT OR IGNORE INTO "_orez___zero_watermark"')) {
|
|
59
|
-
return new FakeResult()
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (sql.startsWith('UPDATE "_orez___zero_watermark" SET last_value = ?')) {
|
|
63
|
-
this.sequence.last_value = Number(params[0])
|
|
64
|
-
this.sequence.is_called = 1
|
|
65
|
-
return new FakeResult()
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
throw new Error(`unexpected fake sql: ${sql}`)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
describe('DurableWatermarkState', () => {
|
|
73
|
-
it('does not reuse watermarks after consumed changes are purged', () => {
|
|
74
|
-
const sql = new FakeSql()
|
|
75
|
-
const watermarks = new DurableWatermarkState(sql)
|
|
76
|
-
|
|
77
|
-
expect(watermarks.current()).toBe(0)
|
|
78
|
-
|
|
79
|
-
const first = watermarks.next()
|
|
80
|
-
sql.changes.push({ watermark: first })
|
|
81
|
-
watermarks.mark(first)
|
|
82
|
-
expect(first).toBe(1)
|
|
83
|
-
|
|
84
|
-
sql.changes = []
|
|
85
|
-
|
|
86
|
-
const second = watermarks.next()
|
|
87
|
-
sql.changes.push({ watermark: second })
|
|
88
|
-
watermarks.mark(second)
|
|
89
|
-
|
|
90
|
-
expect(second).toBe(2)
|
|
91
|
-
expect(sql.sequence).toMatchObject({ last_value: 2, is_called: 1 })
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('synchronizes from existing change rows and sequence state', () => {
|
|
95
|
-
const sql = new FakeSql()
|
|
96
|
-
sql.changes.push({ watermark: 7 })
|
|
97
|
-
const watermarks = new DurableWatermarkState(sql)
|
|
98
|
-
|
|
99
|
-
expect(watermarks.current()).toBe(7)
|
|
100
|
-
expect(sql.state).toBe(7)
|
|
101
|
-
expect(sql.sequence).toMatchObject({ last_value: 7, is_called: 1 })
|
|
102
|
-
})
|
|
103
|
-
})
|
package/src/cf-do/watermark.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
export interface DurableSqlResult {
|
|
2
|
-
one(): Record<string, unknown> | undefined
|
|
3
|
-
toArray(): Array<Record<string, unknown>>
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface DurableSqlStorage {
|
|
7
|
-
exec(sql: string, ...params: unknown[]): DurableSqlResult
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const WATERMARK_STATE_TABLE = '_zero_change_state'
|
|
11
|
-
|
|
12
|
-
function quoteIdent(name: string): string {
|
|
13
|
-
return `"${name.replace(/"/g, '""')}"`
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function finitePositiveNumber(value: unknown): number {
|
|
17
|
-
const number = Number(value ?? 0)
|
|
18
|
-
return Number.isFinite(number) && number > 0 ? number : 0
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class DurableWatermarkState {
|
|
22
|
-
constructor(private readonly sql: DurableSqlStorage) {}
|
|
23
|
-
|
|
24
|
-
ensureTables(): void {
|
|
25
|
-
this.sql.exec(
|
|
26
|
-
"CREATE TABLE IF NOT EXISTS _zero_changes (watermark INTEGER PRIMARY KEY AUTOINCREMENT, table_name TEXT NOT NULL, op TEXT NOT NULL CHECK (op IN ('INSERT', 'UPDATE', 'DELETE')), row_data TEXT, old_data TEXT, created_at INTEGER NOT NULL DEFAULT (unixepoch()))"
|
|
27
|
-
)
|
|
28
|
-
this.sql.exec(
|
|
29
|
-
`CREATE TABLE IF NOT EXISTS ${quoteIdent(WATERMARK_STATE_TABLE)} (id INTEGER PRIMARY KEY CHECK (id = 1), last_value INTEGER NOT NULL DEFAULT 0)`
|
|
30
|
-
)
|
|
31
|
-
this.setWatermarkState(this.watermarkState())
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
next(): number {
|
|
35
|
-
return this.current() + 1
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
mark(watermark: number): void {
|
|
39
|
-
this.setWatermarkState(watermark)
|
|
40
|
-
this.updateWatermarkSequences(watermark)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
current(): number {
|
|
44
|
-
this.ensureTables()
|
|
45
|
-
const state = this.watermarkState()
|
|
46
|
-
const row = this.sql
|
|
47
|
-
.exec('SELECT COALESCE(MAX(watermark), 0) AS watermark FROM _zero_changes')
|
|
48
|
-
.one() as { watermark?: unknown } | undefined
|
|
49
|
-
const tableWatermark = finitePositiveNumber(row?.watermark)
|
|
50
|
-
const sequenceWatermark = this.watermarkSequenceValue()
|
|
51
|
-
const watermark = Math.max(state, tableWatermark, sequenceWatermark)
|
|
52
|
-
if (watermark > state) this.setWatermarkState(watermark)
|
|
53
|
-
if (watermark > sequenceWatermark) this.updateWatermarkSequences(watermark)
|
|
54
|
-
return watermark
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
private watermarkState(): number {
|
|
58
|
-
try {
|
|
59
|
-
const table = quoteIdent(WATERMARK_STATE_TABLE)
|
|
60
|
-
const row = this.sql.exec(`SELECT last_value FROM ${table} WHERE id = 1`).one() as
|
|
61
|
-
| { last_value?: unknown }
|
|
62
|
-
| undefined
|
|
63
|
-
return finitePositiveNumber(row?.last_value)
|
|
64
|
-
} catch {
|
|
65
|
-
return 0
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
private setWatermarkState(watermark: number): void {
|
|
70
|
-
const table = quoteIdent(WATERMARK_STATE_TABLE)
|
|
71
|
-
this.sql.exec(`INSERT OR IGNORE INTO ${table} (id, last_value) VALUES (1, 0)`)
|
|
72
|
-
this.sql.exec(`UPDATE ${table} SET last_value = ? WHERE id = 1`, watermark)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
private watermarkSequenceValue(): number {
|
|
76
|
-
let watermark = 0
|
|
77
|
-
for (const name of this.watermarkSequenceTables()) {
|
|
78
|
-
try {
|
|
79
|
-
const row = this.sql
|
|
80
|
-
.exec(`SELECT last_value, is_called FROM ${quoteIdent(name)} WHERE dummy = 1`)
|
|
81
|
-
.one() as { last_value?: unknown; is_called?: unknown } | undefined
|
|
82
|
-
if (!row || !row.is_called) continue
|
|
83
|
-
watermark = Math.max(watermark, finitePositiveNumber(row.last_value))
|
|
84
|
-
} catch {
|
|
85
|
-
/* not an orez sequence table */
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return watermark
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
private watermarkSequenceTables(): string[] {
|
|
92
|
-
return this.sql
|
|
93
|
-
.exec(
|
|
94
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%zero_watermark%'"
|
|
95
|
-
)
|
|
96
|
-
.toArray()
|
|
97
|
-
.map((row) => String(row.name || ''))
|
|
98
|
-
.filter(Boolean)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private updateWatermarkSequences(watermark: number): void {
|
|
102
|
-
for (const name of this.watermarkSequenceTables()) {
|
|
103
|
-
const table = quoteIdent(name)
|
|
104
|
-
try {
|
|
105
|
-
this.sql.exec(
|
|
106
|
-
`INSERT OR IGNORE INTO ${table} (dummy, last_value, is_called) VALUES (1, ?, 1)`,
|
|
107
|
-
watermark
|
|
108
|
-
)
|
|
109
|
-
this.sql.exec(
|
|
110
|
-
`UPDATE ${table} SET last_value = ?, is_called = 1 WHERE dummy = 1`,
|
|
111
|
-
watermark
|
|
112
|
-
)
|
|
113
|
-
} catch {
|
|
114
|
-
/* not an orez sequence table */
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|