@korajs/store 0.1.0
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/dist/adapters/better-sqlite3.cjs +166 -0
- package/dist/adapters/better-sqlite3.cjs.map +1 -0
- package/dist/adapters/better-sqlite3.d.cts +31 -0
- package/dist/adapters/better-sqlite3.d.ts +31 -0
- package/dist/adapters/better-sqlite3.js +117 -0
- package/dist/adapters/better-sqlite3.js.map +1 -0
- package/dist/adapters/indexeddb.cjs +550 -0
- package/dist/adapters/indexeddb.cjs.map +1 -0
- package/dist/adapters/indexeddb.d.cts +52 -0
- package/dist/adapters/indexeddb.d.ts +52 -0
- package/dist/adapters/indexeddb.js +205 -0
- package/dist/adapters/indexeddb.js.map +1 -0
- package/dist/adapters/sqlite-wasm-worker.cjs +215 -0
- package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -0
- package/dist/adapters/sqlite-wasm-worker.d.cts +2 -0
- package/dist/adapters/sqlite-wasm-worker.d.ts +2 -0
- package/dist/adapters/sqlite-wasm-worker.js +191 -0
- package/dist/adapters/sqlite-wasm-worker.js.map +1 -0
- package/dist/adapters/sqlite-wasm.cjs +354 -0
- package/dist/adapters/sqlite-wasm.cjs.map +1 -0
- package/dist/adapters/sqlite-wasm.d.cts +68 -0
- package/dist/adapters/sqlite-wasm.d.ts +68 -0
- package/dist/adapters/sqlite-wasm.js +14 -0
- package/dist/adapters/sqlite-wasm.js.map +1 -0
- package/dist/chunk-DXKLAQ6P.js +111 -0
- package/dist/chunk-DXKLAQ6P.js.map +1 -0
- package/dist/chunk-LAWV6CFH.js +62 -0
- package/dist/chunk-LAWV6CFH.js.map +1 -0
- package/dist/chunk-ZP5AXQ3Z.js +179 -0
- package/dist/chunk-ZP5AXQ3Z.js.map +1 -0
- package/dist/index.cjs +1288 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +376 -0
- package/dist/index.d.ts +376 -0
- package/dist/index.js +1194 -0
- package/dist/index.js.map +1 -0
- package/dist/sqlite-wasm-channel-46AOWNPM.js +10 -0
- package/dist/sqlite-wasm-channel-46AOWNPM.js.map +1 -0
- package/dist/sqlite-wasm-channel-Lakjuk2E.d.cts +104 -0
- package/dist/sqlite-wasm-channel-Lakjuk2E.d.ts +104 -0
- package/dist/types-DF-KDSK1.d.cts +106 -0
- package/dist/types-DF-KDSK1.d.ts +106 -0
- package/package.json +95 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { SchemaDefinition } from '@korajs/core';
|
|
2
|
+
import { S as StorageAdapter, T as Transaction, M as MigrationPlan } from '../types-DF-KDSK1.js';
|
|
3
|
+
import { W as WorkerBridge } from '../sqlite-wasm-channel-Lakjuk2E.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for creating an IndexedDbAdapter.
|
|
7
|
+
*/
|
|
8
|
+
interface IndexedDbAdapterOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Database name used as the IndexedDB key for persistence.
|
|
11
|
+
* Defaults to 'kora-db'.
|
|
12
|
+
*/
|
|
13
|
+
dbName?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Injected WorkerBridge for testing. If omitted, a WebWorkerBridge is created
|
|
16
|
+
* in browser environments.
|
|
17
|
+
*/
|
|
18
|
+
bridge?: WorkerBridge;
|
|
19
|
+
/**
|
|
20
|
+
* URL to the sqlite-wasm-worker script. Required in browsers if no bridge is provided.
|
|
21
|
+
*/
|
|
22
|
+
workerUrl?: string | URL;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* IndexedDB-backed adapter that uses SQLite WASM in-memory and serializes
|
|
26
|
+
* the entire database to IndexedDB after each transaction.
|
|
27
|
+
*
|
|
28
|
+
* This is the fallback adapter for browsers where OPFS is not available.
|
|
29
|
+
* It provides the same SQL interface as SqliteWasmAdapter, but persists by
|
|
30
|
+
* serializing the full SQLite database to a single IndexedDB blob.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const adapter = new IndexedDbAdapter({ workerUrl: '/sqlite-wasm-worker.js' })
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare class IndexedDbAdapter implements StorageAdapter {
|
|
38
|
+
private inner;
|
|
39
|
+
private readonly dbName;
|
|
40
|
+
constructor(options?: IndexedDbAdapterOptions);
|
|
41
|
+
open(schema: SchemaDefinition): Promise<void>;
|
|
42
|
+
close(): Promise<void>;
|
|
43
|
+
execute(sql: string, params?: unknown[]): Promise<void>;
|
|
44
|
+
query<T>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
45
|
+
transaction(fn: (tx: Transaction) => Promise<void>): Promise<void>;
|
|
46
|
+
migrate(from: number, to: number, migration: MigrationPlan): Promise<void>;
|
|
47
|
+
private persistSnapshot;
|
|
48
|
+
private restoreFromDumpFallback;
|
|
49
|
+
private exportDump;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { IndexedDbAdapter, type IndexedDbAdapterOptions };
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SqliteWasmAdapter
|
|
3
|
+
} from "../chunk-ZP5AXQ3Z.js";
|
|
4
|
+
import "../chunk-DXKLAQ6P.js";
|
|
5
|
+
import {
|
|
6
|
+
AdapterError,
|
|
7
|
+
PersistenceError
|
|
8
|
+
} from "../chunk-LAWV6CFH.js";
|
|
9
|
+
|
|
10
|
+
// src/adapters/sqlite-wasm-persistence.ts
|
|
11
|
+
var IDB_DATABASE_NAME = "kora-persistence";
|
|
12
|
+
var IDB_STORE_NAME = "databases";
|
|
13
|
+
var IDB_VERSION = 1;
|
|
14
|
+
var DUMP_SUFFIX = "::dump";
|
|
15
|
+
function openIdb() {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const request = indexedDB.open(IDB_DATABASE_NAME, IDB_VERSION);
|
|
18
|
+
request.onupgradeneeded = () => {
|
|
19
|
+
const db = request.result;
|
|
20
|
+
if (!db.objectStoreNames.contains(IDB_STORE_NAME)) {
|
|
21
|
+
db.createObjectStore(IDB_STORE_NAME);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
request.onsuccess = () => resolve(request.result);
|
|
25
|
+
request.onerror = () => reject(
|
|
26
|
+
new PersistenceError(
|
|
27
|
+
`Failed to open IndexedDB: ${request.error?.message ?? "unknown error"}`,
|
|
28
|
+
{ database: IDB_DATABASE_NAME }
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async function saveToIndexedDB(dbName, data) {
|
|
34
|
+
const idb = await openIdb();
|
|
35
|
+
try {
|
|
36
|
+
await new Promise((resolve, reject) => {
|
|
37
|
+
const tx = idb.transaction(IDB_STORE_NAME, "readwrite");
|
|
38
|
+
const store = tx.objectStore(IDB_STORE_NAME);
|
|
39
|
+
store.put(data, dbName);
|
|
40
|
+
tx.oncomplete = () => resolve();
|
|
41
|
+
tx.onerror = () => reject(new PersistenceError(`Failed to save database "${dbName}" to IndexedDB`, { dbName }));
|
|
42
|
+
});
|
|
43
|
+
} finally {
|
|
44
|
+
idb.close();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function saveDumpToIndexedDB(dbName, dump) {
|
|
48
|
+
const idb = await openIdb();
|
|
49
|
+
try {
|
|
50
|
+
await new Promise((resolve, reject) => {
|
|
51
|
+
const tx = idb.transaction(IDB_STORE_NAME, "readwrite");
|
|
52
|
+
const store = tx.objectStore(IDB_STORE_NAME);
|
|
53
|
+
store.put(dump, `${dbName}${DUMP_SUFFIX}`);
|
|
54
|
+
tx.oncomplete = () => resolve();
|
|
55
|
+
tx.onerror = () => reject(new PersistenceError(`Failed to save dump for database "${dbName}"`, { dbName }));
|
|
56
|
+
});
|
|
57
|
+
} finally {
|
|
58
|
+
idb.close();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function loadFromIndexedDB(dbName) {
|
|
62
|
+
const idb = await openIdb();
|
|
63
|
+
try {
|
|
64
|
+
return await new Promise((resolve, reject) => {
|
|
65
|
+
const tx = idb.transaction(IDB_STORE_NAME, "readonly");
|
|
66
|
+
const store = tx.objectStore(IDB_STORE_NAME);
|
|
67
|
+
const request = store.get(dbName);
|
|
68
|
+
request.onsuccess = () => {
|
|
69
|
+
const result = request.result;
|
|
70
|
+
if (result instanceof Uint8Array) {
|
|
71
|
+
resolve(result);
|
|
72
|
+
} else if (result) {
|
|
73
|
+
resolve(new Uint8Array(result));
|
|
74
|
+
} else {
|
|
75
|
+
resolve(null);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
request.onerror = () => reject(
|
|
79
|
+
new PersistenceError(`Failed to load database "${dbName}" from IndexedDB`, { dbName })
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
} finally {
|
|
83
|
+
idb.close();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function loadDumpFromIndexedDB(dbName) {
|
|
87
|
+
const idb = await openIdb();
|
|
88
|
+
try {
|
|
89
|
+
return await new Promise((resolve, reject) => {
|
|
90
|
+
const tx = idb.transaction(IDB_STORE_NAME, "readonly");
|
|
91
|
+
const store = tx.objectStore(IDB_STORE_NAME);
|
|
92
|
+
const request = store.get(`${dbName}${DUMP_SUFFIX}`);
|
|
93
|
+
request.onsuccess = () => {
|
|
94
|
+
resolve(request.result ?? null);
|
|
95
|
+
};
|
|
96
|
+
request.onerror = () => reject(
|
|
97
|
+
new PersistenceError(`Failed to load dump for database "${dbName}" from IndexedDB`, {
|
|
98
|
+
dbName
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
} finally {
|
|
103
|
+
idb.close();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/adapters/indexeddb-adapter.ts
|
|
108
|
+
var IndexedDbAdapter = class {
|
|
109
|
+
inner;
|
|
110
|
+
dbName;
|
|
111
|
+
constructor(options = {}) {
|
|
112
|
+
this.dbName = options.dbName ?? "kora-db";
|
|
113
|
+
this.inner = new SqliteWasmAdapter({
|
|
114
|
+
bridge: options.bridge,
|
|
115
|
+
workerUrl: options.workerUrl,
|
|
116
|
+
dbName: this.dbName
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async open(schema) {
|
|
120
|
+
await this.inner.open(schema);
|
|
121
|
+
const persisted = await loadFromIndexedDB(this.dbName);
|
|
122
|
+
if (!persisted) return;
|
|
123
|
+
try {
|
|
124
|
+
await this.inner.importDatabase(persisted);
|
|
125
|
+
} catch {
|
|
126
|
+
await this.restoreFromDumpFallback();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async close() {
|
|
130
|
+
await this.persistSnapshot();
|
|
131
|
+
await this.inner.close();
|
|
132
|
+
}
|
|
133
|
+
async execute(sql, params) {
|
|
134
|
+
await this.inner.execute(sql, params);
|
|
135
|
+
await this.persistSnapshot();
|
|
136
|
+
}
|
|
137
|
+
async query(sql, params) {
|
|
138
|
+
return this.inner.query(sql, params);
|
|
139
|
+
}
|
|
140
|
+
async transaction(fn) {
|
|
141
|
+
await this.inner.transaction(fn);
|
|
142
|
+
await this.persistSnapshot();
|
|
143
|
+
}
|
|
144
|
+
async migrate(from, to, migration) {
|
|
145
|
+
await this.inner.migrate(from, to, migration);
|
|
146
|
+
await this.persistSnapshot();
|
|
147
|
+
}
|
|
148
|
+
async persistSnapshot() {
|
|
149
|
+
try {
|
|
150
|
+
const data = await this.inner.exportDatabase();
|
|
151
|
+
await saveToIndexedDB(this.dbName, data);
|
|
152
|
+
const dump = await this.exportDump();
|
|
153
|
+
await saveDumpToIndexedDB(this.dbName, dump);
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async restoreFromDumpFallback() {
|
|
158
|
+
const dump = await loadDumpFromIndexedDB(this.dbName);
|
|
159
|
+
if (!dump) return;
|
|
160
|
+
for (const table of dump.tables) {
|
|
161
|
+
const name = ensureSafeIdentifier(table.name);
|
|
162
|
+
await this.inner.execute(`DELETE FROM ${name}`);
|
|
163
|
+
if (table.rows.length === 0) continue;
|
|
164
|
+
for (const row of table.rows) {
|
|
165
|
+
const columns = table.columns.filter((column) => Object.prototype.hasOwnProperty.call(row, column));
|
|
166
|
+
if (columns.length === 0) continue;
|
|
167
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
168
|
+
const quotedColumns = columns.map((column) => ensureSafeIdentifier(column)).join(", ");
|
|
169
|
+
const values = columns.map((column) => row[column]);
|
|
170
|
+
await this.inner.execute(
|
|
171
|
+
`INSERT INTO ${name} (${quotedColumns}) VALUES (${placeholders})`,
|
|
172
|
+
values
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async exportDump() {
|
|
178
|
+
const tableRows = await this.inner.query(
|
|
179
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
|
180
|
+
);
|
|
181
|
+
const tables = [];
|
|
182
|
+
for (const tableRow of tableRows) {
|
|
183
|
+
const tableName = ensureSafeIdentifier(tableRow.name);
|
|
184
|
+
const columns = await this.inner.query(`PRAGMA table_info(${tableName})`);
|
|
185
|
+
const columnNames = columns.map((column) => column.name);
|
|
186
|
+
const rows = await this.inner.query(`SELECT * FROM ${tableName}`);
|
|
187
|
+
tables.push({
|
|
188
|
+
name: tableName,
|
|
189
|
+
columns: columnNames,
|
|
190
|
+
rows
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return { tables };
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
function ensureSafeIdentifier(identifier) {
|
|
197
|
+
if (!/^[a-zA-Z0-9_]+$/.test(identifier)) {
|
|
198
|
+
throw new AdapterError(`Unsafe SQL identifier: ${identifier}`);
|
|
199
|
+
}
|
|
200
|
+
return identifier;
|
|
201
|
+
}
|
|
202
|
+
export {
|
|
203
|
+
IndexedDbAdapter
|
|
204
|
+
};
|
|
205
|
+
//# sourceMappingURL=indexeddb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/sqlite-wasm-persistence.ts","../../src/adapters/indexeddb-adapter.ts"],"sourcesContent":["/// <reference lib=\"dom\" />\nimport { PersistenceError } from '../errors'\n\nconst IDB_DATABASE_NAME = 'kora-persistence'\nconst IDB_STORE_NAME = 'databases'\nconst IDB_VERSION = 1\n\nconst DUMP_SUFFIX = '::dump'\n\n/**\n * Open the IndexedDB database used for SQLite persistence.\n * Creates the object store on first access.\n */\nfunction openIdb(): Promise<IDBDatabase> {\n\treturn new Promise<IDBDatabase>((resolve, reject) => {\n\t\tconst request = indexedDB.open(IDB_DATABASE_NAME, IDB_VERSION)\n\t\trequest.onupgradeneeded = () => {\n\t\t\tconst db = request.result\n\t\t\tif (!db.objectStoreNames.contains(IDB_STORE_NAME)) {\n\t\t\t\tdb.createObjectStore(IDB_STORE_NAME)\n\t\t\t}\n\t\t}\n\t\trequest.onsuccess = () => resolve(request.result)\n\t\trequest.onerror = () =>\n\t\t\treject(\n\t\t\t\tnew PersistenceError(\n\t\t\t\t\t`Failed to open IndexedDB: ${request.error?.message ?? 'unknown error'}`,\n\t\t\t\t\t{ database: IDB_DATABASE_NAME },\n\t\t\t\t),\n\t\t\t)\n\t})\n}\n\n/**\n * Save a serialized SQLite database to IndexedDB.\n *\n * @param dbName - Key under which to store the data\n * @param data - Serialized database as Uint8Array\n */\nexport async function saveToIndexedDB(dbName: string, data: Uint8Array): Promise<void> {\n\tconst idb = await openIdb()\n\ttry {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst tx = idb.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\tstore.put(data, dbName)\n\t\t\ttx.oncomplete = () => resolve()\n\t\t\ttx.onerror = () =>\n\t\t\t\treject(new PersistenceError(`Failed to save database \"${dbName}\" to IndexedDB`, { dbName }))\n\t\t})\n\t} finally {\n\t\tidb.close()\n\t}\n}\n\n/**\n * Save a logical SQL dump payload to IndexedDB for import-fallback restore.\n */\nexport async function saveDumpToIndexedDB(dbName: string, dump: unknown): Promise<void> {\n\tconst idb = await openIdb()\n\ttry {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst tx = idb.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\tstore.put(dump, `${dbName}${DUMP_SUFFIX}`)\n\t\t\ttx.oncomplete = () => resolve()\n\t\t\ttx.onerror = () =>\n\t\t\t\treject(new PersistenceError(`Failed to save dump for database \"${dbName}\"`, { dbName }))\n\t\t})\n\t} finally {\n\t\tidb.close()\n\t}\n}\n\n/**\n * Load a serialized SQLite database from IndexedDB.\n *\n * @param dbName - Key under which the data was stored\n * @returns The serialized database, or null if not found\n */\nexport async function loadFromIndexedDB(dbName: string): Promise<Uint8Array | null> {\n\tconst idb = await openIdb()\n\ttry {\n\t\treturn await new Promise<Uint8Array | null>((resolve, reject) => {\n\t\t\tconst tx = idb.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\tconst request = store.get(dbName)\n\t\t\trequest.onsuccess = () => {\n\t\t\t\tconst result = request.result\n\t\t\t\tif (result instanceof Uint8Array) {\n\t\t\t\t\tresolve(result)\n\t\t\t\t} else if (result) {\n\t\t\t\t\t// Handle ArrayBuffer or other typed array forms\n\t\t\t\t\tresolve(new Uint8Array(result as ArrayBuffer))\n\t\t\t\t} else {\n\t\t\t\t\tresolve(null)\n\t\t\t\t}\n\t\t\t}\n\t\t\trequest.onerror = () =>\n\t\t\t\treject(\n\t\t\t\t\tnew PersistenceError(`Failed to load database \"${dbName}\" from IndexedDB`, { dbName }),\n\t\t\t\t)\n\t\t})\n\t} finally {\n\t\tidb.close()\n\t}\n}\n\n/**\n * Load a logical SQL dump payload from IndexedDB.\n */\nexport async function loadDumpFromIndexedDB<T>(dbName: string): Promise<T | null> {\n\tconst idb = await openIdb()\n\ttry {\n\t\treturn await new Promise<T | null>((resolve, reject) => {\n\t\t\tconst tx = idb.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\tconst request = store.get(`${dbName}${DUMP_SUFFIX}`)\n\t\t\trequest.onsuccess = () => {\n\t\t\t\tresolve((request.result as T | undefined) ?? null)\n\t\t\t}\n\t\t\trequest.onerror = () =>\n\t\t\t\treject(\n\t\t\t\t\tnew PersistenceError(`Failed to load dump for database \"${dbName}\" from IndexedDB`, {\n\t\t\t\t\t\tdbName,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t})\n\t} finally {\n\t\tidb.close()\n\t}\n}\n\n/**\n * Delete a serialized SQLite database from IndexedDB.\n *\n * @param dbName - Key to delete\n */\nexport async function deleteFromIndexedDB(dbName: string): Promise<void> {\n\tconst idb = await openIdb()\n\ttry {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst tx = idb.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\tstore.delete(dbName)\n\t\t\tstore.delete(`${dbName}${DUMP_SUFFIX}`)\n\t\t\ttx.oncomplete = () => resolve()\n\t\t\ttx.onerror = () =>\n\t\t\t\treject(\n\t\t\t\t\tnew PersistenceError(`Failed to delete database \"${dbName}\" from IndexedDB`, { dbName }),\n\t\t\t\t)\n\t\t})\n\t} finally {\n\t\tidb.close()\n\t}\n}\n","import type { SchemaDefinition } from '@korajs/core'\nimport { AdapterError } from '../errors'\nimport type { MigrationPlan, StorageAdapter, Transaction } from '../types'\nimport { SqliteWasmAdapter } from './sqlite-wasm-adapter'\nimport type { WorkerBridge } from './sqlite-wasm-channel'\nimport {\n\tloadDumpFromIndexedDB,\n\tloadFromIndexedDB,\n\tsaveDumpToIndexedDB,\n\tsaveToIndexedDB,\n} from './sqlite-wasm-persistence'\n\ninterface DatabaseDump {\n\ttables: Array<{\n\t\tname: string\n\t\tcolumns: string[]\n\t\trows: Array<Record<string, unknown>>\n\t}>\n}\n\n/**\n * Options for creating an IndexedDbAdapter.\n */\nexport interface IndexedDbAdapterOptions {\n\t/**\n\t * Database name used as the IndexedDB key for persistence.\n\t * Defaults to 'kora-db'.\n\t */\n\tdbName?: string\n\n\t/**\n\t * Injected WorkerBridge for testing. If omitted, a WebWorkerBridge is created\n\t * in browser environments.\n\t */\n\tbridge?: WorkerBridge\n\n\t/**\n\t * URL to the sqlite-wasm-worker script. Required in browsers if no bridge is provided.\n\t */\n\tworkerUrl?: string | URL\n}\n\n/**\n * IndexedDB-backed adapter that uses SQLite WASM in-memory and serializes\n * the entire database to IndexedDB after each transaction.\n *\n * This is the fallback adapter for browsers where OPFS is not available.\n * It provides the same SQL interface as SqliteWasmAdapter, but persists by\n * serializing the full SQLite database to a single IndexedDB blob.\n *\n * @example\n * ```typescript\n * const adapter = new IndexedDbAdapter({ workerUrl: '/sqlite-wasm-worker.js' })\n * ```\n */\nexport class IndexedDbAdapter implements StorageAdapter {\n\tprivate inner: SqliteWasmAdapter\n\tprivate readonly dbName: string\n\n\tconstructor(options: IndexedDbAdapterOptions = {}) {\n\t\tthis.dbName = options.dbName ?? 'kora-db'\n\t\tthis.inner = new SqliteWasmAdapter({\n\t\t\tbridge: options.bridge,\n\t\t\tworkerUrl: options.workerUrl,\n\t\t\tdbName: this.dbName,\n\t\t})\n\t}\n\n\tasync open(schema: SchemaDefinition): Promise<void> {\n\t\tawait this.inner.open(schema)\n\n\t\tconst persisted = await loadFromIndexedDB(this.dbName)\n\t\tif (!persisted) return\n\n\t\ttry {\n\t\t\tawait this.inner.importDatabase(persisted)\n\t\t} catch {\n\t\t\tawait this.restoreFromDumpFallback()\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tawait this.persistSnapshot()\n\t\tawait this.inner.close()\n\t}\n\n\tasync execute(sql: string, params?: unknown[]): Promise<void> {\n\t\tawait this.inner.execute(sql, params)\n\t\tawait this.persistSnapshot()\n\t}\n\n\tasync query<T>(sql: string, params?: unknown[]): Promise<T[]> {\n\t\treturn this.inner.query<T>(sql, params)\n\t}\n\n\tasync transaction(fn: (tx: Transaction) => Promise<void>): Promise<void> {\n\t\tawait this.inner.transaction(fn)\n\t\tawait this.persistSnapshot()\n\t}\n\n\tasync migrate(from: number, to: number, migration: MigrationPlan): Promise<void> {\n\t\tawait this.inner.migrate(from, to, migration)\n\t\tawait this.persistSnapshot()\n\t}\n\n\tprivate async persistSnapshot(): Promise<void> {\n\t\ttry {\n\t\t\tconst data = await this.inner.exportDatabase()\n\t\t\tawait saveToIndexedDB(this.dbName, data)\n\t\t\tconst dump = await this.exportDump()\n\t\t\tawait saveDumpToIndexedDB(this.dbName, dump)\n\t\t} catch {\n\t\t\t// Non-fatal persistence failure. Next write/close retries persistence.\n\t\t}\n\t}\n\n\tprivate async restoreFromDumpFallback(): Promise<void> {\n\t\tconst dump = await loadDumpFromIndexedDB<DatabaseDump>(this.dbName)\n\t\tif (!dump) return\n\n\t\tfor (const table of dump.tables) {\n\t\t\tconst name = ensureSafeIdentifier(table.name)\n\t\t\tawait this.inner.execute(`DELETE FROM ${name}`)\n\n\t\t\tif (table.rows.length === 0) continue\n\n\t\t\tfor (const row of table.rows) {\n\t\t\t\tconst columns = table.columns.filter((column) => Object.prototype.hasOwnProperty.call(row, column))\n\t\t\t\tif (columns.length === 0) continue\n\n\t\t\t\tconst placeholders = columns.map(() => '?').join(', ')\n\t\t\t\tconst quotedColumns = columns.map((column) => ensureSafeIdentifier(column)).join(', ')\n\t\t\t\tconst values = columns.map((column) => row[column])\n\n\t\t\t\tawait this.inner.execute(\n\t\t\t\t\t`INSERT INTO ${name} (${quotedColumns}) VALUES (${placeholders})`,\n\t\t\t\t\tvalues,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async exportDump(): Promise<DatabaseDump> {\n\t\tconst tableRows = await this.inner.query<{ name: string }>(\n\t\t\t\"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'\",\n\t\t)\n\n\t\tconst tables: DatabaseDump['tables'] = []\n\t\tfor (const tableRow of tableRows) {\n\t\t\tconst tableName = ensureSafeIdentifier(tableRow.name)\n\t\t\tconst columns = await this.inner.query<{ name: string }>(`PRAGMA table_info(${tableName})`)\n\t\t\tconst columnNames = columns.map((column) => column.name)\n\t\t\tconst rows = await this.inner.query<Record<string, unknown>>(`SELECT * FROM ${tableName}`)\n\n\t\t\ttables.push({\n\t\t\t\tname: tableName,\n\t\t\t\tcolumns: columnNames,\n\t\t\t\trows,\n\t\t\t})\n\t\t}\n\n\t\treturn { tables }\n\t}\n}\n\nfunction ensureSafeIdentifier(identifier: string): string {\n\tif (!/^[a-zA-Z0-9_]+$/.test(identifier)) {\n\t\tthrow new AdapterError(`Unsafe SQL identifier: ${identifier}`)\n\t}\n\treturn identifier\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
|
|
25
|
+
// src/adapters/sqlite-wasm-worker.ts
|
|
26
|
+
var db = null;
|
|
27
|
+
var sqlite3Api = null;
|
|
28
|
+
function sendResponse(response) {
|
|
29
|
+
self.postMessage(response);
|
|
30
|
+
}
|
|
31
|
+
function handleExecute(id, sql, params) {
|
|
32
|
+
if (!db) {
|
|
33
|
+
sendResponse({ id, type: "error", message: "Database is not open", code: "DB_NOT_OPEN" });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
db.exec({ sql, bind: params });
|
|
38
|
+
sendResponse({ id, type: "success" });
|
|
39
|
+
} catch (error) {
|
|
40
|
+
sendResponse({ id, type: "error", message: error.message, code: "EXEC_ERROR" });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function handleQuery(id, sql, params) {
|
|
44
|
+
if (!db) {
|
|
45
|
+
sendResponse({ id, type: "error", message: "Database is not open", code: "DB_NOT_OPEN" });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const rows = [];
|
|
50
|
+
db.exec({
|
|
51
|
+
sql,
|
|
52
|
+
bind: params,
|
|
53
|
+
rowMode: "object",
|
|
54
|
+
callback: (row) => {
|
|
55
|
+
rows.push({ ...row });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
sendResponse({ id, type: "success", data: rows });
|
|
59
|
+
} catch (error) {
|
|
60
|
+
sendResponse({ id, type: "error", message: error.message, code: "QUERY_ERROR" });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function handleOpen(id, ddlStatements) {
|
|
64
|
+
try {
|
|
65
|
+
const sqlite3InitModule = (await import("@sqlite.org/sqlite-wasm")).default;
|
|
66
|
+
const sqlite3 = await sqlite3InitModule();
|
|
67
|
+
sqlite3Api = sqlite3;
|
|
68
|
+
let useOpfs = false;
|
|
69
|
+
if (sqlite3.installOpfsSAHPoolVfs) {
|
|
70
|
+
try {
|
|
71
|
+
const pool = await sqlite3.installOpfsSAHPoolVfs({ name: "kora-opfs" });
|
|
72
|
+
db = new pool.OpfsSAHPoolDb("kora.db");
|
|
73
|
+
useOpfs = true;
|
|
74
|
+
} catch {
|
|
75
|
+
console.warn("[kora] OPFS unavailable, falling back to in-memory SQLite");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!useOpfs) {
|
|
79
|
+
db = new sqlite3.oo1.DB({ filename: ":memory:" });
|
|
80
|
+
}
|
|
81
|
+
db?.exec({ sql: "PRAGMA journal_mode = WAL" });
|
|
82
|
+
db?.exec({ sql: "PRAGMA foreign_keys = ON" });
|
|
83
|
+
for (const sql of ddlStatements) {
|
|
84
|
+
db?.exec({ sql });
|
|
85
|
+
}
|
|
86
|
+
sendResponse({ id, type: "success" });
|
|
87
|
+
} catch (error) {
|
|
88
|
+
sendResponse({
|
|
89
|
+
id,
|
|
90
|
+
type: "error",
|
|
91
|
+
message: error.message,
|
|
92
|
+
code: "INIT_ERROR"
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function handleClose(id) {
|
|
97
|
+
if (db) {
|
|
98
|
+
db.close();
|
|
99
|
+
db = null;
|
|
100
|
+
}
|
|
101
|
+
sendResponse({ id, type: "success" });
|
|
102
|
+
}
|
|
103
|
+
function handleImport(id, data) {
|
|
104
|
+
if (!db) {
|
|
105
|
+
sendResponse({ id, type: "error", message: "Database is not open", code: "DB_NOT_OPEN" });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const dbWithDeserialize = db;
|
|
109
|
+
if (typeof dbWithDeserialize.deserialize === "function") {
|
|
110
|
+
try {
|
|
111
|
+
dbWithDeserialize.deserialize(data);
|
|
112
|
+
sendResponse({ id, type: "success" });
|
|
113
|
+
return;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
sendResponse({ id, type: "error", message: error.message, code: "IMPORT_ERROR" });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const sqlite3 = sqlite3Api;
|
|
120
|
+
if (!sqlite3 || typeof sqlite3.capi?.sqlite3_deserialize === "undefined") {
|
|
121
|
+
sendResponse({
|
|
122
|
+
id,
|
|
123
|
+
type: "error",
|
|
124
|
+
message: "Import not supported in this SQLite WASM runtime",
|
|
125
|
+
code: "IMPORT_NOT_SUPPORTED"
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
sendResponse({
|
|
130
|
+
id,
|
|
131
|
+
type: "error",
|
|
132
|
+
message: "Import requires runtime-specific deserialize wiring and is unavailable in this worker build",
|
|
133
|
+
code: "IMPORT_NOT_SUPPORTED"
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function handleMessage(request) {
|
|
137
|
+
try {
|
|
138
|
+
switch (request.type) {
|
|
139
|
+
case "open":
|
|
140
|
+
handleOpen(request.id, request.ddlStatements);
|
|
141
|
+
return;
|
|
142
|
+
case "close":
|
|
143
|
+
handleClose(request.id);
|
|
144
|
+
return;
|
|
145
|
+
case "execute":
|
|
146
|
+
handleExecute(request.id, request.sql, request.params);
|
|
147
|
+
return;
|
|
148
|
+
case "query":
|
|
149
|
+
handleQuery(request.id, request.sql, request.params);
|
|
150
|
+
return;
|
|
151
|
+
case "begin":
|
|
152
|
+
handleExecute(request.id, "BEGIN");
|
|
153
|
+
return;
|
|
154
|
+
case "commit":
|
|
155
|
+
handleExecute(request.id, "COMMIT");
|
|
156
|
+
return;
|
|
157
|
+
case "rollback":
|
|
158
|
+
handleExecute(request.id, "ROLLBACK");
|
|
159
|
+
return;
|
|
160
|
+
case "migrate":
|
|
161
|
+
if (!db) {
|
|
162
|
+
sendResponse({
|
|
163
|
+
id: request.id,
|
|
164
|
+
type: "error",
|
|
165
|
+
message: "Database is not open",
|
|
166
|
+
code: "DB_NOT_OPEN"
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
for (const sql of request.statements) {
|
|
172
|
+
db.exec({ sql });
|
|
173
|
+
}
|
|
174
|
+
sendResponse({ id: request.id, type: "success" });
|
|
175
|
+
} catch (error) {
|
|
176
|
+
sendResponse({
|
|
177
|
+
id: request.id,
|
|
178
|
+
type: "error",
|
|
179
|
+
message: error.message,
|
|
180
|
+
code: "MIGRATE_ERROR"
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
case "export":
|
|
185
|
+
sendResponse({
|
|
186
|
+
id: request.id,
|
|
187
|
+
type: "error",
|
|
188
|
+
message: "Export not yet supported in browser worker",
|
|
189
|
+
code: "EXPORT_NOT_SUPPORTED"
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
case "import":
|
|
193
|
+
handleImport(request.id, request.data);
|
|
194
|
+
return;
|
|
195
|
+
default:
|
|
196
|
+
sendResponse({
|
|
197
|
+
id: request.id,
|
|
198
|
+
type: "error",
|
|
199
|
+
message: "Unknown request type",
|
|
200
|
+
code: "UNKNOWN_REQUEST"
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
} catch (error) {
|
|
204
|
+
sendResponse({
|
|
205
|
+
id: request.id,
|
|
206
|
+
type: "error",
|
|
207
|
+
message: error.message,
|
|
208
|
+
code: "WORKER_ERROR"
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
self.onmessage = (event) => {
|
|
213
|
+
handleMessage(event.data);
|
|
214
|
+
};
|
|
215
|
+
//# sourceMappingURL=sqlite-wasm-worker.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/sqlite-wasm-worker.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n/**\n * Web Worker script for running SQLite WASM.\n *\n * This file is intended to run inside a Web Worker in browsers.\n * It loads @sqlite.org/sqlite-wasm, initializes SQLite with OPFS persistence\n * (falling back to in-memory if unavailable), and processes messages from\n * the main thread via the WorkerRequest/WorkerResponse protocol.\n *\n * This script cannot be tested in Node.js — it is validated in E2E browser tests.\n */\n\nimport type { WorkerRequest, WorkerResponse } from './sqlite-wasm-channel'\n\ninterface SqliteDb {\n\texec(opts: {\n\t\tsql: string\n\t\tbind?: unknown[]\n\t\treturnValue?: string\n\t\trowMode?: string\n\t\tcallback?: (row: Record<string, unknown>) => void\n\t}): void\n\tclose(): void\n\tdeserialize?: (data: Uint8Array) => void\n}\n\ndeclare const self: DedicatedWorkerGlobalScope\n\nlet db: SqliteDb | null = null\nlet sqlite3Api: unknown = null\n\nfunction sendResponse(response: WorkerResponse): void {\n\tself.postMessage(response)\n}\n\nfunction handleExecute(id: number, sql: string, params?: unknown[]): void {\n\tif (!db) {\n\t\tsendResponse({ id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' })\n\t\treturn\n\t}\n\ttry {\n\t\tdb.exec({ sql, bind: params })\n\t\tsendResponse({ id, type: 'success' })\n\t} catch (error) {\n\t\tsendResponse({ id, type: 'error', message: (error as Error).message, code: 'EXEC_ERROR' })\n\t}\n}\n\nfunction handleQuery(id: number, sql: string, params?: unknown[]): void {\n\tif (!db) {\n\t\tsendResponse({ id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' })\n\t\treturn\n\t}\n\ttry {\n\t\tconst rows: Record<string, unknown>[] = []\n\t\tdb.exec({\n\t\t\tsql,\n\t\t\tbind: params,\n\t\t\trowMode: 'object',\n\t\t\tcallback: (row: Record<string, unknown>) => {\n\t\t\t\trows.push({ ...row })\n\t\t\t},\n\t\t})\n\t\tsendResponse({ id, type: 'success', data: rows })\n\t} catch (error) {\n\t\tsendResponse({ id, type: 'error', message: (error as Error).message, code: 'QUERY_ERROR' })\n\t}\n}\n\nasync function handleOpen(id: number, ddlStatements: string[]): Promise<void> {\n\ttry {\n\t\tconst sqlite3InitModule = (await import('@sqlite.org/sqlite-wasm')).default\n\t\tconst sqlite3 = await sqlite3InitModule()\n\t\tsqlite3Api = sqlite3\n\n\t\t// Try OPFS persistence first\n\t\tlet useOpfs = false\n\t\tif (sqlite3.installOpfsSAHPoolVfs) {\n\t\t\ttry {\n\t\t\t\tconst pool = await sqlite3.installOpfsSAHPoolVfs({ name: 'kora-opfs' })\n\t\t\t\tdb = new pool.OpfsSAHPoolDb('kora.db')\n\t\t\t\tuseOpfs = true\n\t\t\t} catch {\n\t\t\t\t// OPFS unavailable, fall back to in-memory\n\t\t\t\tconsole.warn('[kora] OPFS unavailable, falling back to in-memory SQLite')\n\t\t\t}\n\t\t}\n\n\t\tif (!useOpfs) {\n\t\t\tdb = new sqlite3.oo1.DB({ filename: ':memory:' })\n\t\t}\n\n\t\t// Set pragmas\n\t\tdb?.exec({ sql: 'PRAGMA journal_mode = WAL' })\n\t\tdb?.exec({ sql: 'PRAGMA foreign_keys = ON' })\n\n\t\t// Execute DDL statements\n\t\tfor (const sql of ddlStatements) {\n\t\t\tdb?.exec({ sql })\n\t\t}\n\n\t\tsendResponse({ id, type: 'success' })\n\t} catch (error) {\n\t\tsendResponse({\n\t\t\tid,\n\t\t\ttype: 'error',\n\t\t\tmessage: (error as Error).message,\n\t\t\tcode: 'INIT_ERROR',\n\t\t})\n\t}\n}\n\nfunction handleClose(id: number): void {\n\tif (db) {\n\t\tdb.close()\n\t\tdb = null\n\t}\n\tsendResponse({ id, type: 'success' })\n}\n\nfunction handleImport(id: number, data: Uint8Array): void {\n\tif (!db) {\n\t\tsendResponse({ id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' })\n\t\treturn\n\t}\n\n\tconst dbWithDeserialize = db as SqliteDb & { deserialize?: (bytes: Uint8Array) => void }\n\tif (typeof dbWithDeserialize.deserialize === 'function') {\n\t\ttry {\n\t\t\tdbWithDeserialize.deserialize(data)\n\t\t\tsendResponse({ id, type: 'success' })\n\t\t\treturn\n\t\t} catch (error) {\n\t\t\tsendResponse({ id, type: 'error', message: (error as Error).message, code: 'IMPORT_ERROR' })\n\t\t\treturn\n\t\t}\n\t}\n\n\tconst sqlite3 = sqlite3Api as\n\t\t| {\n\t\t\t\too1?: { DB?: new (...args: unknown[]) => SqliteDb }\n\t\t\t\tcapi?: { sqlite3_deserialize?: unknown }\n\t\t\t}\n\t\t| null\n\n\tif (!sqlite3 || typeof sqlite3.capi?.sqlite3_deserialize === 'undefined') {\n\t\tsendResponse({\n\t\t\tid,\n\t\t\ttype: 'error',\n\t\t\tmessage: 'Import not supported in this SQLite WASM runtime',\n\t\t\tcode: 'IMPORT_NOT_SUPPORTED',\n\t\t})\n\t\treturn\n\t}\n\n\tsendResponse({\n\t\tid,\n\t\ttype: 'error',\n\t\tmessage: 'Import requires runtime-specific deserialize wiring and is unavailable in this worker build',\n\t\tcode: 'IMPORT_NOT_SUPPORTED',\n\t})\n}\n\nfunction handleMessage(request: WorkerRequest): void {\n\ttry {\n\t\tswitch (request.type) {\n\t\t\tcase 'open':\n\t\t\t\t// open is async due to WASM loading\n\t\t\t\thandleOpen(request.id, request.ddlStatements)\n\t\t\t\treturn\n\t\t\tcase 'close':\n\t\t\t\thandleClose(request.id)\n\t\t\t\treturn\n\t\t\tcase 'execute':\n\t\t\t\thandleExecute(request.id, request.sql, request.params)\n\t\t\t\treturn\n\t\t\tcase 'query':\n\t\t\t\thandleQuery(request.id, request.sql, request.params)\n\t\t\t\treturn\n\t\t\tcase 'begin':\n\t\t\t\thandleExecute(request.id, 'BEGIN')\n\t\t\t\treturn\n\t\t\tcase 'commit':\n\t\t\t\thandleExecute(request.id, 'COMMIT')\n\t\t\t\treturn\n\t\t\tcase 'rollback':\n\t\t\t\thandleExecute(request.id, 'ROLLBACK')\n\t\t\t\treturn\n\t\t\tcase 'migrate':\n\t\t\t\tif (!db) {\n\t\t\t\t\tsendResponse({\n\t\t\t\t\t\tid: request.id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage: 'Database is not open',\n\t\t\t\t\t\tcode: 'DB_NOT_OPEN',\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tfor (const sql of request.statements) {\n\t\t\t\t\t\tdb.exec({ sql })\n\t\t\t\t\t}\n\t\t\t\t\tsendResponse({ id: request.id, type: 'success' })\n\t\t\t\t} catch (error) {\n\t\t\t\t\tsendResponse({\n\t\t\t\t\t\tid: request.id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage: (error as Error).message,\n\t\t\t\t\t\tcode: 'MIGRATE_ERROR',\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\tcase 'export':\n\t\t\t\t// Export is not trivially supported via the oo1 API in the browser.\n\t\t\t\t// In a real implementation, we'd use the C API's sqlite3_serialize.\n\t\t\t\tsendResponse({\n\t\t\t\t\tid: request.id,\n\t\t\t\t\ttype: 'error',\n\t\t\t\t\tmessage: 'Export not yet supported in browser worker',\n\t\t\t\t\tcode: 'EXPORT_NOT_SUPPORTED',\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\tcase 'import':\n\t\t\t\thandleImport(request.id, request.data)\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tsendResponse({\n\t\t\t\t\tid: (request as WorkerRequest).id,\n\t\t\t\t\ttype: 'error',\n\t\t\t\t\tmessage: 'Unknown request type',\n\t\t\t\t\tcode: 'UNKNOWN_REQUEST',\n\t\t\t\t})\n\t\t}\n\t} catch (error) {\n\t\tsendResponse({\n\t\t\tid: request.id,\n\t\t\ttype: 'error',\n\t\t\tmessage: (error as Error).message,\n\t\t\tcode: 'WORKER_ERROR',\n\t\t})\n\t}\n}\n\n// Listen for messages from the main thread\nself.onmessage = (event: MessageEvent<WorkerRequest>) => {\n\thandleMessage(event.data)\n}\n"],"mappings":"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","names":[]}
|