orez 0.2.27 → 0.2.30

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 (157) hide show
  1. package/dist/cf-do/worker.d.ts +3 -0
  2. package/dist/cf-do/worker.d.ts.map +1 -1
  3. package/dist/cf-do/worker.js +37 -15
  4. package/dist/cf-do/worker.js.map +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +8 -0
  7. package/dist/index.js.map +1 -1
  8. package/package.json +3 -4
  9. package/src/admin/admin-data.test.ts +0 -348
  10. package/src/admin/http-proxy.ts +0 -252
  11. package/src/admin/log-store.ts +0 -192
  12. package/src/admin/server.ts +0 -471
  13. package/src/admin/ui.ts +0 -1322
  14. package/src/bench/proxy-throughput.bench.ts +0 -343
  15. package/src/bench/serial-mutations.bench.ts +0 -270
  16. package/src/browser.ts +0 -203
  17. package/src/cf-do/.wrangler/cache/cf.json +0 -1
  18. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
  19. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
  20. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
  21. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0ffaabee41a60e04dd0eb7db3073f0a40139e6a97ccd26823967acb652b89a7b.sqlite +0 -0
  22. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
  23. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
  24. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
  25. package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-insertion-facade.js +0 -11
  26. package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-loader.entry.ts +0 -134
  27. package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-insertion-facade.js +0 -11
  28. package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-loader.entry.ts +0 -134
  29. package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js +0 -1059
  30. package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js.map +0 -8
  31. package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js +0 -1059
  32. package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js.map +0 -8
  33. package/src/cf-do/ARCHITECTURE.md +0 -93
  34. package/src/cf-do/CHAT_E2E.md +0 -213
  35. package/src/cf-do/watermark.test.ts +0 -103
  36. package/src/cf-do/watermark.ts +0 -118
  37. package/src/cf-do/worker.ts +0 -1041
  38. package/src/cf-do/wrangler.toml +0 -11
  39. package/src/cf-pglite/README.md +0 -19
  40. package/src/change-tracking.ts +0 -25
  41. package/src/child-process.test.ts +0 -147
  42. package/src/child-process.ts +0 -90
  43. package/src/cli-entry.ts +0 -72
  44. package/src/cli.test.ts +0 -40
  45. package/src/cli.ts +0 -1214
  46. package/src/config.ts +0 -150
  47. package/src/do-sql-tracking.test.ts +0 -19
  48. package/src/do-sql-tracking.ts +0 -19
  49. package/src/index.ts +0 -1215
  50. package/src/integration/integration.test.ts +0 -517
  51. package/src/integration/native-binary.guard.test.ts +0 -13
  52. package/src/integration/native-startup.test.ts +0 -44
  53. package/src/integration/replication-latency.test.ts +0 -428
  54. package/src/integration/restore-live-stress.test.ts +0 -433
  55. package/src/integration/restore-reset.test.ts +0 -400
  56. package/src/integration/restore.test.ts +0 -274
  57. package/src/integration/test-permissions.ts +0 -147
  58. package/src/load-config.ts +0 -46
  59. package/src/log.ts +0 -96
  60. package/src/mutex.ts +0 -47
  61. package/src/pg-proxy-browser.singledb.test.ts +0 -233
  62. package/src/pg-proxy-browser.ts +0 -2022
  63. package/src/pg-proxy-do-backend.test.ts +0 -3890
  64. package/src/pg-proxy-do-backend.ts +0 -7191
  65. package/src/pg-proxy.ts +0 -1087
  66. package/src/pg-sqlite-compiler/README.md +0 -53
  67. package/src/pg-sqlite-compiler/catalog/seed.ts +0 -524
  68. package/src/pg-sqlite-compiler/fixtures/pgsqlite/arithmetic.json +0 -307
  69. package/src/pg-sqlite-compiler/fixtures/pgsqlite/array.json +0 -377
  70. package/src/pg-sqlite-compiler/fixtures/pgsqlite/cast.json +0 -12
  71. package/src/pg-sqlite-compiler/fixtures/pgsqlite/catalog.json +0 -447
  72. package/src/pg-sqlite-compiler/fixtures/pgsqlite/create-table.json +0 -32
  73. package/src/pg-sqlite-compiler/fixtures/pgsqlite/datetime.json +0 -397
  74. package/src/pg-sqlite-compiler/fixtures/pgsqlite/enum.json +0 -337
  75. package/src/pg-sqlite-compiler/fixtures/pgsqlite/insert.json +0 -337
  76. package/src/pg-sqlite-compiler/fixtures/pgsqlite/json.json +0 -537
  77. package/src/pg-sqlite-compiler/fixtures/pgsqlite/misc.json +0 -1837
  78. package/src/pg-sqlite-compiler/index.ts +0 -73
  79. package/src/pg-sqlite-compiler/integration.test.ts +0 -136
  80. package/src/pg-sqlite-compiler/passes/ast-utils.ts +0 -113
  81. package/src/pg-sqlite-compiler/passes/catalog.ts +0 -65
  82. package/src/pg-sqlite-compiler/passes/datetime.ts +0 -74
  83. package/src/pg-sqlite-compiler/passes/index.ts +0 -49
  84. package/src/pg-sqlite-compiler/passes/types.ts +0 -156
  85. package/src/pg-sqlite-compiler/smoke.test.ts +0 -69
  86. package/src/pg-sqlite-compiler/test/catalog.test.ts +0 -171
  87. package/src/pg-sqlite-compiler/test/corpus.test.ts +0 -161
  88. package/src/pg-sqlite-compiler/test/datetime.oracle.test.ts +0 -102
  89. package/src/pg-sqlite-compiler/test/oracle.ts +0 -237
  90. package/src/pg-sqlite-compiler/test/types.test.ts +0 -109
  91. package/src/pg-sqlite-compiler/types.ts +0 -63
  92. package/src/pglite-ipc.test.ts +0 -116
  93. package/src/pglite-ipc.ts +0 -266
  94. package/src/pglite-manager.ts +0 -557
  95. package/src/pglite-web-proxy.test.ts +0 -57
  96. package/src/pglite-web-proxy.ts +0 -221
  97. package/src/pglite-web-worker.ts +0 -152
  98. package/src/pglite-worker-thread.ts +0 -253
  99. package/src/port.ts +0 -25
  100. package/src/process-title.ts +0 -9
  101. package/src/recovery.ts +0 -155
  102. package/src/replication/change-tracker.test.ts +0 -357
  103. package/src/replication/change-tracker.ts +0 -279
  104. package/src/replication/handler.test.ts +0 -511
  105. package/src/replication/handler.ts +0 -1190
  106. package/src/replication/pgoutput-encoder.test.ts +0 -697
  107. package/src/replication/pgoutput-encoder.ts +0 -373
  108. package/src/replication/tcp-replication.test.ts +0 -876
  109. package/src/replication/zero-compat.test.ts +0 -1150
  110. package/src/restore-stress.test.ts +0 -188
  111. package/src/s3-local.ts +0 -203
  112. package/src/shim/hooks.mjs +0 -120
  113. package/src/shim/register.mjs +0 -4
  114. package/src/sqlite-mode/apply-mode.ts +0 -224
  115. package/src/sqlite-mode/index.ts +0 -15
  116. package/src/sqlite-mode/native-binary.ts +0 -89
  117. package/src/sqlite-mode/package-resolve.ts +0 -17
  118. package/src/sqlite-mode/resolve-mode.ts +0 -80
  119. package/src/sqlite-mode/shim-template.ts +0 -159
  120. package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
  121. package/src/sqlite-mode/types.ts +0 -30
  122. package/src/vite-plugin.ts +0 -67
  123. package/src/wasm-sqlite.test.ts +0 -537
  124. package/src/worker/browser-admin.ts +0 -52
  125. package/src/worker/browser-build-config.test.ts +0 -71
  126. package/src/worker/browser-build-config.ts +0 -109
  127. package/src/worker/browser-embed-admin.test.ts +0 -75
  128. package/src/worker/browser-embed.ts +0 -345
  129. package/src/worker/cf-patches.ts +0 -384
  130. package/src/worker/embed-integration.test.ts +0 -321
  131. package/src/worker/index.ts +0 -138
  132. package/src/worker/shims/fastify.test.ts +0 -255
  133. package/src/worker/shims/fastify.ts +0 -306
  134. package/src/worker/shims/http-service.test.ts +0 -355
  135. package/src/worker/shims/http-service.ts +0 -293
  136. package/src/worker/shims/node-stub.ts +0 -290
  137. package/src/worker/shims/oxfmt.ts +0 -3
  138. package/src/worker/shims/postgres-browser.ts +0 -59
  139. package/src/worker/shims/postgres-socket.test.ts +0 -576
  140. package/src/worker/shims/postgres-socket.ts +0 -310
  141. package/src/worker/shims/postgres.test.ts +0 -364
  142. package/src/worker/shims/postgres.ts +0 -1454
  143. package/src/worker/shims/sqlite-browser.test.ts +0 -233
  144. package/src/worker/shims/sqlite-browser.ts +0 -175
  145. package/src/worker/shims/sqlite.test.ts +0 -786
  146. package/src/worker/shims/sqlite.ts +0 -978
  147. package/src/worker/shims/stream-browser.ts +0 -15
  148. package/src/worker/shims/ws-browser.test.ts +0 -205
  149. package/src/worker/shims/ws-browser.ts +0 -248
  150. package/src/worker/shims/ws.test.ts +0 -288
  151. package/src/worker/shims/ws.ts +0 -467
  152. package/src/worker/shims/zero-process-env.ts +0 -11
  153. package/src/worker/types.ts +0 -75
  154. package/src/worker/worker-integration.test.ts +0 -223
  155. package/src/worker/worker.test.ts +0 -136
  156. package/src/worker/zero-cache-embed-cf.ts +0 -463
  157. 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
- })