feat(editor): Add boilerplate for SQLite WASM integration and runData worker (no-changelog) (#18959)

This commit is contained in:
Alex Grozav
2025-08-29 13:50:45 +01:00
committed by GitHub
parent 140e1b082e
commit fe99b7773d
7 changed files with 231 additions and 25 deletions

View File

@@ -48,6 +48,7 @@
"@n8n/utils": "workspace:*",
"@replit/codemirror-indentation-markers": "^6.5.3",
"@sentry/vue": "catalog:frontend",
"@sqlite.org/sqlite-wasm": "3.50.4-build1",
"@types/semver": "^7.7.0",
"@typescript/vfs": "^1.6.0",
"@vue-flow/background": "^1.3.2",

View File

@@ -0,0 +1,48 @@
import { sqlite3Worker1Promiser } from '@sqlite.org/sqlite-wasm';
import type { Promiser, DbId } from '@sqlite.org/sqlite-wasm';
export type DatabaseTable = {
name: string;
schema: string;
};
export type DatabaseConfig = {
filename: `file:${string}.sqlite3?vfs=opfs`;
tables: Record<string, DatabaseTable>;
};
export async function initializeDatabase(config: DatabaseConfig) {
// Initialize the SQLite worker
const promiser: Promiser = await new Promise((resolve) => {
const _promiser = sqlite3Worker1Promiser({
onready: () => resolve(_promiser),
});
});
if (!promiser) throw new Error('Failed to initialize promiser');
// Get configuration and open database
const cfg = await promiser('config-get', {});
const openResponse = await promiser('open', {
filename: config.filename,
});
if (openResponse.type === 'error') {
throw new Error(openResponse.result.message);
}
const dbId: DbId = openResponse.result.dbId;
for (const table of Object.values(config.tables)) {
await promiser('exec', {
dbId,
sql: table.schema,
});
}
return {
promiser,
dbId,
cfg,
};
}

View File

@@ -0,0 +1,19 @@
import type { DatabaseConfig } from '@/workers/database';
export const databaseConfig: DatabaseConfig = {
filename: 'file:n8n.sqlite3?vfs=opfs',
tables: {
executions: {
name: 'executions',
schema: `
CREATE TABLE IF NOT EXISTS executions (
id INTEGER PRIMARY KEY,
workflow_id INTEGER NOT NULL,
data TEXT CHECK (json_valid(data)) NOT NULL,
workflow TEXT CHECK (json_valid(workflow)) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
`,
},
},
} as const;

View File

@@ -0,0 +1,8 @@
import * as Comlink from 'comlink';
import type { RunDataWorker } from '@/workers/run-data/worker';
const worker = new Worker(new URL('./worker.ts', import.meta.url), {
type: 'module',
});
export const runDataWorker = Comlink.wrap<RunDataWorker>(worker);

View File

@@ -0,0 +1,30 @@
import * as Comlink from 'comlink';
import { databaseConfig } from '@/workers/run-data/db';
import { initializeDatabase } from '@/workers/database';
import type { Promiser, DbId } from '@sqlite.org/sqlite-wasm';
const state: {
initialized: boolean;
promiser: Promiser | undefined;
dbId: DbId;
} = {
initialized: false,
promiser: undefined,
dbId: undefined,
};
export const actions = {
async initialize() {
if (state.initialized) return;
const { promiser, dbId } = await initializeDatabase(databaseConfig);
state.promiser = promiser;
state.dbId = dbId;
state.initialized = true;
},
};
export type RunDataWorker = typeof actions;
Comlink.expose(actions);

View File

@@ -0,0 +1,96 @@
import type { Worker } from 'node:worker_threads';
declare module '@sqlite.org/sqlite-wasm' {
type OnreadyFunction = () => void;
export type Sqlite3Worker1PromiserConfig = {
onready?: OnreadyFunction;
worker?: Worker | (() => Worker);
generateMessageId?: (messageObject: unknown) => string;
debug?: (...args: any[]) => void;
onunhandled?: (event: MessageEvent) => void;
};
export type DbId = string | undefined;
export type PromiserMethods = {
'config-get': {
args: Record<string, never>;
result: {
dbID: DbId;
version: {
libVersion: string;
sourceId: string;
libVersionNumber: number;
downloadVersion: number;
};
bigIntEnabled: boolean;
opfsEnabled: boolean;
vfsList: string[];
};
};
open: {
args: Partial<{
filename?: string;
vfs?: string;
}>;
result: {
dbId: DbId;
filename: string;
persistent: boolean;
vfs: string;
};
};
exec: {
args: {
sql: string;
dbId?: DbId;
bind?: unknown[];
returnValue?: string;
};
result: {
dbId: DbId;
sql: string;
bind: unknown[];
returnValue: string;
resultRows?: unknown[][];
};
};
};
export type PromiserResponseSuccess<T extends keyof PromiserMethods> = {
type: T;
result: PromiserMethods[T]['result'];
messageId: string;
dbId: DbId;
workerReceivedTime: number;
workerRespondTime: number;
departureTime: number;
};
export type PromiserResponseError = {
type: 'error';
result: {
operation: string;
message: string;
errorClass: string;
input: object;
stack: unknown[];
};
messageId: string;
dbId: DbId;
};
export type PromiserResponse<T extends keyof PromiserMethods> =
| PromiserResponseSuccess<T>
| PromiserResponseError;
export type Promiser = <T extends keyof PromiserMethods>(
messageType: T,
messageArguments: PromiserMethods[T]['args'],
) => Promise<PromiserResponse<T>>;
export function sqlite3Worker1Promiser(
config?: Sqlite3Worker1PromiserConfig | OnreadyFunction,
): Promiser;
}