orez 0.1.4 → 0.1.6

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 (49) hide show
  1. package/README.md +4 -4
  2. package/dist/admin/server.d.ts.map +1 -1
  3. package/dist/admin/server.js +1 -0
  4. package/dist/admin/server.js.map +1 -1
  5. package/dist/admin/ui.d.ts.map +1 -1
  6. package/dist/admin/ui.js +12 -0
  7. package/dist/admin/ui.js.map +1 -1
  8. package/dist/cli.js +5 -5
  9. package/dist/cli.js.map +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +44 -64
  12. package/dist/index.js.map +1 -1
  13. package/dist/log.d.ts +1 -1
  14. package/dist/log.d.ts.map +1 -1
  15. package/dist/log.js +2 -2
  16. package/dist/log.js.map +1 -1
  17. package/dist/sqlite-mode/apply-mode.d.ts +49 -0
  18. package/dist/sqlite-mode/apply-mode.d.ts.map +1 -0
  19. package/dist/sqlite-mode/apply-mode.js +190 -0
  20. package/dist/sqlite-mode/apply-mode.js.map +1 -0
  21. package/dist/sqlite-mode/index.d.ts +14 -0
  22. package/dist/sqlite-mode/index.d.ts.map +1 -0
  23. package/dist/sqlite-mode/index.js +14 -0
  24. package/dist/sqlite-mode/index.js.map +1 -0
  25. package/dist/sqlite-mode/resolve-mode.d.ts +24 -0
  26. package/dist/sqlite-mode/resolve-mode.d.ts.map +1 -0
  27. package/dist/sqlite-mode/resolve-mode.js +61 -0
  28. package/dist/sqlite-mode/resolve-mode.js.map +1 -0
  29. package/dist/sqlite-mode/shim-template.d.ts +20 -0
  30. package/dist/sqlite-mode/shim-template.d.ts.map +1 -0
  31. package/dist/sqlite-mode/shim-template.js +144 -0
  32. package/dist/sqlite-mode/shim-template.js.map +1 -0
  33. package/dist/sqlite-mode/types.d.ts +16 -0
  34. package/dist/sqlite-mode/types.d.ts.map +1 -0
  35. package/dist/sqlite-mode/types.js +17 -0
  36. package/dist/sqlite-mode/types.js.map +1 -0
  37. package/package.json +3 -2
  38. package/src/admin/server.ts +1 -0
  39. package/src/admin/ui.ts +12 -0
  40. package/src/cli.ts +5 -5
  41. package/src/index.ts +79 -63
  42. package/src/log.ts +2 -2
  43. package/src/shim/hooks.mjs +42 -18
  44. package/src/sqlite-mode/apply-mode.ts +224 -0
  45. package/src/sqlite-mode/index.ts +14 -0
  46. package/src/sqlite-mode/resolve-mode.ts +71 -0
  47. package/src/sqlite-mode/shim-template.ts +158 -0
  48. package/src/sqlite-mode/sqlite-mode.test.ts +421 -0
  49. package/src/sqlite-mode/types.ts +29 -0
@@ -0,0 +1,144 @@
1
+ /**
2
+ * shim template generator - single source of truth for both cjs and esm shims.
3
+ * generates the shim code that wraps bedrock-sqlite to be compatible with
4
+ * @rocicorp/zero-sqlite3 (better-sqlite3 api).
5
+ */
6
+ import { COMMON_PRAGMAS, JOURNAL_MODE } from './types.js';
7
+ /**
8
+ * generate the core shim code (shared between cjs and esm)
9
+ * this is the constructor wrapper and api polyfills
10
+ */
11
+ function generateShimCore(opts) {
12
+ const journalMode = JOURNAL_MODE[opts.mode];
13
+ const { busy_timeout, synchronous } = COMMON_PRAGMAS;
14
+ return `
15
+ var OrigDatabase = mod.Database;
16
+ var SqliteError = mod.SqliteError;
17
+
18
+ function Database() {
19
+ var db = new OrigDatabase(...arguments);
20
+ try {
21
+ db.pragma('journal_mode = ${journalMode}');
22
+ db.pragma('busy_timeout = ${busy_timeout}');
23
+ db.pragma('synchronous = ${synchronous}');
24
+ } catch(e) {}
25
+ return db;
26
+ }
27
+
28
+ Database.prototype = OrigDatabase.prototype;
29
+ Database.prototype.constructor = Database;
30
+ Object.keys(OrigDatabase).forEach(function(k) { Database[k] = OrigDatabase[k]; });
31
+
32
+ // api polyfills for better-sqlite3 compatibility
33
+ Database.prototype.unsafeMode = function() { return this; };
34
+ if (!Database.prototype.defaultSafeIntegers) {
35
+ Database.prototype.defaultSafeIntegers = function() { return this; };
36
+ }
37
+ if (!Database.prototype.serialize) {
38
+ Database.prototype.serialize = function() { throw new Error('not supported in wasm'); };
39
+ }
40
+ if (!Database.prototype.backup) {
41
+ Database.prototype.backup = function() { throw new Error('not supported in wasm'); };
42
+ }
43
+
44
+ // wrap pragma to skip optimize (can corrupt wasm vfs) and swallow sqlite errors
45
+ var origPragma = OrigDatabase.prototype.pragma;
46
+ Database.prototype.pragma = function(str, opts) {
47
+ if (str && str.trim().toLowerCase().startsWith('optimize')) return [];
48
+ try { return origPragma.call(this, str, opts); }
49
+ catch(e) { if (e && (e.code === 'SQLITE_CORRUPT' || e.code === 'SQLITE_IOERR')) return []; throw e; }
50
+ };
51
+
52
+ // wrap close to swallow wasm errors during shutdown
53
+ var origClose = OrigDatabase.prototype.close;
54
+ Database.prototype.close = function() {
55
+ try { return origClose.call(this); }
56
+ catch(e) { console.error('[orez-shim] close error (swallowed):', e?.message || e); }
57
+ };
58
+
59
+ // statement prototype polyfills
60
+ var tmpDb = new OrigDatabase(':memory:');
61
+ var tmpStmt = tmpDb.prepare('SELECT 1');
62
+ var SP = Object.getPrototypeOf(tmpStmt);
63
+ if (!SP.safeIntegers) SP.safeIntegers = function() { return this; };
64
+ SP.scanStatus = function() { return undefined; };
65
+ SP.scanStatusV2 = function() { return []; };
66
+ SP.scanStatusReset = function() {};
67
+ tmpDb.close();
68
+
69
+ // scanstat constants for query planner compatibility
70
+ Database.SQLITE_SCANSTAT_NLOOP = 0;
71
+ Database.SQLITE_SCANSTAT_NVISIT = 1;
72
+ Database.SQLITE_SCANSTAT_EST = 2;
73
+ Database.SQLITE_SCANSTAT_NAME = 3;
74
+ Database.SQLITE_SCANSTAT_EXPLAIN = 4;
75
+ Database.SQLITE_SCANSTAT_SELECTID = 5;
76
+ Database.SQLITE_SCANSTAT_PARENTID = 6;
77
+ Database.SQLITE_SCANSTAT_NCYCLE = 7;
78
+ Database.SQLITE_SCANSTAT_COMPLEX = 8;
79
+ `.trim();
80
+ }
81
+ /**
82
+ * generate debug tracing code for run() method
83
+ */
84
+ function generateTracing() {
85
+ return `
86
+ // trace writes to _zero.changeLog and _zero.replicationState for debugging
87
+ var origRun = OrigDatabase.prototype.run;
88
+ Database.prototype.run = function(sql) {
89
+ var args = Array.prototype.slice.call(arguments, 1);
90
+ if (typeof sql === 'string') {
91
+ if (sql.includes('_zero.changeLog')) {
92
+ console.info('[orez-shim] changeLog write:', sql.slice(0, 120), args.length ? JSON.stringify(args[0]).slice(0, 80) : '');
93
+ }
94
+ if (sql.includes('_zero.replicationState') && (sql.includes('UPDATE') || sql.includes('INSERT'))) {
95
+ console.info('[orez-shim] replicationState update:', sql.slice(0, 120), args.length ? JSON.stringify(args[0]).slice(0, 80) : '');
96
+ }
97
+ }
98
+ return origRun.apply(this, arguments);
99
+ };
100
+ `.trim();
101
+ }
102
+ /**
103
+ * generate commonjs shim for in-place patching of @rocicorp/zero-sqlite3
104
+ */
105
+ export function generateCjsShim(opts) {
106
+ const core = generateShimCore(opts);
107
+ const tracing = opts.includeTracing ? '\n' + generateTracing() : '';
108
+ return `'use strict';
109
+ // orez sqlite shim - wraps bedrock-sqlite for zero-cache compatibility
110
+ // mode: ${opts.mode}, journal_mode: ${JOURNAL_MODE[opts.mode]}
111
+ var mod = require('${opts.bedrockPath}');
112
+ ${core}
113
+ ${tracing}
114
+ module.exports = Database;
115
+ module.exports.SqliteError = SqliteError;
116
+ `;
117
+ }
118
+ /**
119
+ * generate esm shim for loader hooks
120
+ */
121
+ export function generateEsmShim(opts) {
122
+ const core = generateShimCore(opts);
123
+ const tracing = opts.includeTracing ? '\n' + generateTracing() : '';
124
+ return `// orez sqlite shim - wraps bedrock-sqlite for zero-cache compatibility
125
+ // mode: ${opts.mode}, journal_mode: ${JOURNAL_MODE[opts.mode]}
126
+
127
+ // catch uncaught exceptions from bedrock-sqlite wasm clearly
128
+ process.on('uncaughtException', (err) => {
129
+ console.error('[orez-shim] UNCAUGHT EXCEPTION:', err?.message || err);
130
+ console.error('[orez-shim] code:', err?.code, 'name:', err?.name);
131
+ console.error('[orez-shim] stack:', err?.stack?.split('\\n').slice(0, 5).join('\\n'));
132
+ process.exit(1);
133
+ });
134
+
135
+ import { createRequire } from 'node:module';
136
+ const require = createRequire('${opts.bedrockPath}');
137
+ var mod = require('${opts.bedrockPath}');
138
+ ${core}
139
+ ${tracing}
140
+ export default Database;
141
+ export { SqliteError };
142
+ `;
143
+ }
144
+ //# sourceMappingURL=shim-template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shim-template.js","sourceRoot":"","sources":["../../src/sqlite-mode/shim-template.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,YAAY,EAAmB,MAAM,YAAY,CAAA;AAS1E;;;GAGG;AACH,SAAS,gBAAgB,CAAC,IAAiB;IACzC,MAAM,WAAW,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3C,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,GAAG,cAAc,CAAA;IAEpD,OAAO;;;;;;;gCAOuB,WAAW;gCACX,YAAY;+BACb,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwDzC,CAAC,IAAI,EAAE,CAAA;AACR,CAAC;AAED;;GAEG;AACH,SAAS,eAAe;IACtB,OAAO;;;;;;;;;;;;;;;CAeR,CAAC,IAAI,EAAE,CAAA;AACR,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,IAAiB;IAC/C,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,GAAG,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAEnE,OAAO;;WAEE,IAAI,CAAC,IAAI,mBAAmB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;qBACzC,IAAI,CAAC,WAAW;EACnC,IAAI;EACJ,OAAO;;;CAGR,CAAA;AACD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,IAAiB;IAC/C,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,GAAG,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAEnE,OAAO;WACE,IAAI,CAAC,IAAI,mBAAmB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;;;;;;;iCAW7B,IAAI,CAAC,WAAW;qBAC5B,IAAI,CAAC,WAAW;EACnC,IAAI;EACJ,OAAO;;;CAGR,CAAA;AACD,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * sqlite mode types and constants
3
+ */
4
+ export type SqliteMode = 'native' | 'wasm';
5
+ export interface SqliteModeConfig {
6
+ mode: SqliteMode;
7
+ bedrockPath?: string;
8
+ zeroSqlitePath?: string;
9
+ }
10
+ export declare const JOURNAL_MODE: Record<SqliteMode, string>;
11
+ export declare const COMMON_PRAGMAS: {
12
+ busy_timeout: string;
13
+ synchronous: string;
14
+ };
15
+ export declare const BACKUP_MARKER = ".orez-backup";
16
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sqlite-mode/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAA;AAE1C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,UAAU,CAAA;IAEhB,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAID,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAGnD,CAAA;AAGD,eAAO,MAAM,cAAc;;;CAG1B,CAAA;AAGD,eAAO,MAAM,aAAa,iBAAiB,CAAA"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * sqlite mode types and constants
3
+ */
4
+ // journal mode - zero-cache requires wal2 for replica sync (BEGIN CONCURRENT)
5
+ // both modes use wal2 now - bedrock-sqlite wasm should support it
6
+ export const JOURNAL_MODE = {
7
+ native: 'wal2',
8
+ wasm: 'wal2',
9
+ };
10
+ // common pragmas shared by both modes
11
+ export const COMMON_PRAGMAS = {
12
+ busy_timeout: '30000',
13
+ synchronous: 'normal',
14
+ };
15
+ // backup file marker for identifying orez-shimmed packages
16
+ export const BACKUP_MARKER = '.orez-backup';
17
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/sqlite-mode/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH,8EAA8E;AAC9E,kEAAkE;AAClE,MAAM,CAAC,MAAM,YAAY,GAA+B;IACtD,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,MAAM;CACb,CAAA;AAED,sCAAsC;AACtC,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,YAAY,EAAE,OAAO;IACrB,WAAW,EAAE,QAAQ;CACtB,CAAA;AAED,2DAA2D;AAC3D,MAAM,CAAC,MAAM,aAAa,GAAG,cAAc,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orez",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "PGlite-powered zero-sync development backend. No Docker required.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -49,7 +49,7 @@
49
49
  "@electric-sql/pglite": "^0.3.15",
50
50
  "@electric-sql/pglite-tools": "^0.2.20",
51
51
  "@rocicorp/zero": ">=0.1.0",
52
- "bedrock-sqlite": "0.1.4",
52
+ "bedrock-sqlite": "0.1.NaN",
53
53
  "citty": "^0.2.0",
54
54
  "pg-gateway": "0.3.0-beta.4",
55
55
  "pgsql-parser": "^17.9.11",
@@ -64,6 +64,7 @@
64
64
  }
65
65
  },
66
66
  "devDependencies": {
67
+ "@rocicorp/zero-sqlite3": "^1.0.15",
67
68
  "@types/node": "^22.0.0",
68
69
  "@types/ws": "^8.18.1",
69
70
  "oxfmt": "latest",
@@ -93,6 +93,7 @@ export function startAdminServer(opts: AdminServerOpts): Promise<Server> {
93
93
  uptime: Math.floor((Date.now() - startTime) / 1000),
94
94
  logLevel: config.logLevel,
95
95
  skipZeroCache: config.skipZeroCache,
96
+ sqliteMode: config.disableWasmSqlite ? 'native' : 'wasm',
96
97
  })
97
98
  return
98
99
  }
package/src/admin/ui.ts CHANGED
@@ -341,6 +341,7 @@ export function getAdminHtml(): string {
341
341
  ' <div class="spacer"></div>\n' +
342
342
  ' <span class="badge"><span class="dot"></span> pg <span id="pg-port">-</span></span>\n' +
343
343
  ' <span class="badge"><span class="dot"></span> zero <span id="zero-port">-</span></span>\n' +
344
+ ' <span class="badge" id="sqlite-badge">sqlite: --</span>\n' +
344
345
  ' <span class="badge" id="uptime-badge">&#9201; --</span>\n' +
345
346
  ' </div>\n' +
346
347
  '\n' +
@@ -458,6 +459,16 @@ export function getAdminHtml(): string {
458
459
  ' logView.style.display = "block";\n' +
459
460
  ' toolbar.style.display = "flex";\n' +
460
461
  ' activeSource = source;\n' +
462
+ ' // default zero tab to info level (too verbose otherwise)\n' +
463
+ ' var levelSelect = document.getElementById("level-filter");\n' +
464
+ ' if (source === "zero") {\n' +
465
+ ' activeLevel = "info";\n' +
466
+ ' levelSelect.value = "info";\n' +
467
+ ' } else if (activeLevel === "info" && levelSelect.value === "info") {\n' +
468
+ ' // reset to all levels when leaving zero tab if still on info\n' +
469
+ ' activeLevel = "";\n' +
470
+ ' levelSelect.value = "";\n' +
471
+ ' }\n' +
461
472
  ' lastCursor = 0;\n' +
462
473
  ' logView.innerHTML = "";\n' +
463
474
  ' fetchLogs();\n' +
@@ -624,6 +635,7 @@ export function getAdminHtml(): string {
624
635
  ' fetch("/api/status").then(function(res) { return res.json(); }).then(function(data) {\n' +
625
636
  ' document.getElementById("pg-port").textContent = ":" + data.pgPort;\n' +
626
637
  ' document.getElementById("zero-port").textContent = ":" + data.zeroPort;\n' +
638
+ ' document.getElementById("sqlite-badge").textContent = "sqlite: " + (data.sqliteMode || "wasm");\n' +
627
639
  ' var m = Math.floor(data.uptime / 60);\n' +
628
640
  ' var s = data.uptime % 60;\n' +
629
641
  ' document.getElementById("uptime-badge").textContent = "\\u23F1 " + (m > 0 ? m + "m " : "") + s + "s";\n' +
package/src/cli.ts CHANGED
@@ -895,9 +895,9 @@ const main = defineCommand({
895
895
  description: 'command to run once all services are healthy',
896
896
  default: '',
897
897
  },
898
- admin: {
898
+ 'disable-admin': {
899
899
  type: 'boolean',
900
- description: 'start admin dashboard',
900
+ description: 'disable admin dashboard',
901
901
  default: false,
902
902
  },
903
903
  'admin-port': {
@@ -912,7 +912,7 @@ const main = defineCommand({
912
912
  pg_restore: pgRestoreCommand,
913
913
  },
914
914
  async run({ args }) {
915
- const adminPort = args.admin ? Number(args['admin-port']) : 0
915
+ const adminPort = args['disable-admin'] ? 0 : Number(args['admin-port'])
916
916
  const {
917
917
  config,
918
918
  stop,
@@ -948,7 +948,7 @@ const main = defineCommand({
948
948
  }
949
949
 
950
950
  let adminServer: import('node:http').Server | null = null
951
- if (args.admin && logStore && zeroEnv) {
951
+ if (!args['disable-admin'] && logStore && zeroEnv) {
952
952
  const { startAdminServer } = await import('./admin/server.js')
953
953
  adminServer = await startAdminServer({
954
954
  port: config.adminPort,
@@ -963,7 +963,7 @@ const main = defineCommand({
963
963
  }
964
964
 
965
965
  log.pg(
966
- `postgresql://${config.pgUser}:${config.pgPassword}@127.0.0.1:${config.pgPort}/postgres`
966
+ `ready ${url(`postgresql://${config.pgUser}:${config.pgPassword}@127.0.0.1:${config.pgPort}/postgres`)}`
967
967
  )
968
968
 
969
969
  let stopping = false
package/src/index.ts CHANGED
@@ -8,7 +8,6 @@
8
8
 
9
9
  import { spawn, type ChildProcess } from 'node:child_process'
10
10
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'
11
- import { createRequire } from 'node:module'
12
11
  import { resolve } from 'node:path'
13
12
 
14
13
  import {
@@ -23,6 +22,12 @@ import { startPgProxy } from './pg-proxy.js'
23
22
  import { createPGliteInstances, runMigrations } from './pglite-manager.js'
24
23
  import { findPort } from './port.js'
25
24
  import { installChangeTracking } from './replication/change-tracker.js'
25
+ import {
26
+ resolveSqliteMode,
27
+ resolveSqliteModeConfig,
28
+ type SqliteMode,
29
+ type SqliteModeConfig,
30
+ } from './sqlite-mode/index.js'
26
31
 
27
32
  import type { ZeroLiteConfig } from './config.js'
28
33
  import type { PGlite } from '@electric-sql/pglite'
@@ -65,18 +70,8 @@ async function runHook(
65
70
  })
66
71
  }
67
72
 
68
- // resolve a package entry — import.meta.resolve doesn't work in vitest
69
- function resolvePackage(pkg: string): string {
70
- try {
71
- const resolved = import.meta.resolve(pkg)
72
- if (resolved) return resolved.replace('file://', '')
73
- } catch {}
74
- try {
75
- const require = createRequire(import.meta.url)
76
- return require.resolve(pkg)
77
- } catch {}
78
- return ''
79
- }
73
+ // resolvePackage moved to sqlite-mode/resolve-mode.ts
74
+ import { resolvePackage } from './sqlite-mode/resolve-mode.js'
80
75
 
81
76
  export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
82
77
  const config = getConfig(overrides)
@@ -111,6 +106,20 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
111
106
 
112
107
  log.debug.orez(`data dir: ${resolve(config.dataDir)}`)
113
108
 
109
+ // resolve sqlite mode config early (used for shim application and cleanup)
110
+ // requested wasm can fall back to native if required packages are missing.
111
+ let sqliteMode = resolveSqliteMode(config.disableWasmSqlite)
112
+ let sqliteModeConfig = resolveSqliteModeConfig(config.disableWasmSqlite)
113
+ if (sqliteMode === 'wasm' && !sqliteModeConfig) {
114
+ log.orez(
115
+ 'warning: wasm sqlite requested but dependencies are missing, falling back to native sqlite'
116
+ )
117
+ sqliteMode = 'native'
118
+ config.disableWasmSqlite = true
119
+ sqliteModeConfig = resolveSqliteModeConfig(true)
120
+ }
121
+ log.orez(`sqlite: ${sqliteMode}`)
122
+
114
123
  mkdirSync(config.dataDir, { recursive: true })
115
124
 
116
125
  // write pid file for IPC (pg_restore uses this to signal restart)
@@ -174,7 +183,12 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
174
183
  if (!config.skipZeroCache) {
175
184
  // use internal port when http proxy is enabled
176
185
  const zeroConfig = httpLog ? { ...config, zeroPort: zeroInternalPort } : config
177
- const result = await startZeroCache(zeroConfig, logStore)
186
+ const result = await startZeroCache(
187
+ zeroConfig,
188
+ logStore,
189
+ sqliteMode,
190
+ sqliteModeConfig
191
+ )
178
192
  zeroCacheProcess = result.process
179
193
  zeroEnv = result.env
180
194
  await waitForZeroCache(zeroConfig)
@@ -189,7 +203,7 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
189
203
  log.debug.orez(`http proxy listening on ${config.zeroPort}`)
190
204
  }
191
205
 
192
- log.zero(`ready ${port(config.zeroPort, 'magenta')}`)
206
+ log.zero(`ready ${port(config.zeroPort, 'magenta')} (sqlite: ${sqliteMode})`)
193
207
  } else {
194
208
  log.orez('skip zero-cache')
195
209
  }
@@ -225,7 +239,12 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
225
239
  await killZeroCache()
226
240
  // use internal port when http proxy is enabled
227
241
  const zeroConfig = httpLog ? { ...config, zeroPort: zeroInternalPort } : config
228
- const result = await startZeroCache(zeroConfig, logStore)
242
+ const result = await startZeroCache(
243
+ zeroConfig,
244
+ logStore,
245
+ sqliteMode,
246
+ sqliteModeConfig
247
+ )
229
248
  zeroCacheProcess = result.process
230
249
  zeroEnv = result.env
231
250
  await waitForZeroCache(zeroConfig)
@@ -322,7 +341,12 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
322
341
  log.orez('starting zero-cache...')
323
342
  // use internal port when http proxy is enabled
324
343
  const zeroConfig = httpLog ? { ...config, zeroPort: zeroInternalPort } : config
325
- const result = await startZeroCache(zeroConfig, logStore)
344
+ const result = await startZeroCache(
345
+ zeroConfig,
346
+ logStore,
347
+ sqliteMode,
348
+ sqliteModeConfig
349
+ )
326
350
  zeroCacheProcess = result.process
327
351
  zeroEnv = result.env
328
352
 
@@ -435,38 +459,30 @@ async function seedIfNeeded(db: PGlite, config: ZeroLiteConfig): Promise<void> {
435
459
  log.orez('seeded')
436
460
  }
437
461
 
438
- // overwrite @rocicorp/zero-sqlite3 with a shim that uses bedrock-sqlite (wasm).
439
- // NODE_PATH doesn't work because local node_modules is searched first, so we
440
- // have to overwrite the actual package in place.
441
- function writeSqliteShim(): void {
442
- const zeroSqlitePath = resolvePackage('@rocicorp/zero-sqlite3')
443
- if (!zeroSqlitePath) {
444
- log.debug.orez('warning: @rocicorp/zero-sqlite3 not found, skipping shim')
445
- return
446
- }
447
-
448
- // find the package root (contains package.json)
449
- let dir = zeroSqlitePath
450
- while (dir && !existsSync(resolve(dir, 'package.json'))) {
451
- const parent = resolve(dir, '..')
452
- if (parent === dir) break
453
- dir = parent
454
- }
455
-
456
- const bedrockEntry = resolvePackage('bedrock-sqlite')
457
- if (!bedrockEntry) {
458
- log.debug.orez('warning: bedrock-sqlite not found, skipping shim')
459
- return
460
- }
461
-
462
- const shimCode = `'use strict';
463
- var mod = require('${bedrockEntry}');
462
+ // write shim to tmpdir and return node_modules path for NODE_PATH
463
+ // this approach doesn't modify node_modules - just shadows it via NODE_PATH
464
+ function writeTmpShim(bedrockPath: string): string {
465
+ const tmp = process.env.TMPDIR || process.env.TEMP || '/tmp'
466
+ const dir = resolve(tmp, 'orez-sqlite', 'node_modules', '@rocicorp', 'zero-sqlite3')
467
+ mkdirSync(dir, { recursive: true })
468
+
469
+ writeFileSync(
470
+ resolve(dir, 'package.json'),
471
+ '{"name":"@rocicorp/zero-sqlite3","main":"./index.js"}\n'
472
+ )
473
+
474
+ // use wal2 journal mode - required by zero-cache for replica sync
475
+ writeFileSync(
476
+ resolve(dir, 'index.js'),
477
+ `'use strict';
478
+ // orez wasm shim - shadows @rocicorp/zero-sqlite3 via NODE_PATH
479
+ var mod = require('${bedrockPath}');
464
480
  var OrigDatabase = mod.Database;
465
481
  var SqliteError = mod.SqliteError;
466
482
  function Database() {
467
483
  var db = new OrigDatabase(...arguments);
468
484
  try {
469
- db.pragma('journal_mode = delete');
485
+ db.pragma('journal_mode = wal2');
470
486
  db.pragma('busy_timeout = 30000');
471
487
  db.pragma('synchronous = normal');
472
488
  } catch(e) {}
@@ -499,23 +515,16 @@ Database.SQLITE_SCANSTAT_COMPLEX = 8;
499
515
  module.exports = Database;
500
516
  module.exports.SqliteError = SqliteError;
501
517
  `
518
+ )
502
519
 
503
- // overwrite the package's index.js and lib/index.js
504
- const indexPath = resolve(dir, 'lib', 'index.js')
505
- if (existsSync(indexPath)) {
506
- writeFileSync(indexPath, shimCode)
507
- log.debug.orez(`shimmed @rocicorp/zero-sqlite3 at ${indexPath}`)
508
- } else {
509
- // fallback: try root index.js
510
- const rootIndex = resolve(dir, 'index.js')
511
- writeFileSync(rootIndex, shimCode)
512
- log.debug.orez(`shimmed @rocicorp/zero-sqlite3 at ${rootIndex}`)
513
- }
520
+ return resolve(tmp, 'orez-sqlite', 'node_modules')
514
521
  }
515
522
 
516
523
  async function startZeroCache(
517
524
  config: ZeroLiteConfig,
518
- logStore?: LogStore
525
+ logStore?: LogStore,
526
+ sqliteMode: SqliteMode = resolveSqliteMode(config.disableWasmSqlite),
527
+ sqliteModeConfig?: SqliteModeConfig | null
519
528
  ): Promise<{ process: ChildProcess; env: Record<string, string> }> {
520
529
  // resolve @rocicorp/zero entry for finding zero-cache modules
521
530
  const zeroEntry = resolvePackage('@rocicorp/zero')
@@ -524,7 +533,7 @@ async function startZeroCache(
524
533
  throw new Error('zero-cache not found. install @rocicorp/zero')
525
534
  }
526
535
 
527
- if (config.disableWasmSqlite) {
536
+ if (sqliteMode === 'native') {
528
537
  log.debug.orez('wasm sqlite disabled, using native @rocicorp/zero-sqlite3')
529
538
  }
530
539
 
@@ -565,14 +574,21 @@ async function startZeroCache(
565
574
  throw new Error('zero-cache cli.js not found. install @rocicorp/zero')
566
575
  }
567
576
 
568
- // wasm sqlite: overwrite @rocicorp/zero-sqlite3 with our bedrock-sqlite shim
569
- if (!config.disableWasmSqlite) {
570
- writeSqliteShim()
577
+ // wasm mode: write shim to tmpdir and use NODE_PATH to shadow the real package
578
+ // this is non-destructive - doesn't modify node_modules
579
+ if (sqliteMode === 'wasm' && sqliteModeConfig?.bedrockPath) {
580
+ const shimNodeModules = writeTmpShim(sqliteModeConfig.bedrockPath)
581
+ const existingNodePath = process.env.NODE_PATH || ''
582
+ env.NODE_PATH = existingNodePath
583
+ ? `${shimNodeModules}:${existingNodePath}`
584
+ : shimNodeModules
585
+ log.debug.orez(`using wasm sqlite shim via NODE_PATH: ${shimNodeModules}`)
571
586
  }
572
587
 
573
- const nodeOptions = !config.disableWasmSqlite
574
- ? `--max-old-space-size=16384 ${process.env.NODE_OPTIONS || ''}`
575
- : process.env.NODE_OPTIONS || ''
588
+ const nodeOptions =
589
+ sqliteMode === 'wasm'
590
+ ? `--max-old-space-size=16384 ${process.env.NODE_OPTIONS || ''}`
591
+ : process.env.NODE_OPTIONS || ''
576
592
  if (nodeOptions.trim()) env.NODE_OPTIONS = nodeOptions.trim()
577
593
 
578
594
  const child = spawn(zeroCacheBin, [], {
package/src/log.ts CHANGED
@@ -41,9 +41,9 @@ export function port(n: number, color: keyof typeof COLORS): string {
41
41
  return `${DIM}${COLORS[color]}:${n}${RESET}`
42
42
  }
43
43
 
44
- /** format a url with yellow color */
44
+ /** format a url with green color */
45
45
  export function url(u: string): string {
46
- return `${COLORS.yellow}${u}${RESET}`
46
+ return `${COLORS.green}${u}${RESET}`
47
47
  }
48
48
 
49
49
  // map logger labels to logStore source names