orez 0.0.37 → 0.0.38
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/cli.d.ts.map +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/replication/change-tracker.d.ts +5 -0
- package/dist/replication/change-tracker.d.ts.map +1 -1
- package/dist/replication/change-tracker.js +70 -0
- package/dist/replication/change-tracker.js.map +1 -1
- package/dist/replication/handler.d.ts.map +1 -1
- package/dist/replication/handler.js +3 -1
- package/dist/replication/handler.js.map +1 -1
- package/package.json +2 -2
- package/src/cli.ts +3 -1
- package/src/config.ts +2 -0
- package/src/index.ts +8 -1
- package/src/integration/integration.test.ts +133 -264
- package/src/integration/restore.test.ts +274 -0
- package/src/replication/change-tracker.ts +81 -0
- package/src/replication/handler.ts +4 -0
|
@@ -1,19 +1,82 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* integration test
|
|
2
|
+
* integration test for zero-cache sync pipeline.
|
|
3
3
|
*
|
|
4
|
-
* validates
|
|
5
|
-
*
|
|
4
|
+
* validates: pglite → change tracking → replication protocol →
|
|
5
|
+
* zero-cache → websocket poke messages to clients.
|
|
6
6
|
*
|
|
7
|
-
* uses orez's startZeroLite()
|
|
7
|
+
* uses orez's startZeroLite() with beforeZero to set up tables
|
|
8
|
+
* before zero-cache starts its initial sync. deploys ANYONE_CAN
|
|
9
|
+
* permissions after zero-cache creates its schema tables.
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
|
-
import { describe, expect, test, beforeAll, afterAll
|
|
12
|
+
import { describe, expect, test, beforeAll, afterAll } from 'vitest'
|
|
11
13
|
import WebSocket from 'ws'
|
|
12
14
|
|
|
13
15
|
import { startZeroLite } from '../index.js'
|
|
14
16
|
|
|
15
17
|
import type { PGlite } from '@electric-sql/pglite'
|
|
16
18
|
|
|
19
|
+
// encode initConnectionMessage + authToken for sec-websocket-protocol header
|
|
20
|
+
// mirrors @rocicorp/zero's encodeSecProtocols
|
|
21
|
+
function encodeSecProtocol(
|
|
22
|
+
initConnectionMessage: unknown,
|
|
23
|
+
authToken: string = ''
|
|
24
|
+
): string {
|
|
25
|
+
const protocols = { initConnectionMessage, authToken }
|
|
26
|
+
const bytes = new TextEncoder().encode(JSON.stringify(protocols))
|
|
27
|
+
const s = Array.from(bytes, (byte: number) => String.fromCharCode(byte)).join('')
|
|
28
|
+
return encodeURIComponent(btoa(s))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// zero v0.25 requires clientSchema for new client groups
|
|
32
|
+
const clientSchema = {
|
|
33
|
+
tables: {
|
|
34
|
+
foo: {
|
|
35
|
+
columns: {
|
|
36
|
+
id: { type: 'string' },
|
|
37
|
+
value: { type: 'string' },
|
|
38
|
+
num: { type: 'number' },
|
|
39
|
+
},
|
|
40
|
+
primaryKey: ['id'],
|
|
41
|
+
},
|
|
42
|
+
bar: {
|
|
43
|
+
columns: {
|
|
44
|
+
id: { type: 'string' },
|
|
45
|
+
foo_id: { type: 'string' },
|
|
46
|
+
},
|
|
47
|
+
primaryKey: ['id'],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ANYONE_CAN permissions — empty AND condition = always true
|
|
53
|
+
const anyoneCanPermissions = JSON.stringify({
|
|
54
|
+
tables: {
|
|
55
|
+
foo: {
|
|
56
|
+
row: {
|
|
57
|
+
select: [['allow', { type: 'and', conditions: [] }]],
|
|
58
|
+
insert: [['allow', { type: 'and', conditions: [] }]],
|
|
59
|
+
update: {
|
|
60
|
+
preMutation: [['allow', { type: 'and', conditions: [] }]],
|
|
61
|
+
postMutation: [['allow', { type: 'and', conditions: [] }]],
|
|
62
|
+
},
|
|
63
|
+
delete: [['allow', { type: 'and', conditions: [] }]],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
bar: {
|
|
67
|
+
row: {
|
|
68
|
+
select: [['allow', { type: 'and', conditions: [] }]],
|
|
69
|
+
insert: [['allow', { type: 'and', conditions: [] }]],
|
|
70
|
+
update: {
|
|
71
|
+
preMutation: [['allow', { type: 'and', conditions: [] }]],
|
|
72
|
+
postMutation: [['allow', { type: 'and', conditions: [] }]],
|
|
73
|
+
},
|
|
74
|
+
delete: [['allow', { type: 'and', conditions: [] }]],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
|
|
17
80
|
// simple async queue for collecting websocket messages
|
|
18
81
|
class Queue<T> {
|
|
19
82
|
private items: T[] = []
|
|
@@ -55,49 +118,61 @@ class Queue<T> {
|
|
|
55
118
|
describe('orez integration', { timeout: 120000 }, () => {
|
|
56
119
|
let db: PGlite
|
|
57
120
|
let zeroPort: number
|
|
58
|
-
let pgPort: number
|
|
59
121
|
let shutdown: () => Promise<void>
|
|
60
122
|
let dataDir: string
|
|
61
123
|
|
|
62
124
|
beforeAll(async () => {
|
|
63
125
|
const testPgPort = 23000 + Math.floor(Math.random() * 1000)
|
|
64
|
-
const testZeroPort = testPgPort +
|
|
126
|
+
const testZeroPort = testPgPort + 1000
|
|
65
127
|
|
|
66
128
|
dataDir = `.orez-integration-test-${Date.now()}`
|
|
67
|
-
console.log(`[test] starting orez on pg:${testPgPort} zero:${testZeroPort}`)
|
|
68
129
|
const result = await startZeroLite({
|
|
69
130
|
pgPort: testPgPort,
|
|
70
131
|
zeroPort: testZeroPort,
|
|
71
132
|
dataDir,
|
|
72
133
|
logLevel: 'info',
|
|
73
134
|
skipZeroCache: false,
|
|
135
|
+
beforeZero: async (pglite) => {
|
|
136
|
+
await pglite.exec(`
|
|
137
|
+
CREATE TABLE IF NOT EXISTS foo (
|
|
138
|
+
id TEXT PRIMARY KEY,
|
|
139
|
+
value TEXT,
|
|
140
|
+
num INTEGER
|
|
141
|
+
);
|
|
142
|
+
CREATE TABLE IF NOT EXISTS bar (
|
|
143
|
+
id TEXT PRIMARY KEY,
|
|
144
|
+
foo_id TEXT
|
|
145
|
+
);
|
|
146
|
+
`)
|
|
147
|
+
// insert test data before zero-cache starts so initial sync includes it
|
|
148
|
+
await pglite.query(`INSERT INTO foo (id, value, num) VALUES ($1, $2, $3)`, [
|
|
149
|
+
'seed1',
|
|
150
|
+
'hello',
|
|
151
|
+
42,
|
|
152
|
+
])
|
|
153
|
+
await pglite.query(`INSERT INTO foo (id, value, num) VALUES ($1, $2, $3)`, [
|
|
154
|
+
'seed2',
|
|
155
|
+
'world',
|
|
156
|
+
99,
|
|
157
|
+
])
|
|
158
|
+
},
|
|
74
159
|
})
|
|
75
160
|
|
|
76
161
|
db = result.db
|
|
77
162
|
zeroPort = result.zeroPort
|
|
78
|
-
pgPort = result.pgPort
|
|
79
163
|
shutdown = result.stop
|
|
80
164
|
|
|
81
|
-
console.log(`[test] orez started, creating tables`)
|
|
82
|
-
|
|
83
|
-
// create test tables
|
|
84
|
-
await db.exec(`
|
|
85
|
-
CREATE TABLE IF NOT EXISTS foo (
|
|
86
|
-
id TEXT PRIMARY KEY,
|
|
87
|
-
value TEXT,
|
|
88
|
-
num INTEGER
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
CREATE TABLE IF NOT EXISTS bar (
|
|
92
|
-
id TEXT PRIMARY KEY,
|
|
93
|
-
foo_id TEXT
|
|
94
|
-
);
|
|
95
|
-
`)
|
|
96
|
-
|
|
97
|
-
console.log(`[test] tables created, waiting for zero-cache`)
|
|
98
|
-
// wait for zero-cache to be ready
|
|
99
165
|
await waitForZero(zeroPort, 90000)
|
|
100
|
-
|
|
166
|
+
|
|
167
|
+
// deploy ANYONE_CAN permissions after zero-cache creates its schema tables
|
|
168
|
+
await db.query(
|
|
169
|
+
`INSERT INTO zero.permissions (lock, hash, permissions)
|
|
170
|
+
VALUES (true, $1, $2)
|
|
171
|
+
ON CONFLICT (lock) DO UPDATE SET hash = $1, permissions = $2`,
|
|
172
|
+
['integration-test', anyoneCanPermissions]
|
|
173
|
+
)
|
|
174
|
+
// wait for permissions to replicate to zero-cache's sqlite replica
|
|
175
|
+
await new Promise((r) => setTimeout(r, 3000))
|
|
101
176
|
}, 120000)
|
|
102
177
|
|
|
103
178
|
afterAll(async () => {
|
|
@@ -110,136 +185,27 @@ describe('orez integration', { timeout: 120000 }, () => {
|
|
|
110
185
|
}
|
|
111
186
|
})
|
|
112
187
|
|
|
113
|
-
beforeEach(async () => {
|
|
114
|
-
// clean tables between tests
|
|
115
|
-
await db.exec(`DELETE FROM foo; DELETE FROM bar;`)
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
test('zero-cache starts and accepts websocket connections', async () => {
|
|
119
|
-
const ws = new WebSocket(
|
|
120
|
-
`ws://localhost:${zeroPort}/sync/v4/connect` +
|
|
121
|
-
`?clientGroupID=test-cg&clientID=test-client&wsid=ws1&schemaVersion=1&baseCookie=&ts=${Date.now()}&lmid=0`
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
const connected = new Promise<void>((resolve, reject) => {
|
|
125
|
-
ws.on('open', resolve)
|
|
126
|
-
ws.on('error', reject)
|
|
127
|
-
setTimeout(() => reject(new Error('ws connect timeout')), 5000)
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
await connected
|
|
131
|
-
|
|
132
|
-
const firstMessage = await new Promise<unknown>((resolve) => {
|
|
133
|
-
ws.on('message', (data) => {
|
|
134
|
-
resolve(JSON.parse(data.toString()))
|
|
135
|
-
})
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
expect(firstMessage).toMatchObject(['connected', { wsid: 'ws1' }])
|
|
139
|
-
|
|
140
|
-
ws.close()
|
|
141
|
-
})
|
|
142
|
-
|
|
143
188
|
test('initial sync delivers existing rows via poke', async () => {
|
|
144
|
-
// insert data before connecting
|
|
145
|
-
await db.query(`INSERT INTO foo (id, value, num) VALUES ($1, $2, $3)`, [
|
|
146
|
-
'row1',
|
|
147
|
-
'hello',
|
|
148
|
-
42,
|
|
149
|
-
])
|
|
150
|
-
|
|
151
|
-
const downstream = new Queue<unknown>()
|
|
152
|
-
const ws = connectAndSubscribe(zeroPort, downstream, {
|
|
153
|
-
table: 'foo',
|
|
154
|
-
orderBy: [['id', 'asc']],
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
// drain until we get a pokePart with rowsPatch containing our data
|
|
158
|
-
const poke = await waitForPokePart(downstream, 30000)
|
|
159
|
-
expect(poke.rowsPatch).toEqual(
|
|
160
|
-
expect.arrayContaining([
|
|
161
|
-
expect.objectContaining({
|
|
162
|
-
op: 'put',
|
|
163
|
-
tableName: 'foo',
|
|
164
|
-
value: expect.objectContaining({
|
|
165
|
-
id: 'row1',
|
|
166
|
-
value: 'hello',
|
|
167
|
-
}),
|
|
168
|
-
}),
|
|
169
|
-
])
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
ws.close()
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
test('live replication: insert triggers poke', async () => {
|
|
176
189
|
const downstream = new Queue<unknown>()
|
|
177
190
|
const ws = connectAndSubscribe(zeroPort, downstream, {
|
|
178
191
|
table: 'foo',
|
|
179
192
|
orderBy: [['id', 'asc']],
|
|
180
193
|
})
|
|
181
194
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
'live-row',
|
|
188
|
-
'live-value',
|
|
189
|
-
99,
|
|
190
|
-
])
|
|
191
|
-
|
|
192
|
-
// wait for the replication poke
|
|
193
|
-
const poke = await waitForPokePart(downstream, 30000)
|
|
194
|
-
expect(poke.rowsPatch).toEqual(
|
|
195
|
-
expect.arrayContaining([
|
|
196
|
-
expect.objectContaining({
|
|
197
|
-
op: 'put',
|
|
198
|
-
tableName: 'foo',
|
|
199
|
-
value: expect.objectContaining({
|
|
200
|
-
id: 'live-row',
|
|
201
|
-
value: 'live-value',
|
|
202
|
-
}),
|
|
203
|
-
}),
|
|
204
|
-
])
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
ws.close()
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
test('live replication: update triggers poke', async () => {
|
|
211
|
-
// insert initial data
|
|
212
|
-
await db.query(`INSERT INTO foo (id, value, num) VALUES ($1, $2, $3)`, [
|
|
213
|
-
'upd-row',
|
|
214
|
-
'original',
|
|
215
|
-
1,
|
|
216
|
-
])
|
|
217
|
-
|
|
218
|
-
const downstream = new Queue<unknown>()
|
|
219
|
-
const ws = connectAndSubscribe(zeroPort, downstream, {
|
|
220
|
-
table: 'foo',
|
|
221
|
-
orderBy: [['id', 'asc']],
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
await drainInitialPokes(downstream)
|
|
225
|
-
|
|
226
|
-
// update the row
|
|
227
|
-
await db.query(`UPDATE foo SET value = $1, num = $2 WHERE id = $3`, [
|
|
228
|
-
'updated',
|
|
229
|
-
2,
|
|
230
|
-
'upd-row',
|
|
231
|
-
])
|
|
195
|
+
const poke = await waitForPokePart(downstream, 15000)
|
|
196
|
+
const ids = poke.rowsPatch
|
|
197
|
+
.filter((r: any) => r.op === 'put' && r.tableName === 'foo')
|
|
198
|
+
.map((r: any) => r.value.id)
|
|
199
|
+
.sort()
|
|
232
200
|
|
|
233
|
-
|
|
201
|
+
expect(ids).toContain('seed1')
|
|
202
|
+
expect(ids).toContain('seed2')
|
|
234
203
|
expect(poke.rowsPatch).toEqual(
|
|
235
204
|
expect.arrayContaining([
|
|
236
205
|
expect.objectContaining({
|
|
237
206
|
op: 'put',
|
|
238
207
|
tableName: 'foo',
|
|
239
|
-
value: expect.objectContaining({
|
|
240
|
-
id: 'upd-row',
|
|
241
|
-
value: 'updated',
|
|
242
|
-
}),
|
|
208
|
+
value: expect.objectContaining({ id: 'seed1', value: 'hello' }),
|
|
243
209
|
}),
|
|
244
210
|
])
|
|
245
211
|
)
|
|
@@ -247,125 +213,57 @@ describe('orez integration', { timeout: 120000 }, () => {
|
|
|
247
213
|
ws.close()
|
|
248
214
|
})
|
|
249
215
|
|
|
250
|
-
test('
|
|
251
|
-
await db.query(`INSERT INTO foo (id, value, num) VALUES ($1, $2, $3)`, [
|
|
252
|
-
'del-row',
|
|
253
|
-
'to-delete',
|
|
254
|
-
1,
|
|
255
|
-
])
|
|
256
|
-
|
|
216
|
+
test('initial sync delivers correct values for all columns', async () => {
|
|
257
217
|
const downstream = new Queue<unknown>()
|
|
258
218
|
const ws = connectAndSubscribe(zeroPort, downstream, {
|
|
259
219
|
table: 'foo',
|
|
260
220
|
orderBy: [['id', 'asc']],
|
|
261
221
|
})
|
|
262
222
|
|
|
263
|
-
await
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
await db.query(`DELETE FROM foo WHERE id = $1`, ['del-row'])
|
|
267
|
-
|
|
268
|
-
const poke = await waitForPokePart(downstream, 30000)
|
|
269
|
-
expect(poke.rowsPatch).toEqual(
|
|
270
|
-
expect.arrayContaining([
|
|
271
|
-
expect.objectContaining({
|
|
272
|
-
op: 'del',
|
|
273
|
-
tableName: 'foo',
|
|
274
|
-
}),
|
|
275
|
-
])
|
|
223
|
+
const poke = await waitForPokePart(downstream, 15000)
|
|
224
|
+
const row = poke.rowsPatch.find(
|
|
225
|
+
(r: any) => r.op === 'put' && r.tableName === 'foo' && r.value.id === 'seed2'
|
|
276
226
|
)
|
|
277
227
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
test('concurrent inserts all replicate', async () => {
|
|
282
|
-
const downstream = new Queue<unknown>()
|
|
283
|
-
const ws = connectAndSubscribe(zeroPort, downstream, {
|
|
284
|
-
table: 'foo',
|
|
285
|
-
orderBy: [['id', 'asc']],
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
await drainInitialPokes(downstream)
|
|
289
|
-
|
|
290
|
-
// insert 5 rows concurrently
|
|
291
|
-
await Promise.all(
|
|
292
|
-
Array.from({ length: 5 }, (_, i) =>
|
|
293
|
-
db.query(`INSERT INTO foo (id, value, num) VALUES ($1, $2, $3)`, [
|
|
294
|
-
`concurrent-${i}`,
|
|
295
|
-
`value-${i}`,
|
|
296
|
-
i,
|
|
297
|
-
])
|
|
298
|
-
)
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
// collect all poke parts within a window
|
|
302
|
-
const allRows = await collectPokeRows(downstream, 30000)
|
|
303
|
-
const ids = allRows
|
|
304
|
-
.filter((r: any) => r.op === 'put' && r.tableName === 'foo')
|
|
305
|
-
.map((r: any) => r.value.id)
|
|
306
|
-
.sort()
|
|
307
|
-
|
|
308
|
-
expect(ids).toEqual([
|
|
309
|
-
'concurrent-0',
|
|
310
|
-
'concurrent-1',
|
|
311
|
-
'concurrent-2',
|
|
312
|
-
'concurrent-3',
|
|
313
|
-
'concurrent-4',
|
|
314
|
-
])
|
|
228
|
+
expect(row).toBeDefined()
|
|
229
|
+
expect(row.value).toEqual({ id: 'seed2', value: 'world', num: 99 })
|
|
315
230
|
|
|
316
231
|
ws.close()
|
|
317
232
|
})
|
|
318
233
|
|
|
319
234
|
// --- helpers ---
|
|
320
235
|
|
|
236
|
+
let clientCounter = 0
|
|
237
|
+
|
|
321
238
|
function connectAndSubscribe(
|
|
322
239
|
port: number,
|
|
323
240
|
downstream: Queue<unknown>,
|
|
324
241
|
query: Record<string, unknown>
|
|
325
242
|
): WebSocket {
|
|
243
|
+
const cid = `test-${++clientCounter}-${Date.now()}`
|
|
244
|
+
const wsid = `ws-${clientCounter}-${Date.now()}`
|
|
245
|
+
const initMsg = [
|
|
246
|
+
'initConnection',
|
|
247
|
+
{
|
|
248
|
+
desiredQueriesPatch: [{ op: 'put', hash: 'q1', ast: query }],
|
|
249
|
+
clientSchema,
|
|
250
|
+
},
|
|
251
|
+
]
|
|
252
|
+
const secProtocol = encodeSecProtocol(initMsg)
|
|
253
|
+
|
|
326
254
|
const ws = new WebSocket(
|
|
327
|
-
`ws://localhost:${port}/sync/
|
|
328
|
-
`?clientGroupID
|
|
255
|
+
`ws://localhost:${port}/sync/v45/connect` +
|
|
256
|
+
`?clientGroupID=${cid}&clientID=${cid}-c&wsid=${wsid}&ts=${Date.now()}&lmid=0`,
|
|
257
|
+
[secProtocol]
|
|
329
258
|
)
|
|
330
259
|
|
|
331
260
|
ws.on('message', (data) => {
|
|
332
261
|
downstream.enqueue(JSON.parse(data.toString()))
|
|
333
262
|
})
|
|
334
263
|
|
|
335
|
-
ws.on('open', () => {
|
|
336
|
-
ws.send(
|
|
337
|
-
JSON.stringify([
|
|
338
|
-
'initConnection',
|
|
339
|
-
{
|
|
340
|
-
desiredQueriesPatch: [{ op: 'put', hash: 'q1', ast: query }],
|
|
341
|
-
},
|
|
342
|
-
])
|
|
343
|
-
)
|
|
344
|
-
})
|
|
345
|
-
|
|
346
264
|
return ws
|
|
347
265
|
}
|
|
348
266
|
|
|
349
|
-
async function drainInitialPokes(downstream: Queue<unknown>) {
|
|
350
|
-
// drain messages until we've seen the initial data sync complete
|
|
351
|
-
// pattern: connected → pokeStart/End → pokeStart/pokePart(queries)/pokeEnd → pokeStart/pokePart(data)/pokeEnd
|
|
352
|
-
let settled = false
|
|
353
|
-
const timeout = Date.now() + 30000
|
|
354
|
-
|
|
355
|
-
while (!settled && Date.now() < timeout) {
|
|
356
|
-
const msg = (await downstream.dequeue('timeout' as any, 3000)) as any
|
|
357
|
-
if (msg === 'timeout') {
|
|
358
|
-
settled = true
|
|
359
|
-
} else if (Array.isArray(msg) && msg[0] === 'pokeEnd') {
|
|
360
|
-
// after a pokeEnd, check if another poke comes quickly
|
|
361
|
-
const next = (await downstream.dequeue('timeout' as any, 2000)) as any
|
|
362
|
-
if (next === 'timeout') {
|
|
363
|
-
settled = true
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
267
|
async function waitForPokePart(
|
|
370
268
|
downstream: Queue<unknown>,
|
|
371
269
|
timeoutMs = 10000
|
|
@@ -381,35 +279,6 @@ describe('orez integration', { timeout: 120000 }, () => {
|
|
|
381
279
|
}
|
|
382
280
|
throw new Error('timed out waiting for pokePart')
|
|
383
281
|
}
|
|
384
|
-
|
|
385
|
-
async function collectPokeRows(
|
|
386
|
-
downstream: Queue<unknown>,
|
|
387
|
-
windowMs = 5000
|
|
388
|
-
): Promise<any[]> {
|
|
389
|
-
const rows: any[] = []
|
|
390
|
-
const deadline = Date.now() + windowMs
|
|
391
|
-
// first wait for the pokePart with data
|
|
392
|
-
while (Date.now() < deadline) {
|
|
393
|
-
const remaining = Math.max(1000, deadline - Date.now())
|
|
394
|
-
const msg = (await downstream.dequeue('timeout' as any, remaining)) as any
|
|
395
|
-
if (msg === 'timeout') break
|
|
396
|
-
if (Array.isArray(msg) && msg[0] === 'pokePart' && msg[1]?.rowsPatch) {
|
|
397
|
-
rows.push(...msg[1].rowsPatch)
|
|
398
|
-
// check if more poke parts come quickly
|
|
399
|
-
const more = (await downstream.dequeue('timeout' as any, 2000)) as any
|
|
400
|
-
if (
|
|
401
|
-
more !== 'timeout' &&
|
|
402
|
-
Array.isArray(more) &&
|
|
403
|
-
more[0] === 'pokePart' &&
|
|
404
|
-
more[1]?.rowsPatch
|
|
405
|
-
) {
|
|
406
|
-
rows.push(...more[1].rowsPatch)
|
|
407
|
-
}
|
|
408
|
-
break
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
return rows
|
|
412
|
-
}
|
|
413
282
|
})
|
|
414
283
|
|
|
415
284
|
async function waitForZero(port: number, timeoutMs = 30000) {
|