@agentshouse/mdruntime 0.1.0-ca9afacd5aebd → 0.1.0-caee7332d2235

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 (155) hide show
  1. package/AGENTS.md +271 -290
  2. package/README.md +8 -8
  3. package/dist/adapter.d.ts +52 -10
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/attempt.d.ts.map +1 -1
  6. package/dist/attempt.js +14 -1
  7. package/dist/bootstrap.d.ts +3 -2
  8. package/dist/bootstrap.d.ts.map +1 -1
  9. package/dist/bootstrap.js +33 -7
  10. package/dist/change-set.d.ts +7 -2
  11. package/dist/change-set.d.ts.map +1 -1
  12. package/dist/change-set.js +41 -10
  13. package/dist/chat/acts.d.ts +3 -4
  14. package/dist/chat/acts.d.ts.map +1 -1
  15. package/dist/chat/acts.js +10 -10
  16. package/dist/chat/earlier.d.ts +10 -0
  17. package/dist/chat/earlier.d.ts.map +1 -0
  18. package/dist/chat/earlier.js +54 -0
  19. package/dist/chat/kit.d.ts +2 -1
  20. package/dist/chat/kit.d.ts.map +1 -1
  21. package/dist/chat/kit.js +3 -3
  22. package/dist/chat/list.d.ts +8 -1
  23. package/dist/chat/list.d.ts.map +1 -1
  24. package/dist/chat/list.js +48 -8
  25. package/dist/chat/markdown.d.ts +3 -1
  26. package/dist/chat/markdown.d.ts.map +1 -1
  27. package/dist/chat/markdown.js +85 -26
  28. package/dist/chat/message.d.ts.map +1 -1
  29. package/dist/chat/message.js +3 -3
  30. package/dist/chat/messages.d.ts +1 -0
  31. package/dist/chat/messages.d.ts.map +1 -1
  32. package/dist/chat/messages.js +35 -15
  33. package/dist/chat/pieces.d.ts +62 -0
  34. package/dist/chat/pieces.d.ts.map +1 -0
  35. package/dist/chat/pieces.js +1110 -0
  36. package/dist/chat/provider.d.ts +1 -1
  37. package/dist/chat/provider.d.ts.map +1 -1
  38. package/dist/chat/provider.js +65 -80
  39. package/dist/chat/question.d.ts +1 -1
  40. package/dist/chat/question.d.ts.map +1 -1
  41. package/dist/chat/question.js +7 -18
  42. package/dist/chat/ready.d.ts +1 -0
  43. package/dist/chat/ready.d.ts.map +1 -1
  44. package/dist/chat/ready.js +4 -4
  45. package/dist/chat/steady.d.ts +3 -0
  46. package/dist/chat/steady.d.ts.map +1 -0
  47. package/dist/chat/steady.js +26 -0
  48. package/dist/chat/style.js +4 -4
  49. package/dist/chat.d.ts +1 -1
  50. package/dist/chat.d.ts.map +1 -1
  51. package/dist/chat.js +1 -1
  52. package/dist/client.d.ts +17 -9
  53. package/dist/client.d.ts.map +1 -1
  54. package/dist/client.js +19 -6
  55. package/dist/collection.d.ts +20 -5
  56. package/dist/collection.d.ts.map +1 -1
  57. package/dist/collection.js +309 -33
  58. package/dist/connection.d.ts +31 -9
  59. package/dist/connection.d.ts.map +1 -1
  60. package/dist/connection.js +1602 -386
  61. package/dist/conversation.d.ts +20 -7
  62. package/dist/conversation.d.ts.map +1 -1
  63. package/dist/conversation.js +474 -163
  64. package/dist/durability.d.ts +7 -1
  65. package/dist/durability.d.ts.map +1 -1
  66. package/dist/entry.d.ts +17 -0
  67. package/dist/entry.d.ts.map +1 -0
  68. package/dist/entry.js +157 -0
  69. package/dist/fence.d.ts +6 -0
  70. package/dist/fence.d.ts.map +1 -0
  71. package/dist/fence.js +10 -0
  72. package/dist/host.d.ts +15 -3
  73. package/dist/host.d.ts.map +1 -1
  74. package/dist/host.js +94 -12
  75. package/dist/identity.d.ts +2 -0
  76. package/dist/identity.d.ts.map +1 -1
  77. package/dist/identity.js +6 -0
  78. package/dist/index.d.ts +10 -7
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +2 -0
  81. package/dist/memory.d.ts +8 -2
  82. package/dist/memory.d.ts.map +1 -1
  83. package/dist/memory.js +33 -6
  84. package/dist/model.d.ts +4 -2
  85. package/dist/model.d.ts.map +1 -1
  86. package/dist/model.js +10 -1
  87. package/dist/observation.d.ts +7 -2
  88. package/dist/observation.d.ts.map +1 -1
  89. package/dist/observation.js +29 -12
  90. package/dist/outcome.d.ts +12 -2
  91. package/dist/outcome.d.ts.map +1 -1
  92. package/dist/outcome.js +4 -0
  93. package/dist/overlay.d.ts +5 -3
  94. package/dist/overlay.d.ts.map +1 -1
  95. package/dist/overlay.js +203 -159
  96. package/dist/query.d.ts +56 -4
  97. package/dist/query.d.ts.map +1 -1
  98. package/dist/query.js +36 -7
  99. package/dist/reach.d.ts +6 -3
  100. package/dist/reach.d.ts.map +1 -1
  101. package/dist/reach.js +30 -5
  102. package/dist/react.d.ts +8 -6
  103. package/dist/react.d.ts.map +1 -1
  104. package/dist/react.js +7 -17
  105. package/dist/read-side.d.ts +16 -7
  106. package/dist/read-side.d.ts.map +1 -1
  107. package/dist/read-side.js +486 -100
  108. package/dist/readiness.d.ts +53 -0
  109. package/dist/readiness.d.ts.map +1 -0
  110. package/dist/readiness.js +33 -0
  111. package/dist/reads.d.ts +11 -0
  112. package/dist/reads.d.ts.map +1 -0
  113. package/dist/reads.js +98 -0
  114. package/dist/recovery.d.ts +25 -0
  115. package/dist/recovery.d.ts.map +1 -0
  116. package/dist/recovery.js +197 -0
  117. package/dist/refresh.d.ts +4 -4
  118. package/dist/refresh.d.ts.map +1 -1
  119. package/dist/refresh.js +44 -18
  120. package/dist/replica.d.ts +11 -5
  121. package/dist/replica.d.ts.map +1 -1
  122. package/dist/replica.js +111 -56
  123. package/dist/room-draft.d.ts +13 -1
  124. package/dist/room-draft.d.ts.map +1 -1
  125. package/dist/room-draft.js +11 -12
  126. package/dist/rows.d.ts +6 -13
  127. package/dist/rows.d.ts.map +1 -1
  128. package/dist/rows.js +10 -13
  129. package/dist/save.d.ts +10 -4
  130. package/dist/save.d.ts.map +1 -1
  131. package/dist/save.js +216 -108
  132. package/dist/search.d.ts +49 -6
  133. package/dist/search.d.ts.map +1 -1
  134. package/dist/search.js +192 -47
  135. package/dist/seed.d.ts +16 -0
  136. package/dist/seed.d.ts.map +1 -0
  137. package/dist/seed.js +75 -0
  138. package/dist/shared.d.ts +15 -0
  139. package/dist/shared.d.ts.map +1 -0
  140. package/dist/shared.js +398 -0
  141. package/dist/status.d.ts +4 -0
  142. package/dist/status.d.ts.map +1 -1
  143. package/dist/store.d.ts +61 -12
  144. package/dist/store.d.ts.map +1 -1
  145. package/dist/store.js +580 -268
  146. package/dist/turn.d.ts +9 -0
  147. package/dist/turn.d.ts.map +1 -0
  148. package/dist/turn.js +64 -0
  149. package/dist/windows.d.ts +26 -0
  150. package/dist/windows.d.ts.map +1 -0
  151. package/dist/windows.js +93 -0
  152. package/dist/wire.d.ts +152 -32
  153. package/dist/wire.d.ts.map +1 -1
  154. package/dist/wire.js +52 -31
  155. package/package.json +12 -8
package/dist/store.js CHANGED
@@ -1,10 +1,16 @@
1
1
  import { sameBinary, sameChange } from './overlay.js';
2
+ import { differences } from './shared.js';
3
+ import { inUnits, ROW_WEIGHT, stepped } from './turn.js';
4
+ import { precedes } from './windows.js';
2
5
  const DATABASE = 'mdruntime';
3
6
  const AUTHORED = 'mdruntime-authored';
7
+ const FENCES = 'fences';
4
8
  const ROOMS = 'rooms';
5
9
  const DOCUMENTS = 'documents';
6
10
  const BINARY_FILES = 'binaryFiles';
7
11
  const TRANSFERS = 'transfers';
12
+ const RECEIVED = 'received';
13
+ const RECEIVED_PARTS = 'receivedParts';
8
14
  const PENDING_CHANGES = 'pendingChanges';
9
15
  const DECLARATIONS = 'declarations';
10
16
  const ROOM_DRAFTS = 'roomDrafts';
@@ -12,32 +18,97 @@ const PRESENTATION = 'presentation';
12
18
  const CONVERSATIONS = 'conversations';
13
19
  const MESSAGES = 'messages';
14
20
  const CHAT_DRAFTS = 'chatDrafts';
21
+ const CALLS = 'calls';
15
22
  const QUEUES = 'queues';
16
23
  const FACTS = 'facts';
17
24
  const CURRENT = 'current';
18
25
  const MEMBER = 'member';
26
+ const TARGETS = 'targets';
19
27
  const GENERATION = 'generation';
20
- export const STORE_SCHEMA = 23;
21
- export const AUTHORED_SCHEMA = 1;
28
+ const HOLDER = 'holder';
29
+ const BINDING = 'binding';
30
+ export const STORE_SCHEMA = 25;
31
+ const REPLICA_SHAPE = 23;
32
+ export const AUTHORED_SCHEMA = 3;
22
33
  const AUTHORED_STORES = [
23
34
  [QUEUES, { keyPath: 'locator' }],
24
35
  [PENDING_CHANGES, { keyPath: ['locator', 'id'] }],
25
36
  [ROOM_DRAFTS, { keyPath: ['locator', 'path'] }],
26
37
  [CHAT_DRAFTS, { keyPath: 'conversation' }],
38
+ [CALLS, { keyPath: 'operation' }],
27
39
  [FACTS, undefined],
40
+ [FENCES, { keyPath: 'locator' }],
41
+ ];
42
+ const REPLICA_STORES = [
43
+ [ROOMS, { keyPath: 'locator' }],
44
+ [DOCUMENTS, { keyPath: ['locator', 'transfer', 'path'] }],
45
+ [BINARY_FILES, { keyPath: ['locator', 'transfer', 'path'] }],
46
+ [TRANSFERS, { keyPath: 'locator' }],
47
+ [RECEIVED, { keyPath: 'key' }],
48
+ [RECEIVED_PARTS, { keyPath: ['key', 'index'] }],
49
+ [DECLARATIONS, undefined],
50
+ [PRESENTATION, { keyPath: 'app' }],
51
+ [CONVERSATIONS, { keyPath: 'conversation' }],
52
+ [MESSAGES, { keyPath: ['conversation', 'identity'] }],
28
53
  ];
29
54
  const READ_BATCH = 256;
55
+ const MESSAGE_INDEXES = [
56
+ ['conversation', 'conversation'],
57
+ ['position', ['conversation', 'position']],
58
+ ['turn', ['conversation', 'turn']],
59
+ ];
30
60
  function settled(request) {
31
61
  return new Promise((resolve, reject) => {
32
62
  request.onsuccess = () => resolve(request.result);
33
63
  request.onerror = () => reject(request.error);
34
64
  });
35
65
  }
36
- function completed(transaction) {
37
- return new Promise((resolve, reject) => {
38
- transaction.oncomplete = () => resolve();
39
- transaction.onerror = () => reject(transaction.error);
40
- 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);
41
112
  });
42
113
  }
43
114
  const EMPTY_DECLARATIONS = {
@@ -46,31 +117,33 @@ const EMPTY_DECLARATIONS = {
46
117
  capabilities: [],
47
118
  };
48
119
  function declaration(database) {
49
- const transaction = database.transaction(DECLARATIONS, 'readonly');
50
- 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);
51
121
  }
52
122
  function updateDeclaration(database, update) {
53
- const transaction = database.transaction(DECLARATIONS, 'readwrite');
54
- const store = transaction.objectStore(DECLARATIONS);
55
- const prior = store.get(CURRENT);
56
- prior.onsuccess = () => {
57
- store.put(update(prior.result ?? EMPTY_DECLARATIONS), CURRENT);
58
- };
59
- 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
+ });
60
130
  }
61
131
  function amendPending(database, locator, id, amendment) {
62
- const transaction = database.transaction(PENDING_CHANGES, 'readwrite');
63
- const pending = transaction.objectStore(PENDING_CHANGES);
64
- const request = pending.get([locator, id]);
65
- request.onsuccess = () => {
66
- const record = request.result;
67
- if (record !== undefined)
68
- pending.put({ ...record, ...amendment });
69
- };
70
- 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
+ });
141
+ }
142
+ function accepted({ conversation: _held, ...message }) {
143
+ return message;
71
144
  }
72
145
  function roomRecord(record) {
73
- const { documents, binaries, revisions, changes, written, ...room } = record;
146
+ const { documents, binaries, revisions, fileIds, changes, written, ...room } = record;
74
147
  return room;
75
148
  }
76
149
  function chatDraftRecord(record) {
@@ -96,13 +169,15 @@ async function readTransfer(store, locator, transfer, receive) {
96
169
  }
97
170
  }
98
171
  function emptyLayer() {
99
- return { documents: {}, binaries: {}, revisions: {}, changes: {}, written: {} };
172
+ return { documents: {}, binaries: {}, revisions: {}, fileIds: {}, changes: {}, written: {} };
100
173
  }
101
174
  async function readLayer(transaction, locator, transfer) {
102
175
  const layer = emptyLayer();
103
176
  const head = (held) => {
104
177
  if (held.revision !== null)
105
178
  layer.revisions[held.path] = held.revision;
179
+ if (held.fileId !== null)
180
+ layer.fileIds[held.path] = held.fileId;
106
181
  if (held.change !== null)
107
182
  layer.changes[held.path] = held.change;
108
183
  };
@@ -125,6 +200,7 @@ function documentRecord(locator, transfer, layer, path) {
125
200
  path,
126
201
  content: layer.documents[path],
127
202
  revision: layer.revisions[path] ?? null,
203
+ fileId: layer.fileIds[path] ?? null,
128
204
  change: layer.changes[path] ?? null,
129
205
  written: layer.written[path] ?? null,
130
206
  };
@@ -135,36 +211,49 @@ function binaryFileRecord(locator, transfer, layer, path) {
135
211
  transfer,
136
212
  path,
137
213
  revision: layer.revisions[path] ?? null,
214
+ fileId: layer.fileIds[path] ?? null,
138
215
  change: layer.changes[path] ?? null,
139
216
  binary: layer.binaries[path],
140
217
  };
141
218
  }
142
- 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;
143
233
  const documents = transaction.objectStore(DOCUMENTS);
144
- for (const path of Object.keys(base?.documents ?? {})) {
145
- if (next.documents[path] === undefined)
146
- documents.delete([locator, transfer, path]);
147
- }
148
- for (const [path, content] of Object.entries(next.documents)) {
149
- if (base?.documents[path] === content &&
150
- base.revisions[path] === next.revisions[path] &&
151
- base.written[path] === next.written[path] &&
152
- sameChange(base.changes[path], next.changes[path]))
153
- continue;
154
- documents.put(documentRecord(locator, transfer, next, path));
155
- }
156
234
  const files = transaction.objectStore(BINARY_FILES);
157
- for (const path of Object.keys(base?.binaries ?? {})) {
158
- if (next.binaries[path] === undefined)
159
- files.delete([locator, transfer, path]);
160
- }
161
- for (const path of Object.keys(next.binaries)) {
162
- if (!sameBinary(base?.binaries[path], next.binaries[path]) ||
163
- base?.revisions[path] !== next.revisions[path] ||
164
- !sameChange(base?.changes[path], next.changes[path])) {
165
- files.put(binaryFileRecord(locator, transfer, next, path));
235
+ let last = null;
236
+ return inUnits(paths, (path) => {
237
+ if (next.documents[path] !== undefined) {
238
+ last = documents.put(documentRecord(locator, transfer, next, path));
166
239
  }
167
- }
240
+ else if (base?.documents[path] !== undefined) {
241
+ last = documents.delete([locator, transfer, path]);
242
+ }
243
+ if (next.binaries[path] !== undefined) {
244
+ last = files.put(binaryFileRecord(locator, transfer, next, path));
245
+ }
246
+ else if (base?.binaries[path] !== undefined) {
247
+ last = files.delete([locator, transfer, path]);
248
+ }
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
+ });
168
257
  }
169
258
  function removeRoomEntries(store, locator) {
170
259
  const keys = store.getAllKeys();
@@ -182,116 +271,206 @@ function removeDatabase(name) {
182
271
  removal.onerror = () => reject(removal.error);
183
272
  });
184
273
  }
185
- export async function removeRuntimeStore() {
186
- await Promise.all([removeDatabase(DATABASE), removeDatabase(AUTHORED)]);
274
+ export async function authoredHolder() {
275
+ const authored = await openAuthored();
276
+ try {
277
+ const held = await transacted(authored, FACTS, 'readonly', (transaction) => settled(transaction.objectStore(FACTS).get(HOLDER)));
278
+ return typeof held === 'string' ? held : null;
279
+ }
280
+ finally {
281
+ authored.close();
282
+ }
283
+ }
284
+ export async function authoredBy(holder) {
285
+ const authored = await openAuthored();
286
+ try {
287
+ let owned = true;
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
+ });
298
+ return owned;
299
+ }
300
+ finally {
301
+ authored.close();
302
+ }
303
+ }
304
+ export async function authoredBinding() {
305
+ const authored = await openAuthored();
306
+ try {
307
+ const held = await transacted(authored, FACTS, 'readonly', (transaction) => settled(transaction.objectStore(FACTS).get(BINDING)));
308
+ return typeof held === 'string' ? held : null;
309
+ }
310
+ finally {
311
+ authored.close();
312
+ }
313
+ }
314
+ export async function bindAuthored(binding) {
315
+ const authored = await openAuthored();
316
+ try {
317
+ await transacted(authored, FACTS, 'readwrite', (transaction) => {
318
+ transaction.objectStore(FACTS).put(binding, BINDING);
319
+ });
320
+ }
321
+ finally {
322
+ authored.close();
323
+ }
324
+ }
325
+ export async function removeRuntimeStore(holder) {
326
+ await removeDatabase(DATABASE);
327
+ if (holder !== undefined && (await authoredBy(holder)))
328
+ return true;
329
+ await removeDatabase(AUTHORED);
330
+ return false;
331
+ }
332
+ export function openDatabase(name, version, upgrade) {
333
+ return new Promise((resolve, reject) => {
334
+ const opening = indexedDB.open(name, version);
335
+ opening.onupgradeneeded = (event) => upgrade(opening, event.oldVersion);
336
+ opening.onsuccess = () => resolve(opening.result);
337
+ opening.onerror = () => {
338
+ if (opening.error?.name !== 'VersionError') {
339
+ reject(opening.error);
340
+ return;
341
+ }
342
+ removeDatabase(name)
343
+ .then(() => openDatabase(name, version, upgrade))
344
+ .then(resolve, reject);
345
+ };
346
+ });
187
347
  }
188
348
  function openReplica() {
189
- const opening = indexedDB.open(DATABASE, STORE_SCHEMA);
190
- opening.onupgradeneeded = () => {
349
+ return openDatabase(DATABASE, STORE_SCHEMA, (opening, oldVersion) => {
191
350
  const database = opening.result;
192
- for (const name of [...database.objectStoreNames])
193
- database.deleteObjectStore(name);
194
- database.createObjectStore(ROOMS, { keyPath: 'locator' });
195
- database.createObjectStore(DOCUMENTS, { keyPath: ['locator', 'transfer', 'path'] });
196
- database.createObjectStore(BINARY_FILES, { keyPath: ['locator', 'transfer', 'path'] });
197
- database.createObjectStore(TRANSFERS, { keyPath: 'locator' });
198
- database.createObjectStore(DECLARATIONS);
199
- database.createObjectStore(PRESENTATION, { keyPath: 'app' });
200
- database.createObjectStore(CONVERSATIONS, { keyPath: 'conversation' });
201
- const messages = database.createObjectStore(MESSAGES, {
202
- keyPath: ['conversation', 'identity'],
203
- });
204
- messages.createIndex('conversation', 'conversation');
205
- };
206
- return settled(opening);
351
+ if (oldVersion < REPLICA_SHAPE) {
352
+ for (const name of [...database.objectStoreNames])
353
+ database.deleteObjectStore(name);
354
+ }
355
+ for (const [name, parameters] of REPLICA_STORES) {
356
+ if (database.objectStoreNames.contains(name))
357
+ continue;
358
+ database.createObjectStore(name, parameters);
359
+ }
360
+ const messages = opening.transaction.objectStore(MESSAGES);
361
+ for (const [name, keyPath] of MESSAGE_INDEXES) {
362
+ if (!messages.indexNames.contains(name))
363
+ messages.createIndex(name, keyPath);
364
+ }
365
+ });
207
366
  }
208
367
  function openAuthored() {
209
- const opening = indexedDB.open(AUTHORED, AUTHORED_SCHEMA);
210
- opening.onupgradeneeded = () => {
368
+ return openDatabase(AUTHORED, AUTHORED_SCHEMA, (opening) => {
211
369
  const database = opening.result;
212
370
  for (const [name, parameters] of AUTHORED_STORES) {
213
371
  if (!database.objectStoreNames.contains(name))
214
372
  database.createObjectStore(name, parameters);
215
373
  }
216
- };
217
- return settled(opening);
374
+ });
218
375
  }
219
- export async function openRuntimeStore() {
376
+ export async function openRuntimeStore(fenced) {
220
377
  const authored = await openAuthored();
221
378
  const database = await openReplica().catch((error) => {
222
379
  authored.close();
223
380
  throw error;
224
381
  });
225
382
  return {
226
- async all() {
227
- const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES], 'readonly');
228
- const rooms = await settled(transaction.objectStore(ROOMS).getAll());
229
- const records = [];
230
- for (const room of rooms) {
231
- records.push({ ...room, ...(await readLayer(transaction, room.locator, room.transfer)) });
232
- }
233
- return records;
234
- },
235
- async keepAuthored({ locator, queue, saved, roomDrafts, consumedRoomDrafts }) {
236
- const transaction = authored.transaction([QUEUES, PENDING_CHANGES, ROOM_DRAFTS], 'readwrite');
237
- const order = transaction.objectStore(QUEUES);
238
- const pending = transaction.objectStore(PENDING_CHANGES);
239
- const held = order.get(locator);
240
- held.onsuccess = () => {
241
- const before = new Set(held.result?.order ?? []);
242
- const after = new Set(queue.map((batch) => batch.id));
243
- if (queue.length === 0)
244
- order.delete(locator);
245
- else
246
- order.put({ locator, order: [...after] });
247
- for (const id of before) {
248
- if (!after.has(id))
249
- pending.delete([locator, id]);
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
+ });
250
392
  }
251
- for (const batch of queue) {
252
- if (!before.has(batch.id)) {
253
- pending.put({
254
- locator,
255
- id: batch.id,
256
- batch,
257
- drafts: saved?.get(batch.id) ?? [],
258
- submission: { state: 'waiting' },
259
- lost: false,
260
- });
393
+ return records;
394
+ });
395
+ },
396
+ async keepAuthored({ locator, queue, founded, saved, roomDrafts, consumedRoomDrafts }) {
397
+ if (fenced(locator))
398
+ return;
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]);
261
413
  }
262
- }
263
- };
264
- const draftStore = transaction.objectStore(ROOM_DRAFTS);
265
- for (const path of consumedRoomDrafts ?? [])
266
- draftStore.delete([locator, path]);
267
- for (const record of roomDrafts ?? [])
268
- draftStore.put(record);
269
- 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
+ });
270
443
  },
271
444
  async write({ next, previous, stored }) {
272
- if (next === previous)
445
+ if (next === previous || fenced(next.locator))
273
446
  return;
274
- const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES, TRANSFERS], 'readwrite');
275
- transaction.objectStore(ROOMS).put(roomRecord(next));
276
447
  const moved = previous?.transfer !== next.transfer;
277
- if (moved && previous !== null) {
278
- const range = underTransfer(previous.locator, previous.transfer);
279
- transaction.objectStore(DOCUMENTS).delete(range);
280
- transaction.objectStore(BINARY_FILES).delete(range);
281
- }
282
- if (moved)
283
- transaction.objectStore(TRANSFERS).delete(next.locator);
284
- writeLayer(transaction, next.locator, next.transfer, next, moved ? (stored ?? null) : previous);
285
- 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
+ });
286
463
  },
287
464
  async revokeModels(records) {
288
- const transaction = database.transaction(ROOMS, 'readwrite');
289
- const rooms = transaction.objectStore(ROOMS);
290
- const revoked = { status: 'unavailable', reason: 'revoked' };
291
- for (const record of records) {
292
- rooms.put(roomRecord({ ...record, model: revoked, selectionModel: revoked }));
293
- }
294
- 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
+ });
295
474
  },
296
475
  async lastCompleted() {
297
476
  return (await declaration(database)).lastCompleted;
@@ -313,23 +492,32 @@ export async function openRuntimeStore() {
313
492
  }));
314
493
  },
315
494
  async member() {
316
- const transaction = database.transaction(DECLARATIONS, 'readonly');
317
- const held = await settled(transaction.objectStore(DECLARATIONS).get(MEMBER));
495
+ const held = await transacted(database, DECLARATIONS, 'readonly', (transaction) => settled(transaction.objectStore(DECLARATIONS).get(MEMBER)));
318
496
  return held ?? null;
319
497
  },
320
498
  async keepMember(member) {
321
- const transaction = database.transaction(DECLARATIONS, 'readwrite');
322
- transaction.objectStore(DECLARATIONS).put(member, MEMBER);
323
- await completed(transaction);
499
+ await transacted(database, DECLARATIONS, 'readwrite', (transaction) => {
500
+ transaction.objectStore(DECLARATIONS).put(member, MEMBER);
501
+ });
502
+ },
503
+ async targets() {
504
+ const held = await transacted(database, DECLARATIONS, 'readonly', (transaction) => settled(transaction.objectStore(DECLARATIONS).get(TARGETS)));
505
+ return held ?? {};
506
+ },
507
+ async keepTargets(targets) {
508
+ await transacted(database, DECLARATIONS, 'readwrite', (transaction) => {
509
+ transaction.objectStore(DECLARATIONS).put(targets, TARGETS);
510
+ });
324
511
  },
325
512
  async queues() {
326
- const transaction = authored.transaction([QUEUES, PENDING_CHANGES], 'readonly');
327
- const [queues, pending] = await Promise.all([
513
+ const [queues, pending] = await transacted(authored, [QUEUES, PENDING_CHANGES], 'readonly', (transaction) => Promise.all([
328
514
  settled(transaction.objectStore(QUEUES).getAll()),
329
515
  settled(transaction.objectStore(PENDING_CHANGES).getAll()),
330
- ]);
516
+ ]));
331
517
  const byId = new Map(pending.map((record) => [`${record.locator}\0${record.id}`, record]));
332
- return queues.map((queue) => ({
518
+ return queues
519
+ .filter((queue) => !fenced(queue.locator))
520
+ .map((queue) => ({
333
521
  locator: queue.locator,
334
522
  pending: queue.order.map((id) => {
335
523
  const record = byId.get(`${queue.locator}\0${id}`);
@@ -340,165 +528,289 @@ export async function openRuntimeStore() {
340
528
  }),
341
529
  }));
342
530
  },
343
- submit(locator, id, submission, lost) {
344
- return amendPending(authored, locator, id, { submission, lost });
531
+ async submit(locator, id, submission, lost) {
532
+ if (!fenced(locator))
533
+ await amendPending(authored, locator, id, { submission, lost });
345
534
  },
346
- accept(locator, id, roomVersion) {
347
- return amendPending(authored, locator, id, { accepted: roomVersion });
535
+ async accept(locator, id, roomVersion) {
536
+ if (!fenced(locator))
537
+ await amendPending(authored, locator, id, { accepted: roomVersion });
348
538
  },
349
- keepUpload(locator, id, upload) {
350
- return amendPending(authored, locator, id, { upload });
539
+ async keepUpload(locator, id, upload) {
540
+ if (!fenced(locator))
541
+ await amendPending(authored, locator, id, { upload });
351
542
  },
352
543
  async roomDrafts() {
353
- const transaction = authored.transaction(ROOM_DRAFTS, 'readonly');
354
- return settled(transaction.objectStore(ROOM_DRAFTS).getAll());
544
+ const held = await transacted(authored, ROOM_DRAFTS, 'readonly', (transaction) => settled(transaction.objectStore(ROOM_DRAFTS).getAll()));
545
+ return held.filter((record) => !fenced(record.locator));
355
546
  },
356
- async presentation() {
357
- const transaction = database.transaction(PRESENTATION, 'readonly');
358
- return settled(transaction.objectStore(PRESENTATION).getAll());
547
+ presentation() {
548
+ return transacted(database, PRESENTATION, 'readonly', (transaction) => settled(transaction.objectStore(PRESENTATION).getAll()));
359
549
  },
360
550
  async present(record) {
361
- const transaction = database.transaction(PRESENTATION, 'readwrite');
362
- transaction.objectStore(PRESENTATION).put(record);
363
- await completed(transaction);
364
- },
365
- async conversations() {
366
- const transaction = database.transaction([CONVERSATIONS, MESSAGES], 'readonly');
367
- const [reads, messages] = await Promise.all([
368
- settled(transaction.objectStore(CONVERSATIONS).getAll()),
369
- settled(transaction.objectStore(MESSAGES).getAll()),
370
- ]);
371
- const held = new Map();
372
- for (const { conversation, read } of reads) {
373
- held.set(conversation, { conversation, read, messages: [] });
374
- }
375
- for (const { conversation, ...message } of messages) {
376
- const entry = held.get(conversation) ?? { conversation, read: undefined, messages: [] };
377
- entry.messages.push(message);
378
- held.set(conversation, entry);
379
- }
380
- return [...held.values()];
381
- },
382
- async keepMessages({ conversation, messages, read, sealed }) {
383
- if (sealed === true) {
384
- const sealing = authored.transaction(CHAT_DRAFTS, 'readwrite');
385
- sealing.objectStore(CHAT_DRAFTS).delete(conversation);
386
- await completed(sealing);
387
- }
388
- const transaction = database.transaction([CONVERSATIONS, MESSAGES], 'readwrite');
389
- const stored = transaction.objectStore(MESSAGES);
390
- for (const message of messages) {
391
- stored.put({ ...message, conversation });
392
- }
393
- if (read !== undefined) {
394
- transaction
395
- .objectStore(CONVERSATIONS)
396
- .put({ conversation, read });
397
- }
398
- await completed(transaction);
551
+ await transacted(database, PRESENTATION, 'readwrite', (transaction) => {
552
+ transaction.objectStore(PRESENTATION).put(record);
553
+ });
554
+ },
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 }) => [
559
+ conversation,
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
+ };
578
+ });
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));
623
+ at.continue();
624
+ };
625
+ });
626
+ return { messages: latest.reverse(), more };
627
+ });
628
+ },
629
+ messageCount(conversation) {
630
+ return transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).index('conversation').count(conversation)));
631
+ },
632
+ async messagesAfter(conversation, identity) {
633
+ const range = identity === null
634
+ ? IDBKeyRange.bound([conversation], [conversation, []])
635
+ : IDBKeyRange.bound([conversation, identity], [conversation, []], true);
636
+ const held = await transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).getAll(range, READ_BATCH)));
637
+ return held.map(accepted);
638
+ },
639
+ async messageIdentities(conversation) {
640
+ const keys = await transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).index('conversation').getAllKeys(conversation)));
641
+ return keys.map((key) => key[1]);
642
+ },
643
+ async turnArrived(conversation, turn) {
644
+ const held = await transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).index('turn').count([conversation, turn])));
645
+ return held > 0;
399
646
  },
400
647
  async dropConversations(conversations) {
401
- const drafts = authored.transaction(CHAT_DRAFTS, 'readwrite');
402
- for (const conversation of conversations)
403
- drafts.objectStore(CHAT_DRAFTS).delete(conversation);
404
- await completed(drafts);
405
- const transaction = database.transaction([CONVERSATIONS, MESSAGES], 'readwrite');
406
- const messages = transaction.objectStore(MESSAGES);
407
- for (const conversation of conversations) {
408
- transaction.objectStore(CONVERSATIONS).delete(conversation);
409
- const keys = messages.index('conversation').getAllKeys(conversation);
410
- keys.onsuccess = () => {
411
- for (const key of keys.result)
412
- messages.delete(key);
413
- };
414
- }
415
- 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
+ });
416
663
  },
417
664
  async chatDrafts() {
418
- const transaction = authored.transaction(CHAT_DRAFTS, 'readonly');
419
- 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()));
420
666
  return held.map(chatDraftRecord);
421
667
  },
422
- async keepChatDraft(record) {
423
- const transaction = authored.transaction(CHAT_DRAFTS, 'readwrite');
424
- transaction.objectStore(CHAT_DRAFTS).put(record);
425
- await completed(transaction);
668
+ async keepChatDrafts(records) {
669
+ await transacted(authored, CHAT_DRAFTS, 'readwrite', (transaction) => {
670
+ for (const record of records)
671
+ transaction.objectStore(CHAT_DRAFTS).put(record);
672
+ });
426
673
  },
427
674
  async dropChatDraft(conversation) {
428
- const transaction = authored.transaction(CHAT_DRAFTS, 'readwrite');
429
- transaction.objectStore(CHAT_DRAFTS).delete(conversation);
430
- 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
+ });
431
691
  },
432
692
  async keepRoomDraft(record) {
433
- const transaction = authored.transaction(ROOM_DRAFTS, 'readwrite');
434
- transaction.objectStore(ROOM_DRAFTS).put(record);
435
- await completed(transaction);
693
+ if (fenced(record.locator))
694
+ return;
695
+ await transacted(authored, ROOM_DRAFTS, 'readwrite', (transaction) => {
696
+ transaction.objectStore(ROOM_DRAFTS).put(record);
697
+ });
436
698
  },
437
699
  async discardRoomDraft(locator, path) {
438
- const transaction = authored.transaction(ROOM_DRAFTS, 'readwrite');
439
- transaction.objectStore(ROOM_DRAFTS).delete([locator, path]);
440
- await completed(transaction);
441
- },
442
- async dropRooms(locators) {
443
- const work = authored.transaction([QUEUES, PENDING_CHANGES, ROOM_DRAFTS], 'readwrite');
444
- for (const locator of locators) {
445
- work.objectStore(QUEUES).delete(locator);
446
- for (const name of [PENDING_CHANGES, ROOM_DRAFTS]) {
447
- removeRoomEntries(work.objectStore(name), locator);
700
+ if (fenced(locator))
701
+ return;
702
+ await transacted(authored, ROOM_DRAFTS, 'readwrite', (transaction) => {
703
+ transaction.objectStore(ROOM_DRAFTS).delete([locator, path]);
704
+ });
705
+ },
706
+ async fences() {
707
+ const held = await transacted(authored, FENCES, 'readonly', (transaction) => settled(transaction.objectStore(FENCES).getAll()));
708
+ return held.map((fence) => fence.locator);
709
+ },
710
+ async erase(locators) {
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
+ }
448
718
  }
449
- }
450
- await completed(work);
451
- const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES, TRANSFERS], 'readwrite');
452
- for (const locator of locators) {
453
- transaction.objectStore(ROOMS).delete(locator);
454
- transaction.objectStore(TRANSFERS).delete(locator);
455
- transaction.objectStore(DOCUMENTS).delete(underRoom(locator));
456
- transaction.objectStore(BINARY_FILES).delete(underRoom(locator));
457
- }
458
- await completed(transaction);
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
+ });
732
+ },
733
+ async discard(locator) {
734
+ await transacted(authored, [QUEUES, PENDING_CHANGES], 'readwrite', (transaction) => {
735
+ transaction.objectStore(QUEUES).delete(locator);
736
+ removeRoomEntries(transaction.objectStore(PENDING_CHANGES), locator);
737
+ });
459
738
  },
460
739
  async transfers() {
461
- const transaction = database.transaction(TRANSFERS, 'readonly');
462
- return settled(transaction.objectStore(TRANSFERS).getAll());
740
+ const held = await transacted(database, TRANSFERS, 'readonly', (transaction) => settled(transaction.objectStore(TRANSFERS).getAll()));
741
+ return held.filter((record) => !fenced(record.locator));
463
742
  },
464
743
  staged(record) {
465
- const transaction = database.transaction([DOCUMENTS, BINARY_FILES], 'readonly');
466
- return readLayer(transaction, record.locator, record.transfer);
744
+ return transacted(database, [DOCUMENTS, BINARY_FILES], 'readonly', (transaction) => readLayer(transaction, record.locator, record.transfer));
467
745
  },
468
746
  async stage(record, page) {
469
- const transaction = database.transaction([TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite');
470
- writeLayer(transaction, record.locator, record.transfer, page, null);
471
- transaction.objectStore(TRANSFERS).put(record);
472
- await completed(transaction);
747
+ if (fenced(record.locator))
748
+ return;
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
+ });
473
760
  },
474
761
  async abandon(locator) {
475
- const transaction = database.transaction([TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite');
476
- const transfers = transaction.objectStore(TRANSFERS);
477
- const held = transfers.get(locator);
478
- held.onsuccess = () => {
479
- const record = held.result;
480
- if (record === undefined)
481
- return;
482
- transfers.delete(locator);
483
- const range = underTransfer(locator, record.transfer);
484
- transaction.objectStore(DOCUMENTS).delete(range);
485
- transaction.objectStore(BINARY_FILES).delete(range);
486
- };
487
- await completed(transaction);
762
+ if (fenced(locator))
763
+ return;
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
+ });
777
+ },
778
+ receivedHeads() {
779
+ return transacted(database, RECEIVED, 'readonly', (transaction) => settled(transaction.objectStore(RECEIVED).getAll()));
780
+ },
781
+ async receivedParts(key, count) {
782
+ const kept = await transacted(database, RECEIVED_PARTS, 'readonly', (transaction) => settled(transaction
783
+ .objectStore(RECEIVED_PARTS)
784
+ .getAll(IDBKeyRange.bound([key, 0], [key, count], false, true))));
785
+ return kept.map((part) => part.value);
786
+ },
787
+ async keepReceived(key, head, part) {
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
+ });
793
+ },
794
+ async dropReceived(keys) {
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
+ });
488
801
  },
489
802
  async generation() {
490
- const transaction = authored.transaction(FACTS, 'readonly');
491
- const held = await settled(transaction.objectStore(FACTS).get(GENERATION));
803
+ const held = await transacted(authored, FACTS, 'readonly', (transaction) => settled(transaction.objectStore(FACTS).get(GENERATION)));
492
804
  return held ?? null;
493
805
  },
494
806
  async rememberGeneration(value) {
495
- const transaction = authored.transaction(FACTS, 'readwrite');
496
- transaction.objectStore(FACTS).put(value, GENERATION);
497
- await completed(transaction);
807
+ await transacted(authored, FACTS, 'readwrite', (transaction) => {
808
+ transaction.objectStore(FACTS).put(value, GENERATION);
809
+ });
498
810
  },
499
811
  close() {
500
- authored.close();
501
- database.close();
812
+ for (const held of [authored, database])
813
+ void idle(held).then(() => held.close());
502
814
  },
503
815
  };
504
816
  }