orez 0.0.48 → 0.0.49

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 (53) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +6 -112
  3. package/dist/cli.js.map +1 -1
  4. package/dist/config.d.ts +0 -5
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +0 -5
  7. package/dist/config.js.map +1 -1
  8. package/dist/index.d.ts +0 -9
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +91 -280
  11. package/dist/index.js.map +1 -1
  12. package/dist/log.d.ts +0 -9
  13. package/dist/log.d.ts.map +1 -1
  14. package/dist/log.js +1 -24
  15. package/dist/log.js.map +1 -1
  16. package/dist/mutex.d.ts.map +1 -1
  17. package/dist/mutex.js +2 -13
  18. package/dist/mutex.js.map +1 -1
  19. package/dist/pg-proxy.d.ts +2 -3
  20. package/dist/pg-proxy.d.ts.map +1 -1
  21. package/dist/pg-proxy.js +167 -377
  22. package/dist/pg-proxy.js.map +1 -1
  23. package/dist/pglite-manager.d.ts +0 -1
  24. package/dist/pglite-manager.d.ts.map +1 -1
  25. package/dist/pglite-manager.js +2 -8
  26. package/dist/pglite-manager.js.map +1 -1
  27. package/dist/replication/change-tracker.d.ts +0 -6
  28. package/dist/replication/change-tracker.d.ts.map +1 -1
  29. package/dist/replication/change-tracker.js +1 -62
  30. package/dist/replication/change-tracker.js.map +1 -1
  31. package/dist/replication/handler.d.ts.map +1 -1
  32. package/dist/replication/handler.js +7 -66
  33. package/dist/replication/handler.js.map +1 -1
  34. package/dist/vite-plugin.d.ts +0 -3
  35. package/dist/vite-plugin.d.ts.map +1 -1
  36. package/dist/vite-plugin.js +0 -24
  37. package/dist/vite-plugin.js.map +1 -1
  38. package/package.json +5 -4
  39. package/src/cli.ts +18 -124
  40. package/src/config.ts +0 -10
  41. package/src/index.ts +92 -309
  42. package/src/integration/integration.test.ts +264 -133
  43. package/src/log.ts +1 -25
  44. package/src/mutex.ts +2 -12
  45. package/src/pg-proxy.ts +187 -451
  46. package/src/pglite-manager.ts +2 -9
  47. package/src/replication/change-tracker.ts +1 -83
  48. package/src/replication/handler.ts +6 -79
  49. package/src/replication/pgoutput-encoder.test.ts +0 -217
  50. package/src/replication/zero-compat.test.ts +1 -232
  51. package/src/shim/hooks.mjs +1 -1
  52. package/src/vite-plugin.ts +0 -28
  53. package/src/wasm-sqlite.test.ts +1 -2
@@ -16,7 +16,7 @@ export interface PGliteInstances {
16
16
  }
17
17
 
18
18
  // create a single pglite instance with given dataDir suffix
19
- export async function createInstance(
19
+ async function createInstance(
20
20
  config: ZeroLiteConfig,
21
21
  name: string,
22
22
  withExtensions: boolean
@@ -133,15 +133,8 @@ export async function runMigrations(db: PGlite, config: ZeroLiteConfig): Promise
133
133
  continue
134
134
  }
135
135
 
136
- const filePath = join(migrationsDir, file)
137
- if (!existsSync(filePath)) {
138
- // .ts-only custom migrations are handled by the app's own migration runner
139
- log.debug.orez(`skipping migration (no .sql file): ${name}`)
140
- continue
141
- }
142
-
143
136
  log.debug.orez(`applying migration: ${name}`)
144
- const sql = readFileSync(filePath, 'utf-8')
137
+ const sql = readFileSync(join(migrationsDir, file), 'utf-8')
145
138
 
146
139
  // split by drizzle's statement-breakpoint marker
147
140
  const statements = sql
@@ -140,58 +140,6 @@ async function installTriggersOnAllTables(db: PGlite): Promise<void> {
140
140
  log.debug.pglite(`installed change tracking triggers on ${count} tables`)
141
141
  }
142
142
 
143
- /**
144
- * re-install change tracking triggers on any public tables that don't have them.
145
- * catches tables created between startup and replication start.
146
- */
147
- export async function ensureChangeTrackingOnAllTables(db: PGlite): Promise<void> {
148
- const pubName = process.env.ZERO_APP_PUBLICATIONS
149
- let tables: { tablename: string }[]
150
-
151
- if (pubName) {
152
- const result = await db.query<{ tablename: string }>(
153
- `SELECT tablename FROM pg_publication_tables
154
- WHERE pubname = $1
155
- AND schemaname = 'public'
156
- AND tablename NOT LIKE '_zero_%'`,
157
- [pubName]
158
- )
159
- tables = result.rows
160
- } else {
161
- const result = await db.query<{ tablename: string }>(
162
- `SELECT tablename FROM pg_tables
163
- WHERE schemaname = 'public'
164
- AND tablename NOT IN ('migrations', '_zero_changes')
165
- AND tablename NOT LIKE '_zero_%'`
166
- )
167
- tables = result.rows
168
- }
169
-
170
- // find tables missing the change trigger
171
- const triggered = await db.query<{ event_object_table: string }>(
172
- `SELECT DISTINCT event_object_table FROM information_schema.triggers
173
- WHERE trigger_name = '_zero_change_trigger'
174
- AND event_object_schema = 'public'`
175
- )
176
- const hasTracker = new Set(triggered.rows.map((r) => r.event_object_table))
177
-
178
- let count = 0
179
- for (const { tablename } of tables) {
180
- if (hasTracker.has(tablename)) continue
181
- const quoted = quoteIdent(tablename)
182
- await db.exec(`
183
- CREATE TRIGGER _zero_change_trigger
184
- AFTER INSERT OR UPDATE OR DELETE ON public.${quoted}
185
- FOR EACH ROW EXECUTE FUNCTION public._zero_track_change();
186
- `)
187
- count++
188
- }
189
-
190
- if (count > 0) {
191
- log.debug.pglite(`installed change tracking on ${count} new tables`)
192
- }
193
- }
194
-
195
143
  /**
196
144
  * install change tracking triggers on tables in shard schemas.
197
145
  * zero-cache creates shard schemas (e.g. chat_0) with clients/mutations
@@ -210,29 +158,10 @@ export async function installTriggersOnShardTables(db: PGlite): Promise<void> {
210
158
 
211
159
  if (result.rows.length === 0) return
212
160
 
213
- // only track `clients` — that's the table zero-cache expects in the
214
- // replication stream (needed for .server promise resolution). other shard
215
- // tables like `replicas` are zero-cache internal state and streaming them
216
- // back causes "Unknown table" crashes in zero-cache's change-processor.
217
161
  let count = 0
218
162
  for (const { nspname } of result.rows) {
219
- // remove stale triggers from non-clients tables (from previous versions)
220
- const stale = await db.query<{ event_object_table: string }>(
221
- `SELECT DISTINCT event_object_table FROM information_schema.triggers
222
- WHERE trigger_name = '_zero_change_trigger'
223
- AND event_object_schema = $1
224
- AND event_object_table != 'clients'`,
225
- [nspname]
226
- )
227
- for (const { event_object_table } of stale.rows) {
228
- const qs = quoteIdent(nspname)
229
- const qt = quoteIdent(event_object_table)
230
- await db.exec(`DROP TRIGGER IF EXISTS _zero_change_trigger ON ${qs}.${qt}`)
231
- log.debug.pglite(`removed stale shard trigger from ${nspname}.${event_object_table}`)
232
- }
233
-
234
163
  const tables = await db.query<{ tablename: string }>(
235
- `SELECT tablename FROM pg_tables WHERE schemaname = $1 AND tablename = 'clients'`,
164
+ `SELECT tablename FROM pg_tables WHERE schemaname = $1`,
236
165
  [nspname]
237
166
  )
238
167
 
@@ -266,17 +195,6 @@ export async function getChangesSince(
266
195
  return result.rows
267
196
  }
268
197
 
269
- export async function purgeConsumedChanges(
270
- db: PGlite,
271
- watermark: number
272
- ): Promise<number> {
273
- const result = await db.query<{ count: string }>(
274
- 'WITH deleted AS (DELETE FROM public._zero_changes WHERE watermark <= $1 RETURNING 1) SELECT count(*)::text AS count FROM deleted',
275
- [watermark]
276
- )
277
- return Number(result.rows[0]?.count || 0)
278
- }
279
-
280
198
  export async function getCurrentWatermark(db: PGlite): Promise<number> {
281
199
  const result = await db.query<{ last_value: string; is_called: boolean }>(
282
200
  'SELECT last_value, is_called FROM public._zero_watermark'
@@ -10,7 +10,6 @@ import { log } from '../log.js'
10
10
  import {
11
11
  getChangesSince,
12
12
  getCurrentWatermark,
13
- purgeConsumedChanges,
14
13
  installTriggersOnShardTables,
15
14
  type ChangeRecord,
16
15
  } from './change-tracker.js'
@@ -32,9 +31,6 @@ import {
32
31
  import type { Mutex } from '../mutex.js'
33
32
  import type { PGlite } from '@electric-sql/pglite'
34
33
 
35
- // track concurrent replication handlers to detect reconnect-purge race
36
- let activeHandlerCount = 0
37
-
38
34
  export interface ReplicationWriter {
39
35
  write(data: Uint8Array): void
40
36
  }
@@ -265,11 +261,6 @@ export async function handleStartReplication(
265
261
  db: PGlite,
266
262
  mutex: Mutex
267
263
  ): Promise<void> {
268
- activeHandlerCount++
269
- const handlerId = activeHandlerCount
270
- console.info(
271
- `[orez-repl#${handlerId}] START_REPLICATION (active handlers: ${activeHandlerCount})`
272
- )
273
264
  log.debug.proxy('replication: entering streaming mode')
274
265
 
275
266
  // send CopyBothResponse to enter streaming mode
@@ -351,7 +342,7 @@ export async function handleStartReplication(
351
342
  for (const schema of relevantSchemas) {
352
343
  if (schema === 'public') continue
353
344
  const shardTables = await db.query<{ tablename: string }>(
354
- `SELECT tablename FROM pg_tables WHERE schemaname = $1 AND tablename = 'clients'`,
345
+ `SELECT tablename FROM pg_tables WHERE schemaname = $1`,
355
346
  [schema]
356
347
  )
357
348
  for (const { tablename } of shardTables.rows) {
@@ -461,23 +452,13 @@ export async function handleStartReplication(
461
452
  mutex.release()
462
453
  }
463
454
 
464
- console.info(
465
- `[orez-repl#${handlerId}] setup complete, starting poll (lastWatermark=${lastWatermark})`
466
- )
467
-
468
455
  // track which tables we've sent RELATION messages for
469
456
  const sentRelations = new Set<string>()
470
457
  let txCounter = 1
471
458
 
472
459
  // polling + notification loop
473
- // adaptive: poll fast when catching up, slow when idle
474
- const pollIntervalIdle = 500
475
- const pollIntervalCatchUp = 20
476
- const batchSize = 2000
477
- const purgeEveryN = 10
460
+ const pollInterval = 500
478
461
  let running = true
479
- let pollsSincePurge = 0
480
- let lastIdleLog = 0
481
462
 
482
463
  const poll = async () => {
483
464
  while (running) {
@@ -486,35 +467,12 @@ export async function handleStartReplication(
486
467
  await mutex.acquire()
487
468
  let changes: Awaited<ReturnType<typeof getChangesSince>>
488
469
  try {
489
- changes = await getChangesSince(db, lastWatermark, batchSize)
470
+ changes = await getChangesSince(db, lastWatermark, 100)
490
471
  } finally {
491
472
  mutex.release()
492
473
  }
493
474
 
494
475
  if (changes.length > 0) {
495
- // filter out shard tables that zero-cache doesn't expect.
496
- // only `clients` is needed (for .server promise resolution).
497
- // other shard tables (replicas, mutations) crash zero-cache
498
- // with "Unknown table" in change-processor.
499
- const batchEnd = changes[changes.length - 1].watermark
500
- changes = changes.filter((c) => {
501
- const dot = c.table_name.indexOf('.')
502
- if (dot === -1) return true
503
- const schema = c.table_name.substring(0, dot)
504
- if (schema === 'public') return true
505
- const table = c.table_name.substring(dot + 1)
506
- return table === 'clients'
507
- })
508
-
509
- if (changes.length === 0) {
510
- lastWatermark = batchEnd
511
- continue
512
- }
513
-
514
- const tables = [...new Set(changes.map((c) => c.table_name))].join(',')
515
- console.info(
516
- `[orez-repl#${handlerId}] found ${changes.length} changes [${tables}] (wm ${lastWatermark}→${changes[changes.length - 1].watermark}, type=${typeof changes[0].watermark})`
517
- )
518
476
  await streamChanges(
519
477
  changes,
520
478
  writer,
@@ -524,42 +482,14 @@ export async function handleStartReplication(
524
482
  excludedColumns,
525
483
  columnTypeOids
526
484
  )
527
- lastWatermark = batchEnd
528
-
529
- // purge consumed changes periodically to free wasm memory
530
- pollsSincePurge++
531
- if (pollsSincePurge >= purgeEveryN) {
532
- pollsSincePurge = 0
533
- await mutex.acquire()
534
- try {
535
- const purged = await purgeConsumedChanges(db, lastWatermark)
536
- if (purged > 0) {
537
- console.info(
538
- `[orez-repl#${handlerId}] purged ${purged} changes (wm<=${lastWatermark})`
539
- )
540
- }
541
- } finally {
542
- mutex.release()
543
- }
544
- }
545
- } else {
546
- // throttled idle logging (every 10s)
547
- const now = Date.now()
548
- if (now - lastIdleLog > 10000) {
549
- lastIdleLog = now
550
- console.info(
551
- `[orez-repl#${handlerId}] idle (lastWatermark=${lastWatermark}, type=${typeof lastWatermark})`
552
- )
553
- }
485
+ lastWatermark = changes[changes.length - 1].watermark
554
486
  }
555
487
 
556
488
  // send keepalive
557
489
  const ts = nowMicros()
558
490
  writer.write(encodeKeepalive(currentLsn, ts, false))
559
491
 
560
- // if we got a full batch, there's likely more - poll fast
561
- const delay = changes.length >= batchSize ? pollIntervalCatchUp : pollIntervalIdle
562
- await new Promise((resolve) => setTimeout(resolve, delay))
492
+ await new Promise((resolve) => setTimeout(resolve, pollInterval))
563
493
  } catch (err: unknown) {
564
494
  const msg = err instanceof Error ? err.message : String(err)
565
495
  log.debug.proxy(`replication poll error: ${msg}`)
@@ -574,10 +504,7 @@ export async function handleStartReplication(
574
504
 
575
505
  log.debug.proxy('replication: starting poll loop')
576
506
  await poll()
577
- activeHandlerCount--
578
- console.info(
579
- `[orez-repl#${handlerId}] poll loop exited (remaining handlers: ${activeHandlerCount})`
580
- )
507
+ log.debug.proxy('replication: poll loop exited')
581
508
  }
582
509
 
583
510
  async function streamChanges(
@@ -364,223 +364,6 @@ describe('pgoutput-encoder', () => {
364
364
  })
365
365
  })
366
366
 
367
- // roundtrip tests: encode with orez → parse with zero-cache's parser
368
- // this validates the fundamental contract between orez and zero-cache
369
- describe('roundtrip: orez encoder → zero-cache parser', () => {
370
- // absolute path bypasses package.json exports restriction
371
- // eslint-disable-next-line @typescript-eslint/no-require-imports
372
- const { PgoutputParser } = require('/Users/n8/orez/node_modules/@rocicorp/zero/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput-parser.js')
373
-
374
- // mock type parsers: unknown OIDs default to String (identity for text)
375
- const typeParsers = { getTypeParser: () => String }
376
-
377
- function makeParser() {
378
- return new PgoutputParser(typeParsers)
379
- }
380
-
381
- it('BEGIN roundtrip', () => {
382
- const lsn = 0x1000200n
383
- const ts = BigInt(Date.now()) * 1000n
384
- const parser = makeParser()
385
- const parsed = parser.parse(encodeBegin(lsn, ts, 42))
386
-
387
- expect(parsed.tag).toBe('begin')
388
- expect(parsed.commitLsn).toBe('00000000/01000200')
389
- expect(parsed.xid).toBe(42)
390
- expect(parsed.commitTime).toBe(ts)
391
- })
392
-
393
- it('COMMIT roundtrip', () => {
394
- const lsn = 0x1000200n
395
- const endLsn = 0x1000300n
396
- const ts = BigInt(Date.now()) * 1000n
397
- const parser = makeParser()
398
- const parsed = parser.parse(encodeCommit(0, lsn, endLsn, ts))
399
-
400
- expect(parsed.tag).toBe('commit')
401
- expect(parsed.commitLsn).toBe('00000000/01000200')
402
- expect(parsed.commitEndLsn).toBe('00000000/01000300')
403
- expect(parsed.commitTime).toBe(ts)
404
- })
405
-
406
- it('RELATION roundtrip', () => {
407
- const oid = getTableOid('rt.rel_test')
408
- const cols: ColumnInfo[] = [
409
- { name: 'id', typeOid: 25, typeMod: -1, isKey: true },
410
- { name: 'name', typeOid: 25, typeMod: -1 },
411
- ]
412
- const parser = makeParser()
413
- const parsed = parser.parse(encodeRelation(oid, 'public', 'rel_test', 0x64, cols))
414
-
415
- expect(parsed.tag).toBe('relation')
416
- expect(parsed.schema).toBe('public')
417
- expect(parsed.name).toBe('rel_test')
418
- expect(parsed.columns).toHaveLength(2)
419
- expect(parsed.keyColumns).toEqual(['id'])
420
- })
421
-
422
- it('INSERT roundtrip', () => {
423
- const oid = getTableOid('rt.ins_test')
424
- const cols: ColumnInfo[] = [
425
- { name: 'id', typeOid: 25, typeMod: -1, isKey: true },
426
- { name: 'val', typeOid: 25, typeMod: -1 },
427
- ]
428
- const parser = makeParser()
429
- parser.parse(encodeRelation(oid, 'public', 'ins_test', 0x64, cols))
430
-
431
- const parsed = parser.parse(encodeInsert(oid, { id: 'abc', val: 'hello' }, cols))
432
- expect(parsed.tag).toBe('insert')
433
- expect(parsed.new.id).toBe('abc')
434
- expect(parsed.new.val).toBe('hello')
435
- })
436
-
437
- it('INSERT with null', () => {
438
- const oid = getTableOid('rt.null_test')
439
- const cols: ColumnInfo[] = [
440
- { name: 'id', typeOid: 25, typeMod: -1, isKey: true },
441
- { name: 'opt', typeOid: 25, typeMod: -1 },
442
- ]
443
- const parser = makeParser()
444
- parser.parse(encodeRelation(oid, 'public', 'null_test', 0x64, cols))
445
-
446
- const parsed = parser.parse(encodeInsert(oid, { id: 'x', opt: null }, cols))
447
- expect(parsed.new.opt).toBeNull()
448
- })
449
-
450
- it('UPDATE with old row roundtrip', () => {
451
- const oid = getTableOid('rt.upd_test')
452
- const cols: ColumnInfo[] = [
453
- { name: 'id', typeOid: 25, typeMod: -1, isKey: true },
454
- { name: 'val', typeOid: 25, typeMod: -1 },
455
- ]
456
- const parser = makeParser()
457
- parser.parse(encodeRelation(oid, 'public', 'upd_test', 0x64, cols))
458
-
459
- const parsed = parser.parse(
460
- encodeUpdate(oid, { id: '1', val: 'new' }, { id: '1', val: 'old' }, cols)
461
- )
462
- expect(parsed.tag).toBe('update')
463
- expect(parsed.new.val).toBe('new')
464
- expect(parsed.old.val).toBe('old')
465
- })
466
-
467
- it('UPDATE without old row', () => {
468
- const oid = getTableOid('rt.upd_no_old')
469
- const cols: ColumnInfo[] = [
470
- { name: 'id', typeOid: 25, typeMod: -1, isKey: true },
471
- { name: 'val', typeOid: 25, typeMod: -1 },
472
- ]
473
- const parser = makeParser()
474
- parser.parse(encodeRelation(oid, 'public', 'upd_no_old', 0x64, cols))
475
-
476
- const parsed = parser.parse(encodeUpdate(oid, { id: '1', val: 'v' }, null, cols))
477
- expect(parsed.tag).toBe('update')
478
- expect(parsed.new.val).toBe('v')
479
- expect(parsed.old).toBeNull()
480
- expect(parsed.key).toBeNull()
481
- })
482
-
483
- it('DELETE roundtrip', () => {
484
- const oid = getTableOid('rt.del_test')
485
- const cols: ColumnInfo[] = [
486
- { name: 'id', typeOid: 25, typeMod: -1, isKey: true },
487
- { name: 'val', typeOid: 25, typeMod: -1 },
488
- ]
489
- const parser = makeParser()
490
- parser.parse(encodeRelation(oid, 'public', 'del_test', 0x64, cols))
491
-
492
- const parsed = parser.parse(encodeDelete(oid, { id: 'gone', val: 'x' }, cols))
493
- expect(parsed.tag).toBe('delete')
494
- expect(parsed.key.id).toBe('gone')
495
- })
496
-
497
- it('full transaction: BEGIN → RELATION → INSERT → COMMIT', () => {
498
- const parser = makeParser()
499
- const lsn = 0x2000000n
500
- const endLsn = 0x2000100n
501
- const ts = BigInt(Date.now()) * 1000n
502
-
503
- const begin = parser.parse(encodeBegin(lsn, ts, 1))
504
- expect(begin.commitLsn).toBe('00000000/02000000')
505
-
506
- const oid = getTableOid('rt.full_tx')
507
- const cols: ColumnInfo[] = [
508
- { name: 'id', typeOid: 25, typeMod: -1, isKey: true },
509
- { name: 'data', typeOid: 25, typeMod: -1 },
510
- ]
511
- parser.parse(encodeRelation(oid, 'public', 'full_tx', 0x64, cols))
512
-
513
- const ins = parser.parse(encodeInsert(oid, { id: '1', data: 'test' }, cols))
514
- expect(ins.new.id).toBe('1')
515
-
516
- const commit = parser.parse(encodeCommit(0, lsn, endLsn, ts))
517
- expect(commit.commitLsn).toBe(begin.commitLsn)
518
- })
519
-
520
- it('XLogData + CopyData wrapper roundtrip with parser', () => {
521
- const lsn = 0x3000000n
522
- const ts = BigInt(Date.now()) * 1000n
523
- const pgoutput = encodeBegin(lsn, ts, 1)
524
- const xlog = wrapXLogData(lsn, lsn, ts, pgoutput)
525
- const frame = wrapCopyData(xlog)
526
-
527
- // unwrap CopyData
528
- const copyLen = r32(frame, 1)
529
- const inner = frame.subarray(5, 1 + copyLen)
530
-
531
- // parse like stream.js
532
- expect(inner[0]).toBe(0x77)
533
- const streamLsn = new DataView(inner.buffer, inner.byteOffset).getBigUint64(1)
534
- expect(streamLsn).toBe(lsn)
535
-
536
- // parse pgoutput
537
- const parser = makeParser()
538
- const parsed = parser.parse(inner.subarray(25))
539
- expect(parsed.tag).toBe('begin')
540
- expect(parsed.commitLsn).toBe('00000000/03000000')
541
- })
542
-
543
- it('shard schema encoding', () => {
544
- const oid = getTableOid('rt.chat_0.clients')
545
- const cols: ColumnInfo[] = [
546
- { name: 'id', typeOid: 25, typeMod: -1, isKey: true },
547
- { name: 'lastMutationID', typeOid: 20, typeMod: -1 },
548
- ]
549
- const parser = makeParser()
550
- const rel = parser.parse(encodeRelation(oid, 'chat_0', 'clients', 0x64, cols))
551
- expect(rel.schema).toBe('chat_0')
552
- expect(rel.name).toBe('clients')
553
- })
554
-
555
- it('LSN ordering: slot < streaming changes', () => {
556
- // validates that streaming changes will be seen as "new" by zero-cache
557
- let testLsn = 0x1000000n
558
- const next = () => { testLsn += 0x100n; return testLsn }
559
-
560
- const slotLsn = next() // CREATE_REPLICATION_SLOT
561
- const beginLsn = next() // first streaming BEGIN
562
- const commitLsn = next() // first streaming COMMIT
563
-
564
- expect(beginLsn).toBeGreaterThan(slotLsn)
565
- expect(commitLsn).toBeGreaterThan(beginLsn)
566
-
567
- // verify lexi version ordering is preserved
568
- // eslint-disable-next-line @typescript-eslint/no-require-imports
569
- const { versionToLexi } = require('/Users/n8/orez/node_modules/@rocicorp/zero/out/zero-cache/src/types/lexi-version.js')
570
- // eslint-disable-next-line @typescript-eslint/no-require-imports
571
- const { toBigInt: lsnToBigInt } = require('/Users/n8/orez/node_modules/@rocicorp/zero/out/zero-cache/src/services/change-source/pg/lsn.js')
572
-
573
- const slotHex = `00000000/${slotLsn.toString(16).padStart(8, '0')}`.toUpperCase()
574
- const beginHex = `00000000/${beginLsn.toString(16).padStart(8, '0')}`.toUpperCase()
575
-
576
- const slotVersion = versionToLexi(lsnToBigInt(slotHex))
577
- const beginVersion = versionToLexi(lsnToBigInt(beginHex))
578
-
579
- // lexi versions must maintain ordering
580
- expect(beginVersion > slotVersion).toBe(true)
581
- })
582
- })
583
-
584
367
  describe('double-wrap: CopyData(XLogData(message))', () => {
585
368
  // this is the exact framing zero-cache expects for every replication message
586
369
  it('produces parseable nested structure', () => {