Skip to content

Commit fe99b77

Browse files
authored
feat(editor): Add boilerplate for SQLite WASM integration and runData worker (no-changelog) (#18959)
1 parent 140e1b0 commit fe99b77

File tree

7 files changed

+231
-25
lines changed

7 files changed

+231
-25
lines changed

packages/frontend/editor-ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"@n8n/utils": "workspace:*",
4949
"@replit/codemirror-indentation-markers": "^6.5.3",
5050
"@sentry/vue": "catalog:frontend",
51+
"@sqlite.org/sqlite-wasm": "3.50.4-build1",
5152
"@types/semver": "^7.7.0",
5253
"@typescript/vfs": "^1.6.0",
5354
"@vue-flow/background": "^1.3.2",
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { sqlite3Worker1Promiser } from '@sqlite.org/sqlite-wasm';
2+
import type { Promiser, DbId } from '@sqlite.org/sqlite-wasm';
3+
4+
export type DatabaseTable = {
5+
name: string;
6+
schema: string;
7+
};
8+
9+
export type DatabaseConfig = {
10+
filename: `file:${string}.sqlite3?vfs=opfs`;
11+
tables: Record<string, DatabaseTable>;
12+
};
13+
14+
export async function initializeDatabase(config: DatabaseConfig) {
15+
// Initialize the SQLite worker
16+
const promiser: Promiser = await new Promise((resolve) => {
17+
const _promiser = sqlite3Worker1Promiser({
18+
onready: () => resolve(_promiser),
19+
});
20+
});
21+
22+
if (!promiser) throw new Error('Failed to initialize promiser');
23+
24+
// Get configuration and open database
25+
const cfg = await promiser('config-get', {});
26+
const openResponse = await promiser('open', {
27+
filename: config.filename,
28+
});
29+
30+
if (openResponse.type === 'error') {
31+
throw new Error(openResponse.result.message);
32+
}
33+
34+
const dbId: DbId = openResponse.result.dbId;
35+
36+
for (const table of Object.values(config.tables)) {
37+
await promiser('exec', {
38+
dbId,
39+
sql: table.schema,
40+
});
41+
}
42+
43+
return {
44+
promiser,
45+
dbId,
46+
cfg,
47+
};
48+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { DatabaseConfig } from '@/workers/database';
2+
3+
export const databaseConfig: DatabaseConfig = {
4+
filename: 'file:n8n.sqlite3?vfs=opfs',
5+
tables: {
6+
executions: {
7+
name: 'executions',
8+
schema: `
9+
CREATE TABLE IF NOT EXISTS executions (
10+
id INTEGER PRIMARY KEY,
11+
workflow_id INTEGER NOT NULL,
12+
data TEXT CHECK (json_valid(data)) NOT NULL,
13+
workflow TEXT CHECK (json_valid(workflow)) NOT NULL,
14+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
15+
);
16+
`,
17+
},
18+
},
19+
} as const;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as Comlink from 'comlink';
2+
import type { RunDataWorker } from '@/workers/run-data/worker';
3+
4+
const worker = new Worker(new URL('./worker.ts', import.meta.url), {
5+
type: 'module',
6+
});
7+
8+
export const runDataWorker = Comlink.wrap<RunDataWorker>(worker);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as Comlink from 'comlink';
2+
import { databaseConfig } from '@/workers/run-data/db';
3+
import { initializeDatabase } from '@/workers/database';
4+
import type { Promiser, DbId } from '@sqlite.org/sqlite-wasm';
5+
6+
const state: {
7+
initialized: boolean;
8+
promiser: Promiser | undefined;
9+
dbId: DbId;
10+
} = {
11+
initialized: false,
12+
promiser: undefined,
13+
dbId: undefined,
14+
};
15+
16+
export const actions = {
17+
async initialize() {
18+
if (state.initialized) return;
19+
20+
const { promiser, dbId } = await initializeDatabase(databaseConfig);
21+
22+
state.promiser = promiser;
23+
state.dbId = dbId;
24+
state.initialized = true;
25+
},
26+
};
27+
28+
export type RunDataWorker = typeof actions;
29+
30+
Comlink.expose(actions);
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import type { Worker } from 'node:worker_threads';
2+
3+
declare module '@sqlite.org/sqlite-wasm' {
4+
type OnreadyFunction = () => void;
5+
6+
export type Sqlite3Worker1PromiserConfig = {
7+
onready?: OnreadyFunction;
8+
worker?: Worker | (() => Worker);
9+
generateMessageId?: (messageObject: unknown) => string;
10+
debug?: (...args: any[]) => void;
11+
onunhandled?: (event: MessageEvent) => void;
12+
};
13+
14+
export type DbId = string | undefined;
15+
16+
export type PromiserMethods = {
17+
'config-get': {
18+
args: Record<string, never>;
19+
result: {
20+
dbID: DbId;
21+
version: {
22+
libVersion: string;
23+
sourceId: string;
24+
libVersionNumber: number;
25+
downloadVersion: number;
26+
};
27+
bigIntEnabled: boolean;
28+
opfsEnabled: boolean;
29+
vfsList: string[];
30+
};
31+
};
32+
open: {
33+
args: Partial<{
34+
filename?: string;
35+
vfs?: string;
36+
}>;
37+
result: {
38+
dbId: DbId;
39+
filename: string;
40+
persistent: boolean;
41+
vfs: string;
42+
};
43+
};
44+
exec: {
45+
args: {
46+
sql: string;
47+
dbId?: DbId;
48+
bind?: unknown[];
49+
returnValue?: string;
50+
};
51+
result: {
52+
dbId: DbId;
53+
sql: string;
54+
bind: unknown[];
55+
returnValue: string;
56+
resultRows?: unknown[][];
57+
};
58+
};
59+
};
60+
61+
export type PromiserResponseSuccess<T extends keyof PromiserMethods> = {
62+
type: T;
63+
result: PromiserMethods[T]['result'];
64+
messageId: string;
65+
dbId: DbId;
66+
workerReceivedTime: number;
67+
workerRespondTime: number;
68+
departureTime: number;
69+
};
70+
71+
export type PromiserResponseError = {
72+
type: 'error';
73+
result: {
74+
operation: string;
75+
message: string;
76+
errorClass: string;
77+
input: object;
78+
stack: unknown[];
79+
};
80+
messageId: string;
81+
dbId: DbId;
82+
};
83+
84+
export type PromiserResponse<T extends keyof PromiserMethods> =
85+
| PromiserResponseSuccess<T>
86+
| PromiserResponseError;
87+
88+
export type Promiser = <T extends keyof PromiserMethods>(
89+
messageType: T,
90+
messageArguments: PromiserMethods[T]['args'],
91+
) => Promise<PromiserResponse<T>>;
92+
93+
export function sqlite3Worker1Promiser(
94+
config?: Sqlite3Worker1PromiserConfig | OnreadyFunction,
95+
): Promiser;
96+
}

0 commit comments

Comments
 (0)