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.
- package/README.md +4 -4
- package/dist/admin/server.d.ts.map +1 -1
- package/dist/admin/server.js +1 -0
- package/dist/admin/server.js.map +1 -1
- package/dist/admin/ui.d.ts.map +1 -1
- package/dist/admin/ui.js +12 -0
- package/dist/admin/ui.js.map +1 -1
- package/dist/cli.js +5 -5
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -64
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts +1 -1
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +2 -2
- package/dist/log.js.map +1 -1
- package/dist/sqlite-mode/apply-mode.d.ts +49 -0
- package/dist/sqlite-mode/apply-mode.d.ts.map +1 -0
- package/dist/sqlite-mode/apply-mode.js +190 -0
- package/dist/sqlite-mode/apply-mode.js.map +1 -0
- package/dist/sqlite-mode/index.d.ts +14 -0
- package/dist/sqlite-mode/index.d.ts.map +1 -0
- package/dist/sqlite-mode/index.js +14 -0
- package/dist/sqlite-mode/index.js.map +1 -0
- package/dist/sqlite-mode/resolve-mode.d.ts +24 -0
- package/dist/sqlite-mode/resolve-mode.d.ts.map +1 -0
- package/dist/sqlite-mode/resolve-mode.js +61 -0
- package/dist/sqlite-mode/resolve-mode.js.map +1 -0
- package/dist/sqlite-mode/shim-template.d.ts +20 -0
- package/dist/sqlite-mode/shim-template.d.ts.map +1 -0
- package/dist/sqlite-mode/shim-template.js +144 -0
- package/dist/sqlite-mode/shim-template.js.map +1 -0
- package/dist/sqlite-mode/types.d.ts +16 -0
- package/dist/sqlite-mode/types.d.ts.map +1 -0
- package/dist/sqlite-mode/types.js +17 -0
- package/dist/sqlite-mode/types.js.map +1 -0
- package/package.json +3 -2
- package/src/admin/server.ts +1 -0
- package/src/admin/ui.ts +12 -0
- package/src/cli.ts +5 -5
- package/src/index.ts +79 -63
- package/src/log.ts +2 -2
- package/src/shim/hooks.mjs +42 -18
- package/src/sqlite-mode/apply-mode.ts +224 -0
- package/src/sqlite-mode/index.ts +14 -0
- package/src/sqlite-mode/resolve-mode.ts +71 -0
- package/src/sqlite-mode/shim-template.ts +158 -0
- package/src/sqlite-mode/sqlite-mode.test.ts +421 -0
- 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.
|
|
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.
|
|
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",
|
package/src/admin/server.ts
CHANGED
|
@@ -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">⏱ --</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: '
|
|
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
|
|
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
|
|
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
|
-
//
|
|
69
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
//
|
|
439
|
-
//
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
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
|
|
569
|
-
|
|
570
|
-
|
|
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 =
|
|
574
|
-
|
|
575
|
-
|
|
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
|
|
44
|
+
/** format a url with green color */
|
|
45
45
|
export function url(u: string): string {
|
|
46
|
-
return `${COLORS.
|
|
46
|
+
return `${COLORS.green}${u}${RESET}`
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// map logger labels to logStore source names
|