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.
- package/dist/cf-do/worker.d.ts +3 -0
- package/dist/cf-do/worker.d.ts.map +1 -1
- package/dist/cf-do/worker.js +37 -15
- package/dist/cf-do/worker.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
- package/src/admin/admin-data.test.ts +0 -348
- package/src/admin/http-proxy.ts +0 -252
- package/src/admin/log-store.ts +0 -192
- package/src/admin/server.ts +0 -471
- package/src/admin/ui.ts +0 -1322
- package/src/bench/proxy-throughput.bench.ts +0 -343
- package/src/bench/serial-mutations.bench.ts +0 -270
- package/src/browser.ts +0 -203
- package/src/cf-do/.wrangler/cache/cf.json +0 -1
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0ffaabee41a60e04dd0eb7db3073f0a40139e6a97ccd26823967acb652b89a7b.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/.wrangler/tmp/bundle-0z4CpE/middleware-insertion-facade.js +0 -11
- package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-loader.entry.ts +0 -134
- package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-insertion-facade.js +0 -11
- package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-loader.entry.ts +0 -134
- package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js +0 -1059
- package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js.map +0 -8
- package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js +0 -1059
- package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js.map +0 -8
- package/src/cf-do/ARCHITECTURE.md +0 -93
- package/src/cf-do/CHAT_E2E.md +0 -213
- package/src/cf-do/watermark.test.ts +0 -103
- package/src/cf-do/watermark.ts +0 -118
- package/src/cf-do/worker.ts +0 -1041
- package/src/cf-do/wrangler.toml +0 -11
- package/src/cf-pglite/README.md +0 -19
- package/src/change-tracking.ts +0 -25
- package/src/child-process.test.ts +0 -147
- package/src/child-process.ts +0 -90
- package/src/cli-entry.ts +0 -72
- package/src/cli.test.ts +0 -40
- package/src/cli.ts +0 -1214
- package/src/config.ts +0 -150
- package/src/do-sql-tracking.test.ts +0 -19
- package/src/do-sql-tracking.ts +0 -19
- package/src/index.ts +0 -1215
- package/src/integration/integration.test.ts +0 -517
- package/src/integration/native-binary.guard.test.ts +0 -13
- package/src/integration/native-startup.test.ts +0 -44
- package/src/integration/replication-latency.test.ts +0 -428
- package/src/integration/restore-live-stress.test.ts +0 -433
- package/src/integration/restore-reset.test.ts +0 -400
- package/src/integration/restore.test.ts +0 -274
- package/src/integration/test-permissions.ts +0 -147
- package/src/load-config.ts +0 -46
- package/src/log.ts +0 -96
- package/src/mutex.ts +0 -47
- package/src/pg-proxy-browser.singledb.test.ts +0 -233
- package/src/pg-proxy-browser.ts +0 -2022
- package/src/pg-proxy-do-backend.test.ts +0 -3890
- package/src/pg-proxy-do-backend.ts +0 -7191
- package/src/pg-proxy.ts +0 -1087
- package/src/pg-sqlite-compiler/README.md +0 -53
- package/src/pg-sqlite-compiler/catalog/seed.ts +0 -524
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/arithmetic.json +0 -307
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/array.json +0 -377
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/cast.json +0 -12
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/catalog.json +0 -447
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/create-table.json +0 -32
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/datetime.json +0 -397
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/enum.json +0 -337
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/insert.json +0 -337
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/json.json +0 -537
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/misc.json +0 -1837
- package/src/pg-sqlite-compiler/index.ts +0 -73
- package/src/pg-sqlite-compiler/integration.test.ts +0 -136
- package/src/pg-sqlite-compiler/passes/ast-utils.ts +0 -113
- package/src/pg-sqlite-compiler/passes/catalog.ts +0 -65
- package/src/pg-sqlite-compiler/passes/datetime.ts +0 -74
- package/src/pg-sqlite-compiler/passes/index.ts +0 -49
- package/src/pg-sqlite-compiler/passes/types.ts +0 -156
- package/src/pg-sqlite-compiler/smoke.test.ts +0 -69
- package/src/pg-sqlite-compiler/test/catalog.test.ts +0 -171
- package/src/pg-sqlite-compiler/test/corpus.test.ts +0 -161
- package/src/pg-sqlite-compiler/test/datetime.oracle.test.ts +0 -102
- package/src/pg-sqlite-compiler/test/oracle.ts +0 -237
- package/src/pg-sqlite-compiler/test/types.test.ts +0 -109
- package/src/pg-sqlite-compiler/types.ts +0 -63
- package/src/pglite-ipc.test.ts +0 -116
- package/src/pglite-ipc.ts +0 -266
- package/src/pglite-manager.ts +0 -557
- package/src/pglite-web-proxy.test.ts +0 -57
- package/src/pglite-web-proxy.ts +0 -221
- package/src/pglite-web-worker.ts +0 -152
- package/src/pglite-worker-thread.ts +0 -253
- package/src/port.ts +0 -25
- package/src/process-title.ts +0 -9
- package/src/recovery.ts +0 -155
- package/src/replication/change-tracker.test.ts +0 -357
- package/src/replication/change-tracker.ts +0 -279
- package/src/replication/handler.test.ts +0 -511
- package/src/replication/handler.ts +0 -1190
- package/src/replication/pgoutput-encoder.test.ts +0 -697
- package/src/replication/pgoutput-encoder.ts +0 -373
- package/src/replication/tcp-replication.test.ts +0 -876
- package/src/replication/zero-compat.test.ts +0 -1150
- package/src/restore-stress.test.ts +0 -188
- package/src/s3-local.ts +0 -203
- package/src/shim/hooks.mjs +0 -120
- package/src/shim/register.mjs +0 -4
- package/src/sqlite-mode/apply-mode.ts +0 -224
- package/src/sqlite-mode/index.ts +0 -15
- package/src/sqlite-mode/native-binary.ts +0 -89
- package/src/sqlite-mode/package-resolve.ts +0 -17
- package/src/sqlite-mode/resolve-mode.ts +0 -80
- package/src/sqlite-mode/shim-template.ts +0 -159
- package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
- package/src/sqlite-mode/types.ts +0 -30
- package/src/vite-plugin.ts +0 -67
- package/src/wasm-sqlite.test.ts +0 -537
- package/src/worker/browser-admin.ts +0 -52
- package/src/worker/browser-build-config.test.ts +0 -71
- package/src/worker/browser-build-config.ts +0 -109
- package/src/worker/browser-embed-admin.test.ts +0 -75
- package/src/worker/browser-embed.ts +0 -345
- package/src/worker/cf-patches.ts +0 -384
- package/src/worker/embed-integration.test.ts +0 -321
- package/src/worker/index.ts +0 -138
- package/src/worker/shims/fastify.test.ts +0 -255
- package/src/worker/shims/fastify.ts +0 -306
- package/src/worker/shims/http-service.test.ts +0 -355
- package/src/worker/shims/http-service.ts +0 -293
- package/src/worker/shims/node-stub.ts +0 -290
- package/src/worker/shims/oxfmt.ts +0 -3
- package/src/worker/shims/postgres-browser.ts +0 -59
- package/src/worker/shims/postgres-socket.test.ts +0 -576
- package/src/worker/shims/postgres-socket.ts +0 -310
- package/src/worker/shims/postgres.test.ts +0 -364
- package/src/worker/shims/postgres.ts +0 -1454
- package/src/worker/shims/sqlite-browser.test.ts +0 -233
- package/src/worker/shims/sqlite-browser.ts +0 -175
- package/src/worker/shims/sqlite.test.ts +0 -786
- package/src/worker/shims/sqlite.ts +0 -978
- package/src/worker/shims/stream-browser.ts +0 -15
- package/src/worker/shims/ws-browser.test.ts +0 -205
- package/src/worker/shims/ws-browser.ts +0 -248
- package/src/worker/shims/ws.test.ts +0 -288
- package/src/worker/shims/ws.ts +0 -467
- package/src/worker/shims/zero-process-env.ts +0 -11
- package/src/worker/types.ts +0 -75
- package/src/worker/worker-integration.test.ts +0 -223
- package/src/worker/worker.test.ts +0 -136
- package/src/worker/zero-cache-embed-cf.ts +0 -463
- package/src/worker/zero-cache-embed.ts +0 -277
|
@@ -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
|
-
})
|