Add SQLite storage foundation

Introduce a core-owned Kysely SQLite storage layer backed by
$WorkDir/db/rowboat.sqlite, with startup initialization, shutdown
cleanup, in-code migrations, and initial storage metadata schema.

Ignore database files in the workspace watcher, add focused
storage/watcher tests, and update Electron packaging to stage and rebuild
better-sqlite3 against Electron's native module ABI.
This commit is contained in:
Ramnique Singh 2026-06-09 22:03:17 +05:30
parent 1632b16dfc
commit 883872064f
14 changed files with 511 additions and 14 deletions

View file

@ -27,11 +27,13 @@ await esbuild.build({
platform: 'node',
target: 'node20',
outfile: './.package/dist/main.cjs',
// electron is provided by the runtime. node-pty is a NATIVE module: it can't
// be inlined (its loader requires .node binaries + a spawn-helper relative to
// its own package dir), so it stays external and is copied into
// .package/node_modules below, where require() from dist/main.cjs finds it.
external: ['electron', 'node-pty'],
// electron is provided by the runtime. node-pty and better-sqlite3 are NATIVE
// modules: they can't be inlined (their loaders require .node binaries — and
// node-pty a spawn-helper — relative to their own package dirs), so they stay
// external and are staged into .package/node_modules (node-pty below;
// better-sqlite3 via forge.config.cjs generateAssets) where require() from
// dist/main.cjs finds them.
external: ['electron', 'node-pty', 'better-sqlite3'],
// Use CommonJS format - many dependencies use require() which doesn't work
// well with esbuild's ESM shim. CJS handles dynamic requires natively.
format: 'cjs',

View file

@ -35,13 +35,19 @@ module.exports = {
// Regexes are ANCHORED to the app root: .package/node_modules (where
// bundle.mjs stages the native node-pty module) must survive packaging.
prune: false,
ignore: [
/^\/src\//,
/^\/node_modules\//,
/.gitignore/,
/bundle\.mjs/,
/tsconfig.json/,
],
// Keep .package/node_modules (staged native modules: node-pty and
// better-sqlite3) — everything else under those rules is pruned.
ignore: (file) => {
const normalized = file.split(path.sep).join('/');
if (normalized.includes('/.package/node_modules/')) return false;
return [
/\/src\//,
/\/node_modules\//,
/\.gitignore$/,
/\/bundle\.mjs$/,
/\/tsconfig\.json$/,
].some((pattern) => pattern.test(normalized));
},
},
makers: [
{
@ -181,6 +187,44 @@ module.exports = {
stdio: 'inherit'
});
// Copy native runtime dependencies that cannot be bundled by esbuild.
console.log('Copying native runtime dependencies...');
const stagedNodeModules = path.join(packageDir, 'node_modules');
fs.mkdirSync(stagedNodeModules, { recursive: true });
fs.writeFileSync(path.join(packageDir, 'package.json'), JSON.stringify({
name: `${pkg.name}-native-runtime`,
version: pkg.version,
private: true,
dependencies: {
'better-sqlite3': require(require.resolve('better-sqlite3/package.json', {
paths: [path.join(__dirname, '../../packages/core')],
})).version,
},
}, null, 2));
const copyRuntimePackage = (packageName) => {
const packageJsonPath = require.resolve(`${packageName}/package.json`, {
paths: [path.join(__dirname, '../../packages/core')],
});
const packageSrc = path.dirname(packageJsonPath);
const packageDest = path.join(stagedNodeModules, packageName);
fs.rmSync(packageDest, { recursive: true, force: true });
fs.cpSync(packageSrc, packageDest, { recursive: true });
};
for (const packageName of ['better-sqlite3', 'bindings', 'file-uri-to-path']) {
copyRuntimePackage(packageName);
}
const { rebuild } = require(path.join(__dirname, '../../node_modules/.pnpm/node_modules/@electron/rebuild'));
const electronVersion = require(require.resolve('electron/package.json', { paths: [__dirname] })).version;
await rebuild({
buildPath: packageDir,
electronVersion,
platform,
arch,
onlyModules: ['better-sqlite3'],
force: true,
buildFromSource: true,
});
// Copy preload dist into staging directory
console.log('Copying preload...');
const preloadSrc = path.join(__dirname, '../preload/dist');
@ -198,4 +242,4 @@ module.exports = {
console.log('✅ All assets staged in .package/');
},
}
};
};

View file

@ -35,6 +35,7 @@ import { backgroundTaskEventConsumer } from "@x/core/dist/background-tasks/event
import { init as initLocalSites, shutdown as shutdownLocalSites } from "@x/core/dist/local-sites/server.js";
import { shutdown as shutdownAnalytics } from "@x/core/dist/analytics/posthog.js";
import { identifyIfSignedIn } from "@x/core/dist/analytics/identify.js";
import { initStorage, shutdownStorage } from "@x/core/dist/storage/index.js";
import { initConfigs } from "@x/core/dist/config/initConfigs.js";
import { resolveWorkspacePath } from "@x/core/dist/workspace/workspace.js";
@ -329,6 +330,9 @@ app.whenReady().then(async () => {
// Initialize all config files before UI can access them
await initConfigs();
// Initialize SQLite storage before any DB-backed services or repos are used.
await initStorage();
// PostHog identify() is idempotent — call it on every startup so existing
// signed-in installs (and every cold start of v0.3.4+) get re-identified.
// Otherwise main-process events stay anonymous until the user re-signs-in.
@ -457,4 +461,7 @@ app.on("before-quit", () => {
shutdownAnalytics().catch((error) => {
console.error('[Analytics] Failed to flush on quit:', error);
});
shutdownStorage().catch((error) => {
console.error('[storage] Failed to close SQLite storage:', error);
});
});

View file

@ -28,6 +28,7 @@
"@x/shared": "workspace:*",
"ai": "^5.0.133",
"awilix": "^12.0.5",
"better-sqlite3": "^12.10.0",
"chokidar": "^4.0.3",
"cors": "^2.8.6",
"cron-parser": "^5.5.0",
@ -36,6 +37,7 @@
"google-auth-library": "^10.5.0",
"googleapis": "^169.0.0",
"isomorphic-git": "^1.29.0",
"kysely": "^0.29.2",
"mammoth": "^1.11.0",
"node-html-markdown": "^2.0.0",
"ollama-ai-provider-v2": "^1.5.4",
@ -49,6 +51,7 @@
"zod": "^4.2.1"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.13",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/node": "^25.0.3",

View file

@ -12,3 +12,6 @@ export * as versionHistory from './knowledge/version_history.js';
// Voice mode (config + TTS)
export * as voice from './voice/voice.js';
// SQLite storage
export * as storage from './storage/index.js';

View file

@ -0,0 +1,82 @@
import fs from "node:fs";
import { createRequire } from "node:module";
import path from "node:path";
import { Kysely, SqliteDialect, type SqliteDatabase } from "kysely";
import { WorkDir } from "../config/config.js";
import type { Database } from "./schema.js";
import { migrateToLatest } from "./migrations.js";
type BetterSqliteDatabase = SqliteDatabase & {
pragma(source: string, options?: { simple?: boolean }): unknown;
};
type BetterSqliteConstructor = new (
filename?: string,
options?: { timeout?: number },
) => BetterSqliteDatabase;
const require = createRequire(import.meta.url);
const BetterSqlite = require("better-sqlite3") as BetterSqliteConstructor;
let db: Kysely<Database> | null = null;
let initPromise: Promise<void> | null = null;
export function getDatabasePath(): string {
return path.join(WorkDir, "db", "rowboat.sqlite");
}
function createDatabase(): Kysely<Database> {
const databasePath = getDatabasePath();
fs.mkdirSync(path.dirname(databasePath), { recursive: true });
const sqlite = new BetterSqlite(databasePath, { timeout: 5_000 });
sqlite.pragma("foreign_keys = ON");
sqlite.pragma("journal_mode = WAL");
sqlite.pragma("busy_timeout = 5000");
return new Kysely<Database>({
dialect: new SqliteDialect({
database: sqlite,
}),
});
}
export async function initStorage(): Promise<void> {
if (db) return;
if (initPromise) return initPromise;
initPromise = (async () => {
const nextDb = createDatabase();
try {
await migrateToLatest(nextDb);
db = nextDb;
} catch (error) {
await nextDb.destroy().catch((destroyError: unknown) => {
console.error("[storage] failed to close SQLite after init failure:", destroyError);
});
throw error;
} finally {
initPromise = null;
}
})();
return initPromise;
}
export function getDb(): Kysely<Database> {
if (!db) {
throw new Error("SQLite storage has not been initialized. Call initStorage() first.");
}
return db;
}
export async function shutdownStorage(): Promise<void> {
const currentDb = db;
db = null;
initPromise = null;
if (currentDb) {
await currentDb.destroy();
}
}

View file

@ -0,0 +1,2 @@
export { getDatabasePath, getDb, initStorage, shutdownStorage } from "./database.js";
export type { Database, StorageMetadataTable, TimestampColumn } from "./schema.js";

View file

@ -0,0 +1,50 @@
import type { Kysely } from "kysely";
import { Migrator, type Migration, type MigrationProvider } from "kysely/migration";
// Kysely migrations are intentionally schema-agnostic and frozen in time.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type MigrationDb = Kysely<any>;
const migrations: Record<string, Migration> = {
"2026-06-09_0001_initial_storage": {
async up(db: MigrationDb): Promise<void> {
await db.schema
.createTable("storage_metadata")
.ifNotExists()
.addColumn("key", "text", (col) => col.primaryKey())
.addColumn("value", "text", (col) => col.notNull())
.addColumn("updated_at", "text", (col) => col.notNull())
.execute();
},
async down(db: MigrationDb): Promise<void> {
await db.schema.dropTable("storage_metadata").ifExists().execute();
},
},
};
class InCodeMigrationProvider implements MigrationProvider {
async getMigrations(): Promise<Record<string, Migration>> {
return migrations;
}
}
export async function migrateToLatest(db: MigrationDb): Promise<void> {
const migrator = new Migrator({
db,
provider: new InCodeMigrationProvider(),
});
const { error, results } = await migrator.migrateToLatest();
for (const result of results ?? []) {
if (result.status === "Success") {
console.log(`[storage] migration applied: ${result.migrationName}`);
} else if (result.status === "Error") {
console.error(`[storage] migration failed: ${result.migrationName}`);
}
}
if (error) {
throw new Error("Failed to migrate SQLite storage", { cause: error });
}
}

View file

@ -0,0 +1,13 @@
import type { ColumnType } from "kysely";
export type TimestampColumn = ColumnType<string, string, string>;
export interface StorageMetadataTable {
key: string;
value: string;
updated_at: TimestampColumn;
}
export interface Database {
storage_metadata: StorageMetadataTable;
}

View file

@ -0,0 +1,90 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { sql } from "kysely";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
let tmpDir: string;
let workspaceDir: string;
let storageModule: typeof import("./index.js") | null = null;
beforeEach(async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "rowboat-storage-test-"));
workspaceDir = path.join(tmpDir, "workspace");
process.env.ROWBOAT_WORKDIR = workspaceDir;
vi.resetModules();
vi.doMock("../knowledge/version_history.js", () => ({
initRepo: vi.fn(async () => undefined),
}));
vi.doMock("../knowledge/deprecate_today_note.js", () => ({
deprecateTodayNote: vi.fn(async () => undefined),
}));
});
afterEach(async () => {
if (storageModule) {
await storageModule.shutdownStorage().catch(() => undefined);
storageModule = null;
}
delete process.env.ROWBOAT_WORKDIR;
vi.doUnmock("../knowledge/version_history.js");
vi.doUnmock("../knowledge/deprecate_today_note.js");
vi.resetModules();
await fs.rm(tmpDir, { recursive: true, force: true });
});
async function loadStorage() {
storageModule = await import("./index.js");
return storageModule;
}
describe("SQLite storage", () => {
it("throws clearly when accessed before initialization", async () => {
const storage = await loadStorage();
expect(() => storage.getDb()).toThrow("SQLite storage has not been initialized");
});
it("creates the database under ROWBOAT_WORKDIR/db", async () => {
const storage = await loadStorage();
await storage.initStorage();
expect(storage.getDatabasePath()).toBe(path.join(workspaceDir, "db", "rowboat.sqlite"));
await expect(fs.access(storage.getDatabasePath())).resolves.toBeUndefined();
});
it("runs the initial migration", async () => {
const storage = await loadStorage();
await storage.initStorage();
const result = await sql<{ name: string }>`
select name
from sqlite_master
where type = 'table'
and name in ('storage_metadata', 'kysely_migration')
order by name
`.execute(storage.getDb());
expect(result.rows.map((row) => row.name)).toEqual(["kysely_migration", "storage_metadata"]);
});
it("is idempotent", async () => {
const storage = await loadStorage();
await storage.initStorage();
const firstDb = storage.getDb();
await storage.initStorage();
expect(storage.getDb()).toBe(firstDb);
});
it("resets the singleton on shutdown", async () => {
const storage = await loadStorage();
await storage.initStorage();
await storage.shutdownStorage();
expect(() => storage.getDb()).toThrow("SQLite storage has not been initialized");
});
});

View file

@ -0,0 +1,39 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
let tmpDir: string;
let workspaceDir: string;
beforeEach(async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "rowboat-watcher-test-"));
workspaceDir = path.join(tmpDir, "workspace");
process.env.ROWBOAT_WORKDIR = workspaceDir;
vi.resetModules();
vi.doMock("../knowledge/version_history.js", () => ({
initRepo: vi.fn(async () => undefined),
}));
vi.doMock("../knowledge/deprecate_today_note.js", () => ({
deprecateTodayNote: vi.fn(async () => undefined),
}));
});
afterEach(async () => {
delete process.env.ROWBOAT_WORKDIR;
vi.doUnmock("../knowledge/version_history.js");
vi.doUnmock("../knowledge/deprecate_today_note.js");
vi.resetModules();
await fs.rm(tmpDir, { recursive: true, force: true });
});
describe("workspace watcher ignores", () => {
it("ignores SQLite storage files under db", async () => {
const watcher = await import("./watcher.js");
expect(watcher.shouldIgnoreWorkspacePath(path.join(workspaceDir, "db"))).toBe(true);
expect(watcher.shouldIgnoreWorkspacePath(path.join(workspaceDir, "db", "rowboat.sqlite"))).toBe(true);
expect(watcher.shouldIgnoreWorkspacePath(path.join(workspaceDir, "db", "rowboat.sqlite-wal"))).toBe(true);
expect(watcher.shouldIgnoreWorkspacePath(path.join(workspaceDir, "knowledge", "note.md"))).toBe(false);
});
});

View file

@ -9,6 +9,11 @@ import { Stats } from 'node:fs';
export type WorkspaceChangeCallback = (event: z.infer<typeof WorkspaceChangeEvent>) => void;
export function shouldIgnoreWorkspacePath(absPath: string): boolean {
const relPath = absToRelPosix(absPath);
return relPath === 'db' || relPath?.startsWith('db/') === true;
}
/**
* Create a workspace watcher
* Watches the configured workspace root recursively and emits change events via callback
@ -29,8 +34,12 @@ export async function createWorkspaceWatcher(
const codeModeDir = path.join(WorkDir, 'code-mode');
const watcher = chokidar.watch(WorkDir, {
ignoreInitial: true,
// Ignore the SQLite db dir (storage) AND code-section worktrees (full repo
// checkouts that would flood the event stream).
ignored: (watchedPath: string) =>
watchedPath === codeModeDir || watchedPath.startsWith(codeModeDir + path.sep),
shouldIgnoreWorkspacePath(watchedPath) ||
watchedPath === codeModeDir ||
watchedPath.startsWith(codeModeDir + path.sep),
awaitWriteFinish: {
stabilityThreshold: 150,
pollInterval: 50,

152
apps/x/pnpm-lock.yaml generated
View file

@ -457,6 +457,9 @@ importers:
awilix:
specifier: ^12.0.5
version: 12.0.5
better-sqlite3:
specifier: ^12.10.0
version: 12.10.0
chokidar:
specifier: ^4.0.3
version: 4.0.3
@ -481,6 +484,9 @@ importers:
isomorphic-git:
specifier: ^1.29.0
version: 1.37.2
kysely:
specifier: ^0.29.2
version: 0.29.2
mammoth:
specifier: ^1.11.0
version: 1.11.0
@ -515,6 +521,9 @@ importers:
specifier: ^4.2.1
version: 4.2.1
devDependencies:
'@types/better-sqlite3':
specifier: ^7.6.13
version: 7.6.13
'@types/cors':
specifier: ^2.8.19
version: 2.8.19
@ -3669,6 +3678,9 @@ packages:
'@types/babel__traverse@7.28.0':
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
'@types/better-sqlite3@7.6.13':
resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==}
'@types/body-parser@1.19.6':
resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}
@ -4276,12 +4288,19 @@ packages:
before-after-hook@2.2.3:
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
better-sqlite3@12.10.0:
resolution: {integrity: sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==}
engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x}
bidi-js@1.0.3:
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
bignumber.js@9.3.1:
resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
bindings@1.5.0:
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
@ -4441,6 +4460,9 @@ packages:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
@ -4840,6 +4862,10 @@ packages:
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
engines: {node: '>=10'}
deep-extend@0.6.0:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
engines: {node: '>=4.0.0'}
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@ -5215,6 +5241,10 @@ packages:
resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==}
engines: {node: '>=6'}
expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
expect-type@1.3.0:
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
engines: {node: '>=12.0.0'}
@ -5297,6 +5327,9 @@ packages:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
file-uri-to-path@1.0.0:
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
filename-reserved-regex@2.0.0:
resolution: {integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==}
engines: {node: '>=4'}
@ -5389,6 +5422,9 @@ packages:
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
engines: {node: '>= 0.8'}
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
fs-extra@10.1.0:
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
engines: {node: '>=12'}
@ -5502,6 +5538,9 @@ packages:
resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
engines: {node: '>=8'}
github-from-package@0.0.0:
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
github-url-to-object@4.0.6:
resolution: {integrity: sha512-NaqbYHMUAlPcmWFdrAB7bcxrNIiiJWJe8s/2+iOc9vlcHlwHqSGrPk+Yi3nu6ebTwgsZEa7igz+NH2vEq3gYwQ==}
@ -5809,6 +5848,9 @@ packages:
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
ini@1.3.8:
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
ini@2.0.0:
resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==}
engines: {node: '>=10'}
@ -6077,6 +6119,10 @@ packages:
khroma@2.1.0:
resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
kysely@0.29.2:
resolution: {integrity: sha512-s6WVJyEZrbm6jhBpiKHsGHyePMrVQKJ85wZCFCr9W4QHv6WTjWIrdvTmO9hDEA3bNK0xkrE2DqrHsXMLWuZpQg==}
engines: {node: '>=22.0.0'}
langium@4.2.2:
resolution: {integrity: sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==}
engines: {node: '>=20.10.0', npm: '>=10.2.3'}
@ -6592,6 +6638,9 @@ packages:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'}
mkdirp-classic@0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
mkdirp@0.5.6:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true
@ -6650,6 +6699,9 @@ packages:
engines: {node: ^18 || >=20}
hasBin: true
napi-build-utils@2.0.0:
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
@ -7036,6 +7088,12 @@ packages:
preact@10.28.2:
resolution: {integrity: sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==}
prebuild-install@7.1.3:
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
engines: {node: '>=10'}
deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available.
hasBin: true
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@ -7189,6 +7247,10 @@ packages:
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
engines: {node: '>= 0.10'}
rc@1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
react-dom@19.2.3:
resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==}
peerDependencies:
@ -7721,6 +7783,10 @@ packages:
resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==}
engines: {node: '>=0.10.0'}
strip-json-comments@2.0.1:
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
engines: {node: '>=0.10.0'}
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
@ -7778,6 +7844,13 @@ packages:
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
engines: {node: '>=6'}
tar-fs@2.1.4:
resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==}
tar-stream@2.2.0:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
engines: {node: '>=6'}
tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'}
@ -7903,6 +7976,9 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
tw-animate-css@1.4.0:
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
@ -12447,6 +12523,10 @@ snapshots:
dependencies:
'@babel/types': 7.28.5
'@types/better-sqlite3@7.6.13':
dependencies:
'@types/node': 25.0.3
'@types/body-parser@1.19.6':
dependencies:
'@types/connect': 3.4.38
@ -13160,12 +13240,21 @@ snapshots:
before-after-hook@2.2.3: {}
better-sqlite3@12.10.0:
dependencies:
bindings: 1.5.0
prebuild-install: 7.1.3
bidi-js@1.0.3:
dependencies:
require-from-string: 2.0.2
bignumber.js@9.3.1: {}
bindings@1.5.0:
dependencies:
file-uri-to-path: 1.0.0
bl@4.1.0:
dependencies:
buffer: 5.7.1
@ -13366,6 +13455,8 @@ snapshots:
dependencies:
readdirp: 4.1.2
chownr@1.1.4: {}
chownr@2.0.0: {}
chrome-trace-event@1.0.4: {}
@ -13764,6 +13855,8 @@ snapshots:
dependencies:
mimic-response: 3.1.0
deep-extend@0.6.0: {}
deep-is@0.1.4: {}
default-browser-id@5.0.1: {}
@ -14250,6 +14343,8 @@ snapshots:
signal-exit: 3.0.7
strip-eof: 1.0.0
expand-template@2.0.3: {}
expect-type@1.3.0: {}
exponential-backoff@3.1.3: {}
@ -14356,6 +14451,8 @@ snapshots:
dependencies:
flat-cache: 4.0.1
file-uri-to-path@1.0.0: {}
filename-reserved-regex@2.0.0: {}
filenamify@4.3.0:
@ -14457,6 +14554,8 @@ snapshots:
fresh@2.0.0: {}
fs-constants@1.0.0: {}
fs-extra@10.1.0:
dependencies:
graceful-fs: 4.2.11
@ -14615,6 +14714,8 @@ snapshots:
dependencies:
pump: 3.0.3
github-from-package@0.0.0: {}
github-url-to-object@4.0.6:
dependencies:
is-url: 1.2.4
@ -15067,6 +15168,8 @@ snapshots:
inherits@2.0.4: {}
ini@1.3.8: {}
ini@2.0.0: {}
inline-style-parser@0.2.7: {}
@ -15307,6 +15410,8 @@ snapshots:
khroma@2.1.0: {}
kysely@0.29.2: {}
langium@4.2.2:
dependencies:
'@chevrotain/regexp-to-ast': 12.0.0
@ -16078,6 +16183,8 @@ snapshots:
minipass: 3.3.6
yallist: 4.0.0
mkdirp-classic@0.5.3: {}
mkdirp@0.5.6:
dependencies:
minimist: 1.2.8
@ -16126,6 +16233,8 @@ snapshots:
nanoid@5.1.6: {}
napi-build-utils@2.0.0: {}
natural-compare@1.4.0: {}
negotiator@0.6.4: {}
@ -16496,6 +16605,21 @@ snapshots:
preact@10.28.2: {}
prebuild-install@7.1.3:
dependencies:
detect-libc: 2.1.2
expand-template: 2.0.3
github-from-package: 0.0.0
minimist: 1.2.8
mkdirp-classic: 0.5.3
napi-build-utils: 2.0.0
node-abi: 3.86.0
pump: 3.0.3
rc: 1.2.8
simple-get: 4.0.1
tar-fs: 2.1.4
tunnel-agent: 0.6.0
prelude-ls@1.2.1: {}
prettier@3.8.0: {}
@ -16731,6 +16855,13 @@ snapshots:
iconv-lite: 0.7.1
unpipe: 1.0.0
rc@1.2.8:
dependencies:
deep-extend: 0.6.0
ini: 1.3.8
minimist: 1.2.8
strip-json-comments: 2.0.1
react-dom@19.2.3(react@19.2.3):
dependencies:
react: 19.2.3
@ -17401,6 +17532,8 @@ snapshots:
strip-eof@1.0.0: {}
strip-json-comments@2.0.1: {}
strip-json-comments@3.1.1: {}
strip-outer@1.0.1:
@ -17451,6 +17584,21 @@ snapshots:
tapable@2.3.0: {}
tar-fs@2.1.4:
dependencies:
chownr: 1.1.4
mkdirp-classic: 0.5.3
pump: 3.0.3
tar-stream: 2.2.0
tar-stream@2.2.0:
dependencies:
bl: 4.1.0
end-of-stream: 1.4.5
fs-constants: 1.0.0
inherits: 2.0.4
readable-stream: 3.6.2
tar@6.2.1:
dependencies:
chownr: 2.0.0
@ -17571,6 +17719,10 @@ snapshots:
tslib@2.8.1: {}
tunnel-agent@0.6.0:
dependencies:
safe-buffer: 5.2.1
tw-animate-css@1.4.0: {}
tweetnacl@1.0.3: {}

View file

@ -19,6 +19,7 @@ onlyBuiltDependencies:
- electron
- electron-winstaller
- esbuild
- better-sqlite3
- fs-xattr
- macos-alias
- protobufjs