@agentshouse/mdruntime 0.1.0-cdf82f77fae68 → 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 +51 -9
  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 +4 -4
  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 +1573 -372
  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 +95 -32
  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 +59 -12
  135. package/dist/store.d.ts.map +1 -1
  136. package/dist/store.js +350 -86
  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,10 +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';
3
5
  const AUTHORED = 'mdruntime-authored';
6
+ const FENCES = 'fences';
4
7
  const ROOMS = 'rooms';
5
8
  const DOCUMENTS = 'documents';
6
9
  const BINARY_FILES = 'binaryFiles';
7
10
  const TRANSFERS = 'transfers';
11
+ const RECEIVED = 'received';
12
+ const RECEIVED_PARTS = 'receivedParts';
8
13
  const PENDING_CHANGES = 'pendingChanges';
9
14
  const DECLARATIONS = 'declarations';
10
15
  const ROOM_DRAFTS = 'roomDrafts';
@@ -12,21 +17,45 @@ const PRESENTATION = 'presentation';
12
17
  const CONVERSATIONS = 'conversations';
13
18
  const MESSAGES = 'messages';
14
19
  const CHAT_DRAFTS = 'chatDrafts';
20
+ const CALLS = 'calls';
15
21
  const QUEUES = 'queues';
16
22
  const FACTS = 'facts';
17
23
  const CURRENT = 'current';
18
24
  const MEMBER = 'member';
25
+ const TARGETS = 'targets';
19
26
  const GENERATION = 'generation';
20
- export const STORE_SCHEMA = 23;
21
- export const AUTHORED_SCHEMA = 1;
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;
22
32
  const AUTHORED_STORES = [
23
33
  [QUEUES, { keyPath: 'locator' }],
24
34
  [PENDING_CHANGES, { keyPath: ['locator', 'id'] }],
25
35
  [ROOM_DRAFTS, { keyPath: ['locator', 'path'] }],
26
36
  [CHAT_DRAFTS, { keyPath: 'conversation' }],
37
+ [CALLS, { keyPath: 'operation' }],
27
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'] }],
28
52
  ];
29
53
  const READ_BATCH = 256;
54
+ const MESSAGE_INDEXES = [
55
+ ['conversation', 'conversation'],
56
+ ['position', ['conversation', 'position']],
57
+ ['turn', ['conversation', 'turn']],
58
+ ];
30
59
  function settled(request) {
31
60
  return new Promise((resolve, reject) => {
32
61
  request.onsuccess = () => resolve(request.result);
@@ -69,8 +98,11 @@ function amendPending(database, locator, id, amendment) {
69
98
  };
70
99
  return completed(transaction);
71
100
  }
101
+ function accepted({ conversation: _held, ...message }) {
102
+ return message;
103
+ }
72
104
  function roomRecord(record) {
73
- const { documents, binaries, revisions, changes, written, ...room } = record;
105
+ const { documents, binaries, revisions, fileIds, changes, written, ...room } = record;
74
106
  return room;
75
107
  }
76
108
  function chatDraftRecord(record) {
@@ -96,13 +128,15 @@ async function readTransfer(store, locator, transfer, receive) {
96
128
  }
97
129
  }
98
130
  function emptyLayer() {
99
- return { documents: {}, binaries: {}, revisions: {}, changes: {}, written: {} };
131
+ return { documents: {}, binaries: {}, revisions: {}, fileIds: {}, changes: {}, written: {} };
100
132
  }
101
133
  async function readLayer(transaction, locator, transfer) {
102
134
  const layer = emptyLayer();
103
135
  const head = (held) => {
104
136
  if (held.revision !== null)
105
137
  layer.revisions[held.path] = held.revision;
138
+ if (held.fileId !== null)
139
+ layer.fileIds[held.path] = held.fileId;
106
140
  if (held.change !== null)
107
141
  layer.changes[held.path] = held.change;
108
142
  };
@@ -125,6 +159,7 @@ function documentRecord(locator, transfer, layer, path) {
125
159
  path,
126
160
  content: layer.documents[path],
127
161
  revision: layer.revisions[path] ?? null,
162
+ fileId: layer.fileIds[path] ?? null,
128
163
  change: layer.changes[path] ?? null,
129
164
  written: layer.written[path] ?? null,
130
165
  };
@@ -135,35 +170,37 @@ function binaryFileRecord(locator, transfer, layer, path) {
135
170
  transfer,
136
171
  path,
137
172
  revision: layer.revisions[path] ?? null,
173
+ fileId: layer.fileIds[path] ?? null,
138
174
  change: layer.changes[path] ?? null,
139
175
  binary: layer.binaries[path],
140
176
  };
141
177
  }
142
178
  function writeLayer(transaction, locator, transfer, next, base) {
143
179
  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
180
  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])) {
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) {
165
199
  files.put(binaryFileRecord(locator, transfer, next, path));
166
200
  }
201
+ else if (base?.binaries[path] !== undefined) {
202
+ files.delete([locator, transfer, path]);
203
+ }
167
204
  }
168
205
  }
169
206
  function removeRoomEntries(store, locator) {
@@ -182,41 +219,113 @@ function removeDatabase(name) {
182
219
  removal.onerror = () => reject(removal.error);
183
220
  });
184
221
  }
185
- export async function removeRuntimeStore() {
186
- await Promise.all([removeDatabase(DATABASE), removeDatabase(AUTHORED)]);
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);
292
+ return;
293
+ }
294
+ removeDatabase(name)
295
+ .then(() => openDatabase(name, version, upgrade))
296
+ .then(resolve, reject);
297
+ };
298
+ });
187
299
  }
188
300
  function openReplica() {
189
- const opening = indexedDB.open(DATABASE, STORE_SCHEMA);
190
- opening.onupgradeneeded = () => {
301
+ return openDatabase(DATABASE, STORE_SCHEMA, (opening, oldVersion) => {
191
302
  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);
303
+ if (oldVersion < REPLICA_SHAPE) {
304
+ for (const name of [...database.objectStoreNames])
305
+ database.deleteObjectStore(name);
306
+ }
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
+ });
207
318
  }
208
319
  function openAuthored() {
209
- const opening = indexedDB.open(AUTHORED, AUTHORED_SCHEMA);
210
- opening.onupgradeneeded = () => {
320
+ return openDatabase(AUTHORED, AUTHORED_SCHEMA, (opening) => {
211
321
  const database = opening.result;
212
322
  for (const [name, parameters] of AUTHORED_STORES) {
213
323
  if (!database.objectStoreNames.contains(name))
214
324
  database.createObjectStore(name, parameters);
215
325
  }
216
- };
217
- return settled(opening);
326
+ });
218
327
  }
219
- export async function openRuntimeStore() {
328
+ export async function openRuntimeStore(fenced) {
220
329
  const authored = await openAuthored();
221
330
  const database = await openReplica().catch((error) => {
222
331
  authored.close();
@@ -227,12 +336,14 @@ export async function openRuntimeStore() {
227
336
  const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES], 'readonly');
228
337
  const rooms = await settled(transaction.objectStore(ROOMS).getAll());
229
338
  const records = [];
230
- for (const room of rooms) {
339
+ for (const room of rooms.filter((held) => !fenced(held.locator))) {
231
340
  records.push({ ...room, ...(await readLayer(transaction, room.locator, room.transfer)) });
232
341
  }
233
342
  return records;
234
343
  },
235
- async keepAuthored({ locator, queue, saved, roomDrafts, consumedRoomDrafts }) {
344
+ async keepAuthored({ locator, queue, founded, saved, roomDrafts, consumedRoomDrafts }) {
345
+ if (fenced(locator))
346
+ return;
236
347
  const transaction = authored.transaction([QUEUES, PENDING_CHANGES, ROOM_DRAFTS], 'readwrite');
237
348
  const order = transaction.objectStore(QUEUES);
238
349
  const pending = transaction.objectStore(PENDING_CHANGES);
@@ -260,6 +371,16 @@ export async function openRuntimeStore() {
260
371
  });
261
372
  }
262
373
  }
374
+ for (const batch of founded ?? []) {
375
+ if (!before.has(batch.id))
376
+ continue;
377
+ const stored = pending.get([locator, batch.id]);
378
+ stored.onsuccess = () => {
379
+ const record = stored.result;
380
+ if (record !== undefined)
381
+ pending.put({ ...record, batch });
382
+ };
383
+ }
263
384
  };
264
385
  const draftStore = transaction.objectStore(ROOM_DRAFTS);
265
386
  for (const path of consumedRoomDrafts ?? [])
@@ -269,7 +390,7 @@ export async function openRuntimeStore() {
269
390
  await completed(transaction);
270
391
  },
271
392
  async write({ next, previous, stored }) {
272
- if (next === previous)
393
+ if (next === previous || fenced(next.locator))
273
394
  return;
274
395
  const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES, TRANSFERS], 'readwrite');
275
396
  transaction.objectStore(ROOMS).put(roomRecord(next));
@@ -289,6 +410,8 @@ export async function openRuntimeStore() {
289
410
  const rooms = transaction.objectStore(ROOMS);
290
411
  const revoked = { status: 'unavailable', reason: 'revoked' };
291
412
  for (const record of records) {
413
+ if (fenced(record.locator))
414
+ continue;
292
415
  rooms.put(roomRecord({ ...record, model: revoked, selectionModel: revoked }));
293
416
  }
294
417
  await completed(transaction);
@@ -322,6 +445,16 @@ export async function openRuntimeStore() {
322
445
  transaction.objectStore(DECLARATIONS).put(member, MEMBER);
323
446
  await completed(transaction);
324
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
+ },
325
458
  async queues() {
326
459
  const transaction = authored.transaction([QUEUES, PENDING_CHANGES], 'readonly');
327
460
  const [queues, pending] = await Promise.all([
@@ -329,7 +462,9 @@ export async function openRuntimeStore() {
329
462
  settled(transaction.objectStore(PENDING_CHANGES).getAll()),
330
463
  ]);
331
464
  const byId = new Map(pending.map((record) => [`${record.locator}\0${record.id}`, record]));
332
- return queues.map((queue) => ({
465
+ return queues
466
+ .filter((queue) => !fenced(queue.locator))
467
+ .map((queue) => ({
333
468
  locator: queue.locator,
334
469
  pending: queue.order.map((id) => {
335
470
  const record = byId.get(`${queue.locator}\0${id}`);
@@ -340,18 +475,22 @@ export async function openRuntimeStore() {
340
475
  }),
341
476
  }));
342
477
  },
343
- submit(locator, id, submission, lost) {
344
- return amendPending(authored, locator, id, { submission, lost });
478
+ async submit(locator, id, submission, lost) {
479
+ if (!fenced(locator))
480
+ await amendPending(authored, locator, id, { submission, lost });
345
481
  },
346
- accept(locator, id, roomVersion) {
347
- return amendPending(authored, locator, id, { accepted: roomVersion });
482
+ async accept(locator, id, roomVersion) {
483
+ if (!fenced(locator))
484
+ await amendPending(authored, locator, id, { accepted: roomVersion });
348
485
  },
349
- keepUpload(locator, id, upload) {
350
- return amendPending(authored, locator, id, { upload });
486
+ async keepUpload(locator, id, upload) {
487
+ if (!fenced(locator))
488
+ await amendPending(authored, locator, id, { upload });
351
489
  },
352
490
  async roomDrafts() {
353
491
  const transaction = authored.transaction(ROOM_DRAFTS, 'readonly');
354
- return settled(transaction.objectStore(ROOM_DRAFTS).getAll());
492
+ const held = await settled(transaction.objectStore(ROOM_DRAFTS).getAll());
493
+ return held.filter((record) => !fenced(record.locator));
355
494
  },
356
495
  async presentation() {
357
496
  const transaction = database.transaction(PRESENTATION, 'readonly');
@@ -364,39 +503,98 @@ export async function openRuntimeStore() {
364
503
  },
365
504
  async conversations() {
366
505
  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
- }
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
+ });
380
528
  return [...held.values()];
381
529
  },
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
- }
530
+ async keepMessages({ conversation, messages, read }) {
388
531
  const transaction = database.transaction([CONVERSATIONS, MESSAGES], 'readwrite');
389
532
  const stored = transaction.objectStore(MESSAGES);
390
533
  for (const message of messages) {
391
534
  stored.put({ ...message, conversation });
392
535
  }
393
536
  if (read !== undefined) {
394
- transaction
395
- .objectStore(CONVERSATIONS)
396
- .put({ conversation, read });
537
+ transaction.objectStore(CONVERSATIONS).put({
538
+ conversation,
539
+ read: read.at,
540
+ ...(read.partial ? { partial: true } : {}),
541
+ });
397
542
  }
398
543
  await completed(transaction);
399
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
+ },
400
598
  async dropConversations(conversations) {
401
599
  const drafts = authored.transaction(CHAT_DRAFTS, 'readwrite');
402
600
  for (const conversation of conversations)
@@ -419,9 +617,10 @@ export async function openRuntimeStore() {
419
617
  const held = await settled(transaction.objectStore(CHAT_DRAFTS).getAll());
420
618
  return held.map(chatDraftRecord);
421
619
  },
422
- async keepChatDraft(record) {
620
+ async keepChatDrafts(records) {
423
621
  const transaction = authored.transaction(CHAT_DRAFTS, 'readwrite');
424
- transaction.objectStore(CHAT_DRAFTS).put(record);
622
+ for (const record of records)
623
+ transaction.objectStore(CHAT_DRAFTS).put(record);
425
624
  await completed(transaction);
426
625
  },
427
626
  async dropChatDraft(conversation) {
@@ -429,19 +628,43 @@ export async function openRuntimeStore() {
429
628
  transaction.objectStore(CHAT_DRAFTS).delete(conversation);
430
629
  await completed(transaction);
431
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
+ },
432
645
  async keepRoomDraft(record) {
646
+ if (fenced(record.locator))
647
+ return;
433
648
  const transaction = authored.transaction(ROOM_DRAFTS, 'readwrite');
434
649
  transaction.objectStore(ROOM_DRAFTS).put(record);
435
650
  await completed(transaction);
436
651
  },
437
652
  async discardRoomDraft(locator, path) {
653
+ if (fenced(locator))
654
+ return;
438
655
  const transaction = authored.transaction(ROOM_DRAFTS, 'readwrite');
439
656
  transaction.objectStore(ROOM_DRAFTS).delete([locator, path]);
440
657
  await completed(transaction);
441
658
  },
442
- async dropRooms(locators) {
443
- const work = authored.transaction([QUEUES, 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');
444
666
  for (const locator of locators) {
667
+ work.objectStore(FENCES).put({ locator });
445
668
  work.objectStore(QUEUES).delete(locator);
446
669
  for (const name of [PENDING_CHANGES, ROOM_DRAFTS]) {
447
670
  removeRoomEntries(work.objectStore(name), locator);
@@ -456,22 +679,37 @@ export async function openRuntimeStore() {
456
679
  transaction.objectStore(BINARY_FILES).delete(underRoom(locator));
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,6 +724,32 @@ 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
754
  const transaction = authored.transaction(FACTS, 'readonly');
491
755
  const held = await settled(transaction.objectStore(FACTS).get(GENERATION));
package/dist/turn.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ export declare function yielded(): Promise<void>;
2
+ export declare const ROW_WEIGHT = 256;
3
+ export type Later<T> = T | Promise<T>;
4
+ export declare function after<T, R>(value: Later<T>, next: (value: T) => Later<R>): Later<R>;
5
+ export declare function inUnits<T>(items: Iterable<T>, each: (item: T) => number, ended?: () => void): Later<void>;
6
+ export declare function finished<R>(steps: Generator<number, R, void>): R;
7
+ export declare function stepped<R>(steps: Generator<number, R, void>): Later<R>;
8
+ export declare function inChunks<T>(items: readonly T[], weigh: (item: T) => number, each: (chunk: readonly T[]) => Later<void>): Promise<void>;
9
+ //# sourceMappingURL=turn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn.d.ts","sourceRoot":"","sources":["../src/turn.ts"],"names":[],"mappings":"AAAA,wBAAgB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CASvC;AAID,eAAO,MAAM,UAAU,MAAM,CAAC;AAE9B,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAEtC,wBAAgB,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAEnF;AAmBD,wBAAgB,OAAO,CAAC,CAAC,EACvB,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAClB,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,EACzB,KAAK,GAAE,MAAM,IAAe,GAC3B,KAAK,CAAC,IAAI,CAAC,CAWb;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAIhE;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAStE;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,EAC1B,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,KAAK,KAAK,CAAC,IAAI,CAAC,GACzC,OAAO,CAAC,IAAI,CAAC,CAUf"}