@agentshouse/mdruntime 0.1.0-c5d3fa6fb67bc → 0.1.0-c5f333dfa0583

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 (198) hide show
  1. package/AGENTS.md +225 -212
  2. package/README.md +9 -8
  3. package/dist/adapter.d.ts +64 -24
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/attached-model.d.ts +4 -1
  6. package/dist/attached-model.d.ts.map +1 -1
  7. package/dist/attached-model.js +13 -3
  8. package/dist/bootstrap.d.ts +15 -7
  9. package/dist/bootstrap.d.ts.map +1 -1
  10. package/dist/bootstrap.js +90 -61
  11. package/dist/chain.d.ts +11 -0
  12. package/dist/chain.d.ts.map +1 -0
  13. package/dist/chain.js +64 -0
  14. package/dist/change-set.d.ts +8 -9
  15. package/dist/change-set.d.ts.map +1 -1
  16. package/dist/change-set.js +10 -85
  17. package/dist/change.d.ts +31 -1
  18. package/dist/change.d.ts.map +1 -1
  19. package/dist/change.js +10 -1
  20. package/dist/chat/earlier.d.ts +10 -0
  21. package/dist/chat/earlier.d.ts.map +1 -0
  22. package/dist/chat/earlier.js +54 -0
  23. package/dist/chat/house.d.ts +25 -0
  24. package/dist/chat/house.d.ts.map +1 -0
  25. package/dist/chat/house.js +38 -0
  26. package/dist/chat/input.d.ts +3 -1
  27. package/dist/chat/input.d.ts.map +1 -1
  28. package/dist/chat/input.js +11 -18
  29. package/dist/chat/kit.js +1 -1
  30. package/dist/chat/list.d.ts +2 -1
  31. package/dist/chat/list.d.ts.map +1 -1
  32. package/dist/chat/list.js +45 -5
  33. package/dist/chat/markdown.d.ts +1 -0
  34. package/dist/chat/markdown.d.ts.map +1 -1
  35. package/dist/chat/markdown.js +78 -20
  36. package/dist/chat/message.d.ts +1 -0
  37. package/dist/chat/message.d.ts.map +1 -1
  38. package/dist/chat/message.js +73 -11
  39. package/dist/chat/messages.d.ts +14 -1
  40. package/dist/chat/messages.d.ts.map +1 -1
  41. package/dist/chat/messages.js +79 -24
  42. package/dist/chat/name.d.ts +1 -0
  43. package/dist/chat/name.d.ts.map +1 -1
  44. package/dist/chat/name.js +1 -1
  45. package/dist/chat/opened.d.ts +1 -0
  46. package/dist/chat/opened.d.ts.map +1 -1
  47. package/dist/chat/opened.js +2 -0
  48. package/dist/chat/overlay.d.ts +1 -0
  49. package/dist/chat/overlay.d.ts.map +1 -1
  50. package/dist/chat/overlay.js +11 -5
  51. package/dist/chat/parts.d.ts +1 -0
  52. package/dist/chat/parts.d.ts.map +1 -1
  53. package/dist/chat/parts.js +3 -2
  54. package/dist/chat/pieces.d.ts +62 -0
  55. package/dist/chat/pieces.d.ts.map +1 -0
  56. package/dist/chat/pieces.js +1110 -0
  57. package/dist/chat/provider.d.ts +8 -1
  58. package/dist/chat/provider.d.ts.map +1 -1
  59. package/dist/chat/provider.js +98 -92
  60. package/dist/chat/question.d.ts +1 -1
  61. package/dist/chat/question.d.ts.map +1 -1
  62. package/dist/chat/question.js +7 -18
  63. package/dist/chat/ready.d.ts +5 -2
  64. package/dist/chat/ready.d.ts.map +1 -1
  65. package/dist/chat/ready.js +7 -5
  66. package/dist/chat/room-file.d.ts +8 -0
  67. package/dist/chat/room-file.d.ts.map +1 -0
  68. package/dist/chat/room-file.js +72 -0
  69. package/dist/chat/steady.d.ts +3 -0
  70. package/dist/chat/steady.d.ts.map +1 -0
  71. package/dist/chat/steady.js +26 -0
  72. package/dist/chat/style.d.ts.map +1 -1
  73. package/dist/chat/style.js +6 -1
  74. package/dist/chat.d.ts +3 -2
  75. package/dist/chat.d.ts.map +1 -1
  76. package/dist/chat.js +2 -2
  77. package/dist/client.d.ts +11 -2
  78. package/dist/client.d.ts.map +1 -1
  79. package/dist/client.js +45 -7
  80. package/dist/collection.d.ts +6 -19
  81. package/dist/collection.d.ts.map +1 -1
  82. package/dist/collection.js +30 -287
  83. package/dist/compile.d.ts +11 -0
  84. package/dist/compile.d.ts.map +1 -0
  85. package/dist/compile.js +712 -0
  86. package/dist/connection.d.ts +21 -8
  87. package/dist/connection.d.ts.map +1 -1
  88. package/dist/connection.js +2319 -688
  89. package/dist/conversation.d.ts +15 -3
  90. package/dist/conversation.d.ts.map +1 -1
  91. package/dist/conversation.js +233 -80
  92. package/dist/digest.d.ts +7 -0
  93. package/dist/digest.d.ts.map +1 -0
  94. package/dist/digest.js +113 -0
  95. package/dist/durability.d.ts +2 -1
  96. package/dist/durability.d.ts.map +1 -1
  97. package/dist/entry.d.ts +3 -1
  98. package/dist/entry.d.ts.map +1 -1
  99. package/dist/entry.js +18 -23
  100. package/dist/file.d.ts +2 -10
  101. package/dist/file.d.ts.map +1 -1
  102. package/dist/file.js +3 -120
  103. package/dist/host.d.ts +5 -4
  104. package/dist/host.d.ts.map +1 -1
  105. package/dist/host.js +55 -7
  106. package/dist/index.d.ts +10 -6
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +4 -0
  109. package/dist/live.d.ts +22 -0
  110. package/dist/live.d.ts.map +1 -0
  111. package/dist/live.js +123 -0
  112. package/dist/memory.d.ts +7 -3
  113. package/dist/memory.d.ts.map +1 -1
  114. package/dist/memory.js +39 -17
  115. package/dist/name.d.ts +4 -0
  116. package/dist/name.d.ts.map +1 -0
  117. package/dist/name.js +122 -0
  118. package/dist/observation.d.ts +14 -0
  119. package/dist/observation.d.ts.map +1 -1
  120. package/dist/observation.js +93 -3
  121. package/dist/opened.d.ts +22 -0
  122. package/dist/opened.d.ts.map +1 -0
  123. package/dist/opened.js +107 -0
  124. package/dist/outcome.d.ts +35 -4
  125. package/dist/outcome.d.ts.map +1 -1
  126. package/dist/outcome.js +28 -0
  127. package/dist/overlay.d.ts +2 -5
  128. package/dist/overlay.d.ts.map +1 -1
  129. package/dist/overlay.js +86 -171
  130. package/dist/query.d.ts +26 -2
  131. package/dist/query.d.ts.map +1 -1
  132. package/dist/query.js +34 -6
  133. package/dist/reach.d.ts +5 -1
  134. package/dist/reach.d.ts.map +1 -1
  135. package/dist/reach.js +167 -48
  136. package/dist/react.d.ts +3 -3
  137. package/dist/react.d.ts.map +1 -1
  138. package/dist/react.js +7 -17
  139. package/dist/readiness.d.ts +1 -1
  140. package/dist/readiness.d.ts.map +1 -1
  141. package/dist/reads.d.ts +11 -0
  142. package/dist/reads.d.ts.map +1 -0
  143. package/dist/reads.js +98 -0
  144. package/dist/recovery.d.ts +2 -2
  145. package/dist/recovery.d.ts.map +1 -1
  146. package/dist/recovery.js +8 -7
  147. package/dist/refresh.d.ts +12 -15
  148. package/dist/refresh.d.ts.map +1 -1
  149. package/dist/refresh.js +74 -51
  150. package/dist/replica.d.ts +55 -33
  151. package/dist/replica.d.ts.map +1 -1
  152. package/dist/replica.js +295 -186
  153. package/dist/room-draft.d.ts +15 -1
  154. package/dist/room-draft.d.ts.map +1 -1
  155. package/dist/room-draft.js +32 -25
  156. package/dist/rows.d.ts +29 -30
  157. package/dist/rows.d.ts.map +1 -1
  158. package/dist/rows.js +113 -64
  159. package/dist/save.d.ts +51 -14
  160. package/dist/save.d.ts.map +1 -1
  161. package/dist/save.js +518 -180
  162. package/dist/search.d.ts +19 -4
  163. package/dist/search.d.ts.map +1 -1
  164. package/dist/search.js +192 -35
  165. package/dist/seed.d.ts +16 -0
  166. package/dist/seed.d.ts.map +1 -0
  167. package/dist/seed.js +75 -0
  168. package/dist/shared.d.ts +8 -3
  169. package/dist/shared.d.ts.map +1 -1
  170. package/dist/shared.js +129 -66
  171. package/dist/store.d.ts +99 -71
  172. package/dist/store.d.ts.map +1 -1
  173. package/dist/store.js +461 -586
  174. package/dist/stored.d.ts +143 -0
  175. package/dist/stored.d.ts.map +1 -0
  176. package/dist/stored.js +561 -0
  177. package/dist/tables.d.ts +320 -0
  178. package/dist/tables.d.ts.map +1 -0
  179. package/dist/tables.js +537 -0
  180. package/dist/turn.d.ts +5 -3
  181. package/dist/turn.d.ts.map +1 -1
  182. package/dist/turn.js +51 -21
  183. package/dist/validation.d.ts +30 -0
  184. package/dist/validation.d.ts.map +1 -0
  185. package/dist/validation.js +162 -0
  186. package/dist/windows.d.ts +22 -0
  187. package/dist/windows.d.ts.map +1 -0
  188. package/dist/windows.js +89 -0
  189. package/dist/wire.d.ts +32 -0
  190. package/dist/wire.d.ts.map +1 -1
  191. package/dist/wire.js +8 -0
  192. package/package.json +10 -3
  193. package/dist/acquisition.d.ts +0 -14
  194. package/dist/acquisition.d.ts.map +0 -1
  195. package/dist/acquisition.js +0 -24
  196. package/dist/read-side.d.ts +0 -25
  197. package/dist/read-side.d.ts.map +0 -1
  198. package/dist/read-side.js +0 -642
package/dist/store.js CHANGED
@@ -1,49 +1,11 @@
1
- import { sameBinary, sameChange } from './overlay.js';
2
- import { differing } from './shared.js';
1
+ import { precedes } from './windows.js';
2
+ import { binaryLocalId } from './identity.js';
3
3
  const DATABASE = 'mdruntime';
4
- const AUTHORED = 'mdruntime-authored';
5
- const FENCES = 'fences';
6
- const ROOMS = 'rooms';
7
- const DOCUMENTS = 'documents';
8
- const BINARY_FILES = 'binaryFiles';
9
- const TRANSFERS = 'transfers';
10
- const RECEIVED = 'received';
11
- const RECEIVED_PARTS = 'receivedParts';
12
- const PENDING_CHANGES = 'pendingChanges';
13
- const DECLARATIONS = 'declarations';
14
- const ROOM_DRAFTS = 'roomDrafts';
15
- const PRESENTATION = 'presentation';
16
4
  const CONVERSATIONS = 'conversations';
17
5
  const MESSAGES = 'messages';
18
- const CHAT_DRAFTS = 'chatDrafts';
19
- const QUEUES = 'queues';
20
- const FACTS = 'facts';
21
- const CURRENT = 'current';
22
- const MEMBER = 'member';
23
- const TARGETS = 'targets';
24
- const GENERATION = 'generation';
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;
30
- const AUTHORED_STORES = [
31
- [QUEUES, { keyPath: 'locator' }],
32
- [PENDING_CHANGES, { keyPath: ['locator', 'id'] }],
33
- [ROOM_DRAFTS, { keyPath: ['locator', 'path'] }],
34
- [CHAT_DRAFTS, { keyPath: 'conversation' }],
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' }],
6
+ const FILE = '\u0000file';
7
+ export const STORE_SCHEMA = 26;
8
+ const STORES = [
47
9
  [CONVERSATIONS, { keyPath: 'conversation' }],
48
10
  [MESSAGES, { keyPath: ['conversation', 'identity'] }],
49
11
  ];
@@ -59,155 +21,56 @@ function settled(request) {
59
21
  request.onerror = () => reject(request.error);
60
22
  });
61
23
  }
62
- function completed(transaction) {
63
- return new Promise((resolve, reject) => {
64
- transaction.oncomplete = () => resolve();
65
- transaction.onerror = () => reject(transaction.error);
66
- transaction.onabort = () => reject(transaction.error);
67
- });
68
- }
69
- const EMPTY_DECLARATIONS = {
70
- lastCompleted: null,
71
- providerLimits: [],
72
- capabilities: [],
73
- };
74
- function declaration(database) {
75
- const transaction = database.transaction(DECLARATIONS, 'readonly');
76
- return settled(transaction.objectStore(DECLARATIONS).get(CURRENT)).then((record) => record ?? EMPTY_DECLARATIONS);
77
- }
78
- function updateDeclaration(database, update) {
79
- const transaction = database.transaction(DECLARATIONS, 'readwrite');
80
- const store = transaction.objectStore(DECLARATIONS);
81
- const prior = store.get(CURRENT);
82
- prior.onsuccess = () => {
83
- store.put(update(prior.result ?? EMPTY_DECLARATIONS), CURRENT);
84
- };
85
- return completed(transaction);
86
- }
87
- function amendPending(database, locator, id, amendment) {
88
- const transaction = database.transaction(PENDING_CHANGES, 'readwrite');
89
- const pending = transaction.objectStore(PENDING_CHANGES);
90
- const request = pending.get([locator, id]);
91
- request.onsuccess = () => {
92
- const record = request.result;
93
- if (record !== undefined)
94
- pending.put({ ...record, ...amendment });
95
- };
96
- return completed(transaction);
97
- }
98
- function accepted({ conversation: _held, ...message }) {
99
- return message;
100
- }
101
- function roomRecord(record) {
102
- const { documents, binaries, revisions, fileIds, changes, written, ...room } = record;
103
- return room;
104
- }
105
- function chatDraftRecord(record) {
106
- return { ...record, revision: record.revision ?? '' };
107
- }
108
- function underTransfer(locator, transfer, after) {
109
- return after === undefined
110
- ? IDBKeyRange.bound([locator, transfer], [locator, transfer, []])
111
- : IDBKeyRange.bound([locator, transfer, after], [locator, transfer, []], true);
112
- }
113
- function underRoom(locator) {
114
- return IDBKeyRange.bound([locator], [locator, []]);
115
- }
116
- async function readTransfer(store, locator, transfer, receive) {
117
- let range = underTransfer(locator, transfer);
118
- for (;;) {
119
- const batch = await settled(store.getAll(range, READ_BATCH));
120
- for (const record of batch)
121
- receive(record);
122
- if (batch.length < READ_BATCH)
123
- return;
124
- range = underTransfer(locator, transfer, batch[batch.length - 1].path);
125
- }
126
- }
127
- function emptyLayer() {
128
- return { documents: {}, binaries: {}, revisions: {}, fileIds: {}, changes: {}, written: {} };
24
+ const turns = new Map();
25
+ function idle(database) {
26
+ return turns.get(database.name) ?? Promise.resolve();
129
27
  }
130
- async function readLayer(transaction, locator, transfer) {
131
- const layer = emptyLayer();
132
- const head = (held) => {
133
- if (held.revision !== null)
134
- layer.revisions[held.path] = held.revision;
135
- if (held.fileId !== null)
136
- layer.fileIds[held.path] = held.fileId;
137
- if (held.change !== null)
138
- layer.changes[held.path] = held.change;
139
- };
140
- await readTransfer(transaction.objectStore(DOCUMENTS), locator, transfer, (document) => {
141
- layer.documents[document.path] = document.content;
142
- if (document.written !== null)
143
- layer.written[document.path] = document.written;
144
- head(document);
28
+ export function transacted(database, stores, mode, work) {
29
+ let release = () => { };
30
+ const turn = new Promise((resolve) => {
31
+ release = resolve;
145
32
  });
146
- await readTransfer(transaction.objectStore(BINARY_FILES), locator, transfer, (file) => {
147
- layer.binaries[file.path] = file.binary;
148
- head(file);
33
+ const before = idle(database);
34
+ const queued = before.then(() => turn);
35
+ turns.set(database.name, queued);
36
+ void queued.then(() => {
37
+ if (turns.get(database.name) === queued)
38
+ turns.delete(database.name);
149
39
  });
150
- return layer;
151
- }
152
- function documentRecord(locator, transfer, layer, path) {
153
- return {
154
- locator,
155
- transfer,
156
- path,
157
- content: layer.documents[path],
158
- revision: layer.revisions[path] ?? null,
159
- fileId: layer.fileIds[path] ?? null,
160
- change: layer.changes[path] ?? null,
161
- written: layer.written[path] ?? null,
162
- };
163
- }
164
- function binaryFileRecord(locator, transfer, layer, path) {
165
- return {
166
- locator,
167
- transfer,
168
- path,
169
- revision: layer.revisions[path] ?? null,
170
- fileId: layer.fileIds[path] ?? null,
171
- change: layer.changes[path] ?? null,
172
- binary: layer.binaries[path],
173
- };
174
- }
175
- function writeLayer(transaction, locator, transfer, next, base) {
176
- const documents = transaction.objectStore(DOCUMENTS);
177
- const files = transaction.objectStore(BINARY_FILES);
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));
40
+ return before.then(() => {
41
+ let transaction;
42
+ try {
43
+ transaction = database.transaction(stores, mode);
191
44
  }
192
- else if (base?.documents[path] !== undefined) {
193
- documents.delete([locator, transfer, path]);
45
+ catch (error) {
46
+ release();
47
+ throw error;
194
48
  }
195
- if (next.binaries[path] !== undefined) {
196
- files.put(binaryFileRecord(locator, transfer, next, path));
49
+ const committed = new Promise((resolve, reject) => {
50
+ transaction.oncomplete = () => {
51
+ release();
52
+ resolve();
53
+ };
54
+ transaction.onabort = () => {
55
+ release();
56
+ reject(transaction.error);
57
+ };
58
+ transaction.onerror = () => reject(transaction.error);
59
+ });
60
+ let value;
61
+ try {
62
+ value = work(transaction);
197
63
  }
198
- else if (base?.binaries[path] !== undefined) {
199
- files.delete([locator, transfer, path]);
64
+ catch (error) {
65
+ void committed.catch(() => { });
66
+ transaction.abort();
67
+ throw error;
200
68
  }
201
- }
69
+ return Promise.all([value, committed]).then(([held]) => held);
70
+ });
202
71
  }
203
- function removeRoomEntries(store, locator) {
204
- const keys = store.getAllKeys();
205
- keys.onsuccess = () => {
206
- for (const key of keys.result) {
207
- if (Array.isArray(key) && key[0] === locator)
208
- store.delete(key);
209
- }
210
- };
72
+ function accepted({ conversation: _held, ...message }) {
73
+ return message;
211
74
  }
212
75
  function removeDatabase(name) {
213
76
  return new Promise((resolve, reject) => {
@@ -216,67 +79,9 @@ function removeDatabase(name) {
216
79
  removal.onerror = () => reject(removal.error);
217
80
  });
218
81
  }
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) {
82
+ export async function endRuntimeStore(database, holder) {
275
83
  await removeDatabase(DATABASE);
276
- if (holder !== undefined && (await authoredBy(holder)))
277
- return true;
278
- await removeDatabase(AUTHORED);
279
- return false;
84
+ return database.ask({ kind: 'authored.end', holder: holder ?? null });
280
85
  }
281
86
  export function openDatabase(name, version, upgrade) {
282
87
  return new Promise((resolve, reject) => {
@@ -294,14 +99,14 @@ export function openDatabase(name, version, upgrade) {
294
99
  };
295
100
  });
296
101
  }
297
- function openReplica() {
102
+ function openConversations() {
298
103
  return openDatabase(DATABASE, STORE_SCHEMA, (opening, oldVersion) => {
299
104
  const database = opening.result;
300
- if (oldVersion < REPLICA_SHAPE) {
105
+ if (oldVersion < STORE_SCHEMA) {
301
106
  for (const name of [...database.objectStoreNames])
302
107
  database.deleteObjectStore(name);
303
108
  }
304
- for (const [name, parameters] of REPLICA_STORES) {
109
+ for (const [name, parameters] of STORES) {
305
110
  if (database.objectStoreNames.contains(name))
306
111
  continue;
307
112
  database.createObjectStore(name, parameters);
@@ -313,422 +118,492 @@ function openReplica() {
313
118
  }
314
119
  });
315
120
  }
316
- function openAuthored() {
317
- return openDatabase(AUTHORED, AUTHORED_SCHEMA, (opening) => {
318
- const database = opening.result;
319
- for (const [name, parameters] of AUTHORED_STORES) {
320
- if (!database.objectStoreNames.contains(name))
321
- database.createObjectStore(name, parameters);
121
+ function batchFiles(batch) {
122
+ return batch.changes.flatMap((change) => (change.kind === 'binary' ? [change.localId] : []));
123
+ }
124
+ function draftFiles(drafts) {
125
+ return drafts.flatMap(({ parts }) => (parts.file === undefined ? [] : [parts.file.localId]));
126
+ }
127
+ function roomDraftRow(record) {
128
+ return { path: record.path, record: JSON.stringify(record), files: draftFiles([record]) };
129
+ }
130
+ function authoredRows({ locator, queue, founded, saved, roomDrafts, consumedRoomDrafts, accepted }, held) {
131
+ return {
132
+ locator,
133
+ ...(queue === undefined
134
+ ? {}
135
+ : {
136
+ order: queue.map(({ id }) => id),
137
+ added: queue
138
+ .filter(({ id }) => held?.has(id) !== true)
139
+ .map((batch) => {
140
+ const drafts = saved?.get(batch.id) ?? [];
141
+ return {
142
+ id: batch.id,
143
+ batch: JSON.stringify(batch),
144
+ state: JSON.stringify({
145
+ drafts,
146
+ submission: { state: 'waiting' },
147
+ lost: false,
148
+ }),
149
+ files: [...batchFiles(batch), ...draftFiles(drafts)],
150
+ };
151
+ }),
152
+ }),
153
+ ...(founded === undefined
154
+ ? {}
155
+ : {
156
+ founded: founded.map((batch) => ({
157
+ id: batch.id,
158
+ batch: JSON.stringify(batch),
159
+ files: batchFiles(batch),
160
+ })),
161
+ }),
162
+ ...(roomDrafts === undefined ? {} : { roomDrafts: roomDrafts.map(roomDraftRow) }),
163
+ ...(consumedRoomDrafts === undefined ? {} : { consumedRoomDrafts }),
164
+ ...(accepted === undefined
165
+ ? {}
166
+ : {
167
+ amendments: accepted.map(({ id, roomVersion }) => ({
168
+ id,
169
+ amendment: JSON.stringify({ accepted: roomVersion }),
170
+ })),
171
+ }),
172
+ };
173
+ }
174
+ function marked(value) {
175
+ return typeof value === 'object' && value !== null && FILE in value;
176
+ }
177
+ function keyed(keys, value) {
178
+ const stored = [];
179
+ const files = [];
180
+ const record = JSON.stringify(value, (_name, held) => {
181
+ if (!(held instanceof Blob))
182
+ return held;
183
+ let key = keys.get(held);
184
+ if (key === undefined) {
185
+ key = binaryLocalId();
186
+ keys.set(held, key);
322
187
  }
188
+ stored.push({ key, blob: held });
189
+ files.push(key);
190
+ return { [FILE]: key, type: held.type };
323
191
  });
192
+ return { record, files, stored };
324
193
  }
325
- export async function openRuntimeStore(fenced) {
326
- const authored = await openAuthored();
327
- const database = await openReplica().catch((error) => {
328
- authored.close();
329
- throw error;
330
- });
194
+ async function filed(value, read) {
195
+ if (Array.isArray(value))
196
+ return Promise.all(value.map((item) => filed(item, read)));
197
+ if (marked(value))
198
+ return read(value[FILE], value.type);
199
+ if (typeof value !== 'object' || value === null)
200
+ return value;
201
+ const restored = {};
202
+ for (const [name, item] of Object.entries(value))
203
+ restored[name] = await filed(item, read);
204
+ return restored;
205
+ }
206
+ function json(value, fallback) {
207
+ return value === undefined ? fallback : JSON.parse(value);
208
+ }
209
+ export async function openRuntimeStore(fenced, authored) {
210
+ const database = await openConversations();
211
+ const keys = new WeakMap();
212
+ const read = async (key, type) => {
213
+ const blob = await authored.ask({ kind: 'authored.file', key, type });
214
+ keys.set(blob, key);
215
+ return blob;
216
+ };
217
+ const restored = async (records) => Promise.all(records.map(async (record) => (await filed(JSON.parse(record), read))));
218
+ const amend = async (locator, id, amendment) => {
219
+ if (fenced(locator))
220
+ return;
221
+ await authored.ask({
222
+ kind: 'authored.amend',
223
+ locator,
224
+ id,
225
+ amendment: JSON.stringify(amendment),
226
+ });
227
+ };
228
+ const fact = (name) => authored.ask({ kind: 'authored.fact', name });
229
+ const queued = new Map();
230
+ const changed = new Map();
231
+ const change = (key) => changed.set(key, (changed.get(key) ?? 0) + 1);
232
+ const rows = (writes) => writes.map((write) => authoredRows(write, queued.get(write.locator)));
233
+ const noted = (writes) => {
234
+ for (const { locator, queue } of writes) {
235
+ if (queue !== undefined)
236
+ queued.set(locator, new Set(queue.map(({ id }) => id)));
237
+ }
238
+ };
331
239
  return {
332
- async all() {
333
- const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES], 'readonly');
334
- const rooms = await settled(transaction.objectStore(ROOMS).getAll());
335
- const records = [];
336
- for (const room of rooms.filter((held) => !fenced(held.locator))) {
337
- records.push({ ...room, ...(await readLayer(transaction, room.locator, room.transfer)) });
240
+ async opened() {
241
+ const held = await authored.ask({ kind: 'replica.open' });
242
+ const unprepared = new Set(held.keys.flatMap(({ key, rederiving }) => (rederiving === null ? [] : [key])));
243
+ const overlaid = new Map();
244
+ for (const { key, path } of held.overlaid) {
245
+ const paths = overlaid.get(key) ?? new Set();
246
+ paths.add(path);
247
+ overlaid.set(key, paths);
338
248
  }
339
- return records;
340
- },
341
- async keepAuthored({ locator, queue, saved, roomDrafts, consumedRoomDrafts }) {
342
- if (fenced(locator))
343
- return;
344
- const transaction = authored.transaction([QUEUES, PENDING_CHANGES, ROOM_DRAFTS], 'readwrite');
345
- const order = transaction.objectStore(QUEUES);
346
- const pending = transaction.objectStore(PENDING_CHANGES);
347
- const held = order.get(locator);
348
- held.onsuccess = () => {
349
- const before = new Set(held.result?.order ?? []);
350
- const after = new Set(queue.map((batch) => batch.id));
351
- if (queue.length === 0)
352
- order.delete(locator);
353
- else
354
- order.put({ locator, order: [...after] });
355
- for (const id of before) {
356
- if (!after.has(id))
357
- pending.delete([locator, id]);
358
- }
359
- for (const batch of queue) {
360
- if (!before.has(batch.id)) {
361
- pending.put({
362
- locator,
363
- id: batch.id,
364
- batch,
365
- drafts: saved?.get(batch.id) ?? [],
366
- submission: { state: 'waiting' },
367
- lost: false,
368
- });
369
- }
370
- }
249
+ return {
250
+ rooms: new Map(held.rooms
251
+ .filter(({ locator }) => !fenced(locator))
252
+ .map(({ locator, key, record }) => [
253
+ locator,
254
+ { ...JSON.parse(record), key },
255
+ ])),
256
+ transfers: new Map(held.transfers
257
+ .filter(({ locator }) => !fenced(locator))
258
+ .map(({ locator, key, record }) => [
259
+ locator,
260
+ { ...JSON.parse(record), key },
261
+ ])),
262
+ unprepared,
263
+ overlaid,
264
+ erasing: held.erasing.length > 0,
265
+ orphans: held.orphans,
266
+ lastCompleted: json(held.facts.lastCompleted, null),
267
+ providerLimits: json(held.facts.providerLimits, []),
268
+ capabilities: json(held.facts.capabilities, []),
269
+ member: json(held.facts.member, null),
270
+ targets: json(held.facts.targets, {}),
271
+ presentation: held.presentation.map(({ app, value }) => ({
272
+ app,
273
+ value: JSON.parse(value),
274
+ })),
371
275
  };
372
- const draftStore = transaction.objectStore(ROOM_DRAFTS);
373
- for (const path of consumedRoomDrafts ?? [])
374
- draftStore.delete([locator, path]);
375
- for (const record of roomDrafts ?? [])
376
- draftStore.put(record);
377
- await completed(transaction);
378
- },
379
- async write({ next, previous, stored }) {
380
- if (next === previous || fenced(next.locator))
381
- return;
382
- const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES, TRANSFERS], 'readwrite');
383
- transaction.objectStore(ROOMS).put(roomRecord(next));
384
- const moved = previous?.transfer !== next.transfer;
385
- if (moved && previous !== null) {
386
- const range = underTransfer(previous.locator, previous.transfer);
387
- transaction.objectStore(DOCUMENTS).delete(range);
388
- transaction.objectStore(BINARY_FILES).delete(range);
389
- }
390
- if (moved)
391
- transaction.objectStore(TRANSFERS).delete(next.locator);
392
- writeLayer(transaction, next.locator, next.transfer, next, moved ? (stored ?? null) : previous);
393
- await completed(transaction);
394
- },
395
- async revokeModels(records) {
396
- const transaction = database.transaction(ROOMS, 'readwrite');
397
- const rooms = transaction.objectStore(ROOMS);
398
- const revoked = { status: 'unavailable', reason: 'revoked' };
399
- for (const record of records) {
400
- if (fenced(record.locator))
401
- continue;
402
- rooms.put(roomRecord({ ...record, model: revoked, selectionModel: revoked }));
403
- }
404
- await completed(transaction);
405
276
  },
406
- async lastCompleted() {
407
- return (await declaration(database)).lastCompleted;
277
+ counts(rooms) {
278
+ return authored.ask({ kind: 'replica.counts', rooms });
408
279
  },
409
- complete(at) {
410
- return updateDeclaration(database, (record) => ({ ...record, lastCompleted: at }));
280
+ query(plan, rooms, budget) {
281
+ return authored.ask({ kind: 'replica.query', plan, rooms, budget });
411
282
  },
412
- async providerLimits() {
413
- return (await declaration(database)).providerLimits;
283
+ changes(keys) {
284
+ return keys.reduce((total, key) => total + (changed.get(key) ?? 0), 0);
414
285
  },
415
- async capabilities() {
416
- return (await declaration(database)).capabilities;
286
+ entries(key, view, paths) {
287
+ return authored.ask({ kind: 'replica.entries', key, view, paths });
417
288
  },
418
- declare(limits, capabilities) {
419
- return updateDeclaration(database, (record) => ({
420
- ...record,
421
- providerLimits: limits,
422
- capabilities,
423
- }));
289
+ typed(key, view, paths) {
290
+ return authored.ask({ kind: 'replica.typed', key, view, paths });
291
+ },
292
+ referrers(key, view, targets, kinds) {
293
+ return authored.ask({ kind: 'replica.referrers', key, view, targets, kinds });
294
+ },
295
+ walk(key, view, after, bytes) {
296
+ return authored.ask({ kind: 'replica.walk', key, view, after, bytes });
297
+ },
298
+ files(key, view, after, limit) {
299
+ return authored.ask({ kind: 'replica.files', key, view, after, limit });
300
+ },
301
+ typedWithin(key, view, roots, within) {
302
+ return authored.ask({ kind: 'replica.typedWithin', key, view, roots, within });
303
+ },
304
+ searched(key, locator, after, limit, paths) {
305
+ return authored.ask({ kind: 'replica.searched', key, locator, after, limit, paths });
306
+ },
307
+ async stage(record, composition, page, key, last) {
308
+ if (fenced(record.locator))
309
+ return null;
310
+ const { key: _key, ...kept } = record;
311
+ return authored.ask({
312
+ kind: 'replica.stage',
313
+ locator: record.locator,
314
+ transfer: record.transfer,
315
+ record: last ? JSON.stringify(kept) : null,
316
+ key,
317
+ composition,
318
+ entries: page.entries,
319
+ folders: page.folders,
320
+ });
321
+ },
322
+ async refresh(key, page) {
323
+ await authored.ask({
324
+ kind: 'replica.refresh',
325
+ key,
326
+ entries: page.entries,
327
+ removals: page.removals,
328
+ folders: page.folders,
329
+ folderRemovals: page.folderRemovals,
330
+ });
331
+ },
332
+ async unrefresh(key) {
333
+ await authored.ask({ kind: 'replica.unrefresh', key });
334
+ },
335
+ async commit({ rooms, writes = [], files = [], whole = false, discards = [] }) {
336
+ const kept = whole ? writes : writes.filter(({ locator }) => !fenced(locator));
337
+ const refused = whole ? kept.find(({ locator }) => fenced(locator)) : undefined;
338
+ if (refused !== undefined) {
339
+ return { fenced: refused.locator, counts: {}, changed: {}, prepared: {} };
340
+ }
341
+ for (const room of rooms)
342
+ change(room.key);
343
+ const committed = await authored.ask({
344
+ kind: 'replica.commit',
345
+ ...(kept.length === 0 && files.length === 0 && discards.length === 0
346
+ ? {}
347
+ : { authored: { writes: rows(kept), files, whole, discards } }),
348
+ rooms: rooms.filter(({ locator }) => !fenced(locator)),
349
+ });
350
+ if (committed.fenced === null || !whole)
351
+ noted(kept);
352
+ for (const locator of discards)
353
+ queued.delete(locator);
354
+ return committed;
355
+ },
356
+ async fence(locators) {
357
+ await authored.ask({ kind: 'replica.fence', locators });
358
+ for (const locator of locators)
359
+ queued.delete(locator);
424
360
  },
425
- async member() {
426
- const transaction = database.transaction(DECLARATIONS, 'readonly');
427
- const held = await settled(transaction.objectStore(DECLARATIONS).get(MEMBER));
428
- return held ?? null;
361
+ async erase(locator, limit) {
362
+ return (await authored.ask({ kind: 'replica.erase', locator, limit })).remaining;
363
+ },
364
+ async abandon(locator, kept = false) {
365
+ if (fenced(locator))
366
+ return null;
367
+ return authored.ask({ kind: 'replica.abandon', locator, kept });
429
368
  },
430
- async keepMember(member) {
431
- const transaction = database.transaction(DECLARATIONS, 'readwrite');
432
- transaction.objectStore(DECLARATIONS).put(member, MEMBER);
433
- await completed(transaction);
369
+ async release(key) {
370
+ await authored.ask({ kind: 'replica.release', key });
434
371
  },
435
- async targets() {
436
- const transaction = database.transaction(DECLARATIONS, 'readonly');
437
- const held = await settled(transaction.objectStore(DECLARATIONS).get(TARGETS));
438
- return held ?? {};
372
+ async rederive(key, limit) {
373
+ change(key);
374
+ return (await authored.ask({ kind: 'replica.rederive', key, limit })).done;
439
375
  },
440
- async keepTargets(targets) {
441
- const transaction = database.transaction(DECLARATIONS, 'readwrite');
442
- transaction.objectStore(DECLARATIONS).put(targets, TARGETS);
443
- await completed(transaction);
376
+ async fact(name, value) {
377
+ await authored.ask({ kind: 'replica.fact', name, value: JSON.stringify(value) });
444
378
  },
445
379
  async queues() {
446
- const transaction = authored.transaction([QUEUES, PENDING_CHANGES], 'readonly');
447
- const [queues, pending] = await Promise.all([
448
- settled(transaction.objectStore(QUEUES).getAll()),
449
- settled(transaction.objectStore(PENDING_CHANGES).getAll()),
450
- ]);
380
+ const { queues, pending } = await authored.ask({ kind: 'authored.queues' });
381
+ queued.clear();
382
+ for (const queue of queues)
383
+ queued.set(queue.locator, new Set(queue.ids));
451
384
  const byId = new Map(pending.map((record) => [`${record.locator}\0${record.id}`, record]));
452
385
  return queues
453
386
  .filter((queue) => !fenced(queue.locator))
454
387
  .map((queue) => ({
455
388
  locator: queue.locator,
456
- pending: queue.order.map((id) => {
389
+ pending: queue.ids.map((id) => {
457
390
  const record = byId.get(`${queue.locator}\0${id}`);
458
391
  if (record === undefined)
459
392
  throw new Error(`Missing Pending Change ${queue.locator}/${id}`);
460
- const { batch, drafts, submission, lost, upload, accepted } = record;
461
- return { batch, state: { drafts, submission, lost, upload, accepted } };
393
+ return {
394
+ batch: JSON.parse(record.batch),
395
+ state: JSON.parse(record.state),
396
+ };
462
397
  }),
463
398
  }));
464
399
  },
465
- async submit(locator, id, submission, lost) {
466
- if (!fenced(locator))
467
- await amendPending(authored, locator, id, { submission, lost });
400
+ submit(locator, id, submission, lost) {
401
+ return amend(locator, id, { submission, lost });
468
402
  },
469
- async accept(locator, id, roomVersion) {
470
- if (!fenced(locator))
471
- await amendPending(authored, locator, id, { accepted: roomVersion });
472
- },
473
- async keepUpload(locator, id, upload) {
474
- if (!fenced(locator))
475
- await amendPending(authored, locator, id, { upload });
403
+ keepUpload(locator, id, upload) {
404
+ return amend(locator, id, { upload });
476
405
  },
477
406
  async roomDrafts() {
478
- const transaction = authored.transaction(ROOM_DRAFTS, 'readonly');
479
- const held = await settled(transaction.objectStore(ROOM_DRAFTS).getAll());
480
- return held.filter((record) => !fenced(record.locator));
481
- },
482
- async presentation() {
483
- const transaction = database.transaction(PRESENTATION, 'readonly');
484
- return settled(transaction.objectStore(PRESENTATION).getAll());
407
+ const held = await authored.ask({ kind: 'authored.roomDrafts' });
408
+ return held
409
+ .map((record) => JSON.parse(record))
410
+ .filter((record) => !fenced(record.locator));
485
411
  },
486
412
  async present(record) {
487
- const transaction = database.transaction(PRESENTATION, 'readwrite');
488
- transaction.objectStore(PRESENTATION).put(record);
489
- await completed(transaction);
490
- },
491
- async conversations() {
492
- const transaction = database.transaction([CONVERSATIONS, MESSAGES], 'readonly');
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
- };
413
+ await authored.ask({
414
+ kind: 'replica.present',
415
+ app: record.app,
416
+ value: JSON.stringify(record.value ?? null),
514
417
  });
515
- return [...held.values()];
516
418
  },
517
- async keepMessages({ conversation, messages, read }) {
518
- const transaction = database.transaction([CONVERSATIONS, MESSAGES], 'readwrite');
519
- const stored = transaction.objectStore(MESSAGES);
520
- for (const message of messages) {
521
- stored.put({ ...message, conversation });
522
- }
523
- if (read !== undefined) {
524
- transaction.objectStore(CONVERSATIONS).put({
419
+ conversations() {
420
+ return transacted(database, [CONVERSATIONS, MESSAGES], 'readonly', async (transaction) => {
421
+ const records = await settled(transaction.objectStore(CONVERSATIONS).getAll());
422
+ const held = new Map(records.map(({ conversation, read, partial }) => [
525
423
  conversation,
526
- read: read.at,
527
- ...(read.partial ? { partial: true } : {}),
424
+ { conversation, read, partial: partial === true },
425
+ ]));
426
+ const keys = transaction.objectStore(MESSAGES).index('conversation');
427
+ await new Promise((resolve, reject) => {
428
+ const cursor = keys.openKeyCursor(null, 'nextunique');
429
+ cursor.onerror = () => reject(cursor.error);
430
+ cursor.onsuccess = () => {
431
+ const at = cursor.result;
432
+ if (at === null) {
433
+ resolve();
434
+ return;
435
+ }
436
+ const conversation = at.key;
437
+ if (!held.has(conversation)) {
438
+ held.set(conversation, { conversation, read: undefined, partial: true });
439
+ }
440
+ at.continue();
441
+ };
528
442
  });
529
- }
530
- await completed(transaction);
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
443
+ return [...held.values()];
444
+ });
445
+ },
446
+ async keepMessages({ conversation, messages, read }) {
447
+ await transacted(database, [CONVERSATIONS, MESSAGES], 'readwrite', (transaction) => {
448
+ const stored = transaction.objectStore(MESSAGES);
449
+ for (const message of messages) {
450
+ stored.put({ ...message, conversation });
451
+ }
452
+ if (read !== undefined) {
453
+ transaction.objectStore(CONVERSATIONS).put({
454
+ conversation,
455
+ read: read.at,
456
+ ...(read.partial ? { partial: true } : {}),
457
+ });
458
+ }
459
+ });
460
+ },
461
+ messageWindow(conversation, limit, before) {
462
+ return transacted(database, MESSAGES, 'readonly', async (transaction) => {
463
+ const index = transaction.objectStore(MESSAGES).index('position');
464
+ const range = IDBKeyRange.bound([conversation], [conversation, before?.[0] ?? []]);
465
+ const latest = [];
466
+ let more = false;
467
+ await new Promise((resolve, reject) => {
468
+ const cursor = index.openCursor(range, 'prev');
469
+ cursor.onerror = () => reject(cursor.error);
470
+ cursor.onsuccess = () => {
471
+ const at = cursor.result;
472
+ if (at === null) {
473
+ resolve();
474
+ return;
475
+ }
476
+ const record = at.value;
477
+ if (!precedes(record, before)) {
478
+ at.continue();
479
+ return;
480
+ }
481
+ if (latest.length === limit) {
482
+ more = true;
483
+ resolve();
484
+ return;
485
+ }
486
+ latest.push(accepted(record));
555
487
  at.continue();
556
- };
488
+ };
489
+ });
490
+ return { messages: latest.reverse(), more };
557
491
  });
558
- return latest.reverse();
492
+ },
493
+ messageCount(conversation) {
494
+ return transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).index('conversation').count(conversation)));
559
495
  },
560
496
  async messagesAfter(conversation, identity) {
561
- const transaction = database.transaction(MESSAGES, 'readonly');
562
497
  const range = identity === null
563
498
  ? IDBKeyRange.bound([conversation], [conversation, []])
564
499
  : IDBKeyRange.bound([conversation, identity], [conversation, []], true);
565
- const held = await settled(transaction.objectStore(MESSAGES).getAll(range, READ_BATCH));
500
+ const held = await transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).getAll(range, READ_BATCH)));
566
501
  return held.map(accepted);
567
502
  },
568
503
  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]);
504
+ const found = await transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).index('conversation').getAllKeys(conversation)));
505
+ return found.map((key) => key[1]);
572
506
  },
573
507
  async turnArrived(conversation, turn) {
574
- const transaction = database.transaction(MESSAGES, 'readonly');
575
- const held = await settled(transaction.objectStore(MESSAGES).index('turn').count([conversation, turn]));
508
+ const held = await transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).index('turn').count([conversation, turn])));
576
509
  return held > 0;
577
510
  },
578
511
  async dropConversations(conversations) {
579
- const drafts = authored.transaction(CHAT_DRAFTS, 'readwrite');
580
- for (const conversation of conversations)
581
- drafts.objectStore(CHAT_DRAFTS).delete(conversation);
582
- await completed(drafts);
583
- const transaction = database.transaction([CONVERSATIONS, MESSAGES], 'readwrite');
584
- const messages = transaction.objectStore(MESSAGES);
585
- for (const conversation of conversations) {
586
- transaction.objectStore(CONVERSATIONS).delete(conversation);
587
- const keys = messages.index('conversation').getAllKeys(conversation);
588
- keys.onsuccess = () => {
589
- for (const key of keys.result)
590
- messages.delete(key);
591
- };
592
- }
593
- await completed(transaction);
512
+ await authored.ask({ kind: 'authored.chatDraft', kept: [], dropped: conversations });
513
+ await transacted(database, [CONVERSATIONS, MESSAGES], 'readwrite', (transaction) => {
514
+ const messages = transaction.objectStore(MESSAGES);
515
+ for (const conversation of conversations) {
516
+ transaction.objectStore(CONVERSATIONS).delete(conversation);
517
+ const held = messages.index('conversation').getAllKeys(conversation);
518
+ held.onsuccess = () => {
519
+ for (const key of held.result)
520
+ messages.delete(key);
521
+ };
522
+ }
523
+ });
594
524
  },
595
525
  async chatDrafts() {
596
- const transaction = authored.transaction(CHAT_DRAFTS, 'readonly');
597
- const held = await settled(transaction.objectStore(CHAT_DRAFTS).getAll());
598
- return held.map(chatDraftRecord);
526
+ const records = await authored.ask({ kind: 'authored.chatDrafts' });
527
+ return records.map((record) => JSON.parse(record));
599
528
  },
600
529
  async keepChatDrafts(records) {
601
- const transaction = authored.transaction(CHAT_DRAFTS, 'readwrite');
602
- for (const record of records)
603
- transaction.objectStore(CHAT_DRAFTS).put(record);
604
- await completed(transaction);
530
+ await authored.ask({
531
+ kind: 'authored.chatDraft',
532
+ kept: records.map((record) => ({
533
+ conversation: record.conversation,
534
+ record: JSON.stringify(record),
535
+ })),
536
+ dropped: [],
537
+ });
605
538
  },
606
539
  async dropChatDraft(conversation) {
607
- const transaction = authored.transaction(CHAT_DRAFTS, 'readwrite');
608
- transaction.objectStore(CHAT_DRAFTS).delete(conversation);
609
- await completed(transaction);
540
+ await authored.ask({ kind: 'authored.chatDraft', kept: [], dropped: [conversation] });
541
+ },
542
+ async calls() {
543
+ return restored(await authored.ask({ kind: 'authored.calls' }));
544
+ },
545
+ async keepCall(record) {
546
+ const { stored, ...kept } = keyed(keys, record);
547
+ await authored.ask({
548
+ kind: 'authored.call',
549
+ operation: record.operation,
550
+ kept,
551
+ files: stored,
552
+ });
553
+ },
554
+ async dropCall(operation) {
555
+ await authored.ask({ kind: 'authored.call', operation, kept: null, files: [] });
610
556
  },
611
- async keepRoomDraft(record) {
557
+ async keepRoomDraft(record, files) {
612
558
  if (fenced(record.locator))
613
559
  return;
614
- const transaction = authored.transaction(ROOM_DRAFTS, 'readwrite');
615
- transaction.objectStore(ROOM_DRAFTS).put(record);
616
- await completed(transaction);
560
+ const { path, ...kept } = roomDraftRow(record);
561
+ await authored.ask({
562
+ kind: 'authored.roomDraft',
563
+ locator: record.locator,
564
+ path,
565
+ kept,
566
+ files,
567
+ });
617
568
  },
618
569
  async discardRoomDraft(locator, path) {
619
570
  if (fenced(locator))
620
571
  return;
621
- const transaction = authored.transaction(ROOM_DRAFTS, 'readwrite');
622
- transaction.objectStore(ROOM_DRAFTS).delete([locator, path]);
623
- await completed(transaction);
624
- },
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');
632
- for (const locator of locators) {
633
- work.objectStore(FENCES).put({ locator });
634
- work.objectStore(QUEUES).delete(locator);
635
- for (const name of [PENDING_CHANGES, ROOM_DRAFTS]) {
636
- removeRoomEntries(work.objectStore(name), locator);
637
- }
638
- }
639
- await completed(work);
640
- const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_FILES, TRANSFERS], 'readwrite');
641
- for (const locator of locators) {
642
- transaction.objectStore(ROOMS).delete(locator);
643
- transaction.objectStore(TRANSFERS).delete(locator);
644
- transaction.objectStore(DOCUMENTS).delete(underRoom(locator));
645
- transaction.objectStore(BINARY_FILES).delete(underRoom(locator));
646
- }
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);
572
+ await authored.ask({ kind: 'authored.roomDraft', locator, path, kept: null, files: [] });
658
573
  },
659
- async transfers() {
660
- const transaction = database.transaction(TRANSFERS, 'readonly');
661
- const held = await settled(transaction.objectStore(TRANSFERS).getAll());
662
- return held.filter((record) => !fenced(record.locator));
574
+ fences() {
575
+ return authored.ask({ kind: 'authored.fences' });
663
576
  },
664
- staged(record) {
665
- const transaction = database.transaction([DOCUMENTS, BINARY_FILES], 'readonly');
666
- return readLayer(transaction, record.locator, record.transfer);
577
+ receivedHeads() {
578
+ return authored.ask({ kind: 'replica.received' });
667
579
  },
668
- async stage(record, page) {
669
- if (fenced(record.locator))
670
- return;
671
- const transaction = database.transaction([TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite');
672
- writeLayer(transaction, record.locator, record.transfer, page, null);
673
- transaction.objectStore(TRANSFERS).put(record);
674
- await completed(transaction);
675
- },
676
- async abandon(locator) {
677
- if (fenced(locator))
678
- return;
679
- const transaction = database.transaction([TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite');
680
- const transfers = transaction.objectStore(TRANSFERS);
681
- const held = transfers.get(locator);
682
- held.onsuccess = () => {
683
- const record = held.result;
684
- if (record === undefined)
685
- return;
686
- transfers.delete(locator);
687
- const range = underTransfer(locator, record.transfer);
688
- transaction.objectStore(DOCUMENTS).delete(range);
689
- transaction.objectStore(BINARY_FILES).delete(range);
690
- };
691
- await completed(transaction);
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);
580
+ receivedParts(key, count) {
581
+ return authored.ask({ kind: 'replica.receivedParts', key, count });
703
582
  },
704
583
  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);
584
+ await authored.ask({
585
+ kind: 'replica.receive',
586
+ key,
587
+ head: JSON.stringify(head),
588
+ ...(part === undefined
589
+ ? {}
590
+ : { part: { index: part.index, value: JSON.stringify(part.value) } }),
591
+ });
710
592
  },
711
593
  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);
594
+ await authored.ask({ kind: 'replica.unreceive', keys });
718
595
  },
719
- async generation() {
720
- const transaction = authored.transaction(FACTS, 'readonly');
721
- const held = await settled(transaction.objectStore(FACTS).get(GENERATION));
722
- return held ?? null;
596
+ generation() {
597
+ return fact('generation');
723
598
  },
724
599
  async rememberGeneration(value) {
725
- const transaction = authored.transaction(FACTS, 'readwrite');
726
- transaction.objectStore(FACTS).put(value, GENERATION);
727
- await completed(transaction);
600
+ await authored.ask({ kind: 'authored.remember', name: 'generation', value });
601
+ },
602
+ file(key, type) {
603
+ return authored.ask({ kind: 'authored.file', key, type });
728
604
  },
729
605
  close() {
730
- authored.close();
731
- database.close();
606
+ void idle(database).then(() => database.close());
732
607
  },
733
608
  };
734
609
  }