orez 0.2.25 → 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/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 +48 -22
- package/dist/cf-do/worker.d.ts.map +1 -1
- package/dist/cf-do/worker.js +642 -269
- package/dist/cf-do/worker.js.map +1 -1
- package/dist/config.js +1 -1
- 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.map +1 -1
- package/dist/index.js +28 -14
- 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 +96 -17
- package/dist/pg-proxy-do-backend.d.ts.map +1 -1
- package/dist/pg-proxy-do-backend.js +6033 -454
- package/dist/pg-proxy-do-backend.js.map +1 -1
- 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 +1 -1
- package/src/do-sql-tracking.test.ts +19 -0
- package/src/do-sql-tracking.ts +19 -0
- package/src/index.ts +29 -14
- 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 +6799 -482
- 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/query-rewrites.test.ts +0 -30
- package/src/query-rewrites.ts +0 -152
|
@@ -4,28 +4,29 @@
|
|
|
4
4
|
* runs zero-cache in-process with SINGLE_PROCESS=1, using bundler aliases
|
|
5
5
|
* to swap Node.js dependencies for CF-compatible shims:
|
|
6
6
|
*
|
|
7
|
-
* postgres → orez/worker/shims/postgres
|
|
7
|
+
* postgres → orez/worker/shims/postgres-browser
|
|
8
|
+
* (real postgres package over MessagePort)
|
|
8
9
|
* @rocicorp/zero-sqlite3 → orez/worker/shims/sqlite (DO SQLite)
|
|
9
10
|
* fastify → orez/worker/shims/fastify (route capture)
|
|
10
11
|
* ws → orez/worker/shims/ws (CF WebSocket)
|
|
11
12
|
*
|
|
12
|
-
* the
|
|
13
|
-
*
|
|
13
|
+
* the postgres MessagePort proxy is backed by DoBackend, so zero-cache still
|
|
14
|
+
* uses its real PG wire protocol, but storage is Cloudflare DO SQLite instead
|
|
15
|
+
* of PGlite.
|
|
14
16
|
*
|
|
15
17
|
* usage in a Durable Object:
|
|
16
18
|
*
|
|
17
19
|
* import { startZeroCacheEmbedCF } from 'orez/worker'
|
|
18
20
|
*
|
|
19
21
|
* // in ensureInitialized():
|
|
20
|
-
* globalThis.__orez_pglite = pglite // for postgres shim
|
|
21
|
-
* globalThis.__orez_do_sqlite = ctx.storage.sql // for sqlite shim
|
|
22
|
-
*
|
|
23
22
|
* const zc = await startZeroCacheEmbedCF({ ... })
|
|
24
23
|
*
|
|
25
24
|
* // in DO fetch():
|
|
26
25
|
* return zc.handleRequest(request)
|
|
27
26
|
*/
|
|
28
27
|
|
|
28
|
+
import './shims/zero-process-env.js'
|
|
29
|
+
|
|
29
30
|
import EventEmitter from 'node:events'
|
|
30
31
|
|
|
31
32
|
// static import so wrangler can follow the dependency tree and bundle
|
|
@@ -33,7 +34,8 @@ import EventEmitter from 'node:events'
|
|
|
33
34
|
// @ts-expect-error — internal zero-cache module, no type declarations
|
|
34
35
|
import { runWorker as _runWorker } from '@rocicorp/zero/out/zero-cache/src/server/runner/run-worker.js'
|
|
35
36
|
|
|
36
|
-
import type
|
|
37
|
+
import { createBrowserProxy, type BrowserProxy } from '../pg-proxy-browser.js'
|
|
38
|
+
import { DoBackend } from '../pg-proxy-do-backend.js'
|
|
37
39
|
|
|
38
40
|
const runWorkerFn = _runWorker as (
|
|
39
41
|
parent: unknown,
|
|
@@ -41,12 +43,32 @@ const runWorkerFn = _runWorker as (
|
|
|
41
43
|
) => Promise<void>
|
|
42
44
|
|
|
43
45
|
export interface ZeroCacheEmbedCFOptions {
|
|
44
|
-
/** PGlite instance (also registered on globalThis.__orez_pglite) */
|
|
45
|
-
pglite: PGlite
|
|
46
|
-
|
|
47
46
|
/** DO SQLite storage (also registered on globalThis.__orez_do_sqlite) */
|
|
48
47
|
doSqlite: unknown
|
|
49
48
|
|
|
49
|
+
/**
|
|
50
|
+
* base URL for the DO SQL execution endpoints (`/exec`, `/batch`).
|
|
51
|
+
* ignored when `backends` is supplied.
|
|
52
|
+
*/
|
|
53
|
+
backendUrl?: string
|
|
54
|
+
|
|
55
|
+
/** custom fetch used by DoBackend; lets a DO route directly to another DO stub. */
|
|
56
|
+
backendFetch?: typeof fetch
|
|
57
|
+
|
|
58
|
+
/** namespace sent to the DO SQL endpoints. */
|
|
59
|
+
backendNamespace?: string
|
|
60
|
+
|
|
61
|
+
/** pre-created DoBackend instances. mainly useful for tests. */
|
|
62
|
+
backends?: {
|
|
63
|
+
postgres: DoBackend
|
|
64
|
+
cvr: DoBackend
|
|
65
|
+
cdb: DoBackend
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** postgres username/password expected by the in-process proxy. */
|
|
69
|
+
pgUser?: string
|
|
70
|
+
pgPassword?: string
|
|
71
|
+
|
|
50
72
|
/** zero app ID (default: 'zero') */
|
|
51
73
|
appId?: string
|
|
52
74
|
|
|
@@ -56,6 +78,9 @@ export interface ZeroCacheEmbedCFOptions {
|
|
|
56
78
|
/** additional env vars passed to zero-cache */
|
|
57
79
|
env?: Record<string, string>
|
|
58
80
|
|
|
81
|
+
/** fetch implementation for Worker-local mutate/query API URLs. */
|
|
82
|
+
apiFetch?: typeof fetch
|
|
83
|
+
|
|
59
84
|
/** timeout in ms waiting for zero-cache ready (default: 30000) */
|
|
60
85
|
readyTimeout?: number
|
|
61
86
|
}
|
|
@@ -78,9 +103,8 @@ export interface ZeroCacheEmbedCF {
|
|
|
78
103
|
/**
|
|
79
104
|
* start zero-cache in embedded CF Workers mode.
|
|
80
105
|
*
|
|
81
|
-
* must be called
|
|
82
|
-
*
|
|
83
|
-
* globalThis.__orez_do_sqlite = ctx.storage.sql
|
|
106
|
+
* must be called with a DO SQLite handle for zero-cache's replica storage and
|
|
107
|
+
* a DoBackend target for upstream/CVR/change Postgres connections.
|
|
84
108
|
*/
|
|
85
109
|
export async function startZeroCacheEmbedCF(
|
|
86
110
|
opts: ZeroCacheEmbedCFOptions
|
|
@@ -88,10 +112,53 @@ export async function startZeroCacheEmbedCF(
|
|
|
88
112
|
const appId = opts.appId || 'zero'
|
|
89
113
|
const publications = opts.publications?.join(',') || `orez_${appId}_public`
|
|
90
114
|
const readyTimeout = opts.readyTimeout ?? 30000
|
|
115
|
+
const pgUser = opts.pgUser || 'user'
|
|
116
|
+
const pgPassword = opts.pgPassword || ''
|
|
117
|
+
const backendUrl = opts.backendUrl || 'https://orez-do-backend.local'
|
|
118
|
+
const backendNamespace = opts.backendNamespace || appId
|
|
119
|
+
|
|
120
|
+
const backends =
|
|
121
|
+
opts.backends ??
|
|
122
|
+
({
|
|
123
|
+
postgres: new DoBackend(backendUrl, 'postgres', backendNamespace, {
|
|
124
|
+
fetch: opts.backendFetch,
|
|
125
|
+
}),
|
|
126
|
+
cvr: new DoBackend(backendUrl, 'zero_cvr', backendNamespace, {
|
|
127
|
+
fetch: opts.backendFetch,
|
|
128
|
+
}),
|
|
129
|
+
cdb: new DoBackend(backendUrl, 'zero_cdb', backendNamespace, {
|
|
130
|
+
fetch: opts.backendFetch,
|
|
131
|
+
}),
|
|
132
|
+
} satisfies NonNullable<ZeroCacheEmbedCFOptions['backends']>)
|
|
133
|
+
|
|
134
|
+
await Promise.all([
|
|
135
|
+
backends.postgres.waitReady,
|
|
136
|
+
backends.cvr.waitReady,
|
|
137
|
+
backends.cdb.waitReady,
|
|
138
|
+
])
|
|
139
|
+
|
|
140
|
+
const proxy: BrowserProxy = await createBrowserProxy(
|
|
141
|
+
{
|
|
142
|
+
postgres: backends.postgres as any,
|
|
143
|
+
cvr: backends.cvr as any,
|
|
144
|
+
cdb: backends.cdb as any,
|
|
145
|
+
postgresReplicas: [],
|
|
146
|
+
} as any,
|
|
147
|
+
{
|
|
148
|
+
pgUser,
|
|
149
|
+
pgPassword,
|
|
150
|
+
singleDb: false,
|
|
151
|
+
logLevel: opts.env?.ZERO_LOG_LEVEL || 'info',
|
|
152
|
+
}
|
|
153
|
+
)
|
|
91
154
|
|
|
92
155
|
// ensure globals are set for shims
|
|
93
|
-
;(globalThis as any).__orez_pglite = opts.pglite
|
|
94
156
|
;(globalThis as any).__orez_do_sqlite = opts.doSqlite
|
|
157
|
+
;(globalThis as any).__orez_proxy_connect = (port: MessagePort) => {
|
|
158
|
+
proxy.handleConnection(port)
|
|
159
|
+
}
|
|
160
|
+
;(globalThis as any).__orez_proxy_user = pgUser
|
|
161
|
+
;(globalThis as any).__orez_proxy_password = pgPassword
|
|
95
162
|
|
|
96
163
|
// ensure process.env exists (CF Workers doesn't have it natively)
|
|
97
164
|
;(globalThis as any).process ??= {}
|
|
@@ -130,30 +197,45 @@ export async function startZeroCacheEmbedCF(
|
|
|
130
197
|
const origExit = (globalThis as any).process.exit
|
|
131
198
|
const origNodeEnv = (globalThis as any).process.env.NODE_ENV
|
|
132
199
|
const origKill = (globalThis as any).process.kill
|
|
200
|
+
const origFetch = (globalThis as any).fetch
|
|
133
201
|
;(globalThis as any).process.exit = (code?: number) => {
|
|
134
202
|
parent.emit('exit', code ?? 0)
|
|
135
203
|
}
|
|
204
|
+
if (opts.apiFetch) {
|
|
205
|
+
;(globalThis as any).fetch = (input: RequestInfo | URL, init?: RequestInit) => {
|
|
206
|
+
const request = new Request(input, init)
|
|
207
|
+
const url = new URL(request.url)
|
|
208
|
+
if (url.hostname === 'orez-zero-api.local') return opts.apiFetch!(request)
|
|
209
|
+
return origFetch(input as any, init as any)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
136
212
|
|
|
137
213
|
// build env for zero-cache
|
|
138
214
|
const env: Record<string, string> = {
|
|
139
215
|
...((globalThis as any).process.env as Record<string, string>),
|
|
140
216
|
SINGLE_PROCESS: '1',
|
|
141
217
|
NODE_ENV: 'development',
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
218
|
+
// postgres-browser intercepts these URLs and routes PG wire over
|
|
219
|
+
// MessagePort to the DoBackend-backed proxy above.
|
|
220
|
+
ZERO_UPSTREAM_DB: `postgres://${pgUser}:ignored@127.0.0.1/postgres`,
|
|
221
|
+
ZERO_CVR_DB: `postgres://${pgUser}:ignored@127.0.0.1/zero_cvr`,
|
|
222
|
+
ZERO_CHANGE_DB: `postgres://${pgUser}:ignored@127.0.0.1/zero_cdb`,
|
|
146
223
|
// this path is intercepted by the sqlite shim
|
|
147
224
|
ZERO_REPLICA_FILE: ':do-sqlite:',
|
|
148
225
|
// don't bind a port — we route via inject/handoff
|
|
149
226
|
ZERO_PORT: '0',
|
|
150
227
|
ZERO_APP_ID: appId,
|
|
151
228
|
ZERO_APP_PUBLICATIONS: publications,
|
|
229
|
+
ZERO_ADMIN_PASSWORD: opts.env?.ZERO_ADMIN_PASSWORD || crypto.randomUUID(),
|
|
152
230
|
ZERO_LOG_LEVEL: opts.env?.ZERO_LOG_LEVEL || 'info',
|
|
153
231
|
ZERO_NUM_SYNC_WORKERS: opts.env?.ZERO_NUM_SYNC_WORKERS || '1',
|
|
154
232
|
ZERO_ENABLE_QUERY_PLANNER: 'false',
|
|
155
233
|
...opts.env,
|
|
156
234
|
}
|
|
235
|
+
Object.assign((globalThis as any).process.env, env)
|
|
236
|
+
|
|
237
|
+
const debugEmbed =
|
|
238
|
+
env.OREZ_DEBUG_EMBED === '1' || (globalThis as any).__OREZ_DEBUG_EMBED__ === true
|
|
157
239
|
|
|
158
240
|
// wrap parent with onMessageType/onceMessageType helpers
|
|
159
241
|
// must forward sendHandle (second arg) for WebSocket handoff
|
|
@@ -204,6 +286,7 @@ export async function startZeroCacheEmbedCF(
|
|
|
204
286
|
}, readyTimeout)
|
|
205
287
|
|
|
206
288
|
parentEmitter.on('message', (msg: unknown) => {
|
|
289
|
+
if (debugEmbed) console.debug('[orez-zero-cache-cf] parent message', msg)
|
|
207
290
|
if (Array.isArray(msg) && msg[0] === 'ready') {
|
|
208
291
|
clearTimeout(timeout)
|
|
209
292
|
isReady = true
|
|
@@ -214,6 +297,7 @@ export async function startZeroCacheEmbedCF(
|
|
|
214
297
|
|
|
215
298
|
// start zero-cache
|
|
216
299
|
runWorkerPromise = runWorkerFn(wrappedParent, env).catch((err) => {
|
|
300
|
+
if (debugEmbed) console.error('[orez-zero-cache-cf] runWorker error', err)
|
|
217
301
|
if (!isReady) {
|
|
218
302
|
throw err
|
|
219
303
|
}
|
|
@@ -255,6 +339,12 @@ export async function startZeroCacheEmbedCF(
|
|
|
255
339
|
await Promise.race([runWorkerPromise, new Promise((r) => setTimeout(r, 5000))])
|
|
256
340
|
}
|
|
257
341
|
await new Promise((r) => setTimeout(r, 200))
|
|
342
|
+
proxy.close()
|
|
343
|
+
await Promise.all([
|
|
344
|
+
backends.postgres.close(),
|
|
345
|
+
backends.cvr.close(),
|
|
346
|
+
backends.cdb.close(),
|
|
347
|
+
])
|
|
258
348
|
// restore all modified globals
|
|
259
349
|
if (origExit) {
|
|
260
350
|
;(globalThis as any).process.exit = origExit
|
|
@@ -265,7 +355,13 @@ export async function startZeroCacheEmbedCF(
|
|
|
265
355
|
if (origKill) {
|
|
266
356
|
;(globalThis as any).process.kill = origKill
|
|
267
357
|
}
|
|
358
|
+
if (opts.apiFetch) {
|
|
359
|
+
;(globalThis as any).fetch = origFetch
|
|
360
|
+
}
|
|
268
361
|
delete (globalThis as any).process.env.SINGLE_PROCESS
|
|
362
|
+
delete (globalThis as any).__orez_proxy_connect
|
|
363
|
+
delete (globalThis as any).__orez_proxy_user
|
|
364
|
+
delete (globalThis as any).__orez_proxy_password
|
|
269
365
|
},
|
|
270
366
|
}
|
|
271
367
|
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { rewritePgColumnSizeTotalBytesQuery } from './query-rewrites.js'
|
|
4
|
-
|
|
5
|
-
describe('rewritePgColumnSizeTotalBytesQuery', () => {
|
|
6
|
-
test('rewrites zero totalBytes pg_column_size sums into scalar subselects', () => {
|
|
7
|
-
const query =
|
|
8
|
-
'SELECT (SUM(COALESCE(pg_column_size("id"), 0)) + SUM(COALESCE(pg_column_size("parts"), 0)) + SUM(COALESCE(pg_column_size("threadId"), 0))) AS "totalBytes" FROM "public"."message" '
|
|
9
|
-
|
|
10
|
-
expect(rewritePgColumnSizeTotalBytesQuery(query)).toBe(
|
|
11
|
-
'SELECT (SELECT SUM(COALESCE(pg_column_size("id"), 0)) FROM "public"."message") + (SELECT SUM(COALESCE(pg_column_size("parts"), 0)) FROM "public"."message") + (SELECT SUM(COALESCE(pg_column_size("threadId"), 0)) FROM "public"."message") AS "totalBytes"'
|
|
12
|
-
)
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
test('preserves row filters on every scalar subselect', () => {
|
|
16
|
-
const query =
|
|
17
|
-
'SELECT (SUM(COALESCE(pg_column_size("id"), 0)) + SUM(COALESCE(pg_column_size("parts"), 0))) AS "totalBytes" FROM "public"."message" WHERE "projectId" = \'proj_1\' OR "role" = \'user\';'
|
|
18
|
-
|
|
19
|
-
expect(rewritePgColumnSizeTotalBytesQuery(query)).toBe(
|
|
20
|
-
'SELECT (SELECT SUM(COALESCE(pg_column_size("id"), 0)) FROM "public"."message" WHERE "projectId" = \'proj_1\' OR "role" = \'user\') + (SELECT SUM(COALESCE(pg_column_size("parts"), 0)) FROM "public"."message" WHERE "projectId" = \'proj_1\' OR "role" = \'user\') AS "totalBytes"'
|
|
21
|
-
)
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
test('leaves non-matching SQL unchanged', () => {
|
|
25
|
-
const query =
|
|
26
|
-
'SELECT (SUM(COALESCE(pg_column_size("id"), 0)) + count(*)) AS "totalBytes" FROM "public"."message"'
|
|
27
|
-
|
|
28
|
-
expect(rewritePgColumnSizeTotalBytesQuery(query)).toBe(query)
|
|
29
|
-
})
|
|
30
|
-
})
|
package/src/query-rewrites.ts
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
const TOTAL_BYTES_ALIAS_RE = /^AS\s+"totalBytes"\s+/i
|
|
2
|
-
const TOTAL_BYTES_TERM_RE =
|
|
3
|
-
/^SUM\s*\(\s*COALESCE\s*\(\s*pg_column_size\s*\(\s*((?:"(?:[^"]|"")+")|(?:[a-z_][a-z0-9_$]*))\s*\)\s*,\s*0\s*\)\s*\)$/i
|
|
4
|
-
|
|
5
|
-
function findMatchingParen(sql: string, openIndex: number): number {
|
|
6
|
-
let depth = 0
|
|
7
|
-
let inSingleQuote = false
|
|
8
|
-
let inDoubleQuote = false
|
|
9
|
-
|
|
10
|
-
for (let i = openIndex; i < sql.length; i++) {
|
|
11
|
-
const ch = sql[i]
|
|
12
|
-
const next = sql[i + 1]
|
|
13
|
-
|
|
14
|
-
if (inSingleQuote) {
|
|
15
|
-
if (ch === "'" && next === "'") {
|
|
16
|
-
i++
|
|
17
|
-
} else if (ch === "'") {
|
|
18
|
-
inSingleQuote = false
|
|
19
|
-
}
|
|
20
|
-
continue
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (inDoubleQuote) {
|
|
24
|
-
if (ch === '"' && next === '"') {
|
|
25
|
-
i++
|
|
26
|
-
} else if (ch === '"') {
|
|
27
|
-
inDoubleQuote = false
|
|
28
|
-
}
|
|
29
|
-
continue
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (ch === "'") {
|
|
33
|
-
inSingleQuote = true
|
|
34
|
-
continue
|
|
35
|
-
}
|
|
36
|
-
if (ch === '"') {
|
|
37
|
-
inDoubleQuote = true
|
|
38
|
-
continue
|
|
39
|
-
}
|
|
40
|
-
if (ch === '(') {
|
|
41
|
-
depth++
|
|
42
|
-
continue
|
|
43
|
-
}
|
|
44
|
-
if (ch === ')') {
|
|
45
|
-
depth--
|
|
46
|
-
if (depth === 0) return i
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return -1
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function splitTopLevelAddends(expr: string): string[] | null {
|
|
54
|
-
const terms: string[] = []
|
|
55
|
-
let depth = 0
|
|
56
|
-
let start = 0
|
|
57
|
-
let inSingleQuote = false
|
|
58
|
-
let inDoubleQuote = false
|
|
59
|
-
|
|
60
|
-
for (let i = 0; i < expr.length; i++) {
|
|
61
|
-
const ch = expr[i]
|
|
62
|
-
const next = expr[i + 1]
|
|
63
|
-
|
|
64
|
-
if (inSingleQuote) {
|
|
65
|
-
if (ch === "'" && next === "'") {
|
|
66
|
-
i++
|
|
67
|
-
} else if (ch === "'") {
|
|
68
|
-
inSingleQuote = false
|
|
69
|
-
}
|
|
70
|
-
continue
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (inDoubleQuote) {
|
|
74
|
-
if (ch === '"' && next === '"') {
|
|
75
|
-
i++
|
|
76
|
-
} else if (ch === '"') {
|
|
77
|
-
inDoubleQuote = false
|
|
78
|
-
}
|
|
79
|
-
continue
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (ch === "'") {
|
|
83
|
-
inSingleQuote = true
|
|
84
|
-
continue
|
|
85
|
-
}
|
|
86
|
-
if (ch === '"') {
|
|
87
|
-
inDoubleQuote = true
|
|
88
|
-
continue
|
|
89
|
-
}
|
|
90
|
-
if (ch === '(') {
|
|
91
|
-
depth++
|
|
92
|
-
continue
|
|
93
|
-
}
|
|
94
|
-
if (ch === ')') {
|
|
95
|
-
depth--
|
|
96
|
-
if (depth < 0) return null
|
|
97
|
-
continue
|
|
98
|
-
}
|
|
99
|
-
if (ch === '+' && depth === 0) {
|
|
100
|
-
terms.push(expr.slice(start, i).trim())
|
|
101
|
-
start = i + 1
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (depth !== 0 || inSingleQuote || inDoubleQuote) return null
|
|
106
|
-
|
|
107
|
-
terms.push(expr.slice(start).trim())
|
|
108
|
-
return terms
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function stripTrailingSemicolon(sql: string): string {
|
|
112
|
-
const trimmedEnd = sql.trimEnd()
|
|
113
|
-
return trimmedEnd.endsWith(';') ? trimmedEnd.slice(0, -1) : sql
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function rewritePgColumnSizeTotalBytesQuery(query: string): string {
|
|
117
|
-
const leadingWhitespace = query.match(/^\s*/)?.[0] ?? ''
|
|
118
|
-
const trimmedStart = query.slice(leadingWhitespace.length)
|
|
119
|
-
if (!/^SELECT\b/i.test(trimmedStart)) return query
|
|
120
|
-
|
|
121
|
-
const afterSelect = trimmedStart.slice('SELECT'.length).trimStart()
|
|
122
|
-
if (!afterSelect.startsWith('(')) return query
|
|
123
|
-
|
|
124
|
-
const openIndex = trimmedStart.indexOf(afterSelect)
|
|
125
|
-
const closeIndex = findMatchingParen(trimmedStart, openIndex)
|
|
126
|
-
if (closeIndex < 0) return query
|
|
127
|
-
|
|
128
|
-
const expression = trimmedStart.slice(openIndex + 1, closeIndex)
|
|
129
|
-
const afterExpression = trimmedStart.slice(closeIndex + 1).trimStart()
|
|
130
|
-
const aliasMatch = afterExpression.match(TOTAL_BYTES_ALIAS_RE)
|
|
131
|
-
if (!aliasMatch) return query
|
|
132
|
-
|
|
133
|
-
const fromClause = stripTrailingSemicolon(
|
|
134
|
-
afterExpression.slice(aliasMatch[0].length).trim()
|
|
135
|
-
)
|
|
136
|
-
if (!/^FROM\b/i.test(fromClause)) return query
|
|
137
|
-
|
|
138
|
-
const terms = splitTopLevelAddends(expression)
|
|
139
|
-
if (!terms || terms.length === 0) return query
|
|
140
|
-
|
|
141
|
-
const columns: string[] = []
|
|
142
|
-
for (const term of terms) {
|
|
143
|
-
const match = term.match(TOTAL_BYTES_TERM_RE)
|
|
144
|
-
if (!match) return query
|
|
145
|
-
columns.push(match[1])
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const rewrittenTerms = columns.map(
|
|
149
|
-
(column) => `(SELECT SUM(COALESCE(pg_column_size(${column}), 0)) ${fromClause})`
|
|
150
|
-
)
|
|
151
|
-
return `${leadingWhitespace}SELECT ${rewrittenTerms.join(' + ')} AS "totalBytes"`
|
|
152
|
-
}
|