@agentshouse/mdruntime 0.1.0-ce131cb435f8b → 0.1.0-ce9f29a01a271

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.
Files changed (66) hide show
  1. package/dist/adapter.d.ts +12 -0
  2. package/dist/adapter.d.ts.map +1 -1
  3. package/dist/change-set.d.ts +5 -2
  4. package/dist/change-set.d.ts.map +1 -1
  5. package/dist/change-set.js +6 -1
  6. package/dist/chat/list.d.ts +2 -1
  7. package/dist/chat/list.d.ts.map +1 -1
  8. package/dist/chat/list.js +2 -2
  9. package/dist/chat/markdown.d.ts +1 -0
  10. package/dist/chat/markdown.d.ts.map +1 -1
  11. package/dist/chat/markdown.js +78 -20
  12. package/dist/chat/message.d.ts.map +1 -1
  13. package/dist/chat/message.js +9 -2
  14. package/dist/chat/messages.d.ts.map +1 -1
  15. package/dist/chat/messages.js +36 -16
  16. package/dist/chat/overlay.d.ts +1 -0
  17. package/dist/chat/overlay.d.ts.map +1 -1
  18. package/dist/chat/overlay.js +10 -0
  19. package/dist/chat/pieces.d.ts +62 -0
  20. package/dist/chat/pieces.d.ts.map +1 -0
  21. package/dist/chat/pieces.js +1110 -0
  22. package/dist/chat/provider.d.ts +1 -0
  23. package/dist/chat/provider.d.ts.map +1 -1
  24. package/dist/chat/provider.js +12 -3
  25. package/dist/chat/ready.d.ts +1 -1
  26. package/dist/chat/ready.d.ts.map +1 -1
  27. package/dist/chat/ready.js +1 -1
  28. package/dist/chat/steady.d.ts +3 -0
  29. package/dist/chat/steady.d.ts.map +1 -0
  30. package/dist/chat/steady.js +26 -0
  31. package/dist/chat.d.ts +2 -2
  32. package/dist/chat.d.ts.map +1 -1
  33. package/dist/chat.js +2 -2
  34. package/dist/connection.d.ts.map +1 -1
  35. package/dist/connection.js +45 -30
  36. package/dist/conversation.d.ts +5 -0
  37. package/dist/conversation.d.ts.map +1 -1
  38. package/dist/conversation.js +35 -1
  39. package/dist/host.d.ts +1 -1
  40. package/dist/host.d.ts.map +1 -1
  41. package/dist/host.js +1 -1
  42. package/dist/overlay.d.ts +3 -4
  43. package/dist/overlay.d.ts.map +1 -1
  44. package/dist/overlay.js +178 -141
  45. package/dist/read-side.d.ts.map +1 -1
  46. package/dist/read-side.js +4 -2
  47. package/dist/refresh.d.ts.map +1 -1
  48. package/dist/refresh.js +34 -15
  49. package/dist/replica.d.ts +2 -1
  50. package/dist/replica.d.ts.map +1 -1
  51. package/dist/replica.js +18 -15
  52. package/dist/room-draft.d.ts.map +1 -1
  53. package/dist/room-draft.js +8 -9
  54. package/dist/save.d.ts +1 -1
  55. package/dist/save.d.ts.map +1 -1
  56. package/dist/save.js +154 -92
  57. package/dist/shared.d.ts +1 -0
  58. package/dist/shared.d.ts.map +1 -1
  59. package/dist/shared.js +5 -1
  60. package/dist/store.d.ts +2 -0
  61. package/dist/store.d.ts.map +1 -1
  62. package/dist/store.js +387 -339
  63. package/dist/turn.d.ts +1 -1
  64. package/dist/turn.d.ts.map +1 -1
  65. package/dist/turn.js +4 -4
  66. package/package.json +2 -2
package/dist/store.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { sameBinary, sameChange } from './overlay.js';
2
- import { differing } from './shared.js';
2
+ import { differences } from './shared.js';
3
+ import { inUnits, ROW_WEIGHT, stepped } from './turn.js';
3
4
  import { precedes } from './windows.js';
4
5
  const DATABASE = 'mdruntime';
5
6
  const AUTHORED = 'mdruntime-authored';
@@ -62,11 +63,52 @@ function settled(request) {
62
63
  request.onerror = () => reject(request.error);
63
64
  });
64
65
  }
65
- function completed(transaction) {
66
- return new Promise((resolve, reject) => {
67
- transaction.oncomplete = () => resolve();
68
- transaction.onerror = () => reject(transaction.error);
69
- transaction.onabort = () => reject(transaction.error);
66
+ const turns = new Map();
67
+ function idle(database) {
68
+ return turns.get(database.name) ?? Promise.resolve();
69
+ }
70
+ export function transacted(database, stores, mode, work) {
71
+ let release = () => { };
72
+ const turn = new Promise((resolve) => {
73
+ release = resolve;
74
+ });
75
+ const before = idle(database);
76
+ const queued = before.then(() => turn);
77
+ turns.set(database.name, queued);
78
+ void queued.then(() => {
79
+ if (turns.get(database.name) === queued)
80
+ turns.delete(database.name);
81
+ });
82
+ return before.then(() => {
83
+ let transaction;
84
+ try {
85
+ transaction = database.transaction(stores, mode);
86
+ }
87
+ catch (error) {
88
+ release();
89
+ throw error;
90
+ }
91
+ const committed = new Promise((resolve, reject) => {
92
+ transaction.oncomplete = () => {
93
+ release();
94
+ resolve();
95
+ };
96
+ transaction.onabort = () => {
97
+ release();
98
+ reject(transaction.error);
99
+ };
100
+ transaction.onerror = () => reject(transaction.error);
101
+ });
102
+ let value;
103
+ try {
104
+ value = work(transaction);
105
+ }
106
+ catch (error) {
107
+ void committed.catch(() => { });
108
+ transaction.abort();
109
+ throw error;
110
+ }
111
+ return Promise.all([value, committed]).then(([held]) => held);
70
112
  });
71
113
  }
72
114
  const EMPTY_DECLARATIONS = {
@@ -75,28 +117,27 @@ const EMPTY_DECLARATIONS = {
75
117
  capabilities: [],
76
118
  };
77
119
  function declaration(database) {
78
- const transaction = database.transaction(DECLARATIONS, 'readonly');
79
- return settled(transaction.objectStore(DECLARATIONS).get(CURRENT)).then((record) => record ?? EMPTY_DECLARATIONS);
120
+ return transacted(database, DECLARATIONS, 'readonly', (transaction) => settled(transaction.objectStore(DECLARATIONS).get(CURRENT))).then((record) => record ?? EMPTY_DECLARATIONS);
80
121
  }
81
122
  function updateDeclaration(database, update) {
82
- const transaction = database.transaction(DECLARATIONS, 'readwrite');
83
- const store = transaction.objectStore(DECLARATIONS);
84
- const prior = store.get(CURRENT);
85
- prior.onsuccess = () => {
86
- store.put(update(prior.result ?? EMPTY_DECLARATIONS), CURRENT);
87
- };
88
- return completed(transaction);
123
+ return transacted(database, DECLARATIONS, 'readwrite', (transaction) => {
124
+ const store = transaction.objectStore(DECLARATIONS);
125
+ const prior = store.get(CURRENT);
126
+ prior.onsuccess = () => {
127
+ store.put(update(prior.result ?? EMPTY_DECLARATIONS), CURRENT);
128
+ };
129
+ });
89
130
  }
90
131
  function amendPending(database, locator, id, amendment) {
91
- const transaction = database.transaction(PENDING_CHANGES, 'readwrite');
92
- const pending = transaction.objectStore(PENDING_CHANGES);
93
- const request = pending.get([locator, id]);
94
- request.onsuccess = () => {
95
- const record = request.result;
96
- if (record !== undefined)
97
- pending.put({ ...record, ...amendment });
98
- };
99
- return completed(transaction);
132
+ return transacted(database, PENDING_CHANGES, 'readwrite', (transaction) => {
133
+ const pending = transaction.objectStore(PENDING_CHANGES);
134
+ const request = pending.get([locator, id]);
135
+ request.onsuccess = () => {
136
+ const record = request.result;
137
+ if (record !== undefined)
138
+ pending.put({ ...record, ...amendment });
139
+ };
140
+ });
100
141
  }
101
142
  function accepted({ conversation: _held, ...message }) {
102
143
  return message;
@@ -175,33 +216,44 @@ function binaryFileRecord(locator, transfer, layer, path) {
175
216
  binary: layer.binaries[path],
176
217
  };
177
218
  }
178
- function writeLayer(transaction, locator, transfer, next, base) {
219
+ function* changedIn(next, base) {
220
+ if (base === null)
221
+ return new Set([...Object.keys(next.documents), ...Object.keys(next.binaries)]);
222
+ return new Set([
223
+ ...(yield* differences(base.documents, next.documents)),
224
+ ...(yield* differences(base.revisions, next.revisions)),
225
+ ...(yield* differences(base.fileIds, next.fileIds)),
226
+ ...(yield* differences(base.written, next.written)),
227
+ ...(yield* differences(base.changes, next.changes, sameChange)),
228
+ ...(yield* differences(base.binaries, next.binaries, sameBinary)),
229
+ ]);
230
+ }
231
+ function writeLayer(transaction, locator, transfer, layer) {
232
+ const { next, base, paths } = layer;
179
233
  const documents = transaction.objectStore(DOCUMENTS);
180
234
  const files = transaction.objectStore(BINARY_FILES);
181
- const paths = base === null
182
- ? new Set([...Object.keys(next.documents), ...Object.keys(next.binaries)])
183
- : new Set([
184
- ...differing(base.documents, next.documents),
185
- ...differing(base.revisions, next.revisions),
186
- ...differing(base.fileIds, next.fileIds),
187
- ...differing(base.written, next.written),
188
- ...differing(base.changes, next.changes, sameChange),
189
- ...differing(base.binaries, next.binaries, sameBinary),
190
- ]);
191
- for (const path of paths) {
235
+ let last = null;
236
+ return inUnits(paths, (path) => {
192
237
  if (next.documents[path] !== undefined) {
193
- documents.put(documentRecord(locator, transfer, next, path));
238
+ last = documents.put(documentRecord(locator, transfer, next, path));
194
239
  }
195
240
  else if (base?.documents[path] !== undefined) {
196
- documents.delete([locator, transfer, path]);
241
+ last = documents.delete([locator, transfer, path]);
197
242
  }
198
243
  if (next.binaries[path] !== undefined) {
199
- files.put(binaryFileRecord(locator, transfer, next, path));
244
+ last = files.put(binaryFileRecord(locator, transfer, next, path));
200
245
  }
201
246
  else if (base?.binaries[path] !== undefined) {
202
- files.delete([locator, transfer, path]);
247
+ last = files.delete([locator, transfer, path]);
203
248
  }
204
- }
249
+ return ROW_WEIGHT + (next.documents[path]?.length ?? 0);
250
+ }, () => { }, () => {
251
+ const request = last;
252
+ last = null;
253
+ return request === null
254
+ ? Promise.resolve()
255
+ : new Promise((resolve) => request.addEventListener('success', () => resolve()));
256
+ });
205
257
  }
206
258
  function removeRoomEntries(store, locator) {
207
259
  const keys = store.getAllKeys();
@@ -222,10 +274,8 @@ function removeDatabase(name) {
222
274
  export async function authoredHolder() {
223
275
  const authored = await openAuthored();
224
276
  try {
225
- const transaction = authored.transaction(FACTS, 'readonly');
226
- const held = transaction.objectStore(FACTS).get(HOLDER);
227
- await completed(transaction);
228
- return typeof held.result === 'string' ? held.result : null;
277
+ const held = await transacted(authored, FACTS, 'readonly', (transaction) => settled(transaction.objectStore(FACTS).get(HOLDER)));
278
+ return typeof held === 'string' ? held : null;
229
279
  }
230
280
  finally {
231
281
  authored.close();
@@ -234,17 +284,17 @@ export async function authoredHolder() {
234
284
  export async function authoredBy(holder) {
235
285
  const authored = await openAuthored();
236
286
  try {
237
- const transaction = authored.transaction(FACTS, 'readwrite');
238
- const facts = transaction.objectStore(FACTS);
239
- const held = facts.get(HOLDER);
240
287
  let owned = true;
241
- held.onsuccess = () => {
242
- if (held.result === undefined)
243
- facts.put(holder, HOLDER);
244
- else
245
- owned = held.result === holder;
246
- };
247
- await completed(transaction);
288
+ await transacted(authored, FACTS, 'readwrite', (transaction) => {
289
+ const facts = transaction.objectStore(FACTS);
290
+ const held = facts.get(HOLDER);
291
+ held.onsuccess = () => {
292
+ if (held.result === undefined)
293
+ facts.put(holder, HOLDER);
294
+ else
295
+ owned = held.result === holder;
296
+ };
297
+ });
248
298
  return owned;
249
299
  }
250
300
  finally {
@@ -254,10 +304,8 @@ export async function authoredBy(holder) {
254
304
  export async function authoredBinding() {
255
305
  const authored = await openAuthored();
256
306
  try {
257
- const transaction = authored.transaction(FACTS, 'readonly');
258
- const held = transaction.objectStore(FACTS).get(BINDING);
259
- await completed(transaction);
260
- return typeof held.result === 'string' ? held.result : null;
307
+ const held = await transacted(authored, FACTS, 'readonly', (transaction) => settled(transaction.objectStore(FACTS).get(BINDING)));
308
+ return typeof held === 'string' ? held : null;
261
309
  }
262
310
  finally {
263
311
  authored.close();
@@ -266,9 +314,9 @@ export async function authoredBinding() {
266
314
  export async function bindAuthored(binding) {
267
315
  const authored = await openAuthored();
268
316
  try {
269
- const transaction = authored.transaction(FACTS, 'readwrite');
270
- transaction.objectStore(FACTS).put(binding, BINDING);
271
- await completed(transaction);
317
+ await transacted(authored, FACTS, 'readwrite', (transaction) => {
318
+ transaction.objectStore(FACTS).put(binding, BINDING);
319
+ });
272
320
  }
273
321
  finally {
274
322
  authored.close();
@@ -332,89 +380,97 @@ export async function openRuntimeStore(fenced) {
332
380
  throw error;
333
381
  });
334
382
  return {
335
- async all() {
336
- const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES], 'readonly');
337
- const rooms = await settled(transaction.objectStore(ROOMS).getAll());
338
- const records = [];
339
- for (const room of rooms.filter((held) => !fenced(held.locator))) {
340
- records.push({ ...room, ...(await readLayer(transaction, room.locator, room.transfer)) });
341
- }
342
- return records;
383
+ all() {
384
+ return transacted(database, [ROOMS, DOCUMENTS, BINARY_FILES], 'readonly', async (transaction) => {
385
+ const rooms = await settled(transaction.objectStore(ROOMS).getAll());
386
+ const records = [];
387
+ for (const room of rooms.filter((held) => !fenced(held.locator))) {
388
+ records.push({
389
+ ...room,
390
+ ...(await readLayer(transaction, room.locator, room.transfer)),
391
+ });
392
+ }
393
+ return records;
394
+ });
343
395
  },
344
396
  async keepAuthored({ locator, queue, founded, saved, roomDrafts, consumedRoomDrafts }) {
345
397
  if (fenced(locator))
346
398
  return;
347
- const transaction = authored.transaction([QUEUES, PENDING_CHANGES, ROOM_DRAFTS], 'readwrite');
348
- const order = transaction.objectStore(QUEUES);
349
- const pending = transaction.objectStore(PENDING_CHANGES);
350
- const held = order.get(locator);
351
- held.onsuccess = () => {
352
- const before = new Set(held.result?.order ?? []);
353
- const after = new Set(queue.map((batch) => batch.id));
354
- if (queue.length === 0)
355
- order.delete(locator);
356
- else
357
- order.put({ locator, order: [...after] });
358
- for (const id of before) {
359
- if (!after.has(id))
360
- pending.delete([locator, id]);
361
- }
362
- for (const batch of queue) {
363
- if (!before.has(batch.id)) {
364
- pending.put({
365
- locator,
366
- id: batch.id,
367
- batch,
368
- drafts: saved?.get(batch.id) ?? [],
369
- submission: { state: 'waiting' },
370
- lost: false,
371
- });
399
+ await transacted(authored, [QUEUES, PENDING_CHANGES, ROOM_DRAFTS], 'readwrite', (transaction) => {
400
+ const order = transaction.objectStore(QUEUES);
401
+ const pending = transaction.objectStore(PENDING_CHANGES);
402
+ const held = order.get(locator);
403
+ held.onsuccess = () => {
404
+ const before = new Set(held.result?.order ?? []);
405
+ const after = new Set(queue.map((batch) => batch.id));
406
+ if (queue.length === 0)
407
+ order.delete(locator);
408
+ else
409
+ order.put({ locator, order: [...after] });
410
+ for (const id of before) {
411
+ if (!after.has(id))
412
+ pending.delete([locator, id]);
372
413
  }
373
- }
374
- for (const batch of founded ?? []) {
375
- if (!before.has(batch.id))
376
- continue;
377
- const stored = pending.get([locator, batch.id]);
378
- stored.onsuccess = () => {
379
- const record = stored.result;
380
- if (record !== undefined)
381
- pending.put({ ...record, batch });
382
- };
383
- }
384
- };
385
- const draftStore = transaction.objectStore(ROOM_DRAFTS);
386
- for (const path of consumedRoomDrafts ?? [])
387
- draftStore.delete([locator, path]);
388
- for (const record of roomDrafts ?? [])
389
- draftStore.put(record);
390
- await completed(transaction);
414
+ for (const batch of queue) {
415
+ if (!before.has(batch.id)) {
416
+ pending.put({
417
+ locator,
418
+ id: batch.id,
419
+ batch,
420
+ drafts: saved?.get(batch.id) ?? [],
421
+ submission: { state: 'waiting' },
422
+ lost: false,
423
+ });
424
+ }
425
+ }
426
+ for (const batch of founded ?? []) {
427
+ if (!before.has(batch.id))
428
+ continue;
429
+ const stored = pending.get([locator, batch.id]);
430
+ stored.onsuccess = () => {
431
+ const record = stored.result;
432
+ if (record !== undefined)
433
+ pending.put({ ...record, batch });
434
+ };
435
+ }
436
+ };
437
+ const draftStore = transaction.objectStore(ROOM_DRAFTS);
438
+ for (const path of consumedRoomDrafts ?? [])
439
+ draftStore.delete([locator, path]);
440
+ for (const record of roomDrafts ?? [])
441
+ draftStore.put(record);
442
+ });
391
443
  },
392
444
  async write({ next, previous, stored }) {
393
445
  if (next === previous || fenced(next.locator))
394
446
  return;
395
- const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES, TRANSFERS], 'readwrite');
396
- transaction.objectStore(ROOMS).put(roomRecord(next));
397
447
  const moved = previous?.transfer !== next.transfer;
398
- if (moved && previous !== null) {
399
- const range = underTransfer(previous.locator, previous.transfer);
400
- transaction.objectStore(DOCUMENTS).delete(range);
401
- transaction.objectStore(BINARY_FILES).delete(range);
402
- }
403
- if (moved)
404
- transaction.objectStore(TRANSFERS).delete(next.locator);
405
- writeLayer(transaction, next.locator, next.transfer, next, moved ? (stored ?? null) : previous);
406
- await completed(transaction);
448
+ const base = moved ? (stored ?? null) : previous;
449
+ const paths = await stepped(changedIn(next, base));
450
+ if (fenced(next.locator))
451
+ return;
452
+ await transacted(database, [ROOMS, DOCUMENTS, BINARY_FILES, TRANSFERS], 'readwrite', (transaction) => {
453
+ transaction.objectStore(ROOMS).put(roomRecord(next));
454
+ if (moved && previous !== null) {
455
+ const range = underTransfer(previous.locator, previous.transfer);
456
+ transaction.objectStore(DOCUMENTS).delete(range);
457
+ transaction.objectStore(BINARY_FILES).delete(range);
458
+ }
459
+ if (moved)
460
+ transaction.objectStore(TRANSFERS).delete(next.locator);
461
+ return writeLayer(transaction, next.locator, next.transfer, { next, base, paths });
462
+ });
407
463
  },
408
464
  async revokeModels(records) {
409
- const transaction = database.transaction(ROOMS, 'readwrite');
410
- const rooms = transaction.objectStore(ROOMS);
411
- const revoked = { status: 'unavailable', reason: 'revoked' };
412
- for (const record of records) {
413
- if (fenced(record.locator))
414
- continue;
415
- rooms.put(roomRecord({ ...record, model: revoked, selectionModel: revoked }));
416
- }
417
- await completed(transaction);
465
+ await transacted(database, ROOMS, 'readwrite', (transaction) => {
466
+ const rooms = transaction.objectStore(ROOMS);
467
+ const revoked = { status: 'unavailable', reason: 'revoked' };
468
+ for (const record of records) {
469
+ if (fenced(record.locator))
470
+ continue;
471
+ rooms.put(roomRecord({ ...record, model: revoked, selectionModel: revoked }));
472
+ }
473
+ });
418
474
  },
419
475
  async lastCompleted() {
420
476
  return (await declaration(database)).lastCompleted;
@@ -436,31 +492,28 @@ export async function openRuntimeStore(fenced) {
436
492
  }));
437
493
  },
438
494
  async member() {
439
- const transaction = database.transaction(DECLARATIONS, 'readonly');
440
- const held = await settled(transaction.objectStore(DECLARATIONS).get(MEMBER));
495
+ const held = await transacted(database, DECLARATIONS, 'readonly', (transaction) => settled(transaction.objectStore(DECLARATIONS).get(MEMBER)));
441
496
  return held ?? null;
442
497
  },
443
498
  async keepMember(member) {
444
- const transaction = database.transaction(DECLARATIONS, 'readwrite');
445
- transaction.objectStore(DECLARATIONS).put(member, MEMBER);
446
- await completed(transaction);
499
+ await transacted(database, DECLARATIONS, 'readwrite', (transaction) => {
500
+ transaction.objectStore(DECLARATIONS).put(member, MEMBER);
501
+ });
447
502
  },
448
503
  async targets() {
449
- const transaction = database.transaction(DECLARATIONS, 'readonly');
450
- const held = await settled(transaction.objectStore(DECLARATIONS).get(TARGETS));
504
+ const held = await transacted(database, DECLARATIONS, 'readonly', (transaction) => settled(transaction.objectStore(DECLARATIONS).get(TARGETS)));
451
505
  return held ?? {};
452
506
  },
453
507
  async keepTargets(targets) {
454
- const transaction = database.transaction(DECLARATIONS, 'readwrite');
455
- transaction.objectStore(DECLARATIONS).put(targets, TARGETS);
456
- await completed(transaction);
508
+ await transacted(database, DECLARATIONS, 'readwrite', (transaction) => {
509
+ transaction.objectStore(DECLARATIONS).put(targets, TARGETS);
510
+ });
457
511
  },
458
512
  async queues() {
459
- const transaction = authored.transaction([QUEUES, PENDING_CHANGES], 'readonly');
460
- const [queues, pending] = await Promise.all([
513
+ const [queues, pending] = await transacted(authored, [QUEUES, PENDING_CHANGES], 'readonly', (transaction) => Promise.all([
461
514
  settled(transaction.objectStore(QUEUES).getAll()),
462
515
  settled(transaction.objectStore(PENDING_CHANGES).getAll()),
463
- ]);
516
+ ]));
464
517
  const byId = new Map(pending.map((record) => [`${record.locator}\0${record.id}`, record]));
465
518
  return queues
466
519
  .filter((queue) => !fenced(queue.locator))
@@ -488,281 +541,276 @@ export async function openRuntimeStore(fenced) {
488
541
  await amendPending(authored, locator, id, { upload });
489
542
  },
490
543
  async roomDrafts() {
491
- const transaction = authored.transaction(ROOM_DRAFTS, 'readonly');
492
- const held = await settled(transaction.objectStore(ROOM_DRAFTS).getAll());
544
+ const held = await transacted(authored, ROOM_DRAFTS, 'readonly', (transaction) => settled(transaction.objectStore(ROOM_DRAFTS).getAll()));
493
545
  return held.filter((record) => !fenced(record.locator));
494
546
  },
495
- async presentation() {
496
- const transaction = database.transaction(PRESENTATION, 'readonly');
497
- return settled(transaction.objectStore(PRESENTATION).getAll());
547
+ presentation() {
548
+ return transacted(database, PRESENTATION, 'readonly', (transaction) => settled(transaction.objectStore(PRESENTATION).getAll()));
498
549
  },
499
550
  async present(record) {
500
- const transaction = database.transaction(PRESENTATION, 'readwrite');
501
- transaction.objectStore(PRESENTATION).put(record);
502
- await completed(transaction);
503
- },
504
- async conversations() {
505
- const transaction = database.transaction([CONVERSATIONS, MESSAGES], 'readonly');
506
- const records = await settled(transaction.objectStore(CONVERSATIONS).getAll());
507
- const held = new Map(records.map(({ conversation, read, partial }) => [
508
- conversation,
509
- { conversation, read, partial: partial === true },
510
- ]));
511
- const keys = transaction.objectStore(MESSAGES).index('conversation');
512
- await new Promise((resolve, reject) => {
513
- const cursor = keys.openKeyCursor(null, 'nextunique');
514
- cursor.onerror = () => reject(cursor.error);
515
- cursor.onsuccess = () => {
516
- const at = cursor.result;
517
- if (at === null) {
518
- resolve();
519
- return;
520
- }
521
- const conversation = at.key;
522
- if (!held.has(conversation)) {
523
- held.set(conversation, { conversation, read: undefined, partial: true });
524
- }
525
- at.continue();
526
- };
551
+ await transacted(database, PRESENTATION, 'readwrite', (transaction) => {
552
+ transaction.objectStore(PRESENTATION).put(record);
527
553
  });
528
- return [...held.values()];
529
554
  },
530
- async keepMessages({ conversation, messages, read }) {
531
- const transaction = database.transaction([CONVERSATIONS, MESSAGES], 'readwrite');
532
- const stored = transaction.objectStore(MESSAGES);
533
- for (const message of messages) {
534
- stored.put({ ...message, conversation });
535
- }
536
- if (read !== undefined) {
537
- transaction.objectStore(CONVERSATIONS).put({
555
+ conversations() {
556
+ return transacted(database, [CONVERSATIONS, MESSAGES], 'readonly', async (transaction) => {
557
+ const records = await settled(transaction.objectStore(CONVERSATIONS).getAll());
558
+ const held = new Map(records.map(({ conversation, read, partial }) => [
538
559
  conversation,
539
- read: read.at,
540
- ...(read.partial ? { partial: true } : {}),
560
+ { conversation, read, partial: partial === true },
561
+ ]));
562
+ const keys = transaction.objectStore(MESSAGES).index('conversation');
563
+ await new Promise((resolve, reject) => {
564
+ const cursor = keys.openKeyCursor(null, 'nextunique');
565
+ cursor.onerror = () => reject(cursor.error);
566
+ cursor.onsuccess = () => {
567
+ const at = cursor.result;
568
+ if (at === null) {
569
+ resolve();
570
+ return;
571
+ }
572
+ const conversation = at.key;
573
+ if (!held.has(conversation)) {
574
+ held.set(conversation, { conversation, read: undefined, partial: true });
575
+ }
576
+ at.continue();
577
+ };
541
578
  });
542
- }
543
- await completed(transaction);
544
- },
545
- async messageWindow(conversation, limit, before) {
546
- const transaction = database.transaction(MESSAGES, 'readonly');
547
- const index = transaction.objectStore(MESSAGES).index('position');
548
- const range = IDBKeyRange.bound([conversation], [conversation, before?.[0] ?? []]);
549
- const latest = [];
550
- let more = false;
551
- await new Promise((resolve, reject) => {
552
- const cursor = index.openCursor(range, 'prev');
553
- cursor.onerror = () => reject(cursor.error);
554
- cursor.onsuccess = () => {
555
- const at = cursor.result;
556
- if (at === null) {
557
- resolve();
558
- return;
559
- }
560
- const record = at.value;
561
- if (!precedes(record, before)) {
579
+ return [...held.values()];
580
+ });
581
+ },
582
+ async keepMessages({ conversation, messages, read }) {
583
+ await transacted(database, [CONVERSATIONS, MESSAGES], 'readwrite', (transaction) => {
584
+ const stored = transaction.objectStore(MESSAGES);
585
+ for (const message of messages) {
586
+ stored.put({ ...message, conversation });
587
+ }
588
+ if (read !== undefined) {
589
+ transaction.objectStore(CONVERSATIONS).put({
590
+ conversation,
591
+ read: read.at,
592
+ ...(read.partial ? { partial: true } : {}),
593
+ });
594
+ }
595
+ });
596
+ },
597
+ messageWindow(conversation, limit, before) {
598
+ return transacted(database, MESSAGES, 'readonly', async (transaction) => {
599
+ const index = transaction.objectStore(MESSAGES).index('position');
600
+ const range = IDBKeyRange.bound([conversation], [conversation, before?.[0] ?? []]);
601
+ const latest = [];
602
+ let more = false;
603
+ await new Promise((resolve, reject) => {
604
+ const cursor = index.openCursor(range, 'prev');
605
+ cursor.onerror = () => reject(cursor.error);
606
+ cursor.onsuccess = () => {
607
+ const at = cursor.result;
608
+ if (at === null) {
609
+ resolve();
610
+ return;
611
+ }
612
+ const record = at.value;
613
+ if (!precedes(record, before)) {
614
+ at.continue();
615
+ return;
616
+ }
617
+ if (latest.length === limit) {
618
+ more = true;
619
+ resolve();
620
+ return;
621
+ }
622
+ latest.push(accepted(record));
562
623
  at.continue();
563
- return;
564
- }
565
- if (latest.length === limit) {
566
- more = true;
567
- resolve();
568
- return;
569
- }
570
- latest.push(accepted(record));
571
- at.continue();
572
- };
624
+ };
625
+ });
626
+ return { messages: latest.reverse(), more };
573
627
  });
574
- return { messages: latest.reverse(), more };
575
628
  },
576
- async messageCount(conversation) {
577
- const transaction = database.transaction(MESSAGES, 'readonly');
578
- return settled(transaction.objectStore(MESSAGES).index('conversation').count(conversation));
629
+ messageCount(conversation) {
630
+ return transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).index('conversation').count(conversation)));
579
631
  },
580
632
  async messagesAfter(conversation, identity) {
581
- const transaction = database.transaction(MESSAGES, 'readonly');
582
633
  const range = identity === null
583
634
  ? IDBKeyRange.bound([conversation], [conversation, []])
584
635
  : IDBKeyRange.bound([conversation, identity], [conversation, []], true);
585
- const held = await settled(transaction.objectStore(MESSAGES).getAll(range, READ_BATCH));
636
+ const held = await transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).getAll(range, READ_BATCH)));
586
637
  return held.map(accepted);
587
638
  },
588
639
  async messageIdentities(conversation) {
589
- const transaction = database.transaction(MESSAGES, 'readonly');
590
- const keys = await settled(transaction.objectStore(MESSAGES).index('conversation').getAllKeys(conversation));
640
+ const keys = await transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).index('conversation').getAllKeys(conversation)));
591
641
  return keys.map((key) => key[1]);
592
642
  },
593
643
  async turnArrived(conversation, turn) {
594
- const transaction = database.transaction(MESSAGES, 'readonly');
595
- const held = await settled(transaction.objectStore(MESSAGES).index('turn').count([conversation, turn]));
644
+ const held = await transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).index('turn').count([conversation, turn])));
596
645
  return held > 0;
597
646
  },
598
647
  async dropConversations(conversations) {
599
- const drafts = authored.transaction(CHAT_DRAFTS, 'readwrite');
600
- for (const conversation of conversations)
601
- drafts.objectStore(CHAT_DRAFTS).delete(conversation);
602
- await completed(drafts);
603
- const transaction = database.transaction([CONVERSATIONS, MESSAGES], 'readwrite');
604
- const messages = transaction.objectStore(MESSAGES);
605
- for (const conversation of conversations) {
606
- transaction.objectStore(CONVERSATIONS).delete(conversation);
607
- const keys = messages.index('conversation').getAllKeys(conversation);
608
- keys.onsuccess = () => {
609
- for (const key of keys.result)
610
- messages.delete(key);
611
- };
612
- }
613
- await completed(transaction);
648
+ await transacted(authored, CHAT_DRAFTS, 'readwrite', (drafts) => {
649
+ for (const conversation of conversations)
650
+ drafts.objectStore(CHAT_DRAFTS).delete(conversation);
651
+ });
652
+ await transacted(database, [CONVERSATIONS, MESSAGES], 'readwrite', (transaction) => {
653
+ const messages = transaction.objectStore(MESSAGES);
654
+ for (const conversation of conversations) {
655
+ transaction.objectStore(CONVERSATIONS).delete(conversation);
656
+ const keys = messages.index('conversation').getAllKeys(conversation);
657
+ keys.onsuccess = () => {
658
+ for (const key of keys.result)
659
+ messages.delete(key);
660
+ };
661
+ }
662
+ });
614
663
  },
615
664
  async chatDrafts() {
616
- const transaction = authored.transaction(CHAT_DRAFTS, 'readonly');
617
- const held = await settled(transaction.objectStore(CHAT_DRAFTS).getAll());
665
+ const held = await transacted(authored, CHAT_DRAFTS, 'readonly', (transaction) => settled(transaction.objectStore(CHAT_DRAFTS).getAll()));
618
666
  return held.map(chatDraftRecord);
619
667
  },
620
668
  async keepChatDrafts(records) {
621
- const transaction = authored.transaction(CHAT_DRAFTS, 'readwrite');
622
- for (const record of records)
623
- transaction.objectStore(CHAT_DRAFTS).put(record);
624
- await completed(transaction);
669
+ await transacted(authored, CHAT_DRAFTS, 'readwrite', (transaction) => {
670
+ for (const record of records)
671
+ transaction.objectStore(CHAT_DRAFTS).put(record);
672
+ });
625
673
  },
626
674
  async dropChatDraft(conversation) {
627
- const transaction = authored.transaction(CHAT_DRAFTS, 'readwrite');
628
- transaction.objectStore(CHAT_DRAFTS).delete(conversation);
629
- await completed(transaction);
675
+ await transacted(authored, CHAT_DRAFTS, 'readwrite', (transaction) => {
676
+ transaction.objectStore(CHAT_DRAFTS).delete(conversation);
677
+ });
630
678
  },
631
- async calls() {
632
- const transaction = authored.transaction(CALLS, 'readonly');
633
- return settled(transaction.objectStore(CALLS).getAll());
679
+ calls() {
680
+ return transacted(authored, CALLS, 'readonly', (transaction) => settled(transaction.objectStore(CALLS).getAll()));
634
681
  },
635
682
  async keepCall(record) {
636
- const transaction = authored.transaction(CALLS, 'readwrite');
637
- transaction.objectStore(CALLS).put(record);
638
- await completed(transaction);
683
+ await transacted(authored, CALLS, 'readwrite', (transaction) => {
684
+ transaction.objectStore(CALLS).put(record);
685
+ });
639
686
  },
640
687
  async dropCall(operation) {
641
- const transaction = authored.transaction(CALLS, 'readwrite');
642
- transaction.objectStore(CALLS).delete(operation);
643
- await completed(transaction);
688
+ await transacted(authored, CALLS, 'readwrite', (transaction) => {
689
+ transaction.objectStore(CALLS).delete(operation);
690
+ });
644
691
  },
645
692
  async keepRoomDraft(record) {
646
693
  if (fenced(record.locator))
647
694
  return;
648
- const transaction = authored.transaction(ROOM_DRAFTS, 'readwrite');
649
- transaction.objectStore(ROOM_DRAFTS).put(record);
650
- await completed(transaction);
695
+ await transacted(authored, ROOM_DRAFTS, 'readwrite', (transaction) => {
696
+ transaction.objectStore(ROOM_DRAFTS).put(record);
697
+ });
651
698
  },
652
699
  async discardRoomDraft(locator, path) {
653
700
  if (fenced(locator))
654
701
  return;
655
- const transaction = authored.transaction(ROOM_DRAFTS, 'readwrite');
656
- transaction.objectStore(ROOM_DRAFTS).delete([locator, path]);
657
- await completed(transaction);
702
+ await transacted(authored, ROOM_DRAFTS, 'readwrite', (transaction) => {
703
+ transaction.objectStore(ROOM_DRAFTS).delete([locator, path]);
704
+ });
658
705
  },
659
706
  async fences() {
660
- const transaction = authored.transaction(FENCES, 'readonly');
661
- const held = await settled(transaction.objectStore(FENCES).getAll());
707
+ const held = await transacted(authored, FENCES, 'readonly', (transaction) => settled(transaction.objectStore(FENCES).getAll()));
662
708
  return held.map((fence) => fence.locator);
663
709
  },
664
710
  async erase(locators) {
665
- const work = authored.transaction([FENCES, QUEUES, PENDING_CHANGES, ROOM_DRAFTS], 'readwrite');
666
- for (const locator of locators) {
667
- work.objectStore(FENCES).put({ locator });
668
- work.objectStore(QUEUES).delete(locator);
669
- for (const name of [PENDING_CHANGES, ROOM_DRAFTS]) {
670
- removeRoomEntries(work.objectStore(name), locator);
711
+ await transacted(authored, [FENCES, QUEUES, PENDING_CHANGES, ROOM_DRAFTS], 'readwrite', (work) => {
712
+ for (const locator of locators) {
713
+ work.objectStore(FENCES).put({ locator });
714
+ work.objectStore(QUEUES).delete(locator);
715
+ for (const name of [PENDING_CHANGES, ROOM_DRAFTS]) {
716
+ removeRoomEntries(work.objectStore(name), locator);
717
+ }
671
718
  }
672
- }
673
- await completed(work);
674
- const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES, TRANSFERS], 'readwrite');
675
- for (const locator of locators) {
676
- transaction.objectStore(ROOMS).delete(locator);
677
- transaction.objectStore(TRANSFERS).delete(locator);
678
- transaction.objectStore(DOCUMENTS).delete(underRoom(locator));
679
- transaction.objectStore(BINARY_FILES).delete(underRoom(locator));
680
- }
681
- await completed(transaction);
682
- const fences = authored.transaction(FENCES, 'readwrite');
683
- for (const locator of locators)
684
- fences.objectStore(FENCES).delete(locator);
685
- await completed(fences);
719
+ });
720
+ await transacted(database, [ROOMS, DOCUMENTS, BINARY_FILES, TRANSFERS], 'readwrite', (transaction) => {
721
+ for (const locator of locators) {
722
+ transaction.objectStore(ROOMS).delete(locator);
723
+ transaction.objectStore(TRANSFERS).delete(locator);
724
+ transaction.objectStore(DOCUMENTS).delete(underRoom(locator));
725
+ transaction.objectStore(BINARY_FILES).delete(underRoom(locator));
726
+ }
727
+ });
728
+ await transacted(authored, FENCES, 'readwrite', (fences) => {
729
+ for (const locator of locators)
730
+ fences.objectStore(FENCES).delete(locator);
731
+ });
686
732
  },
687
733
  async discard(locator) {
688
- const transaction = authored.transaction([QUEUES, PENDING_CHANGES], 'readwrite');
689
- transaction.objectStore(QUEUES).delete(locator);
690
- removeRoomEntries(transaction.objectStore(PENDING_CHANGES), locator);
691
- await completed(transaction);
734
+ await transacted(authored, [QUEUES, PENDING_CHANGES], 'readwrite', (transaction) => {
735
+ transaction.objectStore(QUEUES).delete(locator);
736
+ removeRoomEntries(transaction.objectStore(PENDING_CHANGES), locator);
737
+ });
692
738
  },
693
739
  async transfers() {
694
- const transaction = database.transaction(TRANSFERS, 'readonly');
695
- const held = await settled(transaction.objectStore(TRANSFERS).getAll());
740
+ const held = await transacted(database, TRANSFERS, 'readonly', (transaction) => settled(transaction.objectStore(TRANSFERS).getAll()));
696
741
  return held.filter((record) => !fenced(record.locator));
697
742
  },
698
743
  staged(record) {
699
- const transaction = database.transaction([DOCUMENTS, BINARY_FILES], 'readonly');
700
- return readLayer(transaction, record.locator, record.transfer);
744
+ return transacted(database, [DOCUMENTS, BINARY_FILES], 'readonly', (transaction) => readLayer(transaction, record.locator, record.transfer));
701
745
  },
702
746
  async stage(record, page) {
703
747
  if (fenced(record.locator))
704
748
  return;
705
- const transaction = database.transaction([TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite');
706
- writeLayer(transaction, record.locator, record.transfer, page, null);
707
- transaction.objectStore(TRANSFERS).put(record);
708
- await completed(transaction);
749
+ const paths = await stepped(changedIn(page, null));
750
+ if (fenced(record.locator))
751
+ return;
752
+ await transacted(database, [TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite', (transaction) => {
753
+ transaction.objectStore(TRANSFERS).put(record);
754
+ return writeLayer(transaction, record.locator, record.transfer, {
755
+ next: page,
756
+ base: null,
757
+ paths,
758
+ });
759
+ });
709
760
  },
710
761
  async abandon(locator) {
711
762
  if (fenced(locator))
712
763
  return;
713
- const transaction = database.transaction([TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite');
714
- const transfers = transaction.objectStore(TRANSFERS);
715
- const held = transfers.get(locator);
716
- held.onsuccess = () => {
717
- const record = held.result;
718
- if (record === undefined)
719
- return;
720
- transfers.delete(locator);
721
- const range = underTransfer(locator, record.transfer);
722
- transaction.objectStore(DOCUMENTS).delete(range);
723
- transaction.objectStore(BINARY_FILES).delete(range);
724
- };
725
- await completed(transaction);
764
+ await transacted(database, [TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite', (transaction) => {
765
+ const transfers = transaction.objectStore(TRANSFERS);
766
+ const held = transfers.get(locator);
767
+ held.onsuccess = () => {
768
+ const record = held.result;
769
+ if (record === undefined)
770
+ return;
771
+ transfers.delete(locator);
772
+ const range = underTransfer(locator, record.transfer);
773
+ transaction.objectStore(DOCUMENTS).delete(range);
774
+ transaction.objectStore(BINARY_FILES).delete(range);
775
+ };
776
+ });
726
777
  },
727
- async receivedHeads() {
728
- const transaction = database.transaction(RECEIVED, 'readonly');
729
- return settled(transaction.objectStore(RECEIVED).getAll());
778
+ receivedHeads() {
779
+ return transacted(database, RECEIVED, 'readonly', (transaction) => settled(transaction.objectStore(RECEIVED).getAll()));
730
780
  },
731
781
  async receivedParts(key, count) {
732
- const transaction = database.transaction(RECEIVED_PARTS, 'readonly');
733
- const kept = await settled(transaction
782
+ const kept = await transacted(database, RECEIVED_PARTS, 'readonly', (transaction) => settled(transaction
734
783
  .objectStore(RECEIVED_PARTS)
735
- .getAll(IDBKeyRange.bound([key, 0], [key, count], false, true)));
784
+ .getAll(IDBKeyRange.bound([key, 0], [key, count], false, true))));
736
785
  return kept.map((part) => part.value);
737
786
  },
738
787
  async keepReceived(key, head, part) {
739
- const transaction = database.transaction([RECEIVED, RECEIVED_PARTS], 'readwrite');
740
- transaction.objectStore(RECEIVED).put({ key, head });
741
- if (part !== undefined)
742
- transaction.objectStore(RECEIVED_PARTS).put({ key, ...part });
743
- await completed(transaction);
788
+ await transacted(database, [RECEIVED, RECEIVED_PARTS], 'readwrite', (transaction) => {
789
+ transaction.objectStore(RECEIVED).put({ key, head });
790
+ if (part !== undefined)
791
+ transaction.objectStore(RECEIVED_PARTS).put({ key, ...part });
792
+ });
744
793
  },
745
794
  async dropReceived(keys) {
746
- const transaction = database.transaction([RECEIVED, RECEIVED_PARTS], 'readwrite');
747
- for (const key of keys) {
748
- transaction.objectStore(RECEIVED).delete(key);
749
- transaction.objectStore(RECEIVED_PARTS).delete(IDBKeyRange.bound([key], [key, []]));
750
- }
751
- await completed(transaction);
795
+ await transacted(database, [RECEIVED, RECEIVED_PARTS], 'readwrite', (transaction) => {
796
+ for (const key of keys) {
797
+ transaction.objectStore(RECEIVED).delete(key);
798
+ transaction.objectStore(RECEIVED_PARTS).delete(IDBKeyRange.bound([key], [key, []]));
799
+ }
800
+ });
752
801
  },
753
802
  async generation() {
754
- const transaction = authored.transaction(FACTS, 'readonly');
755
- const held = await settled(transaction.objectStore(FACTS).get(GENERATION));
803
+ const held = await transacted(authored, FACTS, 'readonly', (transaction) => settled(transaction.objectStore(FACTS).get(GENERATION)));
756
804
  return held ?? null;
757
805
  },
758
806
  async rememberGeneration(value) {
759
- const transaction = authored.transaction(FACTS, 'readwrite');
760
- transaction.objectStore(FACTS).put(value, GENERATION);
761
- await completed(transaction);
807
+ await transacted(authored, FACTS, 'readwrite', (transaction) => {
808
+ transaction.objectStore(FACTS).put(value, GENERATION);
809
+ });
762
810
  },
763
811
  close() {
764
- authored.close();
765
- database.close();
812
+ for (const held of [authored, database])
813
+ void idle(held).then(() => held.close());
766
814
  },
767
815
  };
768
816
  }