@agentshouse/mdruntime 0.1.0-cde2b99d2fcbc → 0.1.0-ce131cb435f8b

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