@agentshouse/mdruntime 0.1.0-cdf82f77fae68 → 0.1.0-ceb97106cc2fd

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 (135) hide show
  1. package/AGENTS.md +259 -272
  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 +37 -9
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/attempt.d.ts.map +1 -1
  8. package/dist/attempt.js +14 -1
  9. package/dist/bootstrap.d.ts +2 -2
  10. package/dist/bootstrap.d.ts.map +1 -1
  11. package/dist/bootstrap.js +32 -6
  12. package/dist/change-set.d.ts +2 -0
  13. package/dist/change-set.d.ts.map +1 -1
  14. package/dist/change-set.js +36 -10
  15. package/dist/chat/acts.d.ts +3 -4
  16. package/dist/chat/acts.d.ts.map +1 -1
  17. package/dist/chat/acts.js +10 -10
  18. package/dist/chat/kit.d.ts +2 -1
  19. package/dist/chat/kit.d.ts.map +1 -1
  20. package/dist/chat/kit.js +3 -3
  21. package/dist/chat/list.d.ts +8 -1
  22. package/dist/chat/list.d.ts.map +1 -1
  23. package/dist/chat/list.js +10 -6
  24. package/dist/chat/markdown.d.ts +2 -1
  25. package/dist/chat/markdown.d.ts.map +1 -1
  26. package/dist/chat/markdown.js +7 -6
  27. package/dist/chat/message.d.ts.map +1 -1
  28. package/dist/chat/message.js +2 -2
  29. package/dist/chat/messages.d.ts +1 -0
  30. package/dist/chat/messages.d.ts.map +1 -1
  31. package/dist/chat/messages.js +4 -0
  32. package/dist/chat/provider.d.ts +1 -1
  33. package/dist/chat/provider.d.ts.map +1 -1
  34. package/dist/chat/provider.js +51 -67
  35. package/dist/chat/ready.d.ts +1 -0
  36. package/dist/chat/ready.d.ts.map +1 -1
  37. package/dist/chat/ready.js +4 -4
  38. package/dist/chat/style.js +3 -3
  39. package/dist/client.d.ts +12 -8
  40. package/dist/client.d.ts.map +1 -1
  41. package/dist/client.js +3 -1
  42. package/dist/collection.d.ts +17 -3
  43. package/dist/collection.d.ts.map +1 -1
  44. package/dist/collection.js +230 -19
  45. package/dist/connection.d.ts +24 -7
  46. package/dist/connection.d.ts.map +1 -1
  47. package/dist/connection.js +1340 -318
  48. package/dist/conversation.d.ts +14 -7
  49. package/dist/conversation.d.ts.map +1 -1
  50. package/dist/conversation.js +328 -137
  51. package/dist/durability.d.ts +7 -1
  52. package/dist/durability.d.ts.map +1 -1
  53. package/dist/entry.d.ts +17 -0
  54. package/dist/entry.d.ts.map +1 -0
  55. package/dist/entry.js +156 -0
  56. package/dist/fence.d.ts +6 -0
  57. package/dist/fence.d.ts.map +1 -0
  58. package/dist/fence.js +10 -0
  59. package/dist/host.d.ts +18 -3
  60. package/dist/host.d.ts.map +1 -1
  61. package/dist/host.js +89 -12
  62. package/dist/identity.d.ts +2 -0
  63. package/dist/identity.d.ts.map +1 -1
  64. package/dist/identity.js +6 -0
  65. package/dist/index.d.ts +9 -6
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +2 -0
  68. package/dist/memory.d.ts +4 -1
  69. package/dist/memory.d.ts.map +1 -1
  70. package/dist/memory.js +23 -2
  71. package/dist/model.d.ts +4 -2
  72. package/dist/model.d.ts.map +1 -1
  73. package/dist/model.js +10 -1
  74. package/dist/observation.d.ts +5 -2
  75. package/dist/observation.d.ts.map +1 -1
  76. package/dist/observation.js +22 -11
  77. package/dist/outcome.d.ts +4 -0
  78. package/dist/outcome.d.ts.map +1 -1
  79. package/dist/outcome.js +1 -0
  80. package/dist/overlay.d.ts +2 -0
  81. package/dist/overlay.d.ts.map +1 -1
  82. package/dist/overlay.js +58 -55
  83. package/dist/query.d.ts +34 -3
  84. package/dist/query.d.ts.map +1 -1
  85. package/dist/query.js +7 -1
  86. package/dist/reach.d.ts +6 -3
  87. package/dist/reach.d.ts.map +1 -1
  88. package/dist/reach.js +17 -5
  89. package/dist/react.d.ts +7 -4
  90. package/dist/react.d.ts.map +1 -1
  91. package/dist/react.js +6 -4
  92. package/dist/read-side.d.ts +13 -6
  93. package/dist/read-side.d.ts.map +1 -1
  94. package/dist/read-side.js +285 -83
  95. package/dist/readiness.d.ts +53 -0
  96. package/dist/readiness.d.ts.map +1 -0
  97. package/dist/readiness.js +33 -0
  98. package/dist/recovery.d.ts +25 -0
  99. package/dist/recovery.d.ts.map +1 -0
  100. package/dist/recovery.js +197 -0
  101. package/dist/refresh.d.ts.map +1 -1
  102. package/dist/refresh.js +17 -10
  103. package/dist/replica.d.ts +1 -1
  104. package/dist/replica.d.ts.map +1 -1
  105. package/dist/replica.js +57 -20
  106. package/dist/room-draft.d.ts +13 -1
  107. package/dist/room-draft.d.ts.map +1 -1
  108. package/dist/room-draft.js +2 -2
  109. package/dist/rows.d.ts +5 -13
  110. package/dist/rows.d.ts.map +1 -1
  111. package/dist/rows.js +5 -13
  112. package/dist/save.d.ts +6 -2
  113. package/dist/save.d.ts.map +1 -1
  114. package/dist/save.js +42 -18
  115. package/dist/search.d.ts +34 -6
  116. package/dist/search.d.ts.map +1 -1
  117. package/dist/search.js +116 -45
  118. package/dist/seed.d.ts +16 -0
  119. package/dist/seed.d.ts.map +1 -0
  120. package/dist/seed.js +61 -0
  121. package/dist/shared.d.ts +11 -0
  122. package/dist/shared.d.ts.map +1 -0
  123. package/dist/shared.js +368 -0
  124. package/dist/status.d.ts +4 -0
  125. package/dist/status.d.ts.map +1 -1
  126. package/dist/store.d.ts +45 -12
  127. package/dist/store.d.ts.map +1 -1
  128. package/dist/store.js +315 -85
  129. package/dist/turn.d.ts +7 -0
  130. package/dist/turn.d.ts.map +1 -0
  131. package/dist/turn.js +45 -0
  132. package/dist/wire.d.ts +136 -32
  133. package/dist/wire.d.ts.map +1 -1
  134. package/dist/wire.js +48 -31
  135. package/package.json +12 -8
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';
@@ -16,17 +20,39 @@ const QUEUES = 'queues';
16
20
  const FACTS = 'facts';
17
21
  const CURRENT = 'current';
18
22
  const MEMBER = 'member';
23
+ const TARGETS = 'targets';
19
24
  const GENERATION = 'generation';
20
- export const STORE_SCHEMA = 23;
21
- export const AUTHORED_SCHEMA = 1;
25
+ const HOLDER = 'holder';
26
+ const BINDING = 'binding';
27
+ export const STORE_SCHEMA = 25;
28
+ const REPLICA_SHAPE = 23;
29
+ export const AUTHORED_SCHEMA = 2;
22
30
  const AUTHORED_STORES = [
23
31
  [QUEUES, { keyPath: 'locator' }],
24
32
  [PENDING_CHANGES, { keyPath: ['locator', 'id'] }],
25
33
  [ROOM_DRAFTS, { keyPath: ['locator', 'path'] }],
26
34
  [CHAT_DRAFTS, { keyPath: 'conversation' }],
27
35
  [FACTS, undefined],
36
+ [FENCES, { keyPath: 'locator' }],
37
+ ];
38
+ const REPLICA_STORES = [
39
+ [ROOMS, { keyPath: 'locator' }],
40
+ [DOCUMENTS, { keyPath: ['locator', 'transfer', 'path'] }],
41
+ [BINARY_FILES, { keyPath: ['locator', 'transfer', 'path'] }],
42
+ [TRANSFERS, { keyPath: 'locator' }],
43
+ [RECEIVED, { keyPath: 'key' }],
44
+ [RECEIVED_PARTS, { keyPath: ['key', 'index'] }],
45
+ [DECLARATIONS, undefined],
46
+ [PRESENTATION, { keyPath: 'app' }],
47
+ [CONVERSATIONS, { keyPath: 'conversation' }],
48
+ [MESSAGES, { keyPath: ['conversation', 'identity'] }],
28
49
  ];
29
50
  const READ_BATCH = 256;
51
+ const MESSAGE_INDEXES = [
52
+ ['conversation', 'conversation'],
53
+ ['position', ['conversation', 'position']],
54
+ ['turn', ['conversation', 'turn']],
55
+ ];
30
56
  function settled(request) {
31
57
  return new Promise((resolve, reject) => {
32
58
  request.onsuccess = () => resolve(request.result);
@@ -69,8 +95,11 @@ function amendPending(database, locator, id, amendment) {
69
95
  };
70
96
  return completed(transaction);
71
97
  }
98
+ function accepted({ conversation: _held, ...message }) {
99
+ return message;
100
+ }
72
101
  function roomRecord(record) {
73
- const { documents, binaries, revisions, changes, written, ...room } = record;
102
+ const { documents, binaries, revisions, fileIds, changes, written, ...room } = record;
74
103
  return room;
75
104
  }
76
105
  function chatDraftRecord(record) {
@@ -96,13 +125,15 @@ async function readTransfer(store, locator, transfer, receive) {
96
125
  }
97
126
  }
98
127
  function emptyLayer() {
99
- return { documents: {}, binaries: {}, revisions: {}, changes: {}, written: {} };
128
+ return { documents: {}, binaries: {}, revisions: {}, fileIds: {}, changes: {}, written: {} };
100
129
  }
101
130
  async function readLayer(transaction, locator, transfer) {
102
131
  const layer = emptyLayer();
103
132
  const head = (held) => {
104
133
  if (held.revision !== null)
105
134
  layer.revisions[held.path] = held.revision;
135
+ if (held.fileId !== null)
136
+ layer.fileIds[held.path] = held.fileId;
106
137
  if (held.change !== null)
107
138
  layer.changes[held.path] = held.change;
108
139
  };
@@ -125,6 +156,7 @@ function documentRecord(locator, transfer, layer, path) {
125
156
  path,
126
157
  content: layer.documents[path],
127
158
  revision: layer.revisions[path] ?? null,
159
+ fileId: layer.fileIds[path] ?? null,
128
160
  change: layer.changes[path] ?? null,
129
161
  written: layer.written[path] ?? null,
130
162
  };
@@ -135,35 +167,37 @@ function binaryFileRecord(locator, transfer, layer, path) {
135
167
  transfer,
136
168
  path,
137
169
  revision: layer.revisions[path] ?? null,
170
+ fileId: layer.fileIds[path] ?? null,
138
171
  change: layer.changes[path] ?? null,
139
172
  binary: layer.binaries[path],
140
173
  };
141
174
  }
142
175
  function writeLayer(transaction, locator, transfer, next, base) {
143
176
  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
177
  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])) {
178
+ const paths = base === null
179
+ ? new Set([...Object.keys(next.documents), ...Object.keys(next.binaries)])
180
+ : new Set([
181
+ ...differing(base.documents, next.documents),
182
+ ...differing(base.revisions, next.revisions),
183
+ ...differing(base.fileIds, next.fileIds),
184
+ ...differing(base.written, next.written),
185
+ ...differing(base.changes, next.changes, sameChange),
186
+ ...differing(base.binaries, next.binaries, sameBinary),
187
+ ]);
188
+ for (const path of paths) {
189
+ if (next.documents[path] !== undefined) {
190
+ documents.put(documentRecord(locator, transfer, next, path));
191
+ }
192
+ else if (base?.documents[path] !== undefined) {
193
+ documents.delete([locator, transfer, path]);
194
+ }
195
+ if (next.binaries[path] !== undefined) {
165
196
  files.put(binaryFileRecord(locator, transfer, next, path));
166
197
  }
198
+ else if (base?.binaries[path] !== undefined) {
199
+ files.delete([locator, transfer, path]);
200
+ }
167
201
  }
168
202
  }
169
203
  function removeRoomEntries(store, locator) {
@@ -182,41 +216,113 @@ function removeDatabase(name) {
182
216
  removal.onerror = () => reject(removal.error);
183
217
  });
184
218
  }
185
- export async function removeRuntimeStore() {
186
- await Promise.all([removeDatabase(DATABASE), removeDatabase(AUTHORED)]);
219
+ export async function authoredHolder() {
220
+ const authored = await openAuthored();
221
+ try {
222
+ const transaction = authored.transaction(FACTS, 'readonly');
223
+ const held = transaction.objectStore(FACTS).get(HOLDER);
224
+ await completed(transaction);
225
+ return typeof held.result === 'string' ? held.result : null;
226
+ }
227
+ finally {
228
+ authored.close();
229
+ }
230
+ }
231
+ export async function authoredBy(holder) {
232
+ const authored = await openAuthored();
233
+ try {
234
+ const transaction = authored.transaction(FACTS, 'readwrite');
235
+ const facts = transaction.objectStore(FACTS);
236
+ const held = facts.get(HOLDER);
237
+ let owned = true;
238
+ held.onsuccess = () => {
239
+ if (held.result === undefined)
240
+ facts.put(holder, HOLDER);
241
+ else
242
+ owned = held.result === holder;
243
+ };
244
+ await completed(transaction);
245
+ return owned;
246
+ }
247
+ finally {
248
+ authored.close();
249
+ }
250
+ }
251
+ export async function authoredBinding() {
252
+ const authored = await openAuthored();
253
+ try {
254
+ const transaction = authored.transaction(FACTS, 'readonly');
255
+ const held = transaction.objectStore(FACTS).get(BINDING);
256
+ await completed(transaction);
257
+ return typeof held.result === 'string' ? held.result : null;
258
+ }
259
+ finally {
260
+ authored.close();
261
+ }
262
+ }
263
+ export async function bindAuthored(binding) {
264
+ const authored = await openAuthored();
265
+ try {
266
+ const transaction = authored.transaction(FACTS, 'readwrite');
267
+ transaction.objectStore(FACTS).put(binding, BINDING);
268
+ await completed(transaction);
269
+ }
270
+ finally {
271
+ authored.close();
272
+ }
273
+ }
274
+ export async function removeRuntimeStore(holder) {
275
+ await removeDatabase(DATABASE);
276
+ if (holder !== undefined && (await authoredBy(holder)))
277
+ return true;
278
+ await removeDatabase(AUTHORED);
279
+ return false;
280
+ }
281
+ export function openDatabase(name, version, upgrade) {
282
+ return new Promise((resolve, reject) => {
283
+ const opening = indexedDB.open(name, version);
284
+ opening.onupgradeneeded = (event) => upgrade(opening, event.oldVersion);
285
+ opening.onsuccess = () => resolve(opening.result);
286
+ opening.onerror = () => {
287
+ if (opening.error?.name !== 'VersionError') {
288
+ reject(opening.error);
289
+ return;
290
+ }
291
+ removeDatabase(name)
292
+ .then(() => openDatabase(name, version, upgrade))
293
+ .then(resolve, reject);
294
+ };
295
+ });
187
296
  }
188
297
  function openReplica() {
189
- const opening = indexedDB.open(DATABASE, STORE_SCHEMA);
190
- opening.onupgradeneeded = () => {
298
+ return openDatabase(DATABASE, STORE_SCHEMA, (opening, oldVersion) => {
191
299
  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);
300
+ if (oldVersion < REPLICA_SHAPE) {
301
+ for (const name of [...database.objectStoreNames])
302
+ database.deleteObjectStore(name);
303
+ }
304
+ for (const [name, parameters] of REPLICA_STORES) {
305
+ if (database.objectStoreNames.contains(name))
306
+ continue;
307
+ database.createObjectStore(name, parameters);
308
+ }
309
+ const messages = opening.transaction.objectStore(MESSAGES);
310
+ for (const [name, keyPath] of MESSAGE_INDEXES) {
311
+ if (!messages.indexNames.contains(name))
312
+ messages.createIndex(name, keyPath);
313
+ }
314
+ });
207
315
  }
208
316
  function openAuthored() {
209
- const opening = indexedDB.open(AUTHORED, AUTHORED_SCHEMA);
210
- opening.onupgradeneeded = () => {
317
+ return openDatabase(AUTHORED, AUTHORED_SCHEMA, (opening) => {
211
318
  const database = opening.result;
212
319
  for (const [name, parameters] of AUTHORED_STORES) {
213
320
  if (!database.objectStoreNames.contains(name))
214
321
  database.createObjectStore(name, parameters);
215
322
  }
216
- };
217
- return settled(opening);
323
+ });
218
324
  }
219
- export async function openRuntimeStore() {
325
+ export async function openRuntimeStore(fenced) {
220
326
  const authored = await openAuthored();
221
327
  const database = await openReplica().catch((error) => {
222
328
  authored.close();
@@ -227,12 +333,14 @@ export async function openRuntimeStore() {
227
333
  const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES], 'readonly');
228
334
  const rooms = await settled(transaction.objectStore(ROOMS).getAll());
229
335
  const records = [];
230
- for (const room of rooms) {
336
+ for (const room of rooms.filter((held) => !fenced(held.locator))) {
231
337
  records.push({ ...room, ...(await readLayer(transaction, room.locator, room.transfer)) });
232
338
  }
233
339
  return records;
234
340
  },
235
341
  async keepAuthored({ locator, queue, saved, roomDrafts, consumedRoomDrafts }) {
342
+ if (fenced(locator))
343
+ return;
236
344
  const transaction = authored.transaction([QUEUES, PENDING_CHANGES, ROOM_DRAFTS], 'readwrite');
237
345
  const order = transaction.objectStore(QUEUES);
238
346
  const pending = transaction.objectStore(PENDING_CHANGES);
@@ -269,7 +377,7 @@ export async function openRuntimeStore() {
269
377
  await completed(transaction);
270
378
  },
271
379
  async write({ next, previous, stored }) {
272
- if (next === previous)
380
+ if (next === previous || fenced(next.locator))
273
381
  return;
274
382
  const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES, TRANSFERS], 'readwrite');
275
383
  transaction.objectStore(ROOMS).put(roomRecord(next));
@@ -289,6 +397,8 @@ export async function openRuntimeStore() {
289
397
  const rooms = transaction.objectStore(ROOMS);
290
398
  const revoked = { status: 'unavailable', reason: 'revoked' };
291
399
  for (const record of records) {
400
+ if (fenced(record.locator))
401
+ continue;
292
402
  rooms.put(roomRecord({ ...record, model: revoked, selectionModel: revoked }));
293
403
  }
294
404
  await completed(transaction);
@@ -322,6 +432,16 @@ export async function openRuntimeStore() {
322
432
  transaction.objectStore(DECLARATIONS).put(member, MEMBER);
323
433
  await completed(transaction);
324
434
  },
435
+ async targets() {
436
+ const transaction = database.transaction(DECLARATIONS, 'readonly');
437
+ const held = await settled(transaction.objectStore(DECLARATIONS).get(TARGETS));
438
+ return held ?? {};
439
+ },
440
+ async keepTargets(targets) {
441
+ const transaction = database.transaction(DECLARATIONS, 'readwrite');
442
+ transaction.objectStore(DECLARATIONS).put(targets, TARGETS);
443
+ await completed(transaction);
444
+ },
325
445
  async queues() {
326
446
  const transaction = authored.transaction([QUEUES, PENDING_CHANGES], 'readonly');
327
447
  const [queues, pending] = await Promise.all([
@@ -329,7 +449,9 @@ export async function openRuntimeStore() {
329
449
  settled(transaction.objectStore(PENDING_CHANGES).getAll()),
330
450
  ]);
331
451
  const byId = new Map(pending.map((record) => [`${record.locator}\0${record.id}`, record]));
332
- return queues.map((queue) => ({
452
+ return queues
453
+ .filter((queue) => !fenced(queue.locator))
454
+ .map((queue) => ({
333
455
  locator: queue.locator,
334
456
  pending: queue.order.map((id) => {
335
457
  const record = byId.get(`${queue.locator}\0${id}`);
@@ -340,18 +462,22 @@ export async function openRuntimeStore() {
340
462
  }),
341
463
  }));
342
464
  },
343
- submit(locator, id, submission, lost) {
344
- return amendPending(authored, locator, id, { submission, lost });
465
+ async submit(locator, id, submission, lost) {
466
+ if (!fenced(locator))
467
+ await amendPending(authored, locator, id, { submission, lost });
345
468
  },
346
- accept(locator, id, roomVersion) {
347
- return amendPending(authored, locator, id, { accepted: roomVersion });
469
+ async accept(locator, id, roomVersion) {
470
+ if (!fenced(locator))
471
+ await amendPending(authored, locator, id, { accepted: roomVersion });
348
472
  },
349
- keepUpload(locator, id, upload) {
350
- return amendPending(authored, locator, id, { upload });
473
+ async keepUpload(locator, id, upload) {
474
+ if (!fenced(locator))
475
+ await amendPending(authored, locator, id, { upload });
351
476
  },
352
477
  async roomDrafts() {
353
478
  const transaction = authored.transaction(ROOM_DRAFTS, 'readonly');
354
- return settled(transaction.objectStore(ROOM_DRAFTS).getAll());
479
+ const held = await settled(transaction.objectStore(ROOM_DRAFTS).getAll());
480
+ return held.filter((record) => !fenced(record.locator));
355
481
  },
356
482
  async presentation() {
357
483
  const transaction = database.transaction(PRESENTATION, 'readonly');
@@ -364,39 +490,91 @@ export async function openRuntimeStore() {
364
490
  },
365
491
  async conversations() {
366
492
  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
- }
493
+ const records = await settled(transaction.objectStore(CONVERSATIONS).getAll());
494
+ const held = new Map(records.map(({ conversation, read, partial }) => [
495
+ conversation,
496
+ { conversation, read, partial: partial === true },
497
+ ]));
498
+ const keys = transaction.objectStore(MESSAGES).index('conversation');
499
+ await new Promise((resolve, reject) => {
500
+ const cursor = keys.openKeyCursor(null, 'nextunique');
501
+ cursor.onerror = () => reject(cursor.error);
502
+ cursor.onsuccess = () => {
503
+ const at = cursor.result;
504
+ if (at === null) {
505
+ resolve();
506
+ return;
507
+ }
508
+ const conversation = at.key;
509
+ if (!held.has(conversation)) {
510
+ held.set(conversation, { conversation, read: undefined, partial: true });
511
+ }
512
+ at.continue();
513
+ };
514
+ });
380
515
  return [...held.values()];
381
516
  },
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
- }
517
+ async keepMessages({ conversation, messages, read }) {
388
518
  const transaction = database.transaction([CONVERSATIONS, MESSAGES], 'readwrite');
389
519
  const stored = transaction.objectStore(MESSAGES);
390
520
  for (const message of messages) {
391
521
  stored.put({ ...message, conversation });
392
522
  }
393
523
  if (read !== undefined) {
394
- transaction
395
- .objectStore(CONVERSATIONS)
396
- .put({ conversation, read });
524
+ transaction.objectStore(CONVERSATIONS).put({
525
+ conversation,
526
+ read: read.at,
527
+ ...(read.partial ? { partial: true } : {}),
528
+ });
397
529
  }
398
530
  await completed(transaction);
399
531
  },
532
+ async messageWindow(conversation, limit) {
533
+ const transaction = database.transaction(MESSAGES, 'readonly');
534
+ const index = transaction.objectStore(MESSAGES).index('position');
535
+ const range = IDBKeyRange.bound([conversation], [conversation, []]);
536
+ if (limit === undefined) {
537
+ return (await settled(index.getAll(range))).map(accepted);
538
+ }
539
+ const latest = [];
540
+ if (limit === 0)
541
+ return latest;
542
+ await new Promise((resolve, reject) => {
543
+ const cursor = index.openCursor(range, 'prev');
544
+ cursor.onerror = () => reject(cursor.error);
545
+ cursor.onsuccess = () => {
546
+ const at = cursor.result;
547
+ if (at === null) {
548
+ resolve();
549
+ return;
550
+ }
551
+ latest.push(accepted(at.value));
552
+ if (latest.length === limit)
553
+ resolve();
554
+ else
555
+ at.continue();
556
+ };
557
+ });
558
+ return latest.reverse();
559
+ },
560
+ async messagesAfter(conversation, identity) {
561
+ const transaction = database.transaction(MESSAGES, 'readonly');
562
+ const range = identity === null
563
+ ? IDBKeyRange.bound([conversation], [conversation, []])
564
+ : IDBKeyRange.bound([conversation, identity], [conversation, []], true);
565
+ const held = await settled(transaction.objectStore(MESSAGES).getAll(range, READ_BATCH));
566
+ return held.map(accepted);
567
+ },
568
+ async messageIdentities(conversation) {
569
+ const transaction = database.transaction(MESSAGES, 'readonly');
570
+ const keys = await settled(transaction.objectStore(MESSAGES).index('conversation').getAllKeys(conversation));
571
+ return keys.map((key) => key[1]);
572
+ },
573
+ async turnArrived(conversation, turn) {
574
+ const transaction = database.transaction(MESSAGES, 'readonly');
575
+ const held = await settled(transaction.objectStore(MESSAGES).index('turn').count([conversation, turn]));
576
+ return held > 0;
577
+ },
400
578
  async dropConversations(conversations) {
401
579
  const drafts = authored.transaction(CHAT_DRAFTS, 'readwrite');
402
580
  for (const conversation of conversations)
@@ -419,9 +597,10 @@ export async function openRuntimeStore() {
419
597
  const held = await settled(transaction.objectStore(CHAT_DRAFTS).getAll());
420
598
  return held.map(chatDraftRecord);
421
599
  },
422
- async keepChatDraft(record) {
600
+ async keepChatDrafts(records) {
423
601
  const transaction = authored.transaction(CHAT_DRAFTS, 'readwrite');
424
- transaction.objectStore(CHAT_DRAFTS).put(record);
602
+ for (const record of records)
603
+ transaction.objectStore(CHAT_DRAFTS).put(record);
425
604
  await completed(transaction);
426
605
  },
427
606
  async dropChatDraft(conversation) {
@@ -430,18 +609,28 @@ export async function openRuntimeStore() {
430
609
  await completed(transaction);
431
610
  },
432
611
  async keepRoomDraft(record) {
612
+ if (fenced(record.locator))
613
+ return;
433
614
  const transaction = authored.transaction(ROOM_DRAFTS, 'readwrite');
434
615
  transaction.objectStore(ROOM_DRAFTS).put(record);
435
616
  await completed(transaction);
436
617
  },
437
618
  async discardRoomDraft(locator, path) {
619
+ if (fenced(locator))
620
+ return;
438
621
  const transaction = authored.transaction(ROOM_DRAFTS, 'readwrite');
439
622
  transaction.objectStore(ROOM_DRAFTS).delete([locator, path]);
440
623
  await completed(transaction);
441
624
  },
442
- async dropRooms(locators) {
443
- const work = authored.transaction([QUEUES, PENDING_CHANGES, ROOM_DRAFTS], 'readwrite');
625
+ async fences() {
626
+ const transaction = authored.transaction(FENCES, 'readonly');
627
+ const held = await settled(transaction.objectStore(FENCES).getAll());
628
+ return held.map((fence) => fence.locator);
629
+ },
630
+ async erase(locators) {
631
+ const work = authored.transaction([FENCES, QUEUES, PENDING_CHANGES, ROOM_DRAFTS], 'readwrite');
444
632
  for (const locator of locators) {
633
+ work.objectStore(FENCES).put({ locator });
445
634
  work.objectStore(QUEUES).delete(locator);
446
635
  for (const name of [PENDING_CHANGES, ROOM_DRAFTS]) {
447
636
  removeRoomEntries(work.objectStore(name), locator);
@@ -456,22 +645,37 @@ export async function openRuntimeStore() {
456
645
  transaction.objectStore(BINARY_FILES).delete(underRoom(locator));
457
646
  }
458
647
  await completed(transaction);
648
+ const fences = authored.transaction(FENCES, 'readwrite');
649
+ for (const locator of locators)
650
+ fences.objectStore(FENCES).delete(locator);
651
+ await completed(fences);
652
+ },
653
+ async discard(locator) {
654
+ const transaction = authored.transaction([QUEUES, PENDING_CHANGES], 'readwrite');
655
+ transaction.objectStore(QUEUES).delete(locator);
656
+ removeRoomEntries(transaction.objectStore(PENDING_CHANGES), locator);
657
+ await completed(transaction);
459
658
  },
460
659
  async transfers() {
461
660
  const transaction = database.transaction(TRANSFERS, 'readonly');
462
- return settled(transaction.objectStore(TRANSFERS).getAll());
661
+ const held = await settled(transaction.objectStore(TRANSFERS).getAll());
662
+ return held.filter((record) => !fenced(record.locator));
463
663
  },
464
664
  staged(record) {
465
665
  const transaction = database.transaction([DOCUMENTS, BINARY_FILES], 'readonly');
466
666
  return readLayer(transaction, record.locator, record.transfer);
467
667
  },
468
668
  async stage(record, page) {
669
+ if (fenced(record.locator))
670
+ return;
469
671
  const transaction = database.transaction([TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite');
470
672
  writeLayer(transaction, record.locator, record.transfer, page, null);
471
673
  transaction.objectStore(TRANSFERS).put(record);
472
674
  await completed(transaction);
473
675
  },
474
676
  async abandon(locator) {
677
+ if (fenced(locator))
678
+ return;
475
679
  const transaction = database.transaction([TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite');
476
680
  const transfers = transaction.objectStore(TRANSFERS);
477
681
  const held = transfers.get(locator);
@@ -486,6 +690,32 @@ export async function openRuntimeStore() {
486
690
  };
487
691
  await completed(transaction);
488
692
  },
693
+ async receivedHeads() {
694
+ const transaction = database.transaction(RECEIVED, 'readonly');
695
+ return settled(transaction.objectStore(RECEIVED).getAll());
696
+ },
697
+ async receivedParts(key, count) {
698
+ const transaction = database.transaction(RECEIVED_PARTS, 'readonly');
699
+ const kept = await settled(transaction
700
+ .objectStore(RECEIVED_PARTS)
701
+ .getAll(IDBKeyRange.bound([key, 0], [key, count], false, true)));
702
+ return kept.map((part) => part.value);
703
+ },
704
+ async keepReceived(key, head, part) {
705
+ const transaction = database.transaction([RECEIVED, RECEIVED_PARTS], 'readwrite');
706
+ transaction.objectStore(RECEIVED).put({ key, head });
707
+ if (part !== undefined)
708
+ transaction.objectStore(RECEIVED_PARTS).put({ key, ...part });
709
+ await completed(transaction);
710
+ },
711
+ async dropReceived(keys) {
712
+ const transaction = database.transaction([RECEIVED, RECEIVED_PARTS], 'readwrite');
713
+ for (const key of keys) {
714
+ transaction.objectStore(RECEIVED).delete(key);
715
+ transaction.objectStore(RECEIVED_PARTS).delete(IDBKeyRange.bound([key], [key, []]));
716
+ }
717
+ await completed(transaction);
718
+ },
489
719
  async generation() {
490
720
  const transaction = authored.transaction(FACTS, 'readonly');
491
721
  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"}
package/dist/turn.js ADDED
@@ -0,0 +1,45 @@
1
+ export function yielded() {
2
+ return new Promise((resolve) => {
3
+ const channel = new MessageChannel();
4
+ channel.port1.onmessage = () => {
5
+ channel.port1.close();
6
+ resolve();
7
+ };
8
+ channel.port2.postMessage(null);
9
+ });
10
+ }
11
+ const UNIT = 1 << 20;
12
+ export const ROW_WEIGHT = 256;
13
+ export function after(value, next) {
14
+ return value instanceof Promise ? value.then(next) : next(value);
15
+ }
16
+ async function resumed(iterator, each) {
17
+ let spent = UNIT;
18
+ for (let next = iterator.next(); next.done !== true; next = iterator.next()) {
19
+ if (spent >= UNIT) {
20
+ await yielded();
21
+ spent = 0;
22
+ }
23
+ spent += each(next.value);
24
+ }
25
+ }
26
+ export function inUnits(items, each) {
27
+ const iterator = items[Symbol.iterator]();
28
+ let spent = 0;
29
+ for (let next = iterator.next(); next.done !== true; next = iterator.next()) {
30
+ spent += each(next.value);
31
+ if (spent >= UNIT)
32
+ return resumed(iterator, each);
33
+ }
34
+ }
35
+ export async function inChunks(items, weigh, each) {
36
+ for (let at = 0; at < items.length;) {
37
+ await yielded();
38
+ const chunk = [];
39
+ for (let spent = 0; at < items.length && spent < UNIT; at += 1) {
40
+ chunk.push(items[at]);
41
+ spent += weigh(items[at]);
42
+ }
43
+ each(chunk);
44
+ }
45
+ }