@agentshouse/mdruntime 0.1.0-c69ca0e865e0b → 0.1.0-c6fd2cbbff187

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 (143) hide show
  1. package/AGENTS.md +275 -275
  2. package/dist/acquisition.d.ts +14 -0
  3. package/dist/acquisition.d.ts.map +1 -0
  4. package/dist/acquisition.js +24 -0
  5. package/dist/adapter.d.ts +50 -9
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/bootstrap.d.ts +3 -2
  8. package/dist/bootstrap.d.ts.map +1 -1
  9. package/dist/bootstrap.js +26 -4
  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 +2 -1
  23. package/dist/chat/list.d.ts.map +1 -1
  24. package/dist/chat/list.js +42 -5
  25. package/dist/chat/message.d.ts.map +1 -1
  26. package/dist/chat/message.js +2 -2
  27. package/dist/chat/messages.d.ts +1 -0
  28. package/dist/chat/messages.d.ts.map +1 -1
  29. package/dist/chat/messages.js +4 -0
  30. package/dist/chat/provider.d.ts +1 -1
  31. package/dist/chat/provider.d.ts.map +1 -1
  32. package/dist/chat/provider.js +59 -69
  33. package/dist/chat/question.d.ts +1 -1
  34. package/dist/chat/question.d.ts.map +1 -1
  35. package/dist/chat/question.js +7 -18
  36. package/dist/chat/ready.d.ts +1 -0
  37. package/dist/chat/ready.d.ts.map +1 -1
  38. package/dist/chat/ready.js +2 -2
  39. package/dist/chat/style.js +1 -1
  40. package/dist/client.d.ts +14 -8
  41. package/dist/client.d.ts.map +1 -1
  42. package/dist/client.js +17 -6
  43. package/dist/collection.d.ts +17 -3
  44. package/dist/collection.d.ts.map +1 -1
  45. package/dist/collection.js +230 -19
  46. package/dist/connection.d.ts +27 -8
  47. package/dist/connection.d.ts.map +1 -1
  48. package/dist/connection.js +1567 -362
  49. package/dist/conversation.d.ts +19 -7
  50. package/dist/conversation.d.ts.map +1 -1
  51. package/dist/conversation.js +438 -152
  52. package/dist/durability.d.ts +7 -1
  53. package/dist/durability.d.ts.map +1 -1
  54. package/dist/entry.d.ts +2 -2
  55. package/dist/entry.d.ts.map +1 -1
  56. package/dist/entry.js +3 -3
  57. package/dist/fence.d.ts +6 -0
  58. package/dist/fence.d.ts.map +1 -0
  59. package/dist/fence.js +10 -0
  60. package/dist/host.d.ts +17 -2
  61. package/dist/host.d.ts.map +1 -1
  62. package/dist/host.js +90 -9
  63. package/dist/identity.d.ts +2 -0
  64. package/dist/identity.d.ts.map +1 -1
  65. package/dist/identity.js +6 -0
  66. package/dist/index.d.ts +9 -6
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +2 -0
  69. package/dist/memory.d.ts +9 -2
  70. package/dist/memory.d.ts.map +1 -1
  71. package/dist/memory.js +36 -7
  72. package/dist/model.d.ts +4 -2
  73. package/dist/model.d.ts.map +1 -1
  74. package/dist/model.js +10 -1
  75. package/dist/observation.d.ts +7 -2
  76. package/dist/observation.d.ts.map +1 -1
  77. package/dist/observation.js +29 -12
  78. package/dist/outcome.d.ts +12 -2
  79. package/dist/outcome.d.ts.map +1 -1
  80. package/dist/outcome.js +4 -0
  81. package/dist/overlay.d.ts +2 -0
  82. package/dist/overlay.d.ts.map +1 -1
  83. package/dist/overlay.js +58 -55
  84. package/dist/query.d.ts +56 -4
  85. package/dist/query.d.ts.map +1 -1
  86. package/dist/query.js +36 -7
  87. package/dist/reach.d.ts +6 -3
  88. package/dist/reach.d.ts.map +1 -1
  89. package/dist/reach.js +20 -5
  90. package/dist/react.d.ts +7 -4
  91. package/dist/react.d.ts.map +1 -1
  92. package/dist/react.js +6 -4
  93. package/dist/read-side.d.ts +13 -6
  94. package/dist/read-side.d.ts.map +1 -1
  95. package/dist/read-side.js +419 -92
  96. package/dist/readiness.d.ts +53 -0
  97. package/dist/readiness.d.ts.map +1 -0
  98. package/dist/readiness.js +33 -0
  99. package/dist/reads.d.ts +11 -0
  100. package/dist/reads.d.ts.map +1 -0
  101. package/dist/reads.js +98 -0
  102. package/dist/recovery.d.ts +25 -0
  103. package/dist/recovery.d.ts.map +1 -0
  104. package/dist/recovery.js +197 -0
  105. package/dist/refresh.d.ts +4 -4
  106. package/dist/refresh.d.ts.map +1 -1
  107. package/dist/refresh.js +17 -10
  108. package/dist/replica.d.ts +6 -2
  109. package/dist/replica.d.ts.map +1 -1
  110. package/dist/replica.js +76 -23
  111. package/dist/room-draft.d.ts +13 -1
  112. package/dist/room-draft.d.ts.map +1 -1
  113. package/dist/room-draft.js +2 -2
  114. package/dist/rows.d.ts +6 -13
  115. package/dist/rows.d.ts.map +1 -1
  116. package/dist/rows.js +10 -13
  117. package/dist/save.d.ts +6 -0
  118. package/dist/save.d.ts.map +1 -1
  119. package/dist/save.js +53 -14
  120. package/dist/search.d.ts +49 -6
  121. package/dist/search.d.ts.map +1 -1
  122. package/dist/search.js +192 -47
  123. package/dist/seed.d.ts +16 -0
  124. package/dist/seed.d.ts.map +1 -0
  125. package/dist/seed.js +75 -0
  126. package/dist/shared.d.ts +11 -0
  127. package/dist/shared.d.ts.map +1 -0
  128. package/dist/shared.js +368 -0
  129. package/dist/status.d.ts +4 -0
  130. package/dist/status.d.ts.map +1 -1
  131. package/dist/store.d.ts +56 -11
  132. package/dist/store.d.ts.map +1 -1
  133. package/dist/store.js +311 -84
  134. package/dist/turn.d.ts +7 -0
  135. package/dist/turn.d.ts.map +1 -0
  136. package/dist/turn.js +45 -0
  137. package/dist/windows.d.ts +19 -0
  138. package/dist/windows.d.ts.map +1 -0
  139. package/dist/windows.js +83 -0
  140. package/dist/wire.d.ts +144 -32
  141. package/dist/wire.d.ts.map +1 -1
  142. package/dist/wire.js +50 -31
  143. package/package.json +2 -2
package/dist/store.js CHANGED
@@ -1,10 +1,14 @@
1
1
  import { sameBinary, sameChange } from './overlay.js';
2
+ import { differing } from './shared.js';
2
3
  const DATABASE = 'mdruntime';
3
4
  const AUTHORED = 'mdruntime-authored';
5
+ const FENCES = 'fences';
4
6
  const ROOMS = 'rooms';
5
7
  const DOCUMENTS = 'documents';
6
8
  const BINARY_FILES = 'binaryFiles';
7
9
  const TRANSFERS = 'transfers';
10
+ const RECEIVED = 'received';
11
+ const RECEIVED_PARTS = 'receivedParts';
8
12
  const PENDING_CHANGES = 'pendingChanges';
9
13
  const DECLARATIONS = 'declarations';
10
14
  const ROOM_DRAFTS = 'roomDrafts';
@@ -12,22 +16,45 @@ const PRESENTATION = 'presentation';
12
16
  const CONVERSATIONS = 'conversations';
13
17
  const MESSAGES = 'messages';
14
18
  const CHAT_DRAFTS = 'chatDrafts';
19
+ const CALLS = 'calls';
15
20
  const QUEUES = 'queues';
16
21
  const FACTS = 'facts';
17
22
  const CURRENT = 'current';
18
23
  const MEMBER = 'member';
24
+ const TARGETS = 'targets';
19
25
  const GENERATION = 'generation';
20
26
  const HOLDER = 'holder';
21
- export const STORE_SCHEMA = 23;
22
- export const AUTHORED_SCHEMA = 1;
27
+ const BINDING = 'binding';
28
+ export const STORE_SCHEMA = 25;
29
+ const REPLICA_SHAPE = 23;
30
+ export const AUTHORED_SCHEMA = 3;
23
31
  const AUTHORED_STORES = [
24
32
  [QUEUES, { keyPath: 'locator' }],
25
33
  [PENDING_CHANGES, { keyPath: ['locator', 'id'] }],
26
34
  [ROOM_DRAFTS, { keyPath: ['locator', 'path'] }],
27
35
  [CHAT_DRAFTS, { keyPath: 'conversation' }],
36
+ [CALLS, { keyPath: 'operation' }],
28
37
  [FACTS, undefined],
38
+ [FENCES, { keyPath: 'locator' }],
39
+ ];
40
+ const REPLICA_STORES = [
41
+ [ROOMS, { keyPath: 'locator' }],
42
+ [DOCUMENTS, { keyPath: ['locator', 'transfer', 'path'] }],
43
+ [BINARY_FILES, { keyPath: ['locator', 'transfer', 'path'] }],
44
+ [TRANSFERS, { keyPath: 'locator' }],
45
+ [RECEIVED, { keyPath: 'key' }],
46
+ [RECEIVED_PARTS, { keyPath: ['key', 'index'] }],
47
+ [DECLARATIONS, undefined],
48
+ [PRESENTATION, { keyPath: 'app' }],
49
+ [CONVERSATIONS, { keyPath: 'conversation' }],
50
+ [MESSAGES, { keyPath: ['conversation', 'identity'] }],
29
51
  ];
30
52
  const READ_BATCH = 256;
53
+ const MESSAGE_INDEXES = [
54
+ ['conversation', 'conversation'],
55
+ ['position', ['conversation', 'position']],
56
+ ['turn', ['conversation', 'turn']],
57
+ ];
31
58
  function settled(request) {
32
59
  return new Promise((resolve, reject) => {
33
60
  request.onsuccess = () => resolve(request.result);
@@ -70,8 +97,11 @@ function amendPending(database, locator, id, amendment) {
70
97
  };
71
98
  return completed(transaction);
72
99
  }
100
+ function accepted({ conversation: _held, ...message }) {
101
+ return message;
102
+ }
73
103
  function roomRecord(record) {
74
- const { documents, binaries, revisions, changes, written, ...room } = record;
104
+ const { documents, binaries, revisions, fileIds, changes, written, ...room } = record;
75
105
  return room;
76
106
  }
77
107
  function chatDraftRecord(record) {
@@ -97,13 +127,15 @@ async function readTransfer(store, locator, transfer, receive) {
97
127
  }
98
128
  }
99
129
  function emptyLayer() {
100
- return { documents: {}, binaries: {}, revisions: {}, changes: {}, written: {} };
130
+ return { documents: {}, binaries: {}, revisions: {}, fileIds: {}, changes: {}, written: {} };
101
131
  }
102
132
  async function readLayer(transaction, locator, transfer) {
103
133
  const layer = emptyLayer();
104
134
  const head = (held) => {
105
135
  if (held.revision !== null)
106
136
  layer.revisions[held.path] = held.revision;
137
+ if (held.fileId !== null)
138
+ layer.fileIds[held.path] = held.fileId;
107
139
  if (held.change !== null)
108
140
  layer.changes[held.path] = held.change;
109
141
  };
@@ -126,6 +158,7 @@ function documentRecord(locator, transfer, layer, path) {
126
158
  path,
127
159
  content: layer.documents[path],
128
160
  revision: layer.revisions[path] ?? null,
161
+ fileId: layer.fileIds[path] ?? null,
129
162
  change: layer.changes[path] ?? null,
130
163
  written: layer.written[path] ?? null,
131
164
  };
@@ -136,35 +169,37 @@ function binaryFileRecord(locator, transfer, layer, path) {
136
169
  transfer,
137
170
  path,
138
171
  revision: layer.revisions[path] ?? null,
172
+ fileId: layer.fileIds[path] ?? null,
139
173
  change: layer.changes[path] ?? null,
140
174
  binary: layer.binaries[path],
141
175
  };
142
176
  }
143
177
  function writeLayer(transaction, locator, transfer, next, base) {
144
178
  const documents = transaction.objectStore(DOCUMENTS);
145
- for (const path of Object.keys(base?.documents ?? {})) {
146
- if (next.documents[path] === undefined)
147
- documents.delete([locator, transfer, path]);
148
- }
149
- for (const [path, content] of Object.entries(next.documents)) {
150
- if (base?.documents[path] === content &&
151
- base.revisions[path] === next.revisions[path] &&
152
- base.written[path] === next.written[path] &&
153
- sameChange(base.changes[path], next.changes[path]))
154
- continue;
155
- documents.put(documentRecord(locator, transfer, next, path));
156
- }
157
179
  const files = transaction.objectStore(BINARY_FILES);
158
- for (const path of Object.keys(base?.binaries ?? {})) {
159
- if (next.binaries[path] === undefined)
160
- files.delete([locator, transfer, path]);
161
- }
162
- for (const path of Object.keys(next.binaries)) {
163
- if (!sameBinary(base?.binaries[path], next.binaries[path]) ||
164
- base?.revisions[path] !== next.revisions[path] ||
165
- !sameChange(base?.changes[path], next.changes[path])) {
180
+ const paths = base === null
181
+ ? new Set([...Object.keys(next.documents), ...Object.keys(next.binaries)])
182
+ : new Set([
183
+ ...differing(base.documents, next.documents),
184
+ ...differing(base.revisions, next.revisions),
185
+ ...differing(base.fileIds, next.fileIds),
186
+ ...differing(base.written, next.written),
187
+ ...differing(base.changes, next.changes, sameChange),
188
+ ...differing(base.binaries, next.binaries, sameBinary),
189
+ ]);
190
+ for (const path of paths) {
191
+ if (next.documents[path] !== undefined) {
192
+ documents.put(documentRecord(locator, transfer, next, path));
193
+ }
194
+ else if (base?.documents[path] !== undefined) {
195
+ documents.delete([locator, transfer, path]);
196
+ }
197
+ if (next.binaries[path] !== undefined) {
166
198
  files.put(binaryFileRecord(locator, transfer, next, path));
167
199
  }
200
+ else if (base?.binaries[path] !== undefined) {
201
+ files.delete([locator, transfer, path]);
202
+ }
168
203
  }
169
204
  }
170
205
  function removeRoomEntries(store, locator) {
@@ -215,6 +250,29 @@ export async function authoredBy(holder) {
215
250
  authored.close();
216
251
  }
217
252
  }
253
+ export async function authoredBinding() {
254
+ const authored = await openAuthored();
255
+ try {
256
+ const transaction = authored.transaction(FACTS, 'readonly');
257
+ const held = transaction.objectStore(FACTS).get(BINDING);
258
+ await completed(transaction);
259
+ return typeof held.result === 'string' ? held.result : null;
260
+ }
261
+ finally {
262
+ authored.close();
263
+ }
264
+ }
265
+ export async function bindAuthored(binding) {
266
+ const authored = await openAuthored();
267
+ try {
268
+ const transaction = authored.transaction(FACTS, 'readwrite');
269
+ transaction.objectStore(FACTS).put(binding, BINDING);
270
+ await completed(transaction);
271
+ }
272
+ finally {
273
+ authored.close();
274
+ }
275
+ }
218
276
  export async function removeRuntimeStore(holder) {
219
277
  await removeDatabase(DATABASE);
220
278
  if (holder !== undefined && (await authoredBy(holder)))
@@ -222,38 +280,51 @@ export async function removeRuntimeStore(holder) {
222
280
  await removeDatabase(AUTHORED);
223
281
  return false;
224
282
  }
283
+ export function openDatabase(name, version, upgrade) {
284
+ return new Promise((resolve, reject) => {
285
+ const opening = indexedDB.open(name, version);
286
+ opening.onupgradeneeded = (event) => upgrade(opening, event.oldVersion);
287
+ opening.onsuccess = () => resolve(opening.result);
288
+ opening.onerror = () => {
289
+ if (opening.error?.name !== 'VersionError') {
290
+ reject(opening.error);
291
+ return;
292
+ }
293
+ removeDatabase(name)
294
+ .then(() => openDatabase(name, version, upgrade))
295
+ .then(resolve, reject);
296
+ };
297
+ });
298
+ }
225
299
  function openReplica() {
226
- const opening = indexedDB.open(DATABASE, STORE_SCHEMA);
227
- opening.onupgradeneeded = () => {
300
+ return openDatabase(DATABASE, STORE_SCHEMA, (opening, oldVersion) => {
228
301
  const database = opening.result;
229
- for (const name of [...database.objectStoreNames])
230
- database.deleteObjectStore(name);
231
- database.createObjectStore(ROOMS, { keyPath: 'locator' });
232
- database.createObjectStore(DOCUMENTS, { keyPath: ['locator', 'transfer', 'path'] });
233
- database.createObjectStore(BINARY_FILES, { keyPath: ['locator', 'transfer', 'path'] });
234
- database.createObjectStore(TRANSFERS, { keyPath: 'locator' });
235
- database.createObjectStore(DECLARATIONS);
236
- database.createObjectStore(PRESENTATION, { keyPath: 'app' });
237
- database.createObjectStore(CONVERSATIONS, { keyPath: 'conversation' });
238
- const messages = database.createObjectStore(MESSAGES, {
239
- keyPath: ['conversation', 'identity'],
240
- });
241
- messages.createIndex('conversation', 'conversation');
242
- };
243
- return settled(opening);
302
+ if (oldVersion < REPLICA_SHAPE) {
303
+ for (const name of [...database.objectStoreNames])
304
+ database.deleteObjectStore(name);
305
+ }
306
+ for (const [name, parameters] of REPLICA_STORES) {
307
+ if (database.objectStoreNames.contains(name))
308
+ continue;
309
+ database.createObjectStore(name, parameters);
310
+ }
311
+ const messages = opening.transaction.objectStore(MESSAGES);
312
+ for (const [name, keyPath] of MESSAGE_INDEXES) {
313
+ if (!messages.indexNames.contains(name))
314
+ messages.createIndex(name, keyPath);
315
+ }
316
+ });
244
317
  }
245
318
  function openAuthored() {
246
- const opening = indexedDB.open(AUTHORED, AUTHORED_SCHEMA);
247
- opening.onupgradeneeded = () => {
319
+ return openDatabase(AUTHORED, AUTHORED_SCHEMA, (opening) => {
248
320
  const database = opening.result;
249
321
  for (const [name, parameters] of AUTHORED_STORES) {
250
322
  if (!database.objectStoreNames.contains(name))
251
323
  database.createObjectStore(name, parameters);
252
324
  }
253
- };
254
- return settled(opening);
325
+ });
255
326
  }
256
- export async function openRuntimeStore() {
327
+ export async function openRuntimeStore(fenced) {
257
328
  const authored = await openAuthored();
258
329
  const database = await openReplica().catch((error) => {
259
330
  authored.close();
@@ -264,12 +335,14 @@ export async function openRuntimeStore() {
264
335
  const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES], 'readonly');
265
336
  const rooms = await settled(transaction.objectStore(ROOMS).getAll());
266
337
  const records = [];
267
- for (const room of rooms) {
338
+ for (const room of rooms.filter((held) => !fenced(held.locator))) {
268
339
  records.push({ ...room, ...(await readLayer(transaction, room.locator, room.transfer)) });
269
340
  }
270
341
  return records;
271
342
  },
272
- async keepAuthored({ locator, queue, saved, roomDrafts, consumedRoomDrafts }) {
343
+ async keepAuthored({ locator, queue, founded, saved, roomDrafts, consumedRoomDrafts }) {
344
+ if (fenced(locator))
345
+ return;
273
346
  const transaction = authored.transaction([QUEUES, PENDING_CHANGES, ROOM_DRAFTS], 'readwrite');
274
347
  const order = transaction.objectStore(QUEUES);
275
348
  const pending = transaction.objectStore(PENDING_CHANGES);
@@ -297,6 +370,16 @@ export async function openRuntimeStore() {
297
370
  });
298
371
  }
299
372
  }
373
+ for (const batch of founded ?? []) {
374
+ if (!before.has(batch.id))
375
+ continue;
376
+ const stored = pending.get([locator, batch.id]);
377
+ stored.onsuccess = () => {
378
+ const record = stored.result;
379
+ if (record !== undefined)
380
+ pending.put({ ...record, batch });
381
+ };
382
+ }
300
383
  };
301
384
  const draftStore = transaction.objectStore(ROOM_DRAFTS);
302
385
  for (const path of consumedRoomDrafts ?? [])
@@ -306,7 +389,7 @@ export async function openRuntimeStore() {
306
389
  await completed(transaction);
307
390
  },
308
391
  async write({ next, previous, stored }) {
309
- if (next === previous)
392
+ if (next === previous || fenced(next.locator))
310
393
  return;
311
394
  const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES, TRANSFERS], 'readwrite');
312
395
  transaction.objectStore(ROOMS).put(roomRecord(next));
@@ -326,6 +409,8 @@ export async function openRuntimeStore() {
326
409
  const rooms = transaction.objectStore(ROOMS);
327
410
  const revoked = { status: 'unavailable', reason: 'revoked' };
328
411
  for (const record of records) {
412
+ if (fenced(record.locator))
413
+ continue;
329
414
  rooms.put(roomRecord({ ...record, model: revoked, selectionModel: revoked }));
330
415
  }
331
416
  await completed(transaction);
@@ -359,6 +444,16 @@ export async function openRuntimeStore() {
359
444
  transaction.objectStore(DECLARATIONS).put(member, MEMBER);
360
445
  await completed(transaction);
361
446
  },
447
+ async targets() {
448
+ const transaction = database.transaction(DECLARATIONS, 'readonly');
449
+ const held = await settled(transaction.objectStore(DECLARATIONS).get(TARGETS));
450
+ return held ?? {};
451
+ },
452
+ async keepTargets(targets) {
453
+ const transaction = database.transaction(DECLARATIONS, 'readwrite');
454
+ transaction.objectStore(DECLARATIONS).put(targets, TARGETS);
455
+ await completed(transaction);
456
+ },
362
457
  async queues() {
363
458
  const transaction = authored.transaction([QUEUES, PENDING_CHANGES], 'readonly');
364
459
  const [queues, pending] = await Promise.all([
@@ -366,7 +461,9 @@ export async function openRuntimeStore() {
366
461
  settled(transaction.objectStore(PENDING_CHANGES).getAll()),
367
462
  ]);
368
463
  const byId = new Map(pending.map((record) => [`${record.locator}\0${record.id}`, record]));
369
- return queues.map((queue) => ({
464
+ return queues
465
+ .filter((queue) => !fenced(queue.locator))
466
+ .map((queue) => ({
370
467
  locator: queue.locator,
371
468
  pending: queue.order.map((id) => {
372
469
  const record = byId.get(`${queue.locator}\0${id}`);
@@ -377,18 +474,22 @@ export async function openRuntimeStore() {
377
474
  }),
378
475
  }));
379
476
  },
380
- submit(locator, id, submission, lost) {
381
- return amendPending(authored, locator, id, { submission, lost });
477
+ async submit(locator, id, submission, lost) {
478
+ if (!fenced(locator))
479
+ await amendPending(authored, locator, id, { submission, lost });
382
480
  },
383
- accept(locator, id, roomVersion) {
384
- return amendPending(authored, locator, id, { accepted: roomVersion });
481
+ async accept(locator, id, roomVersion) {
482
+ if (!fenced(locator))
483
+ await amendPending(authored, locator, id, { accepted: roomVersion });
385
484
  },
386
- keepUpload(locator, id, upload) {
387
- return amendPending(authored, locator, id, { upload });
485
+ async keepUpload(locator, id, upload) {
486
+ if (!fenced(locator))
487
+ await amendPending(authored, locator, id, { upload });
388
488
  },
389
489
  async roomDrafts() {
390
490
  const transaction = authored.transaction(ROOM_DRAFTS, 'readonly');
391
- return settled(transaction.objectStore(ROOM_DRAFTS).getAll());
491
+ const held = await settled(transaction.objectStore(ROOM_DRAFTS).getAll());
492
+ return held.filter((record) => !fenced(record.locator));
392
493
  },
393
494
  async presentation() {
394
495
  const transaction = database.transaction(PRESENTATION, 'readonly');
@@ -401,39 +502,99 @@ export async function openRuntimeStore() {
401
502
  },
402
503
  async conversations() {
403
504
  const transaction = database.transaction([CONVERSATIONS, MESSAGES], 'readonly');
404
- const [reads, messages] = await Promise.all([
405
- settled(transaction.objectStore(CONVERSATIONS).getAll()),
406
- settled(transaction.objectStore(MESSAGES).getAll()),
407
- ]);
408
- const held = new Map();
409
- for (const { conversation, read } of reads) {
410
- held.set(conversation, { conversation, read, messages: [] });
411
- }
412
- for (const { conversation, ...message } of messages) {
413
- const entry = held.get(conversation) ?? { conversation, read: undefined, messages: [] };
414
- entry.messages.push(message);
415
- held.set(conversation, entry);
416
- }
505
+ const records = await settled(transaction.objectStore(CONVERSATIONS).getAll());
506
+ const held = new Map(records.map(({ conversation, read, partial }) => [
507
+ conversation,
508
+ { conversation, read, partial: partial === true },
509
+ ]));
510
+ const keys = transaction.objectStore(MESSAGES).index('conversation');
511
+ await new Promise((resolve, reject) => {
512
+ const cursor = keys.openKeyCursor(null, 'nextunique');
513
+ cursor.onerror = () => reject(cursor.error);
514
+ cursor.onsuccess = () => {
515
+ const at = cursor.result;
516
+ if (at === null) {
517
+ resolve();
518
+ return;
519
+ }
520
+ const conversation = at.key;
521
+ if (!held.has(conversation)) {
522
+ held.set(conversation, { conversation, read: undefined, partial: true });
523
+ }
524
+ at.continue();
525
+ };
526
+ });
417
527
  return [...held.values()];
418
528
  },
419
- async keepMessages({ conversation, messages, read, sealed }) {
420
- if (sealed === true) {
421
- const sealing = authored.transaction(CHAT_DRAFTS, 'readwrite');
422
- sealing.objectStore(CHAT_DRAFTS).delete(conversation);
423
- await completed(sealing);
424
- }
529
+ async keepMessages({ conversation, messages, read }) {
425
530
  const transaction = database.transaction([CONVERSATIONS, MESSAGES], 'readwrite');
426
531
  const stored = transaction.objectStore(MESSAGES);
427
532
  for (const message of messages) {
428
533
  stored.put({ ...message, conversation });
429
534
  }
430
535
  if (read !== undefined) {
431
- transaction
432
- .objectStore(CONVERSATIONS)
433
- .put({ conversation, read });
536
+ transaction.objectStore(CONVERSATIONS).put({
537
+ conversation,
538
+ read: read.at,
539
+ ...(read.partial ? { partial: true } : {}),
540
+ });
434
541
  }
435
542
  await completed(transaction);
436
543
  },
544
+ async messageWindow(conversation, limit, before) {
545
+ const transaction = database.transaction(MESSAGES, 'readonly');
546
+ const index = transaction.objectStore(MESSAGES).index('position');
547
+ const range = IDBKeyRange.bound([conversation], [conversation, before?.[0] ?? []]);
548
+ const earlier = ({ position, identity }) => before === null || position < before[0] || (position === before[0] && identity < before[1]);
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 (!earlier(record)) {
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
+ },
437
598
  async dropConversations(conversations) {
438
599
  const drafts = authored.transaction(CHAT_DRAFTS, 'readwrite');
439
600
  for (const conversation of conversations)
@@ -456,9 +617,10 @@ export async function openRuntimeStore() {
456
617
  const held = await settled(transaction.objectStore(CHAT_DRAFTS).getAll());
457
618
  return held.map(chatDraftRecord);
458
619
  },
459
- async keepChatDraft(record) {
620
+ async keepChatDrafts(records) {
460
621
  const transaction = authored.transaction(CHAT_DRAFTS, 'readwrite');
461
- transaction.objectStore(CHAT_DRAFTS).put(record);
622
+ for (const record of records)
623
+ transaction.objectStore(CHAT_DRAFTS).put(record);
462
624
  await completed(transaction);
463
625
  },
464
626
  async dropChatDraft(conversation) {
@@ -466,19 +628,43 @@ export async function openRuntimeStore() {
466
628
  transaction.objectStore(CHAT_DRAFTS).delete(conversation);
467
629
  await completed(transaction);
468
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
+ },
469
645
  async keepRoomDraft(record) {
646
+ if (fenced(record.locator))
647
+ return;
470
648
  const transaction = authored.transaction(ROOM_DRAFTS, 'readwrite');
471
649
  transaction.objectStore(ROOM_DRAFTS).put(record);
472
650
  await completed(transaction);
473
651
  },
474
652
  async discardRoomDraft(locator, path) {
653
+ if (fenced(locator))
654
+ return;
475
655
  const transaction = authored.transaction(ROOM_DRAFTS, 'readwrite');
476
656
  transaction.objectStore(ROOM_DRAFTS).delete([locator, path]);
477
657
  await completed(transaction);
478
658
  },
479
- async dropRooms(locators) {
480
- 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');
481
666
  for (const locator of locators) {
667
+ work.objectStore(FENCES).put({ locator });
482
668
  work.objectStore(QUEUES).delete(locator);
483
669
  for (const name of [PENDING_CHANGES, ROOM_DRAFTS]) {
484
670
  removeRoomEntries(work.objectStore(name), locator);
@@ -493,22 +679,37 @@ export async function openRuntimeStore() {
493
679
  transaction.objectStore(BINARY_FILES).delete(underRoom(locator));
494
680
  }
495
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);
496
692
  },
497
693
  async transfers() {
498
694
  const transaction = database.transaction(TRANSFERS, 'readonly');
499
- return settled(transaction.objectStore(TRANSFERS).getAll());
695
+ const held = await settled(transaction.objectStore(TRANSFERS).getAll());
696
+ return held.filter((record) => !fenced(record.locator));
500
697
  },
501
698
  staged(record) {
502
699
  const transaction = database.transaction([DOCUMENTS, BINARY_FILES], 'readonly');
503
700
  return readLayer(transaction, record.locator, record.transfer);
504
701
  },
505
702
  async stage(record, page) {
703
+ if (fenced(record.locator))
704
+ return;
506
705
  const transaction = database.transaction([TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite');
507
706
  writeLayer(transaction, record.locator, record.transfer, page, null);
508
707
  transaction.objectStore(TRANSFERS).put(record);
509
708
  await completed(transaction);
510
709
  },
511
710
  async abandon(locator) {
711
+ if (fenced(locator))
712
+ return;
512
713
  const transaction = database.transaction([TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite');
513
714
  const transfers = transaction.objectStore(TRANSFERS);
514
715
  const held = transfers.get(locator);
@@ -523,6 +724,32 @@ export async function openRuntimeStore() {
523
724
  };
524
725
  await completed(transaction);
525
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
+ },
526
753
  async generation() {
527
754
  const transaction = authored.transaction(FACTS, 'readonly');
528
755
  const held = await settled(transaction.objectStore(FACTS).get(GENERATION));
package/dist/turn.d.ts ADDED
@@ -0,0 +1,7 @@
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) => R): Later<R>;
5
+ export declare function inUnits<T>(items: Iterable<T>, each: (item: T) => number): Later<void>;
6
+ export declare function inChunks<T>(items: readonly T[], weigh: (item: T) => number, each: (chunk: readonly T[]) => void): Promise<void>;
7
+ //# 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,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAE5E;AAaD,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAOrF;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,IAAI,GAClC,OAAO,CAAC,IAAI,CAAC,CAUf"}