@taladb/web 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.
@@ -0,0 +1,202 @@
1
+ /**
2
+ * TalaDB SharedWorker
3
+ *
4
+ * Owns the OPFS file handle and the WASM + redb database instance.
5
+ * The main thread connects via SharedWorker and communicates through
6
+ * a typed message protocol.
7
+ *
8
+ * Why SharedWorker (not DedicatedWorker)?
9
+ * ----------------------------------------
10
+ * A SharedWorker persists as long as any tab/page from the same origin
11
+ * has it open. This means multiple tabs share the same database instance —
12
+ * no write conflicts, no duplicate open files.
13
+ *
14
+ * Message protocol
15
+ * ----------------
16
+ * Request → { id: number, op: string, ...args }
17
+ * Response → { id: number, result: unknown }
18
+ * | { id: number, error: string }
19
+ *
20
+ * The `id` field lets the main thread match async responses to pending
21
+ * Promise resolvers even when operations complete out of order.
22
+ *
23
+ * Supported ops
24
+ * -------------
25
+ * init { dbName }
26
+ * insert { collection, docJson }
27
+ * insertMany { collection, docsJson }
28
+ * find { collection, filterJson }
29
+ * findOne { collection, filterJson }
30
+ * updateOne { collection, filterJson, updateJson }
31
+ * updateMany { collection, filterJson, updateJson }
32
+ * deleteOne { collection, filterJson }
33
+ * deleteMany { collection, filterJson }
34
+ * count { collection, filterJson }
35
+ * createIndex { collection, field }
36
+ * dropIndex { collection, field }
37
+ * createFtsIndex { collection, field }
38
+ * dropFtsIndex { collection, field }
39
+ * close {}
40
+ */
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // State
44
+ // ---------------------------------------------------------------------------
45
+
46
+ /** @type {import('../pkg/taladb_wasm').WorkerDB | null} */
47
+ let db = null;
48
+
49
+ /**
50
+ * Maps dbName → Promise<void> so that:
51
+ * - Concurrent init calls for the same dbName share one promise (deduplicated).
52
+ * - Init calls for different dbNames are rejected after the first succeeds,
53
+ * because a SharedWorker instance owns exactly one database file.
54
+ * @type {Map<string, Promise<void>>}
55
+ */
56
+ const initPromises = new Map();
57
+
58
+ /** The dbName that was successfully initialised (or is being initialised). */
59
+ let activeDbName = null;
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // SharedWorker connect handler
63
+ // ---------------------------------------------------------------------------
64
+
65
+ self.onconnect = (connectEvent) => {
66
+ const port = connectEvent.ports[0];
67
+
68
+ port.onmessage = async (e) => {
69
+ const { id, op, ...args } = e.data;
70
+ try {
71
+ const result = await dispatch(op, args);
72
+ port.postMessage({ id, result: result ?? null });
73
+ } catch (err) {
74
+ port.postMessage({ id, error: String(err?.message ?? err) });
75
+ }
76
+ };
77
+
78
+ port.start();
79
+ };
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Dispatcher
83
+ // ---------------------------------------------------------------------------
84
+
85
+ async function dispatch(op, args) {
86
+ if (op === 'init') {
87
+ const { dbName } = args;
88
+
89
+ // Reject if a different database was already opened in this worker instance.
90
+ // A SharedWorker owns exactly one OPFS file handle.
91
+ if (activeDbName !== null && activeDbName !== dbName) {
92
+ throw new Error(
93
+ `TalaDB worker already initialised for "${activeDbName}". ` +
94
+ `Cannot open "${dbName}" in the same SharedWorker instance.`
95
+ );
96
+ }
97
+
98
+ // Deduplicate concurrent init calls for the same dbName.
99
+ if (!initPromises.has(dbName)) {
100
+ activeDbName = dbName;
101
+ initPromises.set(dbName, doInit(dbName));
102
+ }
103
+ await initPromises.get(dbName);
104
+ return null;
105
+ }
106
+
107
+ if (!db) throw new Error('TalaDB worker not initialised — call init first');
108
+
109
+ switch (op) {
110
+ case 'insert':
111
+ return db.insert(args.collection, args.docJson);
112
+
113
+ case 'insertMany':
114
+ return db.insertMany(args.collection, args.docsJson);
115
+
116
+ case 'find':
117
+ return db.find(args.collection, args.filterJson ?? 'null');
118
+
119
+ case 'findOne':
120
+ return db.findOne(args.collection, args.filterJson ?? 'null');
121
+
122
+ case 'updateOne':
123
+ return db.updateOne(args.collection, args.filterJson, args.updateJson);
124
+
125
+ case 'updateMany':
126
+ return db.updateMany(args.collection, args.filterJson, args.updateJson);
127
+
128
+ case 'deleteOne':
129
+ return db.deleteOne(args.collection, args.filterJson);
130
+
131
+ case 'deleteMany':
132
+ return db.deleteMany(args.collection, args.filterJson);
133
+
134
+ case 'count':
135
+ return db.count(args.collection, args.filterJson ?? 'null');
136
+
137
+ case 'createIndex':
138
+ db.createIndex(args.collection, args.field);
139
+ return null;
140
+
141
+ case 'dropIndex':
142
+ db.dropIndex(args.collection, args.field);
143
+ return null;
144
+
145
+ case 'createFtsIndex':
146
+ db.createFtsIndex(args.collection, args.field);
147
+ return null;
148
+
149
+ case 'dropFtsIndex':
150
+ db.dropFtsIndex(args.collection, args.field);
151
+ return null;
152
+
153
+ case 'close':
154
+ db = null;
155
+ return null;
156
+
157
+ default:
158
+ throw new Error(`Unknown op: ${op}`);
159
+ }
160
+ }
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // Initialisation — load WASM, open OPFS file, create WorkerDB
164
+ // ---------------------------------------------------------------------------
165
+
166
+ async function doInit(dbName) {
167
+ // Dynamic import of the WASM module (wasm-pack --target web output)
168
+ // The bundler (Vite/Webpack) will resolve this path correctly.
169
+ const wasm = await import('../pkg/taladb_wasm.js');
170
+ await wasm.default(); // run wasm-bindgen init (sets up memory, panic hook, etc.)
171
+
172
+ const { WorkerDB } = wasm;
173
+
174
+ const opfsAvailable = await checkOpfs();
175
+ if (!opfsAvailable) {
176
+ console.warn('[TalaDB Worker] OPFS unavailable — falling back to in-memory');
177
+ db = WorkerDB.openInMemory();
178
+ return;
179
+ }
180
+
181
+ // Get the OPFS root directory
182
+ const root = await navigator.storage.getDirectory();
183
+
184
+ // Open (or create) the database file
185
+ const fileName = `taladb_${dbName.replaceAll(/[/\\:]/g, '_')}.redb`;
186
+ const fileHandle = await root.getFileHandle(fileName, { create: true });
187
+
188
+ // createSyncAccessHandle — available in workers only
189
+ const syncHandle = await fileHandle.createSyncAccessHandle();
190
+
191
+ db = WorkerDB.openWithOpfs(syncHandle);
192
+ console.log(`[TalaDB Worker] Opened "${fileName}" via OPFS`);
193
+ }
194
+
195
+ async function checkOpfs() {
196
+ try {
197
+ await navigator.storage.getDirectory();
198
+ return true;
199
+ } catch {
200
+ return false;
201
+ }
202
+ }