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
@@ -701,22 +701,17 @@ describe('zero-cache pgoutput compatibility', { timeout: 30000 }, () => {
701
701
  const s = await stream()
702
702
  const q = s.messages
703
703
 
704
- // insert sequentially with waits to avoid batching
705
704
  await db.exec(`INSERT INTO public.foo (id) VALUES ('t1')`)
706
705
  await db.exec(`INSERT INTO public.foo (id) VALUES ('t2')`)
707
706
  await db.exec(`INSERT INTO public.foo (id) VALUES ('t3')`)
708
707
 
709
- // orez may batch changes into fewer transactions, so just verify
710
- // all inserts arrive and every begin has a matching commit
711
708
  const all: ZcMessage[] = []
712
709
  const deadline = Date.now() + 8000
713
710
  while (Date.now() < deadline) {
714
711
  const m = await q.dequeue(2000).catch(() => null)
715
712
  if (!m) break
716
713
  if (m.tag !== 'keepalive') all.push(m)
717
- const inserts = all.filter((x) => x.tag === 'insert')
718
- const commits = all.filter((x) => x.tag === 'commit')
719
- if (inserts.length >= 3 && commits.length >= 1) break
714
+ if (all.filter((x) => x.tag === 'commit').length >= 3) break
720
715
  }
721
716
 
722
717
  const begins = all.filter((m) => m.tag === 'begin')
@@ -946,229 +941,3 @@ describe('zero-cache pgoutput compatibility', { timeout: 30000 }, () => {
946
941
  s.close()
947
942
  })
948
943
  })
949
-
950
- /**
951
- * postgres.js replication stream test.
952
- *
953
- * uses the same postgres library and code path that zero-cache uses
954
- * in its stream.js subscribe function. validates that orez's CopyData
955
- * frames are correctly parsed by postgres.js's wire protocol handler
956
- * and that zero-cache's PgoutputParser can consume the payloads.
957
- */
958
- describe('postgres.js replication stream (zero-cache code path)', { timeout: 30000 }, () => {
959
- let db: PGlite
960
- let server: Server
961
- let port: number
962
-
963
- beforeEach(async () => {
964
- db = new PGlite()
965
- await db.waitReady
966
- await db.exec(`
967
- CREATE TABLE public.items (
968
- id TEXT PRIMARY KEY,
969
- val INTEGER,
970
- note TEXT
971
- )
972
- `)
973
- await db.exec(`CREATE PUBLICATION zero_data FOR ALL TABLES`)
974
- await installChangeTracking(db)
975
-
976
- const config = { ...getConfig(), pgPort: 0 }
977
- server = await startPgProxy(db, config)
978
- port = (server.address() as AddressInfo).port
979
- })
980
-
981
- afterEach(async () => {
982
- server?.close()
983
- await db?.close()
984
- })
985
-
986
- it('postgres.js receives CopyData and parseStreamMessage decodes it', { timeout: 30000 }, async () => {
987
- // import postgres (same lib zero-cache uses)
988
- const pg = (await import('postgres')).default
989
-
990
- // create a regular connection for queries
991
- const regular = pg({
992
- host: '127.0.0.1',
993
- port,
994
- user: 'user',
995
- password: 'password',
996
- database: 'postgres',
997
- max: 1,
998
- })
999
-
1000
- // create replication connection (same as zero-cache's subscribe)
1001
- const session = pg({
1002
- host: '127.0.0.1',
1003
- port,
1004
- user: 'user',
1005
- password: 'password',
1006
- database: 'postgres',
1007
- max: 1,
1008
- fetch_types: false,
1009
- idle_timeout: null,
1010
- max_lifetime: null,
1011
- connection: { replication: 'database' },
1012
- })
1013
-
1014
- try {
1015
- // create slot (same as zero-cache)
1016
- await session.unsafe(
1017
- `CREATE_REPLICATION_SLOT "pgjs_test" TEMPORARY LOGICAL pgoutput NOEXPORT_SNAPSHOT`
1018
- ).simple()
1019
-
1020
- // start replication stream (same pattern as stream.js)
1021
- const stream = session.unsafe(
1022
- `START_REPLICATION SLOT "pgjs_test" LOGICAL 0/0 (proto_version '1', publication_names 'zero_data', messages 'true')`
1023
- ).execute()
1024
-
1025
- const [readable, _writable] = await Promise.all([
1026
- stream.readable(),
1027
- stream.writable(),
1028
- ])
1029
-
1030
- // import zero-cache's actual parser
1031
- // eslint-disable-next-line @typescript-eslint/no-require-imports
1032
- const { PgoutputParser } = require('/Users/n8/orez/node_modules/@rocicorp/zero/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput-parser.js')
1033
- const typeParsers = { getTypeParser: () => String }
1034
- const parser = new PgoutputParser(typeParsers)
1035
-
1036
- // parseStreamMessage from zero-cache's stream.js
1037
- function parseStreamMessage(buffer: Buffer): [bigint, any] | null {
1038
- if (buffer[0] !== 0x77 && buffer[0] !== 0x6b) return null
1039
- const lsn = buffer.readBigUInt64BE(1)
1040
- if (buffer[0] === 0x77) {
1041
- return [lsn, parser.parse(buffer.subarray(25))]
1042
- }
1043
- if (buffer.readInt8(17)) {
1044
- return [lsn, { tag: 'keepalive' }]
1045
- }
1046
- return null // keepalive with shouldRespond=false
1047
- }
1048
-
1049
- // collect parsed messages
1050
- const messages: any[] = []
1051
- const collectDone = new Promise<void>((resolve) => {
1052
- readable.on('data', (chunk: Buffer) => {
1053
- const result = parseStreamMessage(chunk)
1054
- if (result) {
1055
- const [_lsn, msg] = result
1056
- messages.push(msg)
1057
- }
1058
- })
1059
- setTimeout(resolve, 3000)
1060
- })
1061
-
1062
- await new Promise((r) => setTimeout(r, 500))
1063
- await regular.unsafe(`INSERT INTO public.items (id, val, note) VALUES ('pgjs', 42, 'postgres.js test')`)
1064
-
1065
- await collectDone
1066
- readable.destroy()
1067
-
1068
- // filter out keepalives
1069
- const data = messages.filter((m: any) => m.tag !== 'keepalive')
1070
-
1071
- // should have: begin, relation, insert, commit
1072
- const tags = data.map((m: any) => m.tag)
1073
- expect(tags).toContain('begin')
1074
- expect(tags).toContain('relation')
1075
- expect(tags).toContain('insert')
1076
- expect(tags).toContain('commit')
1077
-
1078
- // validate BEGIN has commitLsn as string (not BigInt)
1079
- const begin = data.find((m: any) => m.tag === 'begin')
1080
- expect(typeof begin.commitLsn).toBe('string')
1081
- expect(begin.commitLsn).toMatch(/^[0-9A-F]+\/[0-9A-F]+$/)
1082
-
1083
- // validate RELATION has correct structure
1084
- const rel = data.find((m: any) => m.tag === 'relation')
1085
- expect(rel.schema).toBe('public')
1086
- expect(rel.name).toBe('items')
1087
- expect(rel.columns.length).toBe(3)
1088
-
1089
- // validate INSERT has parsed values
1090
- const ins = data.find((m: any) => m.tag === 'insert')
1091
- expect(ins.relation.name).toBe('items')
1092
- expect(ins.new.id).toBe('pgjs')
1093
- expect(ins.new.val).toBe('42')
1094
- expect(ins.new.note).toBe('postgres.js test')
1095
-
1096
- // validate COMMIT has commitLsn and commitEndLsn
1097
- const commit = data.find((m: any) => m.tag === 'commit')
1098
- expect(typeof commit.commitLsn).toBe('string')
1099
- expect(typeof commit.commitEndLsn).toBe('string')
1100
-
1101
- // validate LSN ordering: commit.commitEndLsn > begin.commitLsn
1102
- // eslint-disable-next-line @typescript-eslint/no-require-imports
1103
- const { toBigInt: lsnToBigInt } = require('/Users/n8/orez/node_modules/@rocicorp/zero/out/zero-cache/src/services/change-source/pg/lsn.js')
1104
- expect(lsnToBigInt(commit.commitEndLsn)).toBeGreaterThan(lsnToBigInt(begin.commitLsn))
1105
-
1106
- // validate lexi version conversion works (storer uses this)
1107
- // eslint-disable-next-line @typescript-eslint/no-require-imports
1108
- const { versionToLexi } = require('/Users/n8/orez/node_modules/@rocicorp/zero/out/zero-cache/src/types/lexi-version.js')
1109
- const beginVersion = versionToLexi(lsnToBigInt(begin.commitLsn))
1110
- const commitVersion = versionToLexi(lsnToBigInt(commit.commitEndLsn))
1111
- expect(typeof beginVersion).toBe('string')
1112
- expect(commitVersion > beginVersion).toBe(true)
1113
-
1114
- } finally {
1115
- await regular.end()
1116
- // session.end() can hang because the replication handler keeps polling.
1117
- // just force-close the underlying connection by destroying the socket.
1118
- await session.end({ timeout: 2 }).catch(() => {})
1119
- }
1120
- })
1121
-
1122
- it('postgres.js handles concurrent regular + replication connections', { timeout: 30000 }, async () => {
1123
- const pg = (await import('postgres')).default
1124
-
1125
- const regular = pg({
1126
- host: '127.0.0.1', port,
1127
- user: 'user', password: 'password', database: 'postgres',
1128
- max: 1,
1129
- })
1130
-
1131
- const session = pg({
1132
- host: '127.0.0.1', port,
1133
- user: 'user', password: 'password', database: 'postgres',
1134
- max: 1,
1135
- fetch_types: false,
1136
- idle_timeout: null,
1137
- max_lifetime: null,
1138
- connection: { replication: 'database' },
1139
- })
1140
-
1141
- try {
1142
- await session.unsafe(
1143
- `CREATE_REPLICATION_SLOT "conc_test" TEMPORARY LOGICAL pgoutput NOEXPORT_SNAPSHOT`
1144
- ).simple()
1145
-
1146
- const stream = session.unsafe(
1147
- `START_REPLICATION SLOT "conc_test" LOGICAL 0/0 (proto_version '1', publication_names 'zero_data', messages 'true')`
1148
- ).execute()
1149
-
1150
- const [readable] = await Promise.all([stream.readable(), stream.writable()])
1151
-
1152
- const received: Buffer[] = []
1153
- readable.on('data', (chunk: Buffer) => received.push(chunk))
1154
-
1155
- await new Promise((r) => setTimeout(r, 300))
1156
-
1157
- // do 5 inserts via regular connection while replication is active
1158
- for (let i = 0; i < 5; i++) {
1159
- await regular.unsafe(`INSERT INTO public.items (id, val) VALUES ('c${i}', ${i})`)
1160
- }
1161
-
1162
- await new Promise((r) => setTimeout(r, 2000))
1163
- readable.destroy()
1164
-
1165
- // should have received XLogData frames (0x77) from replication
1166
- const xlogFrames = received.filter((b) => b[0] === 0x77)
1167
- expect(xlogFrames.length).toBeGreaterThan(0)
1168
-
1169
- } finally {
1170
- await regular.end()
1171
- await session.end({ timeout: 2 }).catch(() => {})
1172
- }
1173
- })
1174
- })
@@ -34,7 +34,7 @@ const OrigDatabase = mod.Database;
34
34
  const SqliteError = mod.SqliteError;
35
35
  function Database(...args) {
36
36
  const db = new OrigDatabase(...args);
37
- try { db.pragma('busy_timeout = 30000'); db.pragma('synchronous = normal'); } catch(e) {}
37
+ try { db.pragma('journal_mode = delete'); db.pragma('busy_timeout = 30000'); db.pragma('synchronous = normal'); } catch(e) {}
38
38
  return db;
39
39
  }
40
40
  Database.prototype = OrigDatabase.prototype;
@@ -7,21 +7,16 @@ import type { Plugin } from 'vite'
7
7
  export interface OrezPluginOptions extends Partial<ZeroLiteConfig> {
8
8
  s3?: boolean
9
9
  s3Port?: number
10
- admin?: boolean
11
- adminPort?: number
12
- adminLogs?: boolean
13
10
  }
14
11
 
15
12
  export default function orez(options?: OrezPluginOptions): Plugin {
16
13
  let stop: (() => Promise<void>) | null = null
17
14
  let s3Server: Server | null = null
18
- let adminServer: Server | null = null
19
15
 
20
16
  return {
21
17
  name: 'orez',
22
18
 
23
19
  async configureServer(server) {
24
- const startTime = Date.now()
25
20
  const result = await startZeroLite(options)
26
21
  stop = result.stop
27
22
 
@@ -33,30 +28,7 @@ export default function orez(options?: OrezPluginOptions): Plugin {
33
28
  })
34
29
  }
35
30
 
36
- if (options?.admin && result.logStore) {
37
- const { findPort } = await import('./port.js')
38
- const { log } = await import('./log.js')
39
- const adminPort = options.adminPort || result.config.zeroPort + 2
40
- const resolvedPort = await findPort(adminPort)
41
- const { startAdminServer } = await import('./admin/server.js')
42
- adminServer = await startAdminServer({
43
- port: resolvedPort,
44
- logStore: result.logStore,
45
- config: result.config,
46
- zeroEnv: result.zeroEnv,
47
- actions: result.actions,
48
- startTime,
49
- httpLog: result.httpLogStore || undefined,
50
- })
51
- log.orez(`admin: http://127.0.0.1:${resolvedPort}`)
52
- if (result.config.adminLogs) {
53
- const { resolve } = await import('node:path')
54
- log.orez(`logs: ${resolve(result.config.dataDir, 'logs', 'orez.log')}`)
55
- }
56
- }
57
-
58
31
  server.httpServer?.on('close', async () => {
59
- adminServer?.close()
60
32
  s3Server?.close()
61
33
  if (stop) {
62
34
  await stop()
@@ -19,8 +19,7 @@ import { resolve } from 'node:path'
19
19
 
20
20
  // import bedrock-sqlite directly (our wasm build)
21
21
  // @ts-expect-error - CJS module
22
- import bedrockSqlite from 'bedrock-sqlite'
23
- const { Database } = bedrockSqlite
22
+ import { Database } from 'bedrock-sqlite'
24
23
  import { describe, test, expect, beforeEach, afterEach } from 'vitest'
25
24
 
26
25
  // helper: temp db file