@onyx-p/imlib-web 3.0.1 → 3.0.3

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,640 @@
1
+ 'use strict';
2
+
3
+ var node_worker_threads = require('node:worker_threads');
4
+ var SQL = require('@signalapp/sqlcipher');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var SQL__default = /*#__PURE__*/_interopDefault(SQL);
9
+
10
+ const READ_METHOD_NAMES = [
11
+ 'getMessages',
12
+ 'getLatestMessage',
13
+ 'getLatestMessages',
14
+ 'getDialogLoadedState',
15
+ 'getMessageById',
16
+ 'getMessageByUId',
17
+ 'getMessageIdRange',
18
+ 'getMessagesByType',
19
+ 'searchMessages',
20
+ 'searchTextMessages',
21
+ 'getLegacyMigrationState'
22
+ ];
23
+ const WRITE_METHOD_NAMES = [
24
+ 'addMessages',
25
+ 'clearConversationCache',
26
+ 'clearAllCache',
27
+ 'removeMessagesByUId',
28
+ 'updateMessageReceiptStatus',
29
+ 'clearBurnAfterReadingExpiredMessages',
30
+ 'upsertMessage',
31
+ 'convertToRecallMessages',
32
+ 'importLegacyBatch',
33
+ 'completeLegacyMigration'
34
+ ];
35
+ const READ_METHODS = new Set(READ_METHOD_NAMES);
36
+ const WRITE_METHODS = new Set(WRITE_METHOD_NAMES);
37
+ function serializeDatabaseError(error) {
38
+ if (error instanceof Error) {
39
+ return {
40
+ name: error.name,
41
+ message: error.message,
42
+ stack: error.stack
43
+ };
44
+ }
45
+ return {
46
+ name: 'Error',
47
+ message: String(error),
48
+ stack: undefined
49
+ };
50
+ }
51
+
52
+ const SCHEMA_MIGRATIONS = [
53
+ {
54
+ version: 1,
55
+ up(database) {
56
+ database.exec(`
57
+ CREATE TABLE metadata (
58
+ key TEXT PRIMARY KEY NOT NULL,
59
+ value TEXT NOT NULL
60
+ );
61
+
62
+ CREATE TABLE messages (
63
+ messageId INTEGER PRIMARY KEY NOT NULL,
64
+ messageUId TEXT UNIQUE,
65
+ dialogId TEXT NOT NULL,
66
+ sentTime INTEGER NOT NULL,
67
+ receivedStatus INTEGER,
68
+ burnAfterReadingFlag INTEGER NOT NULL DEFAULT 0,
69
+ burnAfterReadingTime INTEGER,
70
+ messageType INTEGER,
71
+ json TEXT NOT NULL
72
+ );
73
+
74
+ CREATE TABLE dialog_states (
75
+ dialogId TEXT PRIMARY KEY NOT NULL,
76
+ isEnd INTEGER NOT NULL,
77
+ updateTime INTEGER NOT NULL
78
+ );
79
+ `);
80
+ }
81
+ },
82
+ {
83
+ version: 2,
84
+ up(database) {
85
+ database.exec(`
86
+ CREATE INDEX messages_by_dialog_time
87
+ ON messages (dialogId, sentTime DESC, messageId DESC);
88
+ CREATE INDEX messages_by_dialog
89
+ ON messages (dialogId);
90
+ CREATE INDEX messages_by_receipt
91
+ ON messages (receivedStatus);
92
+ CREATE INDEX messages_by_burn
93
+ ON messages (dialogId, burnAfterReadingFlag, sentTime);
94
+ `);
95
+ }
96
+ },
97
+ {
98
+ version: 3,
99
+ up(database) {
100
+ database.exec(`
101
+ ALTER TABLE messages ADD COLUMN searchText TEXT NOT NULL DEFAULT '';
102
+
103
+ CREATE VIRTUAL TABLE messages_fts USING fts5(
104
+ searchText,
105
+ content='messages',
106
+ content_rowid='messageId',
107
+ tokenize='unicode61'
108
+ );
109
+
110
+ CREATE TRIGGER messages_fts_insert AFTER INSERT ON messages BEGIN
111
+ INSERT INTO messages_fts(rowid, searchText)
112
+ VALUES (new.messageId, new.searchText);
113
+ END;
114
+
115
+ CREATE TRIGGER messages_fts_delete AFTER DELETE ON messages BEGIN
116
+ INSERT INTO messages_fts(messages_fts, rowid, searchText)
117
+ VALUES ('delete', old.messageId, old.searchText);
118
+ END;
119
+
120
+ CREATE TRIGGER messages_fts_update AFTER UPDATE OF searchText ON messages BEGIN
121
+ INSERT INTO messages_fts(messages_fts, rowid, searchText)
122
+ VALUES ('delete', old.messageId, old.searchText);
123
+ INSERT INTO messages_fts(rowid, searchText)
124
+ VALUES (new.messageId, new.searchText);
125
+ END;
126
+ `);
127
+ }
128
+ },
129
+ {
130
+ version: 4,
131
+ up(database) {
132
+ database.exec(`
133
+ CREATE INDEX messages_by_search_order
134
+ ON messages (sentTime DESC, messageId DESC);
135
+ `);
136
+ }
137
+ },
138
+ {
139
+ version: 5,
140
+ up(database) {
141
+ database.exec(`
142
+ CREATE INDEX messages_by_dialog_type_time
143
+ ON messages (dialogId, messageType, sentTime DESC, messageId DESC);
144
+ `);
145
+ }
146
+ }
147
+ ];
148
+ SCHEMA_MIGRATIONS[SCHEMA_MIGRATIONS.length - 1]?.version ?? 0;
149
+ function validateMigrations(migrations) {
150
+ let previous = 0;
151
+ for (const migration of migrations) {
152
+ if (!Number.isSafeInteger(migration.version) || migration.version <= previous) {
153
+ throw new Error('Schema migration versions must be strictly increasing');
154
+ }
155
+ previous = migration.version;
156
+ }
157
+ }
158
+ function runMigrations(database, migrations = SCHEMA_MIGRATIONS) {
159
+ validateMigrations(migrations);
160
+ const currentVersion = Number(database.pragma('user_version', { simple: true }) ?? 0);
161
+ const latestVersion = migrations[migrations.length - 1]?.version ?? 0;
162
+ if (currentVersion > latestVersion) {
163
+ throw new Error(`Database schema version ${currentVersion} is newer than supported version ${latestVersion}`);
164
+ }
165
+ for (const migration of migrations) {
166
+ if (migration.version <= currentVersion) {
167
+ continue;
168
+ }
169
+ database.transaction(() => {
170
+ migration.up(database);
171
+ database.pragma(`user_version = ${migration.version}`);
172
+ })();
173
+ }
174
+ }
175
+
176
+ const LEGACY_MIGRATION_KEY = 'legacy_indexeddb_migration';
177
+ function parseMessage(row) {
178
+ return row ? JSON.parse(row.json) : null;
179
+ }
180
+ function extractSearchText(message) {
181
+ const content = message.content;
182
+ if (typeof content === 'string') {
183
+ return content;
184
+ }
185
+ if (content && typeof content.content === 'string') {
186
+ return content.content;
187
+ }
188
+ return '';
189
+ }
190
+ function normalizeInteger(value, name) {
191
+ const number = typeof value === 'number' ? value : Number(value);
192
+ if (!Number.isSafeInteger(number)) {
193
+ throw new Error(`${name} must be a safe integer`);
194
+ }
195
+ return number;
196
+ }
197
+ class MessageStore {
198
+ database;
199
+ constructor(database) {
200
+ this.database = database;
201
+ }
202
+ addMessages(messages, dialogId, isEnd) {
203
+ const upsert = this.database.prepare(`
204
+ INSERT INTO messages (
205
+ messageId, messageUId, dialogId, sentTime, receivedStatus,
206
+ burnAfterReadingFlag, burnAfterReadingTime, messageType, json, searchText
207
+ ) VALUES (
208
+ $messageId, $messageUId, $dialogId, $sentTime, $receivedStatus,
209
+ $burnAfterReadingFlag, $burnAfterReadingTime, $messageType, $json, $searchText
210
+ )
211
+ ON CONFLICT(messageId) DO UPDATE SET
212
+ messageUId = excluded.messageUId,
213
+ dialogId = excluded.dialogId,
214
+ sentTime = excluded.sentTime,
215
+ receivedStatus = excluded.receivedStatus,
216
+ burnAfterReadingFlag = excluded.burnAfterReadingFlag,
217
+ burnAfterReadingTime = excluded.burnAfterReadingTime,
218
+ messageType = excluded.messageType,
219
+ json = excluded.json,
220
+ searchText = excluded.searchText;
221
+ `);
222
+ this.database.transaction(() => {
223
+ for (const message of messages) {
224
+ if (message.isPersited === false) {
225
+ continue;
226
+ }
227
+ const messageId = normalizeInteger(message.messageId, 'messageId');
228
+ const sentTime = normalizeInteger(message.sentTime, 'sentTime');
229
+ upsert.run({
230
+ messageId,
231
+ messageUId: message.messageUId ?? null,
232
+ dialogId,
233
+ sentTime,
234
+ receivedStatus: message.receivedStatus ?? null,
235
+ burnAfterReadingFlag: message.burnAfterReadingFlag ? 1 : 0,
236
+ burnAfterReadingTime: message.burnAfterReadingTime ?? null,
237
+ messageType: message.messageType ?? null,
238
+ json: JSON.stringify(message),
239
+ searchText: extractSearchText(message)
240
+ });
241
+ }
242
+ if (isEnd !== undefined) {
243
+ this.setDialogState(dialogId, isEnd);
244
+ }
245
+ })();
246
+ }
247
+ upsertMessage(message, dialogId) {
248
+ this.addMessages([message], dialogId);
249
+ }
250
+ getMessages(dialogId, timestamp = '0', count = 20, isForward = true) {
251
+ const safeCount = Math.max(0, Math.floor(count));
252
+ const params = {
253
+ dialogId,
254
+ limit: safeCount + 1
255
+ };
256
+ let timeClause = '';
257
+ if (timestamp !== '0') {
258
+ params.timestamp = normalizeInteger(timestamp, 'timestamp');
259
+ timeClause = isForward ? 'AND sentTime < $timestamp' : 'AND sentTime > $timestamp';
260
+ }
261
+ const direction = isForward ? 'DESC' : 'ASC';
262
+ const rows = this.database
263
+ .prepare(`SELECT json FROM messages
264
+ WHERE dialogId = $dialogId ${timeClause}
265
+ ORDER BY sentTime ${direction}, messageId ${direction}
266
+ LIMIT $limit;`)
267
+ .all(params);
268
+ const hasAdditionalRow = rows.length > safeCount;
269
+ const messages = rows
270
+ .slice(0, safeCount)
271
+ .map(row => parseMessage(row))
272
+ .sort((left, right) => {
273
+ const byTime = Number(left.sentTime) - Number(right.sentTime);
274
+ return byTime || left.messageId - right.messageId;
275
+ });
276
+ return {
277
+ messages,
278
+ hasMore: hasAdditionalRow || !this.getDialogLoadedState(dialogId)
279
+ };
280
+ }
281
+ getMessagesByType(dialogId, messageTypes, timestamp = '0', count = 50, isForward = true) {
282
+ const types = [
283
+ ...new Set(messageTypes.map(type => normalizeInteger(type, 'messageType')))
284
+ ];
285
+ const safeCount = Math.max(0, Math.floor(count));
286
+ if (types.length === 0 || safeCount === 0) {
287
+ return { messages: [], hasMore: false };
288
+ }
289
+ const params = [dialogId, ...types];
290
+ const typePlaceholders = types.map(() => '?').join(', ');
291
+ let timeClause = '';
292
+ if (timestamp !== '0') {
293
+ params.push(normalizeInteger(timestamp, 'timestamp'));
294
+ timeClause = isForward ? 'AND sentTime < ?' : 'AND sentTime > ?';
295
+ }
296
+ params.push(safeCount + 1);
297
+ const direction = isForward ? 'DESC' : 'ASC';
298
+ const rows = this.database
299
+ .prepare(`SELECT json FROM messages
300
+ WHERE dialogId = ?
301
+ AND messageType IN (${typePlaceholders})
302
+ ${timeClause}
303
+ ORDER BY sentTime ${direction}, messageId ${direction}
304
+ LIMIT ?;`)
305
+ .all(params);
306
+ const hasAdditionalRow = rows.length > safeCount;
307
+ const messages = rows
308
+ .slice(0, safeCount)
309
+ .map(row => parseMessage(row))
310
+ .sort((left, right) => {
311
+ const byTime = Number(left.sentTime) - Number(right.sentTime);
312
+ return byTime || left.messageId - right.messageId;
313
+ });
314
+ return {
315
+ messages,
316
+ hasMore: hasAdditionalRow
317
+ };
318
+ }
319
+ getLatestMessage(dialogId) {
320
+ return parseMessage(this.database
321
+ .prepare(`SELECT json FROM messages
322
+ WHERE dialogId = $dialogId
323
+ ORDER BY sentTime DESC, messageId DESC
324
+ LIMIT 1;`)
325
+ .get({ dialogId }));
326
+ }
327
+ getLatestMessages(dialogIds) {
328
+ const result = Object.create(null);
329
+ for (const dialogId of dialogIds) {
330
+ result[dialogId] = null;
331
+ }
332
+ if (dialogIds.length === 0) {
333
+ return result;
334
+ }
335
+ const placeholders = dialogIds.map(() => '?').join(', ');
336
+ const rows = this.database
337
+ .prepare(`SELECT dialogId, json FROM (
338
+ SELECT dialogId, json,
339
+ ROW_NUMBER() OVER (
340
+ PARTITION BY dialogId ORDER BY sentTime DESC, messageId DESC
341
+ ) AS position
342
+ FROM messages
343
+ WHERE dialogId IN (${placeholders})
344
+ ) WHERE position = 1;`)
345
+ .all(dialogIds);
346
+ for (const row of rows) {
347
+ result[row.dialogId] = parseMessage(row);
348
+ }
349
+ return result;
350
+ }
351
+ getMessageById(messageId) {
352
+ return parseMessage(this.database
353
+ .prepare('SELECT json FROM messages WHERE messageId = $messageId;')
354
+ .get({ messageId }));
355
+ }
356
+ getMessageByUId(messageUId) {
357
+ return parseMessage(this.database
358
+ .prepare('SELECT json FROM messages WHERE messageUId = $messageUId;')
359
+ .get({ messageUId }));
360
+ }
361
+ getMessageIdRange() {
362
+ const row = this.database
363
+ .prepare('SELECT MIN(messageId) AS oldest, MAX(messageId) AS newest FROM messages;')
364
+ .get();
365
+ if (row?.oldest == null || row.newest == null) {
366
+ return null;
367
+ }
368
+ return { oldest: row.oldest, newest: row.newest };
369
+ }
370
+ getDialogLoadedState(dialogId) {
371
+ return (this.database
372
+ .prepare('SELECT isEnd FROM dialog_states WHERE dialogId = $dialogId;', {
373
+ pluck: true
374
+ })
375
+ .get({ dialogId }) === 1);
376
+ }
377
+ clearConversationCache(dialogId) {
378
+ this.database.transaction(() => {
379
+ this.database.prepare('DELETE FROM messages WHERE dialogId = $dialogId;').run({ dialogId });
380
+ this.setDialogState(dialogId, true);
381
+ })();
382
+ }
383
+ clearAllCache() {
384
+ this.database.transaction(() => {
385
+ this.database.exec('DELETE FROM messages; DELETE FROM dialog_states;');
386
+ })();
387
+ }
388
+ removeMessagesByUId(messageUIds) {
389
+ const remove = this.database.prepare('DELETE FROM messages WHERE messageUId = $messageUId;');
390
+ this.database.transaction(() => {
391
+ for (const messageUId of messageUIds) {
392
+ remove.run({ messageUId });
393
+ }
394
+ })();
395
+ }
396
+ updateMessageReceiptStatus(messageUIds, receivedStatus) {
397
+ const select = this.database.prepare('SELECT json, receivedStatus FROM messages WHERE messageUId = $messageUId;');
398
+ const update = this.database.prepare(`
399
+ UPDATE messages SET receivedStatus = $receivedStatus, json = $json
400
+ WHERE messageUId = $messageUId;
401
+ `);
402
+ this.database.transaction(() => {
403
+ for (const messageUId of messageUIds) {
404
+ const row = select.get({ messageUId });
405
+ if (!row || (row.receivedStatus ?? -1) >= receivedStatus) {
406
+ continue;
407
+ }
408
+ const message = JSON.parse(row.json);
409
+ message.receivedStatus = receivedStatus;
410
+ update.run({ messageUId, receivedStatus, json: JSON.stringify(message) });
411
+ }
412
+ })();
413
+ }
414
+ clearBurnAfterReadingExpiredMessages(dialogId, now = Date.now()) {
415
+ const rows = this.database
416
+ .prepare(`SELECT messageUId FROM messages
417
+ WHERE dialogId = $dialogId
418
+ AND burnAfterReadingFlag = 1
419
+ AND (burnAfterReadingTime IS NULL OR burnAfterReadingTime = 0
420
+ OR sentTime + burnAfterReadingTime <= $now);`, { pluck: true })
421
+ .all({ dialogId, now });
422
+ this.database
423
+ .prepare(`DELETE FROM messages
424
+ WHERE dialogId = $dialogId
425
+ AND burnAfterReadingFlag = 1
426
+ AND (burnAfterReadingTime IS NULL OR burnAfterReadingTime = 0
427
+ OR sentTime + burnAfterReadingTime <= $now);`)
428
+ .run({ dialogId, now });
429
+ return rows;
430
+ }
431
+ convertToRecallMessages(messageUIds, recallMessageType) {
432
+ const select = this.database.prepare('SELECT json FROM messages WHERE messageUId = $messageUId;');
433
+ const update = this.database.prepare(`
434
+ UPDATE messages SET messageType = $messageType, json = $json, searchText = ''
435
+ WHERE messageUId = $messageUId;
436
+ `);
437
+ this.database.transaction(() => {
438
+ for (const messageUId of messageUIds) {
439
+ const row = select.get({ messageUId });
440
+ const message = parseMessage(row);
441
+ if (!message) {
442
+ continue;
443
+ }
444
+ message.messageType = recallMessageType;
445
+ message.content = { messageUId };
446
+ message.isPersited = true;
447
+ message.isCounted = false;
448
+ message.isMentioned = false;
449
+ message.disableNotification = true;
450
+ update.run({
451
+ messageUId,
452
+ messageType: recallMessageType,
453
+ json: JSON.stringify(message)
454
+ });
455
+ }
456
+ })();
457
+ }
458
+ searchTextMessages(dialogId, keyword) {
459
+ return this.searchMessages(keyword, dialogId, Number.MAX_SAFE_INTEGER);
460
+ }
461
+ searchMessages(keyword, dialogId, limit = dialogId === undefined ? 500 : 100) {
462
+ const query = keyword.trim();
463
+ if (!query) {
464
+ return [];
465
+ }
466
+ const safeLimit = Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 0;
467
+ if (safeLimit === 0) {
468
+ return [];
469
+ }
470
+ const dialogClause = dialogId === undefined ? '' : 'AND messages.dialogId = $dialogId';
471
+ const containsHanText = /[\u3400-\u9fff\uf900-\ufaff]/u.test(query);
472
+ const searchClause = containsHanText
473
+ ? 'instr(lower(messages.searchText), lower($plainQuery)) > 0'
474
+ : `messages.messageId IN (
475
+ SELECT rowid FROM messages_fts WHERE messages_fts MATCH $ftsQuery
476
+ )`;
477
+ const ftsQuery = containsHanText
478
+ ? ''
479
+ : query
480
+ .split(/\s+/u)
481
+ .map(token => `"${token.replace(/"/g, '""')}"*`)
482
+ .join(' ');
483
+ return this.database
484
+ .prepare(`SELECT messages.json
485
+ FROM messages
486
+ WHERE ${searchClause}
487
+ ${dialogClause}
488
+ ORDER BY messages.sentTime DESC, messages.messageId DESC
489
+ LIMIT $limit;`)
490
+ .all({
491
+ ftsQuery,
492
+ plainQuery: query,
493
+ dialogId: dialogId ?? '',
494
+ limit: safeLimit
495
+ })
496
+ .map(row => parseMessage(row));
497
+ }
498
+ getLegacyMigrationState() {
499
+ const value = this.database
500
+ .prepare('SELECT value FROM metadata WHERE key = $key;', { pluck: true })
501
+ .get({ key: LEGACY_MIGRATION_KEY });
502
+ if (value === 'in_progress' || value === 'complete') {
503
+ return value;
504
+ }
505
+ return 'pending';
506
+ }
507
+ importLegacyBatch(batch) {
508
+ this.database.transaction(() => {
509
+ this.setMetadata(LEGACY_MIGRATION_KEY, 'in_progress');
510
+ const byDialog = new Map();
511
+ for (const message of batch.messages) {
512
+ const dialogId = message.dialogId;
513
+ if (!dialogId) {
514
+ throw new Error('Legacy message is missing dialogId');
515
+ }
516
+ const group = byDialog.get(dialogId) ?? [];
517
+ group.push(message);
518
+ byDialog.set(dialogId, group);
519
+ }
520
+ for (const [dialogId, messages] of byDialog) {
521
+ this.addMessages(messages, dialogId);
522
+ }
523
+ for (const state of batch.dialogStates) {
524
+ this.setDialogState(state.dialogId, state.isEnd, state.updateTime);
525
+ }
526
+ })();
527
+ }
528
+ completeLegacyMigration() {
529
+ this.setMetadata(LEGACY_MIGRATION_KEY, 'complete');
530
+ }
531
+ setDialogState(dialogId, isEnd, updateTime = Date.now()) {
532
+ this.database
533
+ .prepare(`
534
+ INSERT INTO dialog_states (dialogId, isEnd, updateTime)
535
+ VALUES ($dialogId, $isEnd, $updateTime)
536
+ ON CONFLICT(dialogId) DO UPDATE SET
537
+ isEnd = excluded.isEnd,
538
+ updateTime = excluded.updateTime;
539
+ `)
540
+ .run({ dialogId, isEnd: isEnd ? 1 : 0, updateTime });
541
+ }
542
+ setMetadata(key, value) {
543
+ this.database
544
+ .prepare(`
545
+ INSERT INTO metadata (key, value) VALUES ($key, $value)
546
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value;
547
+ `)
548
+ .run({ key, value });
549
+ }
550
+ }
551
+
552
+ function openDatabase(dbPath, key, isPrimary) {
553
+ if (!/^[0-9a-f]{64}$/i.test(key)) {
554
+ throw new Error('SQLCipher key must contain exactly 64 hexadecimal characters');
555
+ }
556
+ const database = new SQL__default.default(dbPath, { cacheStatements: true });
557
+ try {
558
+ database.pragma(`key = "x'${key}'"`);
559
+ database.pragma('busy_timeout = 5000');
560
+ database.pragma('foreign_keys = ON');
561
+ if (isPrimary) {
562
+ database.pragma('journal_mode = WAL');
563
+ database.pragma('synchronous = FULL');
564
+ runMigrations(database);
565
+ }
566
+ else {
567
+ database.pragma('query_only = ON');
568
+ }
569
+ return database;
570
+ }
571
+ catch (error) {
572
+ database.close();
573
+ throw error;
574
+ }
575
+ }
576
+ class WorkerDispatcher {
577
+ database = null;
578
+ store = null;
579
+ isPrimary = false;
580
+ handle(request) {
581
+ if (request.type === 'init') {
582
+ if (this.database) {
583
+ throw new Error('Database worker is already initialized');
584
+ }
585
+ this.database = openDatabase(request.dbPath, request.key, request.isPrimary);
586
+ this.store = new MessageStore(this.database);
587
+ this.isPrimary = request.isPrimary;
588
+ return undefined;
589
+ }
590
+ if (request.type === 'close') {
591
+ this.database?.close();
592
+ this.database = null;
593
+ this.store = null;
594
+ this.isPrimary = false;
595
+ return undefined;
596
+ }
597
+ if (!this.store) {
598
+ throw new Error('Database worker is not initialized');
599
+ }
600
+ const allowed = request.access === 'read' ? READ_METHODS : WRITE_METHODS;
601
+ if (!allowed.has(request.method)) {
602
+ throw new Error(`${request.method} is not an allowed ${request.access} method`);
603
+ }
604
+ if (request.access === 'write' && !this.isPrimary) {
605
+ throw new Error('Writes are only allowed on the primary database worker');
606
+ }
607
+ const method = this.store[request.method];
608
+ if (typeof method !== 'function') {
609
+ throw new Error(`Database method is not implemented: ${request.method}`);
610
+ }
611
+ return method.apply(this.store, request.args);
612
+ }
613
+ }
614
+
615
+ if (!node_worker_threads.parentPort) {
616
+ throw new Error('Database worker must run inside a worker thread');
617
+ }
618
+ const port = node_worker_threads.parentPort;
619
+ const dispatcher = new WorkerDispatcher();
620
+ port.on('message', ({ sequence, request }) => {
621
+ let response;
622
+ try {
623
+ response = {
624
+ sequence,
625
+ ok: true,
626
+ value: dispatcher.handle(request)
627
+ };
628
+ }
629
+ catch (error) {
630
+ response = {
631
+ sequence,
632
+ ok: false,
633
+ error: serializeDatabaseError(error)
634
+ };
635
+ }
636
+ port.postMessage(response);
637
+ if (request.type === 'close') {
638
+ port.close();
639
+ }
640
+ });