@agentshouse/mdruntime 0.1.0-c56daf494ecb4 → 0.1.0-c5d09b1c896fb

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 (126) hide show
  1. package/AGENTS.md +261 -261
  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/bootstrap.d.ts +2 -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/kit.d.ts +2 -1
  17. package/dist/chat/kit.d.ts.map +1 -1
  18. package/dist/chat/kit.js +3 -3
  19. package/dist/chat/list.d.ts +2 -1
  20. package/dist/chat/list.d.ts.map +1 -1
  21. package/dist/chat/list.js +4 -3
  22. package/dist/chat/message.d.ts.map +1 -1
  23. package/dist/chat/message.js +2 -2
  24. package/dist/chat/messages.d.ts +1 -0
  25. package/dist/chat/messages.d.ts.map +1 -1
  26. package/dist/chat/messages.js +4 -0
  27. package/dist/chat/provider.d.ts +1 -1
  28. package/dist/chat/provider.d.ts.map +1 -1
  29. package/dist/chat/provider.js +51 -67
  30. package/dist/chat/ready.d.ts +1 -0
  31. package/dist/chat/ready.d.ts.map +1 -1
  32. package/dist/chat/ready.js +2 -2
  33. package/dist/client.d.ts +12 -8
  34. package/dist/client.d.ts.map +1 -1
  35. package/dist/client.js +3 -1
  36. package/dist/collection.d.ts +17 -3
  37. package/dist/collection.d.ts.map +1 -1
  38. package/dist/collection.js +230 -19
  39. package/dist/connection.d.ts +22 -7
  40. package/dist/connection.d.ts.map +1 -1
  41. package/dist/connection.js +1253 -309
  42. package/dist/conversation.d.ts +14 -7
  43. package/dist/conversation.d.ts.map +1 -1
  44. package/dist/conversation.js +328 -137
  45. package/dist/durability.d.ts +7 -1
  46. package/dist/durability.d.ts.map +1 -1
  47. package/dist/entry.d.ts +2 -2
  48. package/dist/entry.d.ts.map +1 -1
  49. package/dist/entry.js +3 -3
  50. package/dist/fence.d.ts +6 -0
  51. package/dist/fence.d.ts.map +1 -0
  52. package/dist/fence.js +10 -0
  53. package/dist/host.d.ts +13 -2
  54. package/dist/host.d.ts.map +1 -1
  55. package/dist/host.js +49 -7
  56. package/dist/identity.d.ts +2 -0
  57. package/dist/identity.d.ts.map +1 -1
  58. package/dist/identity.js +6 -0
  59. package/dist/index.d.ts +9 -6
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +2 -0
  62. package/dist/memory.d.ts +4 -1
  63. package/dist/memory.d.ts.map +1 -1
  64. package/dist/memory.js +23 -2
  65. package/dist/model.d.ts +4 -2
  66. package/dist/model.d.ts.map +1 -1
  67. package/dist/model.js +10 -1
  68. package/dist/observation.d.ts +5 -2
  69. package/dist/observation.d.ts.map +1 -1
  70. package/dist/observation.js +22 -11
  71. package/dist/outcome.d.ts +4 -0
  72. package/dist/outcome.d.ts.map +1 -1
  73. package/dist/outcome.js +1 -0
  74. package/dist/overlay.d.ts +2 -0
  75. package/dist/overlay.d.ts.map +1 -1
  76. package/dist/overlay.js +58 -55
  77. package/dist/query.d.ts +32 -3
  78. package/dist/query.d.ts.map +1 -1
  79. package/dist/query.js +3 -1
  80. package/dist/reach.d.ts +6 -3
  81. package/dist/reach.d.ts.map +1 -1
  82. package/dist/reach.js +17 -5
  83. package/dist/react.d.ts +7 -4
  84. package/dist/react.d.ts.map +1 -1
  85. package/dist/react.js +6 -4
  86. package/dist/read-side.d.ts +13 -6
  87. package/dist/read-side.d.ts.map +1 -1
  88. package/dist/read-side.js +285 -83
  89. package/dist/readiness.d.ts +53 -0
  90. package/dist/readiness.d.ts.map +1 -0
  91. package/dist/readiness.js +33 -0
  92. package/dist/recovery.d.ts +25 -0
  93. package/dist/recovery.d.ts.map +1 -0
  94. package/dist/recovery.js +197 -0
  95. package/dist/refresh.d.ts.map +1 -1
  96. package/dist/refresh.js +17 -10
  97. package/dist/replica.d.ts +1 -1
  98. package/dist/replica.d.ts.map +1 -1
  99. package/dist/replica.js +57 -20
  100. package/dist/room-draft.d.ts +13 -1
  101. package/dist/room-draft.d.ts.map +1 -1
  102. package/dist/room-draft.js +2 -2
  103. package/dist/rows.d.ts +5 -13
  104. package/dist/rows.d.ts.map +1 -1
  105. package/dist/rows.js +5 -13
  106. package/dist/save.d.ts +5 -0
  107. package/dist/save.d.ts.map +1 -1
  108. package/dist/save.js +36 -13
  109. package/dist/search.d.ts +34 -6
  110. package/dist/search.d.ts.map +1 -1
  111. package/dist/search.js +116 -45
  112. package/dist/shared.d.ts +11 -0
  113. package/dist/shared.d.ts.map +1 -0
  114. package/dist/shared.js +368 -0
  115. package/dist/status.d.ts +4 -0
  116. package/dist/status.d.ts.map +1 -1
  117. package/dist/store.d.ts +42 -11
  118. package/dist/store.d.ts.map +1 -1
  119. package/dist/store.js +276 -83
  120. package/dist/turn.d.ts +7 -0
  121. package/dist/turn.d.ts.map +1 -0
  122. package/dist/turn.js +45 -0
  123. package/dist/wire.d.ts +136 -32
  124. package/dist/wire.d.ts.map +1 -1
  125. package/dist/wire.js +48 -31
  126. 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';
@@ -16,18 +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
25
  const HOLDER = 'holder';
21
- export const STORE_SCHEMA = 23;
22
- export const AUTHORED_SCHEMA = 1;
26
+ const BINDING = 'binding';
27
+ export const STORE_SCHEMA = 25;
28
+ const REPLICA_SHAPE = 23;
29
+ export const AUTHORED_SCHEMA = 2;
23
30
  const AUTHORED_STORES = [
24
31
  [QUEUES, { keyPath: 'locator' }],
25
32
  [PENDING_CHANGES, { keyPath: ['locator', 'id'] }],
26
33
  [ROOM_DRAFTS, { keyPath: ['locator', 'path'] }],
27
34
  [CHAT_DRAFTS, { keyPath: 'conversation' }],
28
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'] }],
29
49
  ];
30
50
  const READ_BATCH = 256;
51
+ const MESSAGE_INDEXES = [
52
+ ['conversation', 'conversation'],
53
+ ['position', ['conversation', 'position']],
54
+ ['turn', ['conversation', 'turn']],
55
+ ];
31
56
  function settled(request) {
32
57
  return new Promise((resolve, reject) => {
33
58
  request.onsuccess = () => resolve(request.result);
@@ -70,8 +95,11 @@ function amendPending(database, locator, id, amendment) {
70
95
  };
71
96
  return completed(transaction);
72
97
  }
98
+ function accepted({ conversation: _held, ...message }) {
99
+ return message;
100
+ }
73
101
  function roomRecord(record) {
74
- const { documents, binaries, revisions, changes, written, ...room } = record;
102
+ const { documents, binaries, revisions, fileIds, changes, written, ...room } = record;
75
103
  return room;
76
104
  }
77
105
  function chatDraftRecord(record) {
@@ -97,13 +125,15 @@ async function readTransfer(store, locator, transfer, receive) {
97
125
  }
98
126
  }
99
127
  function emptyLayer() {
100
- return { documents: {}, binaries: {}, revisions: {}, changes: {}, written: {} };
128
+ return { documents: {}, binaries: {}, revisions: {}, fileIds: {}, changes: {}, written: {} };
101
129
  }
102
130
  async function readLayer(transaction, locator, transfer) {
103
131
  const layer = emptyLayer();
104
132
  const head = (held) => {
105
133
  if (held.revision !== null)
106
134
  layer.revisions[held.path] = held.revision;
135
+ if (held.fileId !== null)
136
+ layer.fileIds[held.path] = held.fileId;
107
137
  if (held.change !== null)
108
138
  layer.changes[held.path] = held.change;
109
139
  };
@@ -126,6 +156,7 @@ function documentRecord(locator, transfer, layer, path) {
126
156
  path,
127
157
  content: layer.documents[path],
128
158
  revision: layer.revisions[path] ?? null,
159
+ fileId: layer.fileIds[path] ?? null,
129
160
  change: layer.changes[path] ?? null,
130
161
  written: layer.written[path] ?? null,
131
162
  };
@@ -136,35 +167,37 @@ function binaryFileRecord(locator, transfer, layer, path) {
136
167
  transfer,
137
168
  path,
138
169
  revision: layer.revisions[path] ?? null,
170
+ fileId: layer.fileIds[path] ?? null,
139
171
  change: layer.changes[path] ?? null,
140
172
  binary: layer.binaries[path],
141
173
  };
142
174
  }
143
175
  function writeLayer(transaction, locator, transfer, next, base) {
144
176
  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
177
  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])) {
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) {
166
196
  files.put(binaryFileRecord(locator, transfer, next, path));
167
197
  }
198
+ else if (base?.binaries[path] !== undefined) {
199
+ files.delete([locator, transfer, path]);
200
+ }
168
201
  }
169
202
  }
170
203
  function removeRoomEntries(store, locator) {
@@ -215,6 +248,29 @@ export async function authoredBy(holder) {
215
248
  authored.close();
216
249
  }
217
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
+ }
218
274
  export async function removeRuntimeStore(holder) {
219
275
  await removeDatabase(DATABASE);
220
276
  if (holder !== undefined && (await authoredBy(holder)))
@@ -222,38 +278,51 @@ export async function removeRuntimeStore(holder) {
222
278
  await removeDatabase(AUTHORED);
223
279
  return false;
224
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
+ });
296
+ }
225
297
  function openReplica() {
226
- const opening = indexedDB.open(DATABASE, STORE_SCHEMA);
227
- opening.onupgradeneeded = () => {
298
+ return openDatabase(DATABASE, STORE_SCHEMA, (opening, oldVersion) => {
228
299
  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);
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
+ });
244
315
  }
245
316
  function openAuthored() {
246
- const opening = indexedDB.open(AUTHORED, AUTHORED_SCHEMA);
247
- opening.onupgradeneeded = () => {
317
+ return openDatabase(AUTHORED, AUTHORED_SCHEMA, (opening) => {
248
318
  const database = opening.result;
249
319
  for (const [name, parameters] of AUTHORED_STORES) {
250
320
  if (!database.objectStoreNames.contains(name))
251
321
  database.createObjectStore(name, parameters);
252
322
  }
253
- };
254
- return settled(opening);
323
+ });
255
324
  }
256
- export async function openRuntimeStore() {
325
+ export async function openRuntimeStore(fenced) {
257
326
  const authored = await openAuthored();
258
327
  const database = await openReplica().catch((error) => {
259
328
  authored.close();
@@ -264,12 +333,14 @@ export async function openRuntimeStore() {
264
333
  const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES], 'readonly');
265
334
  const rooms = await settled(transaction.objectStore(ROOMS).getAll());
266
335
  const records = [];
267
- for (const room of rooms) {
336
+ for (const room of rooms.filter((held) => !fenced(held.locator))) {
268
337
  records.push({ ...room, ...(await readLayer(transaction, room.locator, room.transfer)) });
269
338
  }
270
339
  return records;
271
340
  },
272
341
  async keepAuthored({ locator, queue, saved, roomDrafts, consumedRoomDrafts }) {
342
+ if (fenced(locator))
343
+ return;
273
344
  const transaction = authored.transaction([QUEUES, PENDING_CHANGES, ROOM_DRAFTS], 'readwrite');
274
345
  const order = transaction.objectStore(QUEUES);
275
346
  const pending = transaction.objectStore(PENDING_CHANGES);
@@ -306,7 +377,7 @@ export async function openRuntimeStore() {
306
377
  await completed(transaction);
307
378
  },
308
379
  async write({ next, previous, stored }) {
309
- if (next === previous)
380
+ if (next === previous || fenced(next.locator))
310
381
  return;
311
382
  const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES, TRANSFERS], 'readwrite');
312
383
  transaction.objectStore(ROOMS).put(roomRecord(next));
@@ -326,6 +397,8 @@ export async function openRuntimeStore() {
326
397
  const rooms = transaction.objectStore(ROOMS);
327
398
  const revoked = { status: 'unavailable', reason: 'revoked' };
328
399
  for (const record of records) {
400
+ if (fenced(record.locator))
401
+ continue;
329
402
  rooms.put(roomRecord({ ...record, model: revoked, selectionModel: revoked }));
330
403
  }
331
404
  await completed(transaction);
@@ -359,6 +432,16 @@ export async function openRuntimeStore() {
359
432
  transaction.objectStore(DECLARATIONS).put(member, MEMBER);
360
433
  await completed(transaction);
361
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
+ },
362
445
  async queues() {
363
446
  const transaction = authored.transaction([QUEUES, PENDING_CHANGES], 'readonly');
364
447
  const [queues, pending] = await Promise.all([
@@ -366,7 +449,9 @@ export async function openRuntimeStore() {
366
449
  settled(transaction.objectStore(PENDING_CHANGES).getAll()),
367
450
  ]);
368
451
  const byId = new Map(pending.map((record) => [`${record.locator}\0${record.id}`, record]));
369
- return queues.map((queue) => ({
452
+ return queues
453
+ .filter((queue) => !fenced(queue.locator))
454
+ .map((queue) => ({
370
455
  locator: queue.locator,
371
456
  pending: queue.order.map((id) => {
372
457
  const record = byId.get(`${queue.locator}\0${id}`);
@@ -377,18 +462,22 @@ export async function openRuntimeStore() {
377
462
  }),
378
463
  }));
379
464
  },
380
- submit(locator, id, submission, lost) {
381
- 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 });
382
468
  },
383
- accept(locator, id, roomVersion) {
384
- 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 });
385
472
  },
386
- keepUpload(locator, id, upload) {
387
- return amendPending(authored, locator, id, { upload });
473
+ async keepUpload(locator, id, upload) {
474
+ if (!fenced(locator))
475
+ await amendPending(authored, locator, id, { upload });
388
476
  },
389
477
  async roomDrafts() {
390
478
  const transaction = authored.transaction(ROOM_DRAFTS, 'readonly');
391
- 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));
392
481
  },
393
482
  async presentation() {
394
483
  const transaction = database.transaction(PRESENTATION, 'readonly');
@@ -401,39 +490,91 @@ export async function openRuntimeStore() {
401
490
  },
402
491
  async conversations() {
403
492
  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
- }
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
+ });
417
515
  return [...held.values()];
418
516
  },
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
- }
517
+ async keepMessages({ conversation, messages, read }) {
425
518
  const transaction = database.transaction([CONVERSATIONS, MESSAGES], 'readwrite');
426
519
  const stored = transaction.objectStore(MESSAGES);
427
520
  for (const message of messages) {
428
521
  stored.put({ ...message, conversation });
429
522
  }
430
523
  if (read !== undefined) {
431
- transaction
432
- .objectStore(CONVERSATIONS)
433
- .put({ conversation, read });
524
+ transaction.objectStore(CONVERSATIONS).put({
525
+ conversation,
526
+ read: read.at,
527
+ ...(read.partial ? { partial: true } : {}),
528
+ });
434
529
  }
435
530
  await completed(transaction);
436
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
+ },
437
578
  async dropConversations(conversations) {
438
579
  const drafts = authored.transaction(CHAT_DRAFTS, 'readwrite');
439
580
  for (const conversation of conversations)
@@ -456,9 +597,10 @@ export async function openRuntimeStore() {
456
597
  const held = await settled(transaction.objectStore(CHAT_DRAFTS).getAll());
457
598
  return held.map(chatDraftRecord);
458
599
  },
459
- async keepChatDraft(record) {
600
+ async keepChatDrafts(records) {
460
601
  const transaction = authored.transaction(CHAT_DRAFTS, 'readwrite');
461
- transaction.objectStore(CHAT_DRAFTS).put(record);
602
+ for (const record of records)
603
+ transaction.objectStore(CHAT_DRAFTS).put(record);
462
604
  await completed(transaction);
463
605
  },
464
606
  async dropChatDraft(conversation) {
@@ -467,18 +609,28 @@ export async function openRuntimeStore() {
467
609
  await completed(transaction);
468
610
  },
469
611
  async keepRoomDraft(record) {
612
+ if (fenced(record.locator))
613
+ return;
470
614
  const transaction = authored.transaction(ROOM_DRAFTS, 'readwrite');
471
615
  transaction.objectStore(ROOM_DRAFTS).put(record);
472
616
  await completed(transaction);
473
617
  },
474
618
  async discardRoomDraft(locator, path) {
619
+ if (fenced(locator))
620
+ return;
475
621
  const transaction = authored.transaction(ROOM_DRAFTS, 'readwrite');
476
622
  transaction.objectStore(ROOM_DRAFTS).delete([locator, path]);
477
623
  await completed(transaction);
478
624
  },
479
- async dropRooms(locators) {
480
- 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');
481
632
  for (const locator of locators) {
633
+ work.objectStore(FENCES).put({ locator });
482
634
  work.objectStore(QUEUES).delete(locator);
483
635
  for (const name of [PENDING_CHANGES, ROOM_DRAFTS]) {
484
636
  removeRoomEntries(work.objectStore(name), locator);
@@ -493,22 +645,37 @@ export async function openRuntimeStore() {
493
645
  transaction.objectStore(BINARY_FILES).delete(underRoom(locator));
494
646
  }
495
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);
496
658
  },
497
659
  async transfers() {
498
660
  const transaction = database.transaction(TRANSFERS, 'readonly');
499
- return settled(transaction.objectStore(TRANSFERS).getAll());
661
+ const held = await settled(transaction.objectStore(TRANSFERS).getAll());
662
+ return held.filter((record) => !fenced(record.locator));
500
663
  },
501
664
  staged(record) {
502
665
  const transaction = database.transaction([DOCUMENTS, BINARY_FILES], 'readonly');
503
666
  return readLayer(transaction, record.locator, record.transfer);
504
667
  },
505
668
  async stage(record, page) {
669
+ if (fenced(record.locator))
670
+ return;
506
671
  const transaction = database.transaction([TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite');
507
672
  writeLayer(transaction, record.locator, record.transfer, page, null);
508
673
  transaction.objectStore(TRANSFERS).put(record);
509
674
  await completed(transaction);
510
675
  },
511
676
  async abandon(locator) {
677
+ if (fenced(locator))
678
+ return;
512
679
  const transaction = database.transaction([TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite');
513
680
  const transfers = transaction.objectStore(TRANSFERS);
514
681
  const held = transfers.get(locator);
@@ -523,6 +690,32 @@ export async function openRuntimeStore() {
523
690
  };
524
691
  await completed(transaction);
525
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
+ },
526
719
  async generation() {
527
720
  const transaction = authored.transaction(FACTS, 'readonly');
528
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
+ }