orez 0.1.3 → 0.1.5

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 +39 -85
  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 +18 -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 +68 -94
  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 +423 -0
  49. package/src/sqlite-mode/types.ts +30 -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;AAKD,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,18 @@
1
+ /**
2
+ * sqlite mode types and constants
3
+ */
4
+ // journal mode differs between native and wasm
5
+ // - native: wal2 for better concurrency
6
+ // - wasm: delete for compatibility (wal2 corrupts wasm vfs on certain operations)
7
+ export const JOURNAL_MODE = {
8
+ native: 'wal2',
9
+ wasm: 'delete',
10
+ };
11
+ // common pragmas shared by both modes
12
+ export const COMMON_PRAGMAS = {
13
+ busy_timeout: '30000',
14
+ synchronous: 'normal',
15
+ };
16
+ // backup file marker for identifying orez-shimmed packages
17
+ export const BACKUP_MARKER = '.orez-backup';
18
+ //# 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,+CAA+C;AAC/C,wCAAwC;AACxC,kFAAkF;AAClF,MAAM,CAAC,MAAM,YAAY,GAA+B;IACtD,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,QAAQ;CACf,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.3",
3
+ "version": "0.1.5",
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.3",
52
+ "bedrock-sqlite": "0.1.5",
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,14 @@ 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
+ applySqliteMode,
27
+ cleanupShim,
28
+ resolveSqliteMode,
29
+ resolveSqliteModeConfig,
30
+ type SqliteMode,
31
+ type SqliteModeConfig,
32
+ } from './sqlite-mode/index.js'
26
33
 
27
34
  import type { ZeroLiteConfig } from './config.js'
28
35
  import type { PGlite } from '@electric-sql/pglite'
@@ -65,18 +72,8 @@ async function runHook(
65
72
  })
66
73
  }
67
74
 
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
- }
75
+ // resolvePackage moved to sqlite-mode/resolve-mode.ts
76
+ import { resolvePackage } from './sqlite-mode/resolve-mode.js'
80
77
 
81
78
  export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
82
79
  const config = getConfig(overrides)
@@ -111,6 +108,20 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
111
108
 
112
109
  log.debug.orez(`data dir: ${resolve(config.dataDir)}`)
113
110
 
111
+ // resolve sqlite mode config early (used for shim application and cleanup)
112
+ // requested wasm can fall back to native if required packages are missing.
113
+ let sqliteMode = resolveSqliteMode(config.disableWasmSqlite)
114
+ let sqliteModeConfig = resolveSqliteModeConfig(config.disableWasmSqlite)
115
+ if (sqliteMode === 'wasm' && !sqliteModeConfig) {
116
+ log.orez(
117
+ 'warning: wasm sqlite requested but dependencies are missing, falling back to native sqlite'
118
+ )
119
+ sqliteMode = 'native'
120
+ config.disableWasmSqlite = true
121
+ sqliteModeConfig = resolveSqliteModeConfig(true)
122
+ }
123
+ log.orez(`sqlite: ${sqliteMode}`)
124
+
114
125
  mkdirSync(config.dataDir, { recursive: true })
115
126
 
116
127
  // write pid file for IPC (pg_restore uses this to signal restart)
@@ -174,7 +185,12 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
174
185
  if (!config.skipZeroCache) {
175
186
  // use internal port when http proxy is enabled
176
187
  const zeroConfig = httpLog ? { ...config, zeroPort: zeroInternalPort } : config
177
- const result = await startZeroCache(zeroConfig, logStore)
188
+ const result = await startZeroCache(
189
+ zeroConfig,
190
+ logStore,
191
+ sqliteMode,
192
+ sqliteModeConfig
193
+ )
178
194
  zeroCacheProcess = result.process
179
195
  zeroEnv = result.env
180
196
  await waitForZeroCache(zeroConfig)
@@ -189,7 +205,7 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
189
205
  log.debug.orez(`http proxy listening on ${config.zeroPort}`)
190
206
  }
191
207
 
192
- log.zero(`ready ${port(config.zeroPort, 'magenta')}`)
208
+ log.zero(`ready ${port(config.zeroPort, 'magenta')} (sqlite: ${sqliteMode})`)
193
209
  } else {
194
210
  log.orez('skip zero-cache')
195
211
  }
@@ -225,7 +241,12 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
225
241
  await killZeroCache()
226
242
  // use internal port when http proxy is enabled
227
243
  const zeroConfig = httpLog ? { ...config, zeroPort: zeroInternalPort } : config
228
- const result = await startZeroCache(zeroConfig, logStore)
244
+ const result = await startZeroCache(
245
+ zeroConfig,
246
+ logStore,
247
+ sqliteMode,
248
+ sqliteModeConfig
249
+ )
229
250
  zeroCacheProcess = result.process
230
251
  zeroEnv = result.env
231
252
  await waitForZeroCache(zeroConfig)
@@ -322,7 +343,12 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
322
343
  log.orez('starting zero-cache...')
323
344
  // use internal port when http proxy is enabled
324
345
  const zeroConfig = httpLog ? { ...config, zeroPort: zeroInternalPort } : config
325
- const result = await startZeroCache(zeroConfig, logStore)
346
+ const result = await startZeroCache(
347
+ zeroConfig,
348
+ logStore,
349
+ sqliteMode,
350
+ sqliteModeConfig
351
+ )
326
352
  zeroCacheProcess = result.process
327
353
  zeroEnv = result.env
328
354
 
@@ -364,6 +390,9 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
364
390
  try {
365
391
  unlinkSync(pidFile)
366
392
  } catch {}
393
+ // restore original @rocicorp/zero-sqlite3 if we shimmed it
394
+ // (next start will re-apply shim if needed for wasm mode)
395
+ cleanupShim(sqliteModeConfig?.zeroSqlitePath)
367
396
  log.debug.orez('stopped')
368
397
  }
369
398
 
@@ -435,73 +464,13 @@ async function seedIfNeeded(db: PGlite, config: ZeroLiteConfig): Promise<void> {
435
464
  log.orez('seeded')
436
465
  }
437
466
 
438
- // create a fake @rocicorp/zero-sqlite3 package in tmpdir that redirects to
439
- // bedrock-sqlite (wasm). uses NODE_PATH to make node resolve our shim first —
440
- // no require hooks, no Module._resolveFilename monkey-patching, no .cjs files
441
- // in the package (which all break vite).
442
- function writeSqliteShim(): string {
443
- const tmp = process.env.TMPDIR || process.env.TEMP || '/tmp'
444
- const dir = resolve(tmp, 'orez-sqlite', 'node_modules', '@rocicorp', 'zero-sqlite3')
445
- mkdirSync(dir, { recursive: true })
446
-
447
- const bedrockEntry = resolvePackage('bedrock-sqlite')
448
-
449
- writeFileSync(
450
- resolve(dir, 'package.json'),
451
- '{"name":"@rocicorp/zero-sqlite3","main":"./index.js"}\n'
452
- )
453
-
454
- writeFileSync(
455
- resolve(dir, 'index.js'),
456
- `'use strict';
457
- var mod = require('${bedrockEntry}');
458
- var OrigDatabase = mod.Database;
459
- var SqliteError = mod.SqliteError;
460
- function Database() {
461
- var db = new OrigDatabase(...arguments);
462
- try {
463
- db.pragma('journal_mode = delete');
464
- db.pragma('busy_timeout = 30000');
465
- db.pragma('synchronous = normal');
466
- } catch(e) {}
467
- return db;
468
- }
469
- Database.prototype = OrigDatabase.prototype;
470
- Database.prototype.constructor = Database;
471
- Object.keys(OrigDatabase).forEach(function(k) { Database[k] = OrigDatabase[k]; });
472
- Database.prototype.unsafeMode = function() { return this; };
473
- if (!Database.prototype.defaultSafeIntegers) Database.prototype.defaultSafeIntegers = function() { return this; };
474
- if (!Database.prototype.serialize) Database.prototype.serialize = function() { throw new Error('not supported in wasm'); };
475
- if (!Database.prototype.backup) Database.prototype.backup = function() { throw new Error('not supported in wasm'); };
476
- var tmpDb = new OrigDatabase(':memory:');
477
- var tmpStmt = tmpDb.prepare('SELECT 1');
478
- var SP = Object.getPrototypeOf(tmpStmt);
479
- if (!SP.safeIntegers) SP.safeIntegers = function() { return this; };
480
- SP.scanStatus = function() { return undefined; };
481
- SP.scanStatusV2 = function() { return []; };
482
- SP.scanStatusReset = function() {};
483
- tmpDb.close();
484
- Database.SQLITE_SCANSTAT_NLOOP = 0;
485
- Database.SQLITE_SCANSTAT_NVISIT = 1;
486
- Database.SQLITE_SCANSTAT_EST = 2;
487
- Database.SQLITE_SCANSTAT_NAME = 3;
488
- Database.SQLITE_SCANSTAT_EXPLAIN = 4;
489
- Database.SQLITE_SCANSTAT_SELECTID = 5;
490
- Database.SQLITE_SCANSTAT_PARENTID = 6;
491
- Database.SQLITE_SCANSTAT_NCYCLE = 7;
492
- Database.SQLITE_SCANSTAT_COMPLEX = 8;
493
- module.exports = Database;
494
- module.exports.SqliteError = SqliteError;
495
- `
496
- )
497
-
498
- // return the node_modules root so it can be prepended to NODE_PATH
499
- return resolve(tmp, 'orez-sqlite', 'node_modules')
500
- }
467
+ // writeSqliteShim replaced by sqlite-mode/apply-mode.ts applySqliteMode()
501
468
 
502
469
  async function startZeroCache(
503
470
  config: ZeroLiteConfig,
504
- logStore?: LogStore
471
+ logStore?: LogStore,
472
+ sqliteMode: SqliteMode = resolveSqliteMode(config.disableWasmSqlite),
473
+ sqliteModeConfig?: SqliteModeConfig | null
505
474
  ): Promise<{ process: ChildProcess; env: Record<string, string> }> {
506
475
  // resolve @rocicorp/zero entry for finding zero-cache modules
507
476
  const zeroEntry = resolvePackage('@rocicorp/zero')
@@ -510,7 +479,7 @@ async function startZeroCache(
510
479
  throw new Error('zero-cache not found. install @rocicorp/zero')
511
480
  }
512
481
 
513
- if (config.disableWasmSqlite) {
482
+ if (sqliteMode === 'native') {
514
483
  log.debug.orez('wasm sqlite disabled, using native @rocicorp/zero-sqlite3')
515
484
  }
516
485
 
@@ -551,20 +520,25 @@ async function startZeroCache(
551
520
  throw new Error('zero-cache cli.js not found. install @rocicorp/zero')
552
521
  }
553
522
 
554
- // wasm sqlite: create a fake @rocicorp/zero-sqlite3 in tmpdir and prepend
555
- // to NODE_PATH so node resolves our shim first. no require hooks, no
556
- // Module._resolveFilename monkey-patching (which conflicts with vite).
557
- if (!config.disableWasmSqlite) {
558
- const shimNodeModules = writeSqliteShim()
559
- const existingNodePath = process.env.NODE_PATH || ''
560
- env.NODE_PATH = existingNodePath
561
- ? `${shimNodeModules}:${existingNodePath}`
562
- : shimNodeModules
523
+ // apply sqlite mode - either wasm shim or restore to native
524
+ if (sqliteModeConfig) {
525
+ const result = applySqliteMode(sqliteModeConfig)
526
+ if (!result.success) {
527
+ // native mode failure is fatal - user explicitly requested native but we can't provide it
528
+ if (sqliteMode === 'native') {
529
+ throw new Error(`cannot use native sqlite: ${result.error}`)
530
+ }
531
+ // wasm mode failure is also fatal now - can't safely proceed without backup
532
+ throw new Error(`cannot apply sqlite mode: ${result.error}`)
533
+ } else if (result.shimPath) {
534
+ log.debug.orez(`shimmed @rocicorp/zero-sqlite3 at ${result.shimPath}`)
535
+ }
563
536
  }
564
537
 
565
- const nodeOptions = !config.disableWasmSqlite
566
- ? `--max-old-space-size=16384 ${process.env.NODE_OPTIONS || ''}`
567
- : process.env.NODE_OPTIONS || ''
538
+ const nodeOptions =
539
+ sqliteMode === 'wasm'
540
+ ? `--max-old-space-size=16384 ${process.env.NODE_OPTIONS || ''}`
541
+ : process.env.NODE_OPTIONS || ''
568
542
  if (nodeOptions.trim()) env.NODE_OPTIONS = nodeOptions.trim()
569
543
 
570
544
  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