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,517 +0,0 @@
1
- /**
2
- * integration test adapted from zero-cache's integration.pg.test.ts
3
- *
4
- * validates the full sync pipeline: pglite → change tracking → replication
5
- * protocol → zero-cache → websocket poke messages to clients.
6
- *
7
- * uses orez's startZeroLite() instead of real postgres + manual zero-cache.
8
- */
9
-
10
- import { describe, expect, test, beforeAll, afterAll, beforeEach } from 'vitest'
11
- import WebSocket from 'ws'
12
-
13
- import { startZeroLite } from '../index.js'
14
- import { installChangeTracking } from '../replication/change-tracker.js'
15
- import {
16
- ensureTablesInPublications,
17
- hasNonNullPermissions,
18
- installAllowAllPermissions,
19
- } from './test-permissions.js'
20
-
21
- import type { PGlite } from '@electric-sql/pglite'
22
-
23
- const SYNC_PROTOCOL_VERSION = 49
24
- const CLIENT_SCHEMA = {
25
- tables: {
26
- foo: {
27
- columns: {
28
- id: { type: 'string' },
29
- value: { type: 'string' },
30
- num: { type: 'number' },
31
- },
32
- primaryKey: ['id'],
33
- },
34
- },
35
- }
36
-
37
- function encodeSecProtocols(
38
- initConnectionMessage: unknown,
39
- authToken: string | undefined
40
- ): string {
41
- const payload = JSON.stringify({ initConnectionMessage, authToken })
42
- return encodeURIComponent(Buffer.from(payload, 'utf-8').toString('base64'))
43
- }
44
-
45
- // simple async queue for collecting websocket messages
46
- class Queue<T> {
47
- private items: T[] = []
48
- private waiters: Array<{
49
- resolve: (v: T) => void
50
- timer?: ReturnType<typeof setTimeout>
51
- }> = []
52
-
53
- enqueue(item: T) {
54
- const waiter = this.waiters.shift()
55
- if (waiter) {
56
- if (waiter.timer) clearTimeout(waiter.timer)
57
- waiter.resolve(item)
58
- } else {
59
- this.items.push(item)
60
- }
61
- }
62
-
63
- dequeue(fallback?: T, timeoutMs = 10000): Promise<T> {
64
- if (this.items.length > 0) {
65
- return Promise.resolve(this.items.shift()!)
66
- }
67
- return new Promise<T>((resolve) => {
68
- const waiter: { resolve: (v: T) => void; timer?: ReturnType<typeof setTimeout> } = {
69
- resolve,
70
- }
71
- if (fallback !== undefined) {
72
- waiter.timer = setTimeout(() => {
73
- const idx = this.waiters.indexOf(waiter)
74
- if (idx >= 0) this.waiters.splice(idx, 1)
75
- resolve(fallback)
76
- }, timeoutMs)
77
- }
78
- this.waiters.push(waiter)
79
- })
80
- }
81
- }
82
-
83
- describe('orez integration', { timeout: 120000 }, () => {
84
- let db: PGlite
85
- let zeroPort: number
86
- let pgPort: number
87
- let shutdown: () => Promise<void>
88
- let restartZero: (() => Promise<void>) | undefined
89
- let resetZeroFull: (() => Promise<void>) | undefined
90
- let dataDir: string
91
-
92
- beforeAll(async () => {
93
- const testPgPort = 23000 + Math.floor(Math.random() * 1000)
94
- const testZeroPort = testPgPort + 100
95
-
96
- dataDir = `.orez-integration-test-${Date.now()}`
97
- console.log(`[test] starting orez on pg:${testPgPort} zero:${testZeroPort}`)
98
- const result = await startZeroLite({
99
- pgPort: testPgPort,
100
- zeroPort: testZeroPort,
101
- dataDir,
102
- logLevel: 'info',
103
- skipZeroCache: false,
104
- ...(process.env.FORCE_WASM === '1' ? { forceWasmSqlite: true } : {}),
105
- })
106
-
107
- db = result.db
108
- zeroPort = result.zeroPort
109
- pgPort = result.pgPort
110
- shutdown = result.stop
111
- restartZero = result.restartZero
112
- resetZeroFull = result.resetZeroFull
113
-
114
- console.log(`[test] orez started, creating tables`)
115
-
116
- // create test tables
117
- await db.exec(`
118
- CREATE TABLE IF NOT EXISTS foo (
119
- id TEXT PRIMARY KEY,
120
- value TEXT,
121
- num INTEGER
122
- );
123
-
124
- CREATE TABLE IF NOT EXISTS bar (
125
- id TEXT PRIMARY KEY,
126
- foo_id TEXT
127
- );
128
- `)
129
- await ensureTablesInPublications(db, ['foo', 'bar'])
130
- const pubName = process.env.ZERO_APP_PUBLICATIONS?.trim()
131
- if (pubName) {
132
- const quotedPub = '"' + pubName.replace(/"/g, '""') + '"'
133
- await db
134
- .exec(`ALTER PUBLICATION ${quotedPub} ADD TABLE "public"."foo"`)
135
- .catch(() => {})
136
- await db
137
- .exec(`ALTER PUBLICATION ${quotedPub} ADD TABLE "public"."bar"`)
138
- .catch(() => {})
139
- await installChangeTracking(db)
140
- }
141
- await installAllowAllPermissions(db, ['foo', 'bar'])
142
- expect(await hasNonNullPermissions(db)).toBe(true)
143
- if (resetZeroFull) {
144
- await resetZeroFull()
145
- } else if (restartZero) {
146
- await restartZero()
147
- }
148
- const pubNameAfterReset = process.env.ZERO_APP_PUBLICATIONS?.trim()
149
- if (pubNameAfterReset) {
150
- const pubRows = await db.query<{ tablename: string }>(
151
- `SELECT tablename
152
- FROM pg_publication_tables
153
- WHERE pubname = $1
154
- AND schemaname = 'public'`,
155
- [pubNameAfterReset]
156
- )
157
- expect(pubRows.rows.map((r) => r.tablename)).toEqual(
158
- expect.arrayContaining(['foo', 'bar'])
159
- )
160
- const shardCfg = await db.query<{ publications: string[] }>(
161
- `SELECT publications FROM "zero_0"."shardConfig" WHERE lock = true`
162
- )
163
- expect(shardCfg.rows[0]?.publications || []).toContain(pubNameAfterReset)
164
- }
165
- expect(await hasNonNullPermissions(db)).toBe(true)
166
-
167
- console.log(`[test] tables created, waiting for zero-cache`)
168
- // wait for zero-cache to be ready
169
- await waitForZero(zeroPort, 90000)
170
- console.log(`[test] zero-cache ready`)
171
- }, 120000)
172
-
173
- afterAll(async () => {
174
- if (shutdown) await shutdown()
175
- if (dataDir) {
176
- const { rmSync } = await import('node:fs')
177
- try {
178
- rmSync(dataDir, { recursive: true, force: true })
179
- } catch {}
180
- }
181
- })
182
-
183
- beforeEach(async () => {
184
- // clean tables between tests
185
- await db.exec(`DELETE FROM foo; DELETE FROM bar;`)
186
- // wait for replication to consume cleanup changes so zero-cache's replica is clean
187
- await waitForReplicationCatchup(db)
188
- // settle time for zero-cache to finish processing previous client views
189
- await new Promise((r) => setTimeout(r, 2000))
190
- })
191
-
192
- // wait until the replication handler has consumed all pending changes.
193
- // zero-cache queries its own sqlite replica, so data written to pglite
194
- // isn't visible to clients until replication streams it through.
195
- async function waitForReplicationCatchup(
196
- pglite: PGlite,
197
- timeoutMs = 15000
198
- ): Promise<void> {
199
- const deadline = Date.now() + timeoutMs
200
- while (Date.now() < deadline) {
201
- const result = await pglite.query<{ count: string }>(
202
- `SELECT count(*)::text as count FROM _orez._zero_changes`
203
- )
204
- if (Number(result.rows[0]?.count) === 0) return
205
- await new Promise((r) => setTimeout(r, 100))
206
- }
207
- }
208
-
209
- test('zero-cache starts and accepts websocket connections', async () => {
210
- const cg = `test-cg-${Date.now()}`
211
- const cid = `test-client-${Date.now()}`
212
- const secProtocol = encodeSecProtocols(
213
- ['initConnection', { desiredQueriesPatch: [] }],
214
- undefined
215
- )
216
- const ws = new WebSocket(
217
- `ws://localhost:${zeroPort}/sync/v${SYNC_PROTOCOL_VERSION}/connect` +
218
- `?clientGroupID=${cg}&clientID=${cid}&wsid=ws1&schemaVersion=1&baseCookie=&ts=${Date.now()}&lmid=0`,
219
- secProtocol
220
- )
221
-
222
- const connected = new Promise<void>((resolve, reject) => {
223
- ws.on('open', resolve)
224
- ws.on('error', reject)
225
- setTimeout(() => reject(new Error('ws connect timeout')), 5000)
226
- })
227
-
228
- await connected
229
-
230
- const firstMessage = await new Promise<unknown>((resolve) => {
231
- ws.on('message', (data) => {
232
- resolve(JSON.parse(data.toString()))
233
- })
234
- })
235
-
236
- expect(firstMessage).toMatchObject(['connected', { wsid: 'ws1' }])
237
-
238
- ws.close()
239
- })
240
-
241
- test('initial sync delivers existing rows via poke', async () => {
242
- // insert data before connecting
243
- await db.query(`INSERT INTO foo (id, value, num) VALUES ($1, $2, $3)`, [
244
- 'row1',
245
- 'hello',
246
- 42,
247
- ])
248
-
249
- // wait for replication to deliver the row to zero-cache's replica
250
- await waitForReplicationCatchup(db)
251
- // extra settle time for zero-cache to process the replication stream
252
- await new Promise((r) => setTimeout(r, 1000))
253
-
254
- const downstream = new Queue<unknown>()
255
- const ws = connectAndSubscribe(zeroPort, downstream, {
256
- table: 'foo',
257
- orderBy: [['id', 'asc']],
258
- })
259
-
260
- // drain until we get a pokePart with rowsPatch containing our data
261
- const poke = await waitForPokePart(downstream, 30000)
262
- expect(poke.rowsPatch).toEqual(
263
- expect.arrayContaining([
264
- expect.objectContaining({
265
- op: 'put',
266
- tableName: 'foo',
267
- value: expect.objectContaining({
268
- id: 'row1',
269
- value: 'hello',
270
- }),
271
- }),
272
- ])
273
- )
274
-
275
- ws.close()
276
- })
277
-
278
- test('live replication: insert triggers poke', async () => {
279
- const downstream = new Queue<unknown>()
280
- const ws = connectAndSubscribe(zeroPort, downstream, {
281
- table: 'foo',
282
- orderBy: [['id', 'asc']],
283
- })
284
-
285
- // drain initial connection + sync pokes
286
- await drainInitialPokes(downstream)
287
-
288
- // now insert data - this should trigger a replication poke
289
- await db.query(`INSERT INTO foo (id, value, num) VALUES ($1, $2, $3)`, [
290
- 'live-row',
291
- 'live-value',
292
- 99,
293
- ])
294
-
295
- // wait for the replication poke
296
- const poke = await waitForPokePart(downstream, 30000)
297
- expect(poke.rowsPatch).toEqual(
298
- expect.arrayContaining([
299
- expect.objectContaining({
300
- op: 'put',
301
- tableName: 'foo',
302
- value: expect.objectContaining({
303
- id: 'live-row',
304
- value: 'live-value',
305
- }),
306
- }),
307
- ])
308
- )
309
-
310
- ws.close()
311
- })
312
-
313
- test('live replication: update triggers poke', async () => {
314
- // insert initial data
315
- await db.query(`INSERT INTO foo (id, value, num) VALUES ($1, $2, $3)`, [
316
- 'upd-row',
317
- 'original',
318
- 1,
319
- ])
320
-
321
- const downstream = new Queue<unknown>()
322
- const ws = connectAndSubscribe(zeroPort, downstream, {
323
- table: 'foo',
324
- orderBy: [['id', 'asc']],
325
- })
326
-
327
- await drainInitialPokes(downstream)
328
-
329
- // update the row
330
- await db.query(`UPDATE foo SET value = $1, num = $2 WHERE id = $3`, [
331
- 'updated',
332
- 2,
333
- 'upd-row',
334
- ])
335
-
336
- const poke = await waitForPokePart(downstream, 30000)
337
- expect(poke.rowsPatch).toEqual(
338
- expect.arrayContaining([
339
- expect.objectContaining({
340
- op: 'put',
341
- tableName: 'foo',
342
- value: expect.objectContaining({
343
- id: 'upd-row',
344
- value: 'updated',
345
- }),
346
- }),
347
- ])
348
- )
349
-
350
- ws.close()
351
- })
352
-
353
- test('live replication: delete triggers poke', async () => {
354
- await db.query(`INSERT INTO foo (id, value, num) VALUES ($1, $2, $3)`, [
355
- 'del-row',
356
- 'to-delete',
357
- 1,
358
- ])
359
-
360
- const downstream = new Queue<unknown>()
361
- const ws = connectAndSubscribe(zeroPort, downstream, {
362
- table: 'foo',
363
- orderBy: [['id', 'asc']],
364
- })
365
-
366
- await drainInitialPokes(downstream)
367
-
368
- // delete the row
369
- await db.query(`DELETE FROM foo WHERE id = $1`, ['del-row'])
370
-
371
- const poke = await waitForPokePart(downstream, 30000)
372
- expect(poke.rowsPatch).toEqual(
373
- expect.arrayContaining([
374
- expect.objectContaining({
375
- op: 'del',
376
- tableName: 'foo',
377
- }),
378
- ])
379
- )
380
-
381
- ws.close()
382
- })
383
-
384
- test('concurrent inserts all replicate', { timeout: 60000 }, async () => {
385
- const downstream = new Queue<unknown>()
386
- const ws = connectAndSubscribe(zeroPort, downstream, {
387
- table: 'foo',
388
- orderBy: [['id', 'asc']],
389
- })
390
-
391
- await drainInitialPokes(downstream)
392
-
393
- // insert 5 rows concurrently
394
- await Promise.all(
395
- Array.from({ length: 5 }, (_, i) =>
396
- db.query(`INSERT INTO foo (id, value, num) VALUES ($1, $2, $3)`, [
397
- `concurrent-${i}`,
398
- `value-${i}`,
399
- i,
400
- ])
401
- )
402
- )
403
-
404
- // collect all poke parts within a window
405
- const allRows = await collectPokeRows(downstream, 30000)
406
- const ids = allRows
407
- .filter((r: any) => r.op === 'put' && r.tableName === 'foo')
408
- .map((r: any) => r.value.id)
409
- .sort()
410
-
411
- expect(ids).toEqual([
412
- 'concurrent-0',
413
- 'concurrent-1',
414
- 'concurrent-2',
415
- 'concurrent-3',
416
- 'concurrent-4',
417
- ])
418
-
419
- ws.close()
420
- })
421
-
422
- // --- helpers ---
423
-
424
- function connectAndSubscribe(
425
- port: number,
426
- downstream: Queue<unknown>,
427
- query: Record<string, unknown>
428
- ): WebSocket {
429
- const cg = `test-cg-${Date.now()}`
430
- const cid = `test-client-${Date.now()}`
431
- const secProtocol = encodeSecProtocols(
432
- [
433
- 'initConnection',
434
- {
435
- desiredQueriesPatch: [{ op: 'put', hash: 'q1', ast: query }],
436
- clientSchema: CLIENT_SCHEMA,
437
- },
438
- ],
439
- undefined
440
- )
441
- const ws = new WebSocket(
442
- `ws://localhost:${port}/sync/v${SYNC_PROTOCOL_VERSION}/connect` +
443
- `?clientGroupID=${cg}&clientID=${cid}&wsid=ws1&schemaVersion=1&baseCookie=&ts=${Date.now()}&lmid=0`,
444
- secProtocol
445
- )
446
-
447
- ws.on('message', (data) => {
448
- downstream.enqueue(JSON.parse(data.toString()))
449
- })
450
-
451
- return ws
452
- }
453
-
454
- async function drainInitialPokes(downstream: Queue<unknown>) {
455
- // drain messages until we've seen the initial data sync complete
456
- let settled = false
457
- const timeout = Date.now() + 30000
458
-
459
- while (!settled && Date.now() < timeout) {
460
- const msg = (await downstream.dequeue('timeout' as any, 3000)) as any
461
- if (msg === 'timeout') {
462
- settled = true
463
- } else if (Array.isArray(msg) && msg[0] === 'pokeEnd') {
464
- const next = (await downstream.dequeue('timeout' as any, 2000)) as any
465
- if (next === 'timeout') {
466
- settled = true
467
- }
468
- }
469
- }
470
- }
471
-
472
- async function waitForPokePart(
473
- downstream: Queue<unknown>,
474
- timeoutMs = 10000
475
- ): Promise<Record<string, any>> {
476
- const deadline = Date.now() + timeoutMs
477
- while (Date.now() < deadline) {
478
- const remaining = Math.max(1000, deadline - Date.now())
479
- const msg = (await downstream.dequeue('timeout' as any, remaining)) as any
480
- if (msg === 'timeout') throw new Error('timed out waiting for pokePart')
481
- if (Array.isArray(msg) && msg[0] === 'pokePart' && msg[1]?.rowsPatch) {
482
- return msg[1]
483
- }
484
- }
485
- throw new Error('timed out waiting for pokePart')
486
- }
487
-
488
- async function collectPokeRows(
489
- downstream: Queue<unknown>,
490
- windowMs = 5000
491
- ): Promise<any[]> {
492
- const rows: any[] = []
493
- const deadline = Date.now() + windowMs
494
- // collect all poke parts until timeout
495
- while (Date.now() < deadline) {
496
- const remaining = Math.max(1000, deadline - Date.now())
497
- const msg = (await downstream.dequeue('timeout' as any, remaining)) as any
498
- if (msg === 'timeout') break
499
- if (Array.isArray(msg) && msg[0] === 'pokePart' && msg[1]?.rowsPatch) {
500
- rows.push(...msg[1].rowsPatch)
501
- }
502
- }
503
- return rows
504
- }
505
- })
506
-
507
- async function waitForZero(port: number, timeoutMs = 30000) {
508
- const deadline = Date.now() + timeoutMs
509
- while (Date.now() < deadline) {
510
- try {
511
- const res = await fetch(`http://localhost:${port}/`)
512
- if (res.ok || res.status === 404) return
513
- } catch {}
514
- await new Promise((r) => setTimeout(r, 500))
515
- }
516
- throw new Error(`zero-cache not ready on port ${port} after ${timeoutMs}ms`)
517
- }
@@ -1,13 +0,0 @@
1
- import { describe, expect, test } from 'vitest'
2
-
3
- import {
4
- formatNativeBootstrapInstructions,
5
- inspectNativeSqliteBinary,
6
- } from '../sqlite-mode/native-binary.js'
7
-
8
- describe('native sqlite binary guard', () => {
9
- test('better_sqlite3.node is present before native integration tests', () => {
10
- const check = inspectNativeSqliteBinary()
11
- expect(check.found, formatNativeBootstrapInstructions(check)).toBe(true)
12
- })
13
- })
@@ -1,44 +0,0 @@
1
- import { rmSync } from 'node:fs'
2
-
3
- import { afterAll, beforeAll, describe, expect, test } from 'vitest'
4
-
5
- import { startZeroLite } from '../index.js'
6
-
7
- describe('native sqlite startup integration', { timeout: 120_000 }, () => {
8
- let shutdown: (() => Promise<void>) | undefined
9
- let zeroPort = 0
10
- let dataDir = ''
11
-
12
- beforeAll(async () => {
13
- const basePort = 29000 + Math.floor(Math.random() * 1000)
14
- dataDir = `.orez-native-startup-test-${Date.now()}`
15
-
16
- const started = await startZeroLite({
17
- pgPort: basePort,
18
- zeroPort: basePort + 100,
19
- dataDir,
20
- logLevel: 'warn',
21
- skipZeroCache: false,
22
- disableWasmSqlite: true,
23
- })
24
-
25
- shutdown = started.stop
26
- zeroPort = started.zeroPort
27
- }, 60_000)
28
-
29
- afterAll(async () => {
30
- if (shutdown) await shutdown()
31
- if (dataDir) {
32
- try {
33
- rmSync(dataDir, { recursive: true, force: true })
34
- } catch {
35
- // ignore cleanup failures in test teardown
36
- }
37
- }
38
- })
39
-
40
- test('zero-cache responds in native mode', async () => {
41
- const response = await fetch(`http://127.0.0.1:${zeroPort}/`)
42
- expect([200, 404]).toContain(response.status)
43
- })
44
- })