@agentshouse/mdruntime 0.1.0-c97456d63baba → 0.1.0-c9b1e59e5baeb

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