feat(client/lightweight): integrate SQLite

This commit is contained in:
Elian Doran 2026-01-05 19:44:23 +02:00
parent d3941752f1
commit 5d474150da
No known key found for this signature in database
4 changed files with 140 additions and 6 deletions

View File

@ -28,6 +28,7 @@
"@mind-elixir/node-menu": "5.0.1",
"@popperjs/core": "2.11.8",
"@preact/signals": "2.5.1",
"@sqlite.org/sqlite-wasm": "3.51.1-build2",
"@triliumnext/ckeditor5": "workspace:*",
"@triliumnext/codemirror": "workspace:*",
"@triliumnext/commons": "workspace:*",

View File

@ -2,8 +2,16 @@
// This will eventually import your core server and DB provider.
// import { createCoreServer } from "@trilium/core"; (bundled)
import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
const encoder = new TextEncoder();
// SQLite WASM instance
let sqlite3: any = null;
let db: any = null;
let sqliteInitPromise: Promise<void> | null = null;
let sqliteInitError: string | null = null;
function jsonResponse(obj, status = 200, extraHeaders = {}) {
const body = encoder.encode(JSON.stringify(obj)).buffer;
return {
@ -22,8 +30,94 @@ function textResponse(text, status = 200, extraHeaders = {}) {
};
}
// Initialize SQLite WASM
async function initSQLite() {
if (sqlite3) return; // Already initialized
if (sqliteInitError) return; // Failed before, don't retry
if (sqliteInitPromise) return sqliteInitPromise; // Already initializing
sqliteInitPromise = (async () => {
try {
console.log("[Worker] Initializing SQLite WASM...");
const startTime = performance.now();
// Just call the init module without custom locateFile
// The module will use import.meta.url to find sqlite3.wasm
sqlite3 = await sqlite3InitModule({
print: console.log,
printErr: console.error,
});
const initTime = performance.now() - startTime;
console.log(`[Worker] SQLite WASM initialized in ${initTime.toFixed(2)}ms:`, sqlite3.version);
// Open a database in memory for now
db = new sqlite3.oo1.DB(':memory:', 'c');
console.log("[Worker] Database opened");
// Create a simple test table
db.exec(`
CREATE TABLE IF NOT EXISTS options (
name TEXT PRIMARY KEY,
value TEXT
);
INSERT INTO options (name, value) VALUES
('theme', 'dark'),
('layoutOrientation', 'vertical'),
('headingStyle', 'default');
`);
console.log("[Worker] Test table created and populated");
} catch (error) {
sqliteInitError = String(error);
console.error("[Worker] SQLite initialization failed:", error);
throw error;
}
})();
return sqliteInitPromise;
}
// Example: your /bootstrap handler placeholder
function handleBootstrap() {
async function handleBootstrap() {
console.log("[Worker] Bootstrap request received");
// Try to initialize SQLite with timeout
let dbInfo: any = { dbStatus: 'not initialized' };
if (sqliteInitError) {
dbInfo = { dbStatus: 'failed', error: sqliteInitError };
} else {
try {
// Don't wait too long for SQLite initialization
await Promise.race([
initSQLite(),
new Promise((_, reject) => setTimeout(() => reject(new Error("SQLite init timeout")), 5000))
]);
// Query the database if initialized
if (db) {
const stmt = db.prepare('SELECT * FROM options');
const options: Record<string, string> = {};
while (stmt.step()) {
const row = stmt.get({});
options[row.name] = row.value;
}
stmt.finalize();
dbInfo = {
sqliteVersion: sqlite3.version.libVersion,
optionsFromDB: options,
dbStatus: 'connected'
};
}
} catch (e) {
console.error("[Worker] Error during bootstrap:", e);
dbInfo = { dbStatus: 'error', error: String(e) };
}
}
console.log("[Worker] Sending bootstrap response");
// Later: return real globals from your core state/config.
return jsonResponse({
assetPath: "assets",
@ -37,7 +131,9 @@ function handleBootstrap() {
isElectron: false,
hasNativeTitleBar: false,
hasBackgroundEffects: true,
currentLocale: { id: "en", rtl: false }
currentLocale: { id: "en", rtl: false },
// Add SQLite info for testing
sqlite: dbInfo
});
}
@ -45,7 +141,7 @@ function handleBootstrap() {
async function dispatch(request) {
const url = new URL(request.url);
console.log("Dispatch ", url);
console.log("[Worker] Dispatch:", url.pathname);
// NOTE: your core router will do this later.
if (request.method === "GET" && url.pathname === "/bootstrap") {
return handleBootstrap();
@ -58,14 +154,22 @@ async function dispatch(request) {
return textResponse("Not found", 404);
}
// Start SQLite initialization as soon as the worker loads (in background)
console.log("[Worker] Starting background SQLite initialization...");
initSQLite().catch(err => {
console.error("[Worker] Background SQLite init failed:", err);
});
self.onmessage = async (event) => {
const msg = event.data;
if (!msg || msg.type !== "LOCAL_REQUEST") return;
const { id, request } = msg;
console.log("[Worker] Received LOCAL_REQUEST:", id, request.method, request.url);
try {
const response = await dispatch(request);
console.log("[Worker] Dispatch completed, sending response:", id);
// Transfer body back (if any)
self.postMessage({
@ -74,6 +178,7 @@ self.onmessage = async (event) => {
response
}, response.body ? [response.body] : []);
} catch (e) {
console.error("[Worker] Dispatch error:", e);
self.postMessage({
type: "LOCAL_RESPONSE",
id,

View File

@ -8,12 +8,25 @@ import { viteStaticCopy } from 'vite-plugin-static-copy'
const assets = [ "assets", "stylesheets", "fonts", "translations" ];
const isDev = process.env.NODE_ENV === "development";
// Always copy SQLite WASM files so they're available to the module
const sqliteWasmPlugin = viteStaticCopy({
targets: [
{
// Copy the entire jswasm directory to maintain the module's expected structure
src: "../../node_modules/@sqlite.org/sqlite-wasm/sqlite-wasm/jswasm/*",
dest: "node_modules/@sqlite.org/sqlite-wasm/sqlite-wasm/jswasm"
}
]
});
let plugins: any = [
preact({
babel: {
compact: !isDev
}
})
}),
sqliteWasmPlugin // Always include SQLite WASM files
];
if (!isDev) {
@ -43,6 +56,12 @@ export default defineConfig(() => ({
cacheDir: '../../node_modules/.vite/apps/client',
base: "",
plugins,
optimizeDeps: {
exclude: ['@sqlite.org/sqlite-wasm']
},
worker: {
format: 'es'
},
resolve: {
alias: [
{

13
pnpm-lock.yaml generated
View File

@ -193,6 +193,9 @@ importers:
'@preact/signals':
specifier: 2.5.1
version: 2.5.1(preact@10.28.1)
'@sqlite.org/sqlite-wasm':
specifier: 3.51.1-build2
version: 3.51.1-build2
'@triliumnext/ckeditor5':
specifier: workspace:*
version: link:../../packages/ckeditor5
@ -5195,6 +5198,10 @@ packages:
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
'@sqlite.org/sqlite-wasm@3.51.1-build2':
resolution: {integrity: sha512-lVPTBlFsEijJ3wuoIbMfC9QMZKfL8huHN8D/lijNKoVxPqUDNvDtXse0wafe7USSmyfKAMb1JZ3ISSr/Vgbn5w==}
hasBin: true
'@ssddanbrown/codemirror-lang-smarty@1.0.0':
resolution: {integrity: sha512-F0ut1kmdbT3eORk3xVIKfQsGCZiQdh+6sLayBa0+FTex2gyIQlVQZRRA7bPSlchI3uZtWwNnqGNz5O/QLWRlFg==}
@ -15433,6 +15440,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.3.0
'@ckeditor/ckeditor5-watchdog': 47.3.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-dev-build-tools@54.2.3(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)':
dependencies:
@ -16096,8 +16105,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-restricted-editing@47.3.0':
dependencies:
@ -19927,6 +19934,8 @@ snapshots:
'@socket.io/component-emitter@3.1.2': {}
'@sqlite.org/sqlite-wasm@3.51.1-build2': {}
'@ssddanbrown/codemirror-lang-smarty@1.0.0': {}
'@ssddanbrown/codemirror-lang-twig@1.0.0':