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.
Files changed (172) hide show
  1. package/dist/cf-do/worker.d.ts.map +1 -1
  2. package/dist/cf-do/worker.js +9 -1
  3. package/dist/cf-do/worker.js.map +1 -1
  4. package/dist/pg-proxy-do-backend.d.ts +2 -0
  5. package/dist/pg-proxy-do-backend.d.ts.map +1 -1
  6. package/dist/pg-proxy-do-backend.js +49 -7
  7. package/dist/pg-proxy-do-backend.js.map +1 -1
  8. package/dist/pg-sqlite-compiler/catalog/seed.d.ts +67 -0
  9. package/dist/pg-sqlite-compiler/catalog/seed.d.ts.map +1 -0
  10. package/dist/pg-sqlite-compiler/catalog/seed.js +436 -0
  11. package/dist/pg-sqlite-compiler/catalog/seed.js.map +1 -0
  12. package/dist/pg-sqlite-compiler/index.d.ts +12 -0
  13. package/dist/pg-sqlite-compiler/index.d.ts.map +1 -0
  14. package/dist/pg-sqlite-compiler/index.js +59 -0
  15. package/dist/pg-sqlite-compiler/index.js.map +1 -0
  16. package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts +48 -0
  17. package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts.map +1 -0
  18. package/dist/pg-sqlite-compiler/passes/ast-utils.js +93 -0
  19. package/dist/pg-sqlite-compiler/passes/ast-utils.js.map +1 -0
  20. package/dist/pg-sqlite-compiler/passes/catalog.d.ts +34 -0
  21. package/dist/pg-sqlite-compiler/passes/catalog.d.ts.map +1 -0
  22. package/dist/pg-sqlite-compiler/passes/catalog.js +30 -0
  23. package/dist/pg-sqlite-compiler/passes/catalog.js.map +1 -0
  24. package/dist/pg-sqlite-compiler/passes/datetime.d.ts +21 -0
  25. package/dist/pg-sqlite-compiler/passes/datetime.d.ts.map +1 -0
  26. package/dist/pg-sqlite-compiler/passes/datetime.js +53 -0
  27. package/dist/pg-sqlite-compiler/passes/datetime.js.map +1 -0
  28. package/dist/pg-sqlite-compiler/passes/index.d.ts +21 -0
  29. package/dist/pg-sqlite-compiler/passes/index.d.ts.map +1 -0
  30. package/dist/pg-sqlite-compiler/passes/index.js +39 -0
  31. package/dist/pg-sqlite-compiler/passes/index.js.map +1 -0
  32. package/dist/pg-sqlite-compiler/passes/types.d.ts +41 -0
  33. package/dist/pg-sqlite-compiler/passes/types.d.ts.map +1 -0
  34. package/dist/pg-sqlite-compiler/passes/types.js +103 -0
  35. package/dist/pg-sqlite-compiler/passes/types.js.map +1 -0
  36. package/dist/pg-sqlite-compiler/test/oracle.d.ts +34 -0
  37. package/dist/pg-sqlite-compiler/test/oracle.d.ts.map +1 -0
  38. package/dist/pg-sqlite-compiler/test/oracle.js +204 -0
  39. package/dist/pg-sqlite-compiler/test/oracle.js.map +1 -0
  40. package/dist/pg-sqlite-compiler/types.d.ts +55 -0
  41. package/dist/pg-sqlite-compiler/types.d.ts.map +1 -0
  42. package/dist/pg-sqlite-compiler/types.js +2 -0
  43. package/dist/pg-sqlite-compiler/types.js.map +1 -0
  44. package/package.json +8 -4
  45. package/src/admin/admin-data.test.ts +0 -348
  46. package/src/admin/http-proxy.ts +0 -252
  47. package/src/admin/log-store.ts +0 -192
  48. package/src/admin/server.ts +0 -471
  49. package/src/admin/ui.ts +0 -1322
  50. package/src/bench/proxy-throughput.bench.ts +0 -343
  51. package/src/bench/serial-mutations.bench.ts +0 -270
  52. package/src/browser.ts +0 -203
  53. package/src/cf-do/.wrangler/cache/cf.json +0 -1
  54. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
  55. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
  56. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
  57. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0f0f3bdf0abda097eb6f1246db4657d9fc622081362d894d82c1a1ce067b05b6.sqlite +0 -0
  58. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/1ddd3a4a48a11b51658444f5458a1fb175194b1d5b6a5bda20ef3fe3205b900c.sqlite +0 -0
  59. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/204a39120310d37e972c5914cfd71ad55c151bdb9e8ed289a5f8c5b052dd60e4.sqlite +0 -0
  60. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/3835f242df9728adba3d127a238793fd054ed3e51df3f60749ee744c469bf2a2.sqlite +0 -0
  61. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/4aa9c80eb716cf55b8995ccf7afab0b36c683e6da07d7c37a3f9c570136036df.sqlite +0 -0
  62. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/533e2fd1d6ea46e7a9a0017916ef341802d438d72583462755f2c1f8225e9bf2.sqlite +0 -0
  63. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/5ffa1aced1225ecaeac6366f7586aa3de92761cdff8711d81fbd81f248076abd.sqlite +0 -0
  64. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/686c3a9f0d7e59ed2ab607efd4b76d779c97cafeb3818380033bf7c7eb86c819.sqlite +0 -0
  65. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/6e8214e8dcfadd0deb52d64e5e9ca85c6b329ace11193909845995396914c473.sqlite +0 -0
  66. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/78d9ec9ff873d3fe3507ff53c2a6f6dfc408b4268eb0db3f2a146c0678965366.sqlite +0 -0
  67. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/7eff9f0ed7e27ad0d3f9d923de0682fab1928591172c1ba336c5f79a134a5d85.sqlite +0 -0
  68. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/836cda5b995b25867d722ed4f4c2292167e80351a3c6038db626648eb247dd8b.sqlite +0 -0
  69. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/91ef63b112209ab30172763acd8a0935106c248f7f1bcae5545ce37a9f201551.sqlite +0 -0
  70. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/a66ea4293a5f5938bc6d116edfa2522bb85bc37aea3541fbc09c3b613b9b32c0.sqlite +0 -0
  71. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/ceb2ab26b80590840b65651deb6e948d3bf81565c6751f3a58752cf4bf4aecae.sqlite +0 -0
  72. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
  73. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
  74. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
  75. package/src/cf-do/ARCHITECTURE.md +0 -83
  76. package/src/cf-do/watermark.test.ts +0 -103
  77. package/src/cf-do/watermark.ts +0 -118
  78. package/src/cf-do/worker.ts +0 -1033
  79. package/src/cf-do/wrangler.toml +0 -11
  80. package/src/cf-pglite/README.md +0 -19
  81. package/src/change-tracking.ts +0 -25
  82. package/src/child-process.test.ts +0 -147
  83. package/src/child-process.ts +0 -90
  84. package/src/cli-entry.ts +0 -72
  85. package/src/cli.test.ts +0 -38
  86. package/src/cli.ts +0 -1214
  87. package/src/config.ts +0 -150
  88. package/src/do-sql-tracking.test.ts +0 -19
  89. package/src/do-sql-tracking.ts +0 -19
  90. package/src/index.ts +0 -1215
  91. package/src/integration/integration.test.ts +0 -517
  92. package/src/integration/native-binary.guard.test.ts +0 -13
  93. package/src/integration/native-startup.test.ts +0 -44
  94. package/src/integration/replication-latency.test.ts +0 -428
  95. package/src/integration/restore-live-stress.test.ts +0 -433
  96. package/src/integration/restore-reset.test.ts +0 -400
  97. package/src/integration/restore.test.ts +0 -274
  98. package/src/integration/test-permissions.ts +0 -147
  99. package/src/load-config.ts +0 -46
  100. package/src/log.ts +0 -96
  101. package/src/mutex.ts +0 -47
  102. package/src/pg-proxy-browser.singledb.test.ts +0 -233
  103. package/src/pg-proxy-browser.ts +0 -2022
  104. package/src/pg-proxy-do-backend.test.ts +0 -3890
  105. package/src/pg-proxy-do-backend.ts +0 -7157
  106. package/src/pg-proxy.ts +0 -1087
  107. package/src/pglite-ipc.test.ts +0 -116
  108. package/src/pglite-ipc.ts +0 -266
  109. package/src/pglite-manager.ts +0 -557
  110. package/src/pglite-web-proxy.test.ts +0 -57
  111. package/src/pglite-web-proxy.ts +0 -221
  112. package/src/pglite-web-worker.ts +0 -152
  113. package/src/pglite-worker-thread.ts +0 -253
  114. package/src/port.ts +0 -25
  115. package/src/process-title.ts +0 -9
  116. package/src/recovery.ts +0 -155
  117. package/src/replication/change-tracker.test.ts +0 -357
  118. package/src/replication/change-tracker.ts +0 -279
  119. package/src/replication/handler.test.ts +0 -511
  120. package/src/replication/handler.ts +0 -1190
  121. package/src/replication/pgoutput-encoder.test.ts +0 -697
  122. package/src/replication/pgoutput-encoder.ts +0 -373
  123. package/src/replication/tcp-replication.test.ts +0 -876
  124. package/src/replication/zero-compat.test.ts +0 -1150
  125. package/src/restore-stress.test.ts +0 -188
  126. package/src/s3-local.ts +0 -203
  127. package/src/shim/hooks.mjs +0 -120
  128. package/src/shim/register.mjs +0 -4
  129. package/src/sqlite-mode/apply-mode.ts +0 -224
  130. package/src/sqlite-mode/index.ts +0 -15
  131. package/src/sqlite-mode/native-binary.ts +0 -89
  132. package/src/sqlite-mode/package-resolve.ts +0 -17
  133. package/src/sqlite-mode/resolve-mode.ts +0 -80
  134. package/src/sqlite-mode/shim-template.ts +0 -159
  135. package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
  136. package/src/sqlite-mode/types.ts +0 -30
  137. package/src/vite-plugin.ts +0 -67
  138. package/src/wasm-sqlite.test.ts +0 -537
  139. package/src/worker/browser-admin.ts +0 -52
  140. package/src/worker/browser-build-config.test.ts +0 -71
  141. package/src/worker/browser-build-config.ts +0 -109
  142. package/src/worker/browser-embed-admin.test.ts +0 -75
  143. package/src/worker/browser-embed.ts +0 -345
  144. package/src/worker/cf-patches.ts +0 -384
  145. package/src/worker/embed-integration.test.ts +0 -321
  146. package/src/worker/index.ts +0 -138
  147. package/src/worker/shims/fastify.test.ts +0 -255
  148. package/src/worker/shims/fastify.ts +0 -306
  149. package/src/worker/shims/http-service.test.ts +0 -355
  150. package/src/worker/shims/http-service.ts +0 -293
  151. package/src/worker/shims/node-stub.ts +0 -290
  152. package/src/worker/shims/oxfmt.ts +0 -3
  153. package/src/worker/shims/postgres-browser.ts +0 -59
  154. package/src/worker/shims/postgres-socket.test.ts +0 -576
  155. package/src/worker/shims/postgres-socket.ts +0 -310
  156. package/src/worker/shims/postgres.test.ts +0 -364
  157. package/src/worker/shims/postgres.ts +0 -1454
  158. package/src/worker/shims/sqlite-browser.test.ts +0 -233
  159. package/src/worker/shims/sqlite-browser.ts +0 -175
  160. package/src/worker/shims/sqlite.test.ts +0 -786
  161. package/src/worker/shims/sqlite.ts +0 -978
  162. package/src/worker/shims/stream-browser.ts +0 -15
  163. package/src/worker/shims/ws-browser.test.ts +0 -205
  164. package/src/worker/shims/ws-browser.ts +0 -248
  165. package/src/worker/shims/ws.test.ts +0 -288
  166. package/src/worker/shims/ws.ts +0 -467
  167. package/src/worker/shims/zero-process-env.ts +0 -11
  168. package/src/worker/types.ts +0 -75
  169. package/src/worker/worker-integration.test.ts +0 -223
  170. package/src/worker/worker.test.ts +0 -136
  171. package/src/worker/zero-cache-embed-cf.ts +0 -463
  172. package/src/worker/zero-cache-embed.ts +0 -277
@@ -1,433 +0,0 @@
1
- /**
2
- * live restore stress test.
3
- *
4
- * keeps a frontend-like websocket connection active while a large restore runs,
5
- * then triggers the same full reset path used by pg_restore (SIGUSR1) and
6
- * verifies sync still works after restart.
7
- */
8
-
9
- import { readFileSync, rmSync, unlinkSync, writeFileSync } from 'node:fs'
10
- import { tmpdir } from 'node:os'
11
- import { join } from 'node:path'
12
-
13
- import { loadModule } from 'pgsql-parser'
14
- import postgres from 'postgres'
15
- import { afterAll, beforeAll, describe, expect, test } from 'vitest'
16
- import WebSocket from 'ws'
17
-
18
- import { execDumpFile } from '../cli.js'
19
- import { startZeroLite } from '../index.js'
20
- import { installChangeTracking } from '../replication/change-tracker.js'
21
- import {
22
- ensureTablesInPublications,
23
- hasNonNullPermissions,
24
- installAllowAllPermissions,
25
- } from './test-permissions.js'
26
-
27
- import type { PGlite } from '@electric-sql/pglite'
28
-
29
- const SYNC_PROTOCOL_VERSION = 49
30
- const LIVE_CLIENT_SCHEMA = {
31
- tables: {
32
- restore_live_probe: {
33
- columns: {
34
- id: { type: 'string' },
35
- value: { type: 'string' },
36
- },
37
- primaryKey: ['id'],
38
- },
39
- },
40
- }
41
-
42
- function encodeSecProtocols(
43
- initConnectionMessage: unknown,
44
- authToken: string | undefined
45
- ): string {
46
- const payload = JSON.stringify({ initConnectionMessage, authToken })
47
- return encodeURIComponent(Buffer.from(payload, 'utf-8').toString('base64'))
48
- }
49
-
50
- class Queue<T> {
51
- private items: T[] = []
52
- private waiters: Array<{
53
- resolve: (v: T) => void
54
- timer?: ReturnType<typeof setTimeout>
55
- }> = []
56
-
57
- enqueue(item: T) {
58
- const waiter = this.waiters.shift()
59
- if (waiter) {
60
- if (waiter.timer) clearTimeout(waiter.timer)
61
- waiter.resolve(item)
62
- } else {
63
- this.items.push(item)
64
- }
65
- }
66
-
67
- dequeue(fallback?: T, timeoutMs = 10_000): Promise<T> {
68
- if (this.items.length > 0) {
69
- return Promise.resolve(this.items.shift()!)
70
- }
71
- return new Promise<T>((resolve) => {
72
- const waiter: { resolve: (v: T) => void; timer?: ReturnType<typeof setTimeout> } = {
73
- resolve,
74
- }
75
- if (fallback !== undefined) {
76
- waiter.timer = setTimeout(() => {
77
- const idx = this.waiters.indexOf(waiter)
78
- if (idx >= 0) this.waiters.splice(idx, 1)
79
- resolve(fallback)
80
- }, timeoutMs)
81
- }
82
- this.waiters.push(waiter)
83
- })
84
- }
85
- }
86
-
87
- function envInt(name: string, fallback: number): number {
88
- const raw = process.env[name]
89
- if (!raw) return fallback
90
- const n = Number(raw)
91
- return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback
92
- }
93
-
94
- function escapeCopy(val: string): string {
95
- return val
96
- .replace(/\\/g, '\\\\')
97
- .replace(/\t/g, '\\t')
98
- .replace(/\n/g, '\\n')
99
- .replace(/\r/g, '\\r')
100
- }
101
-
102
- function generateStressDump(opts: {
103
- tables: number
104
- rowsPerTable: number
105
- columnsPerTable: number
106
- payloadBytes: number
107
- }): string {
108
- const lines: string[] = []
109
- lines.push('SET statement_timeout = 0;')
110
- lines.push("SET client_encoding = 'UTF8';")
111
- lines.push('SET standard_conforming_strings = on;')
112
- lines.push('')
113
-
114
- for (let t = 0; t < opts.tables; t++) {
115
- const table = `stress_restore_${t}`
116
- const cols = Array.from({ length: opts.columnsPerTable }, (_, i) => `c_${i} TEXT`)
117
- lines.push(
118
- `CREATE TABLE IF NOT EXISTS ${table} (id BIGINT PRIMARY KEY, ${cols.join(', ')});`
119
- )
120
- lines.push(
121
- `COPY ${table} (id, ${Array.from({ length: opts.columnsPerTable }, (_, i) => `c_${i}`).join(', ')}) FROM stdin;`
122
- )
123
-
124
- for (let r = 0; r < opts.rowsPerTable; r++) {
125
- const id = t * 1_000_000 + r + 1
126
- const row = Array.from({ length: opts.columnsPerTable }, (_, c) => {
127
- if (r % 97 === 0 && c === 0) return '\\N'
128
- const base = `t${t}_r${r}_c${c}_`
129
- return escapeCopy(base + 'x'.repeat(Math.max(1, opts.payloadBytes - base.length)))
130
- })
131
- lines.push(`${id}\t${row.join('\t')}`)
132
- }
133
- lines.push('\\.')
134
- lines.push('')
135
- }
136
-
137
- return lines.join('\n')
138
- }
139
-
140
- function connectAndSubscribe(
141
- port: number,
142
- downstream: Queue<unknown>,
143
- query: Record<string, unknown>
144
- ): Promise<WebSocket> {
145
- return new Promise((resolve, reject) => {
146
- const initConnectionMessage: [string, Record<string, unknown>] = [
147
- 'initConnection',
148
- {
149
- desiredQueriesPatch: [{ op: 'put', hash: 'q1', ast: query }],
150
- clientSchema: LIVE_CLIENT_SCHEMA,
151
- },
152
- ]
153
- const secProtocol = encodeSecProtocols(initConnectionMessage, undefined)
154
- const ws = new WebSocket(
155
- `ws://127.0.0.1:${port}/sync/v${SYNC_PROTOCOL_VERSION}/connect` +
156
- `?clientGroupID=restore-live-cg-${Date.now()}` +
157
- `&clientID=restore-live-client` +
158
- `&wsid=ws1&schemaVersion=1&baseCookie=&ts=${Date.now()}&lmid=0`,
159
- secProtocol
160
- )
161
-
162
- let settled = false
163
- const failTimer = setTimeout(() => {
164
- if (settled) return
165
- settled = true
166
- try {
167
- ws.close()
168
- } catch {}
169
- reject(new Error('websocket connected but no downstream messages'))
170
- }, 7000)
171
-
172
- ws.on('message', (data) => {
173
- const msg = JSON.parse(data.toString())
174
- downstream.enqueue(msg)
175
- if (!settled) {
176
- settled = true
177
- clearTimeout(failTimer)
178
- resolve(ws)
179
- }
180
- })
181
- ws.once('error', (err) => {
182
- if (settled) return
183
- settled = true
184
- clearTimeout(failTimer)
185
- reject(err)
186
- })
187
- ws.once('close', () => {
188
- if (settled) return
189
- settled = true
190
- clearTimeout(failTimer)
191
- reject(new Error('websocket closed before initial downstream message'))
192
- })
193
- })
194
- }
195
-
196
- async function connectAndSubscribeWithRetry(
197
- port: number,
198
- downstream: Queue<unknown>,
199
- query: Record<string, unknown>,
200
- timeoutMs = 30_000
201
- ): Promise<WebSocket> {
202
- const deadline = Date.now() + timeoutMs
203
- let lastErr: unknown
204
- while (Date.now() < deadline) {
205
- try {
206
- return await connectAndSubscribe(port, downstream, query)
207
- } catch (err) {
208
- lastErr = err
209
- await new Promise((r) => setTimeout(r, 300))
210
- }
211
- }
212
- throw new Error(
213
- `timed out connecting websocket after reset: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`
214
- )
215
- }
216
-
217
- async function drainInitialPokes(downstream: Queue<unknown>) {
218
- const deadline = Date.now() + 30_000
219
- while (Date.now() < deadline) {
220
- const msg = (await downstream.dequeue('timeout' as any, 3000)) as any
221
- if (msg === 'timeout') return
222
- if (Array.isArray(msg) && msg[0] === 'pokeEnd') return
223
- }
224
- }
225
-
226
- async function waitForPokeWithValue(
227
- downstream: Queue<unknown>,
228
- expectedValue: string,
229
- timeoutMs = 20_000
230
- ): Promise<void> {
231
- const deadline = Date.now() + timeoutMs
232
- const seen: unknown[] = []
233
- while (Date.now() < deadline) {
234
- const remaining = Math.max(1000, deadline - Date.now())
235
- const msg = (await downstream.dequeue('timeout' as any, remaining)) as any
236
- if (msg === 'timeout') {
237
- throw new Error(
238
- `timed out waiting for pokePart; recent messages: ${JSON.stringify(seen.slice(-8))}`
239
- )
240
- }
241
- seen.push(msg)
242
- if (!Array.isArray(msg) || msg[0] !== 'pokePart' || !msg[1]?.rowsPatch) continue
243
- const rowsPatch = msg[1].rowsPatch as Array<Record<string, any>>
244
- if (
245
- rowsPatch.some(
246
- (patch) =>
247
- patch.op === 'put' &&
248
- patch.tableName === 'restore_live_probe' &&
249
- patch.value?.value === expectedValue
250
- )
251
- ) {
252
- return
253
- }
254
- }
255
- throw new Error(
256
- `timed out waiting for restore_live_probe value "${expectedValue}"; recent messages: ${JSON.stringify(seen.slice(-8))}`
257
- )
258
- }
259
-
260
- async function waitForZero(port: number, timeoutMs = 60_000) {
261
- const { Socket } = await import('node:net')
262
- const deadline = Date.now() + timeoutMs
263
- while (Date.now() < deadline) {
264
- const ok = await new Promise<boolean>((resolve) => {
265
- const sock = new Socket()
266
- const done = (value: boolean) => {
267
- sock.removeAllListeners()
268
- try {
269
- sock.destroy()
270
- } catch {}
271
- resolve(value)
272
- }
273
- sock.setTimeout(1000)
274
- sock.once('connect', () => done(true))
275
- sock.once('timeout', () => done(false))
276
- sock.once('error', () => done(false))
277
- sock.connect(port, '127.0.0.1')
278
- })
279
- if (ok) return
280
- await new Promise((r) => setTimeout(r, 500))
281
- }
282
- throw new Error(`zero-cache not ready on port ${port} after ${timeoutMs}ms`)
283
- }
284
-
285
- describe('live restore stress with connected frontend', { timeout: 360_000 }, () => {
286
- let db: PGlite
287
- let pgPort: number
288
- let zeroPort: number
289
- let shutdown: () => Promise<void>
290
- let restartZero: (() => Promise<void>) | undefined
291
- let resetZeroFull: (() => Promise<void>) | undefined
292
- let dataDir: string
293
- let dumpFile: string
294
-
295
- beforeAll(async () => {
296
- await loadModule()
297
-
298
- const tables = envInt('OREZ_STRESS_TABLES', 6)
299
- const rowsPerTable = envInt('OREZ_STRESS_ROWS', 1800)
300
- const columnsPerTable = envInt('OREZ_STRESS_COLS', 8)
301
- const payloadBytes = envInt('OREZ_STRESS_PAYLOAD', 96)
302
-
303
- dumpFile = join(tmpdir(), `orez-live-stress-${Date.now()}.sql`)
304
- writeFileSync(
305
- dumpFile,
306
- generateStressDump({ tables, rowsPerTable, columnsPerTable, payloadBytes })
307
- )
308
-
309
- dataDir = `.orez-live-stress-test-${Date.now()}`
310
- const started = await startZeroLite({
311
- pgPort: 29000 + Math.floor(Math.random() * 1000),
312
- zeroPort: 30000 + Math.floor(Math.random() * 1000),
313
- dataDir,
314
- logLevel: 'warn',
315
- skipZeroCache: false,
316
- })
317
-
318
- db = started.db
319
- pgPort = started.pgPort
320
- zeroPort = started.zeroPort
321
- shutdown = started.stop
322
- restartZero = started.restartZero
323
- resetZeroFull = started.resetZeroFull
324
- await waitForZero(zeroPort, 90_000)
325
- }, 180_000)
326
-
327
- afterAll(async () => {
328
- if (shutdown) await shutdown()
329
- try {
330
- unlinkSync(dumpFile)
331
- } catch {}
332
- if (dataDir) {
333
- try {
334
- rmSync(dataDir, { recursive: true, force: true })
335
- } catch {}
336
- }
337
- })
338
-
339
- test('frontend stays connected through restore lifecycle and syncs after reset', async () => {
340
- await db.exec(`
341
- CREATE TABLE IF NOT EXISTS restore_live_probe (
342
- id TEXT PRIMARY KEY,
343
- value TEXT NOT NULL
344
- )
345
- `)
346
- await ensureTablesInPublications(db, ['restore_live_probe'])
347
- await installAllowAllPermissions(db, ['restore_live_probe'])
348
- expect(await hasNonNullPermissions(db)).toBe(true)
349
- if (resetZeroFull) {
350
- await resetZeroFull()
351
- await waitForZero(zeroPort, 90_000)
352
- } else if (restartZero) {
353
- await restartZero()
354
- await waitForZero(zeroPort, 60_000)
355
- }
356
- const pubName = process.env.ZERO_APP_PUBLICATIONS?.trim()
357
- if (pubName) {
358
- const quotedPub = '"' + pubName.replace(/"/g, '""') + '"'
359
- await db
360
- .exec(`ALTER PUBLICATION ${quotedPub} ADD TABLE "public"."restore_live_probe"`)
361
- .catch(() => {})
362
- await installChangeTracking(db)
363
- }
364
- await db.query(`INSERT INTO restore_live_probe (id, value) VALUES ($1, $2)`, [
365
- 'before-restore',
366
- 'before',
367
- ])
368
-
369
- const downstream = new Queue<unknown>()
370
- let ws = await connectAndSubscribeWithRetry(zeroPort, downstream, {
371
- table: 'restore_live_probe',
372
- orderBy: [['id', 'asc']],
373
- })
374
- await drainInitialPokes(downstream)
375
-
376
- // restore while websocket is connected (frontend simulation)
377
- const sql = postgres({
378
- host: '127.0.0.1',
379
- port: pgPort,
380
- user: 'user',
381
- password: 'password',
382
- database: 'postgres',
383
- max: 1,
384
- onnotice: () => {},
385
- })
386
- try {
387
- const wireDb = { exec: (query: string) => sql.unsafe(query) as Promise<unknown> }
388
- await execDumpFile(wireDb, dumpFile)
389
- } finally {
390
- await sql.end({ timeout: 1 }).catch(() => {})
391
- }
392
-
393
- const pid = Number(readFileSync(join(dataDir, 'orez.pid'), 'utf-8').trim())
394
- expect(pid).toBeGreaterThan(0)
395
- process.kill(pid, 'SIGUSR1')
396
- await waitForZero(zeroPort, 90_000)
397
- if (pubName) {
398
- const quotedPub = '"' + pubName.replace(/"/g, '""') + '"'
399
- await db
400
- .exec(`ALTER PUBLICATION ${quotedPub} ADD TABLE "public"."restore_live_probe"`)
401
- .catch(() => {})
402
- }
403
-
404
- try {
405
- ws.close()
406
- } catch {}
407
- const downstreamAfterReset = new Queue<unknown>()
408
- ws = await connectAndSubscribeWithRetry(zeroPort, downstreamAfterReset, {
409
- table: 'restore_live_probe',
410
- orderBy: [['id', 'asc']],
411
- })
412
- await drainInitialPokes(downstreamAfterReset)
413
-
414
- // verify write is captured in change tracking after reset
415
- const marker = `after-${Date.now()}`
416
- await db.query(`INSERT INTO restore_live_probe (id, value) VALUES ($1, $2)`, [
417
- `post-restore-${Date.now()}`,
418
- marker,
419
- ])
420
- const tracked = await db.query<{ count: string }>(
421
- `SELECT count(*)::text as count
422
- FROM _orez._zero_changes
423
- WHERE table_name = 'public.restore_live_probe'`
424
- )
425
- if (Number(tracked.rows[0]?.count || '0') === 0) {
426
- throw new Error('post-reset write was not captured in _orez._zero_changes')
427
- }
428
-
429
- await waitForPokeWithValue(downstreamAfterReset, marker, 30_000)
430
-
431
- ws.close()
432
- })
433
- })