orez 0.0.50 → 0.0.52

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.
@@ -4,22 +4,40 @@ import { PGlite } from '@electric-sql/pglite';
4
4
  import { pg_trgm } from '@electric-sql/pglite/contrib/pg_trgm';
5
5
  import { vector } from '@electric-sql/pglite/vector';
6
6
  import { log } from './log.js';
7
+ // detect pglite corruption errors (wasm abort during init)
8
+ function isCorruptionError(err) {
9
+ const msg = String(err);
10
+ return msg.includes('Aborted()') || msg.includes('_pg_initdb');
11
+ }
7
12
  // create a single pglite instance with given dataDir suffix
8
- async function createInstance(config, name, withExtensions) {
13
+ async function createInstance(config, name, withExtensions, isRetry = false) {
9
14
  const dataPath = resolve(config.dataDir, `pgdata-${name}`);
10
15
  mkdirSync(dataPath, { recursive: true });
11
16
  log.debug.pglite(`creating ${name} instance at ${dataPath}`);
12
17
  const { dataDir: _d, debug: _dbg, ...userOpts } = config.pgliteOptions;
13
- const db = new PGlite({
14
- dataDir: dataPath,
15
- debug: config.logLevel === 'debug' ? 1 : 0,
16
- relaxedDurability: true,
17
- ...userOpts,
18
- extensions: withExtensions ? userOpts.extensions || { vector, pg_trgm } : {},
19
- });
20
- await db.waitReady;
21
- log.debug.pglite(`${name} ready`);
22
- return db;
18
+ try {
19
+ const db = new PGlite({
20
+ dataDir: dataPath,
21
+ debug: config.logLevel === 'debug' ? 1 : 0,
22
+ relaxedDurability: true,
23
+ ...userOpts,
24
+ extensions: withExtensions ? userOpts.extensions || { vector, pg_trgm } : {},
25
+ });
26
+ await db.waitReady;
27
+ log.debug.pglite(`${name} ready`);
28
+ return db;
29
+ }
30
+ catch (err) {
31
+ if (isCorruptionError(err) && !isRetry) {
32
+ // corrupted data directory - backup and retry with fresh init
33
+ const backupPath = `${dataPath}.corrupt.${Date.now()}`;
34
+ log.pglite(`corrupted data detected in ${name}, backing up to ${backupPath}`);
35
+ renameSync(dataPath, backupPath);
36
+ log.pglite(`retrying ${name} with fresh database`);
37
+ return createInstance(config, name, withExtensions, true);
38
+ }
39
+ throw err;
40
+ }
23
41
  }
24
42
  /**
25
43
  * create separate pglite instances for each "database".
@@ -1 +1 @@
1
- {"version":3,"file":"pglite-manager.js","sourceRoot":"","sources":["../src/pglite-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pglite-manager.js","sourceRoot":"","sources":["../src/pglite-manager.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,10 @@
1
- import type { ZeroLiteConfig } from './config.js';
1
+ import type { Hook, ZeroLiteConfig } from './config.js';
2
2
  import type { Plugin } from 'vite';
3
- export interface OrezPluginOptions extends Partial<ZeroLiteConfig> {
3
+ export interface OrezPluginOptions extends Partial<Omit<ZeroLiteConfig, 'onDbReady' | 'onHealthy'>> {
4
4
  s3?: boolean;
5
5
  s3Port?: number;
6
+ onDbReady?: Hook;
7
+ onHealthy?: Hook;
6
8
  }
7
- export default function orez(options?: OrezPluginOptions): Plugin;
9
+ export declare function orezPlugin(options?: OrezPluginOptions): Plugin;
8
10
  //# sourceMappingURL=vite-plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAElC,MAAM,WAAW,iBAAkB,SAAQ,OAAO,CAAC,cAAc,CAAC;IAChE,EAAE,CAAC,EAAE,OAAO,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,MAAM,CA4BhE"}
1
+ {"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAElC,MAAM,WAAW,iBAAkB,SAAQ,OAAO,CAChD,IAAI,CAAC,cAAc,EAAE,WAAW,GAAG,WAAW,CAAC,CAChD;IACC,EAAE,CAAC,EAAE,OAAO,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf,SAAS,CAAC,EAAE,IAAI,CAAA;IAChB,SAAS,CAAC,EAAE,IAAI,CAAA;CACjB;AAED,wBAAgB,UAAU,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,MAAM,CA4B9D"}
@@ -1,5 +1,5 @@
1
1
  import { startZeroLite } from './index.js';
2
- export default function orez(options) {
2
+ export function orezPlugin(options) {
3
3
  let stop = null;
4
4
  let s3Server = null;
5
5
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"vite-plugin.js","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAW1C,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,OAA2B;IACtD,IAAI,IAAI,GAAiC,IAAI,CAAA;IAC7C,IAAI,QAAQ,GAAkB,IAAI,CAAA;IAElC,OAAO;QACL,IAAI,EAAE,MAAM;QAEZ,KAAK,CAAC,eAAe,CAAC,MAAM;YAC1B,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,CAAA;YAC3C,IAAI,GAAG,MAAM,CAAC,IAAI,CAAA;YAElB,IAAI,OAAO,EAAE,EAAE,EAAE,CAAC;gBAChB,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAA;gBACtD,QAAQ,GAAG,MAAM,YAAY,CAAC;oBAC5B,IAAI,EAAE,OAAO,CAAC,MAAM,IAAI,IAAI;oBAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO;iBAC/B,CAAC,CAAA;YACJ,CAAC;YAED,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;gBACxC,QAAQ,EAAE,KAAK,EAAE,CAAA;gBACjB,IAAI,IAAI,EAAE,CAAC;oBACT,MAAM,IAAI,EAAE,CAAA;oBACZ,IAAI,GAAG,IAAI,CAAA;gBACb,CAAC;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"vite-plugin.js","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAgB1C,MAAM,UAAU,UAAU,CAAC,OAA2B;IACpD,IAAI,IAAI,GAAiC,IAAI,CAAA;IAC7C,IAAI,QAAQ,GAAkB,IAAI,CAAA;IAElC,OAAO;QACL,IAAI,EAAE,MAAM;QAEZ,KAAK,CAAC,eAAe,CAAC,MAAM;YAC1B,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,CAAA;YAC3C,IAAI,GAAG,MAAM,CAAC,IAAI,CAAA;YAElB,IAAI,OAAO,EAAE,EAAE,EAAE,CAAC;gBAChB,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAA;gBACtD,QAAQ,GAAG,MAAM,YAAY,CAAC;oBAC5B,IAAI,EAAE,OAAO,CAAC,MAAM,IAAI,IAAI;oBAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO;iBAC/B,CAAC,CAAA;YACJ,CAAC;YAED,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;gBACxC,QAAQ,EAAE,KAAK,EAAE,CAAA;gBACjB,IAAI,IAAI,EAAE,CAAC;oBACT,MAAM,IAAI,EAAE,CAAA;oBACZ,IAAI,GAAG,IAAI,CAAA;gBACb,CAAC;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orez",
3
- "version": "0.0.50",
3
+ "version": "0.0.52",
4
4
  "description": "PGlite-powered zero-sync development backend. No Docker required.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,8 +30,9 @@
30
30
  "scripts": {
31
31
  "build": "tsc",
32
32
  "dev": "tsc --watch",
33
- "test": "vitest run --exclude src/integration/",
33
+ "test": "vitest run --exclude 'src/integration/' --exclude 'src/wasm-sqlite.test.ts'",
34
34
  "test:integration": "vitest run src/integration/",
35
+ "test:wasm": "vitest run src/wasm-sqlite.test.ts",
35
36
  "test:all": "vitest run",
36
37
  "test:watch": "vitest",
37
38
  "lint": "oxlint --import-plugin --ignore-pattern sqlite-wasm/",
@@ -48,7 +49,7 @@
48
49
  "@electric-sql/pglite": "^0.2.17",
49
50
  "@electric-sql/pglite-tools": "0.2.4",
50
51
  "@rocicorp/zero": ">=0.1.0",
51
- "bedrock-sqlite": "0.0.43",
52
+ "bedrock-sqlite": "0.0.45",
52
53
  "citty": "^0.2.0",
53
54
  "pg-gateway": "0.3.0-beta.4",
54
55
  "pgsql-parser": "^17.9.11",
package/src/cli.ts CHANGED
@@ -757,6 +757,16 @@ const main = defineCommand({
757
757
  description: 'command to run once all services are healthy',
758
758
  default: '',
759
759
  },
760
+ admin: {
761
+ type: 'boolean',
762
+ description: 'start admin dashboard',
763
+ default: false,
764
+ },
765
+ 'admin-port': {
766
+ type: 'string',
767
+ description: 'admin dashboard port',
768
+ default: '6477',
769
+ },
760
770
  },
761
771
  subCommands: {
762
772
  s3: s3Command,
@@ -764,19 +774,23 @@ const main = defineCommand({
764
774
  pg_restore: pgRestoreCommand,
765
775
  },
766
776
  async run({ args }) {
767
- const { config, stop } = await startZeroLite({
768
- pgPort: Number(args['pg-port']),
769
- zeroPort: Number(args['zero-port']),
770
- dataDir: args['data-dir'],
771
- migrationsDir: args.migrations,
772
- seedFile: args.seed,
773
- pgUser: args['pg-user'],
774
- pgPassword: args['pg-password'],
775
- skipZeroCache: args['skip-zero-cache'],
776
- disableWasmSqlite: args['disable-wasm-sqlite'],
777
- logLevel: (args['log-level'] as 'error' | 'warn' | 'info' | 'debug') || undefined,
778
- onDbReady: args['on-db-ready'],
779
- })
777
+ const adminPort = args.admin ? Number(args['admin-port']) : 0
778
+ const { config, stop, zeroEnv, logStore, restartZero, resetZero } =
779
+ await startZeroLite({
780
+ pgPort: Number(args['pg-port']),
781
+ zeroPort: Number(args['zero-port']),
782
+ adminPort,
783
+ dataDir: args['data-dir'],
784
+ migrationsDir: args.migrations,
785
+ seedFile: args.seed,
786
+ pgUser: args['pg-user'],
787
+ pgPassword: args['pg-password'],
788
+ skipZeroCache: args['skip-zero-cache'],
789
+ disableWasmSqlite: args['disable-wasm-sqlite'],
790
+ logLevel: (args['log-level'] as 'error' | 'warn' | 'info' | 'debug') || undefined,
791
+ onDbReady: args['on-db-ready'] || undefined,
792
+ onHealthy: args['on-healthy'] || undefined,
793
+ })
780
794
 
781
795
  let s3Server: import('node:http').Server | null = null
782
796
  if (args.s3) {
@@ -787,6 +801,20 @@ const main = defineCommand({
787
801
  })
788
802
  }
789
803
 
804
+ let adminServer: import('node:http').Server | null = null
805
+ if (args.admin && logStore && zeroEnv) {
806
+ const { startAdminServer } = await import('./admin/server.js')
807
+ adminServer = await startAdminServer({
808
+ port: config.adminPort,
809
+ logStore,
810
+ config,
811
+ zeroEnv,
812
+ actions: { restartZero, resetZero },
813
+ startTime: Date.now(),
814
+ })
815
+ log.orez(`admin: http://localhost:${config.adminPort}`)
816
+ }
817
+
790
818
  log.orez('ready')
791
819
  log.orez(
792
820
  `pg: postgresql://${config.pgUser}:${config.pgPassword}@127.0.0.1:${config.pgPort}/postgres`
@@ -795,30 +823,14 @@ const main = defineCommand({
795
823
  log.zero(`http://localhost:${config.zeroPort}`)
796
824
  }
797
825
 
798
- if (args['on-healthy']) {
799
- log.orez(`running on-healthy: ${args['on-healthy']}`)
800
- const child = spawn(args['on-healthy'], {
801
- shell: true,
802
- stdio: 'inherit',
803
- env: {
804
- ...process.env,
805
- OREZ_PG_PORT: String(config.pgPort),
806
- OREZ_ZERO_PORT: String(config.zeroPort),
807
- },
808
- })
809
- child.on('exit', (code) => {
810
- if (code !== 0 && code !== null) {
811
- log.orez(`on-healthy command exited with code ${code}`)
812
- }
813
- })
814
- }
815
-
816
826
  process.on('SIGINT', async () => {
827
+ adminServer?.close()
817
828
  s3Server?.close()
818
829
  await stop()
819
830
  process.exit(0)
820
831
  })
821
832
  process.on('SIGTERM', async () => {
833
+ adminServer?.close()
822
834
  s3Server?.close()
823
835
  await stop()
824
836
  process.exit(0)
package/src/config.ts CHANGED
@@ -2,10 +2,14 @@ import type { PGliteOptions } from '@electric-sql/pglite'
2
2
 
3
3
  export type LogLevel = 'error' | 'warn' | 'info' | 'debug'
4
4
 
5
+ // lifecycle hooks - can be shell command string (CLI) or callback (programmatic)
6
+ export type Hook = string | (() => void | Promise<void>)
7
+
5
8
  export interface ZeroLiteConfig {
6
9
  dataDir: string
7
10
  pgPort: number
8
11
  zeroPort: number
12
+ adminPort: number
9
13
  pgUser: string
10
14
  pgPassword: string
11
15
  migrationsDir: string
@@ -14,7 +18,9 @@ export interface ZeroLiteConfig {
14
18
  disableWasmSqlite: boolean
15
19
  logLevel: LogLevel
16
20
  pgliteOptions: Partial<PGliteOptions>
17
- onDbReady: string
21
+ // lifecycle hooks
22
+ onDbReady?: Hook // after db+proxy ready, before zero-cache
23
+ onHealthy?: Hook // after all services ready
18
24
  }
19
25
 
20
26
  export function getConfig(overrides: Partial<ZeroLiteConfig> = {}): ZeroLiteConfig {
@@ -22,6 +28,7 @@ export function getConfig(overrides: Partial<ZeroLiteConfig> = {}): ZeroLiteConf
22
28
  dataDir: overrides.dataDir || '.orez',
23
29
  pgPort: overrides.pgPort || 6434,
24
30
  zeroPort: overrides.zeroPort || 5849,
31
+ adminPort: overrides.adminPort || 0,
25
32
  pgUser: overrides.pgUser || 'user',
26
33
  pgPassword: overrides.pgPassword || 'password',
27
34
  migrationsDir: overrides.migrationsDir || '',
@@ -30,7 +37,8 @@ export function getConfig(overrides: Partial<ZeroLiteConfig> = {}): ZeroLiteConf
30
37
  disableWasmSqlite: overrides.disableWasmSqlite ?? false,
31
38
  logLevel: overrides.logLevel || 'warn',
32
39
  pgliteOptions: overrides.pgliteOptions || {},
33
- onDbReady: overrides.onDbReady || '',
40
+ onDbReady: overrides.onDbReady,
41
+ onHealthy: overrides.onHealthy,
34
42
  }
35
43
  }
36
44
 
package/src/index.ts CHANGED
@@ -9,8 +9,9 @@
9
9
  import { spawn, type ChildProcess } from 'node:child_process'
10
10
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'
11
11
  import { createRequire } from 'node:module'
12
- import { dirname, resolve } from 'node:path'
12
+ import { resolve } from 'node:path'
13
13
 
14
+ import { createLogStore, type LogStore } from './admin/log-store.js'
14
15
  import { getConfig, getConnectionString } from './config.js'
15
16
  import { log, port, setLogLevel } from './log.js'
16
17
  import { startPgProxy } from './pg-proxy.js'
@@ -22,7 +23,42 @@ import type { ZeroLiteConfig } from './config.js'
22
23
  import type { PGlite } from '@electric-sql/pglite'
23
24
 
24
25
  export { getConfig, getConnectionString } from './config.js'
25
- export type { LogLevel, ZeroLiteConfig } from './config.js'
26
+ export type { Hook, LogLevel, ZeroLiteConfig } from './config.js'
27
+
28
+ // helper to run a hook (string command or callback function)
29
+ async function runHook(
30
+ hook: string | (() => void | Promise<void>) | undefined,
31
+ name: string,
32
+ env: Record<string, string>
33
+ ): Promise<void> {
34
+ if (!hook) return
35
+
36
+ if (typeof hook === 'function') {
37
+ log.debug.orez(`running ${name} callback`)
38
+ await hook()
39
+ log.orez(`${name} done`)
40
+ return
41
+ }
42
+
43
+ // string command
44
+ log.debug.orez(`running ${name}: ${hook}`)
45
+ await new Promise<void>((resolve, reject) => {
46
+ const child = spawn(hook, {
47
+ shell: true,
48
+ stdio: 'inherit',
49
+ env: { ...process.env, ...env },
50
+ })
51
+ child.on('exit', (code) => {
52
+ if (code === 0) {
53
+ log.orez(`${name} done`)
54
+ resolve()
55
+ } else {
56
+ reject(new Error(`${name} exited with code ${code}`))
57
+ }
58
+ })
59
+ child.on('error', reject)
60
+ })
61
+ }
26
62
 
27
63
  // resolve a package entry — import.meta.resolve doesn't work in vitest
28
64
  function resolvePackage(pkg: string): string {
@@ -46,12 +82,20 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
46
82
  const zeroPort = config.skipZeroCache
47
83
  ? config.zeroPort
48
84
  : await findPort(config.zeroPort)
85
+ const adminPort = config.adminPort > 0 ? await findPort(config.adminPort) : 0
49
86
  if (pgPort !== config.pgPort)
50
87
  log.debug.orez(`port ${config.pgPort} in use, using ${pgPort}`)
51
88
  if (!config.skipZeroCache && zeroPort !== config.zeroPort)
52
89
  log.debug.orez(`port ${config.zeroPort} in use, using ${zeroPort}`)
90
+ if (adminPort > 0 && adminPort !== config.adminPort)
91
+ log.debug.orez(`port ${config.adminPort} in use, using ${adminPort}`)
53
92
  config.pgPort = pgPort
54
93
  config.zeroPort = zeroPort
94
+ config.adminPort = adminPort
95
+
96
+ // create log store for admin dashboard
97
+ const logStore: LogStore | undefined =
98
+ adminPort > 0 ? createLogStore(config.dataDir) : undefined
55
99
 
56
100
  log.debug.orez(`data dir: ${resolve(config.dataDir)}`)
57
101
 
@@ -80,34 +124,17 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
80
124
  // seed data if needed
81
125
  await seedIfNeeded(db, config)
82
126
 
83
- // run on-db-ready command (e.g. migrations) before zero-cache starts
127
+ // run on-db-ready hook (e.g. migrations) before zero-cache starts
84
128
  if (config.onDbReady) {
85
- log.debug.orez(`running on-db-ready: ${config.onDbReady}`)
86
129
  const upstreamUrl = getConnectionString(config, 'postgres')
87
130
  const cvrUrl = getConnectionString(config, 'zero_cvr')
88
131
  const cdbUrl = getConnectionString(config, 'zero_cdb')
89
- await new Promise<void>((resolve, reject) => {
90
- const child = spawn(config.onDbReady, {
91
- shell: true,
92
- stdio: 'inherit',
93
- env: {
94
- ...process.env,
95
- ZERO_UPSTREAM_DB: upstreamUrl,
96
- ZERO_CVR_DB: cvrUrl,
97
- ZERO_CHANGE_DB: cdbUrl,
98
- DATABASE_URL: upstreamUrl,
99
- OREZ_PG_PORT: String(config.pgPort),
100
- },
101
- })
102
- child.on('exit', (code) => {
103
- if (code === 0) {
104
- log.orez('on-db-ready done')
105
- resolve()
106
- } else {
107
- reject(new Error(`on-db-ready exited with code ${code}`))
108
- }
109
- })
110
- child.on('error', reject)
132
+ await runHook(config.onDbReady, 'on-db-ready', {
133
+ ZERO_UPSTREAM_DB: upstreamUrl,
134
+ ZERO_CVR_DB: cvrUrl,
135
+ ZERO_CHANGE_DB: cdbUrl,
136
+ DATABASE_URL: upstreamUrl,
137
+ OREZ_PG_PORT: String(config.pgPort),
111
138
  })
112
139
 
113
140
  // re-install change tracking on tables created by on-db-ready
@@ -120,19 +147,28 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
120
147
 
121
148
  // start zero-cache
122
149
  let zeroCacheProcess: ChildProcess | null = null
150
+ let zeroEnv: Record<string, string> = {}
123
151
  if (!config.skipZeroCache) {
124
- zeroCacheProcess = await startZeroCache(config)
152
+ const result = await startZeroCache(config, logStore)
153
+ zeroCacheProcess = result.process
154
+ zeroEnv = result.env
125
155
  await waitForZeroCache(config)
126
156
  log.zero(`ready ${port(config.zeroPort, 'magenta')}`)
127
157
  } else {
128
158
  log.orez('skip zero-cache')
129
159
  }
130
160
 
131
- const stop = async () => {
132
- log.debug.orez('shutting down')
161
+ // run on-healthy hook after all services are ready
162
+ if (config.onHealthy) {
163
+ await runHook(config.onHealthy, 'on-healthy', {
164
+ OREZ_PG_PORT: String(config.pgPort),
165
+ OREZ_ZERO_PORT: String(config.zeroPort),
166
+ })
167
+ }
168
+
169
+ const killZeroCache = async () => {
133
170
  if (zeroCacheProcess && !zeroCacheProcess.killed) {
134
171
  zeroCacheProcess.kill('SIGTERM')
135
- // wait up to 3s for graceful exit, then force kill
136
172
  await new Promise<void>((r) => {
137
173
  const timeout = setTimeout(() => {
138
174
  if (zeroCacheProcess && !zeroCacheProcess.killed) {
@@ -146,6 +182,20 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
146
182
  })
147
183
  })
148
184
  }
185
+ }
186
+
187
+ const restartZeroCache = async (cleanup = false) => {
188
+ await killZeroCache()
189
+ if (cleanup) cleanupStaleReplica(config)
190
+ const result = await startZeroCache(config, logStore)
191
+ zeroCacheProcess = result.process
192
+ zeroEnv = result.env
193
+ await waitForZeroCache(config)
194
+ }
195
+
196
+ const stop = async () => {
197
+ log.debug.orez('shutting down')
198
+ await killZeroCache()
149
199
  pgServer.close()
150
200
  await Promise.all([
151
201
  instances.postgres.close(),
@@ -155,7 +205,18 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
155
205
  log.debug.orez('stopped')
156
206
  }
157
207
 
158
- return { config, stop, db, instances, pgPort: config.pgPort, zeroPort: config.zeroPort }
208
+ return {
209
+ config,
210
+ stop,
211
+ db,
212
+ instances,
213
+ pgPort: config.pgPort,
214
+ zeroPort: config.zeroPort,
215
+ logStore,
216
+ zeroEnv,
217
+ restartZero: config.skipZeroCache ? undefined : () => restartZeroCache(false),
218
+ resetZero: config.skipZeroCache ? undefined : () => restartZeroCache(true),
219
+ }
159
220
  }
160
221
 
161
222
  function cleanupStaleReplica(config: ZeroLiteConfig): void {
@@ -272,7 +333,10 @@ module.exports.SqliteError = SqliteError;
272
333
  return resolve(tmp, 'orez-sqlite', 'node_modules')
273
334
  }
274
335
 
275
- async function startZeroCache(config: ZeroLiteConfig): Promise<ChildProcess> {
336
+ async function startZeroCache(
337
+ config: ZeroLiteConfig,
338
+ logStore?: LogStore
339
+ ): Promise<{ process: ChildProcess; env: Record<string, string> }> {
276
340
  // resolve @rocicorp/zero entry for finding zero-cache modules
277
341
  const zeroEntry = resolvePackage('@rocicorp/zero')
278
342
 
@@ -342,6 +406,7 @@ async function startZeroCache(config: ZeroLiteConfig): Promise<ChildProcess> {
342
406
  const lines = data.toString().trim().split('\n')
343
407
  for (const line of lines) {
344
408
  log.debug.zero(line)
409
+ logStore?.push('zero', 'info', line)
345
410
  }
346
411
  })
347
412
 
@@ -349,16 +414,18 @@ async function startZeroCache(config: ZeroLiteConfig): Promise<ChildProcess> {
349
414
  const lines = data.toString().trim().split('\n')
350
415
  for (const line of lines) {
351
416
  log.debug.zero(line)
417
+ logStore?.push('zero', 'error', line)
352
418
  }
353
419
  })
354
420
 
355
421
  child.on('exit', (code) => {
356
422
  if (code !== 0 && code !== null) {
357
423
  log.zero(`exited with code ${code}`)
424
+ logStore?.push('zero', 'error', `exited with code ${code}`)
358
425
  }
359
426
  })
360
427
 
361
- return child
428
+ return { process: child, env }
362
429
  }
363
430
 
364
431
  async function waitForZeroCache(
@@ -15,11 +15,18 @@ export interface PGliteInstances {
15
15
  cdb: PGlite
16
16
  }
17
17
 
18
+ // detect pglite corruption errors (wasm abort during init)
19
+ function isCorruptionError(err: unknown): boolean {
20
+ const msg = String(err)
21
+ return msg.includes('Aborted()') || msg.includes('_pg_initdb')
22
+ }
23
+
18
24
  // create a single pglite instance with given dataDir suffix
19
25
  async function createInstance(
20
26
  config: ZeroLiteConfig,
21
27
  name: string,
22
- withExtensions: boolean
28
+ withExtensions: boolean,
29
+ isRetry = false
23
30
  ): Promise<PGlite> {
24
31
  const dataPath = resolve(config.dataDir, `pgdata-${name}`)
25
32
  mkdirSync(dataPath, { recursive: true })
@@ -30,17 +37,30 @@ async function createInstance(
30
37
  debug: _dbg,
31
38
  ...userOpts
32
39
  } = config.pgliteOptions as Record<string, any>
33
- const db = new PGlite({
34
- dataDir: dataPath,
35
- debug: config.logLevel === 'debug' ? 1 : 0,
36
- relaxedDurability: true,
37
- ...userOpts,
38
- extensions: withExtensions ? userOpts.extensions || { vector, pg_trgm } : {},
39
- })
40
-
41
- await db.waitReady
42
- log.debug.pglite(`${name} ready`)
43
- return db
40
+
41
+ try {
42
+ const db = new PGlite({
43
+ dataDir: dataPath,
44
+ debug: config.logLevel === 'debug' ? 1 : 0,
45
+ relaxedDurability: true,
46
+ ...userOpts,
47
+ extensions: withExtensions ? userOpts.extensions || { vector, pg_trgm } : {},
48
+ })
49
+
50
+ await db.waitReady
51
+ log.debug.pglite(`${name} ready`)
52
+ return db
53
+ } catch (err) {
54
+ if (isCorruptionError(err) && !isRetry) {
55
+ // corrupted data directory - backup and retry with fresh init
56
+ const backupPath = `${dataPath}.corrupt.${Date.now()}`
57
+ log.pglite(`corrupted data detected in ${name}, backing up to ${backupPath}`)
58
+ renameSync(dataPath, backupPath)
59
+ log.pglite(`retrying ${name} with fresh database`)
60
+ return createInstance(config, name, withExtensions, true)
61
+ }
62
+ throw err
63
+ }
44
64
  }
45
65
 
46
66
  /**
@@ -1,3 +1,5 @@
1
+ import { join } from 'node:path'
2
+
1
3
  import { describe, it, expect } from 'vitest'
2
4
 
3
5
  import {
@@ -367,11 +369,13 @@ describe('pgoutput-encoder', () => {
367
369
  // roundtrip tests: encode with orez → parse with zero-cache's parser
368
370
  // this validates the fundamental contract between orez and zero-cache
369
371
  describe('roundtrip: orez encoder → zero-cache parser', () => {
370
- // absolute path bypasses package.json exports restriction
372
+ // relative path bypasses package.json exports restriction
371
373
  // eslint-disable-next-line @typescript-eslint/no-require-imports
372
- const {
373
- PgoutputParser,
374
- } = require('/Users/n8/orez/node_modules/@rocicorp/zero/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput-parser.js')
374
+ const parserPath = join(
375
+ import.meta.dirname,
376
+ '../../node_modules/@rocicorp/zero/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput-parser.js'
377
+ )
378
+ const { PgoutputParser } = require(parserPath)
375
379
 
376
380
  // mock type parsers: unknown OIDs default to String (identity for text)
377
381
  const typeParsers = { getTypeParser: () => String }
@@ -571,13 +575,17 @@ describe('pgoutput-encoder', () => {
571
575
 
572
576
  // verify lexi version ordering is preserved
573
577
  // eslint-disable-next-line @typescript-eslint/no-require-imports
574
- const {
575
- versionToLexi,
576
- } = require('/Users/n8/orez/node_modules/@rocicorp/zero/out/zero-cache/src/types/lexi-version.js')
578
+ const lexiPath = join(
579
+ import.meta.dirname,
580
+ '../../node_modules/@rocicorp/zero/out/zero-cache/src/types/lexi-version.js'
581
+ )
582
+ const { versionToLexi } = require(lexiPath)
577
583
  // eslint-disable-next-line @typescript-eslint/no-require-imports
578
- const {
579
- toBigInt: lsnToBigInt,
580
- } = require('/Users/n8/orez/node_modules/@rocicorp/zero/out/zero-cache/src/services/change-source/pg/lsn.js')
584
+ const lsnPath = join(
585
+ import.meta.dirname,
586
+ '../../node_modules/@rocicorp/zero/out/zero-cache/src/services/change-source/pg/lsn.js'
587
+ )
588
+ const { toBigInt: lsnToBigInt } = require(lsnPath)
581
589
 
582
590
  const slotHex = `00000000/${slotLsn.toString(16).padStart(8, '0')}`.toUpperCase()
583
591
  const beginHex = `00000000/${beginLsn.toString(16).padStart(8, '0')}`.toUpperCase()
@@ -1,15 +1,20 @@
1
1
  import { startZeroLite } from './index.js'
2
2
 
3
- import type { ZeroLiteConfig } from './config.js'
3
+ import type { Hook, ZeroLiteConfig } from './config.js'
4
4
  import type { Server } from 'node:http'
5
5
  import type { Plugin } from 'vite'
6
6
 
7
- export interface OrezPluginOptions extends Partial<ZeroLiteConfig> {
7
+ export interface OrezPluginOptions extends Partial<
8
+ Omit<ZeroLiteConfig, 'onDbReady' | 'onHealthy'>
9
+ > {
8
10
  s3?: boolean
9
11
  s3Port?: number
12
+ // lifecycle hooks - callback functions (preferred for vite) or shell commands
13
+ onDbReady?: Hook
14
+ onHealthy?: Hook
10
15
  }
11
16
 
12
- export default function orez(options?: OrezPluginOptions): Plugin {
17
+ export function orezPlugin(options?: OrezPluginOptions): Plugin {
13
18
  let stop: (() => Promise<void>) | null = null
14
19
  let s3Server: Server | null = null
15
20