@agentshouse/mdruntime 0.1.0-c0136de3edfbe → 0.1.0-c014edb0e9d9b

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 (228) hide show
  1. package/README.md +8 -8
  2. package/dist/acceptance.d.ts +0 -3
  3. package/dist/acceptance.d.ts.map +1 -1
  4. package/dist/acceptance.js +74 -145
  5. package/dist/act-lanes.d.ts +33 -0
  6. package/dist/act-lanes.d.ts.map +1 -0
  7. package/dist/act-lanes.js +177 -0
  8. package/dist/actions.d.ts +7 -2
  9. package/dist/actions.d.ts.map +1 -1
  10. package/dist/actions.js +44 -31
  11. package/dist/adapter.d.ts +30 -44
  12. package/dist/adapter.d.ts.map +1 -1
  13. package/dist/bootstrap.d.ts +1 -1
  14. package/dist/bootstrap.d.ts.map +1 -1
  15. package/dist/bootstrap.js +6 -18
  16. package/dist/change-set.d.ts +4 -0
  17. package/dist/change-set.d.ts.map +1 -1
  18. package/dist/change-set.js +6 -0
  19. package/dist/change.d.ts.map +1 -1
  20. package/dist/change.js +4 -2
  21. package/dist/chat-types.d.ts +2 -0
  22. package/dist/chat-types.d.ts.map +1 -1
  23. package/dist/chat-types.js +11 -0
  24. package/dist/client.d.ts +9 -12
  25. package/dist/client.d.ts.map +1 -1
  26. package/dist/client.js +29 -14
  27. package/dist/collection.d.ts +22 -4
  28. package/dist/collection.d.ts.map +1 -1
  29. package/dist/collection.js +12 -2
  30. package/dist/compile.d.ts.map +1 -1
  31. package/dist/compile.js +15 -21
  32. package/dist/connection.d.ts.map +1 -1
  33. package/dist/connection.js +30 -17
  34. package/dist/conversation-live.d.ts +138 -0
  35. package/dist/conversation-live.d.ts.map +1 -0
  36. package/dist/conversation-live.js +249 -0
  37. package/dist/conversation-row.d.ts.map +1 -1
  38. package/dist/conversation-row.js +7 -1
  39. package/dist/conversation.d.ts +44 -44
  40. package/dist/conversation.d.ts.map +1 -1
  41. package/dist/conversation.js +904 -1024
  42. package/dist/declaration.d.ts +10 -0
  43. package/dist/declaration.d.ts.map +1 -0
  44. package/dist/declaration.js +24 -0
  45. package/dist/discovery.d.ts.map +1 -1
  46. package/dist/discovery.js +18 -40
  47. package/dist/durability.d.ts +12 -0
  48. package/dist/durability.d.ts.map +1 -1
  49. package/dist/expectation.d.ts +4 -2
  50. package/dist/expectation.d.ts.map +1 -1
  51. package/dist/expectation.js +33 -11
  52. package/dist/failure.d.ts +3 -0
  53. package/dist/failure.d.ts.map +1 -1
  54. package/dist/failure.js +6 -0
  55. package/dist/figures.d.ts +1 -0
  56. package/dist/figures.d.ts.map +1 -1
  57. package/dist/figures.js +2 -2
  58. package/dist/holdings.d.ts +29 -0
  59. package/dist/holdings.d.ts.map +1 -0
  60. package/dist/holdings.js +95 -0
  61. package/dist/host.d.ts +17 -2
  62. package/dist/host.d.ts.map +1 -1
  63. package/dist/host.js +38 -8
  64. package/dist/index.d.ts +13 -6
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +9 -3
  67. package/dist/install.d.ts +1 -3
  68. package/dist/install.d.ts.map +1 -1
  69. package/dist/install.js +49 -70
  70. package/dist/lifecycle.d.ts +1 -1
  71. package/dist/lifecycle.d.ts.map +1 -1
  72. package/dist/lifecycle.js +42 -30
  73. package/dist/listing.d.ts +6 -11
  74. package/dist/listing.d.ts.map +1 -1
  75. package/dist/listing.js +25 -61
  76. package/dist/live.d.ts.map +1 -1
  77. package/dist/live.js +3 -3
  78. package/dist/locks.d.ts +2 -4
  79. package/dist/locks.d.ts.map +1 -1
  80. package/dist/locks.js +10 -38
  81. package/dist/lost.d.ts.map +1 -1
  82. package/dist/lost.js +10 -21
  83. package/dist/media.d.ts +1 -1
  84. package/dist/media.d.ts.map +1 -1
  85. package/dist/media.js +4 -4
  86. package/dist/memory.d.ts +4 -2
  87. package/dist/memory.d.ts.map +1 -1
  88. package/dist/memory.js +38 -20
  89. package/dist/messages.d.ts +1 -4
  90. package/dist/messages.d.ts.map +1 -1
  91. package/dist/messages.js +14 -32
  92. package/dist/model.d.ts +0 -1
  93. package/dist/model.d.ts.map +1 -1
  94. package/dist/model.js +0 -5
  95. package/dist/models.d.ts +4 -8
  96. package/dist/models.d.ts.map +1 -1
  97. package/dist/models.js +97 -134
  98. package/dist/observation.d.ts.map +1 -1
  99. package/dist/observation.js +12 -17
  100. package/dist/opened.d.ts +1 -1
  101. package/dist/opened.d.ts.map +1 -1
  102. package/dist/opened.js +5 -5
  103. package/dist/opening.d.ts.map +1 -1
  104. package/dist/opening.js +8 -5
  105. package/dist/outcome.d.ts +6 -1
  106. package/dist/outcome.d.ts.map +1 -1
  107. package/dist/outcome.js +15 -0
  108. package/dist/overlay.d.ts +2 -1
  109. package/dist/overlay.d.ts.map +1 -1
  110. package/dist/overlay.js +32 -21
  111. package/dist/page-metadata.d.ts +8 -0
  112. package/dist/page-metadata.d.ts.map +1 -0
  113. package/dist/page-metadata.js +19 -0
  114. package/dist/parts.d.ts +3 -51
  115. package/dist/parts.d.ts.map +1 -1
  116. package/dist/parts.js +14 -111
  117. package/dist/paths.d.ts +1 -0
  118. package/dist/paths.d.ts.map +1 -1
  119. package/dist/paths.js +22 -0
  120. package/dist/preparation.d.ts.map +1 -1
  121. package/dist/preparation.js +22 -15
  122. package/dist/presence.d.ts +52 -0
  123. package/dist/presence.d.ts.map +1 -0
  124. package/dist/presence.js +220 -0
  125. package/dist/queries.d.ts +1 -1
  126. package/dist/queries.d.ts.map +1 -1
  127. package/dist/queries.js +5 -7
  128. package/dist/query.d.ts +0 -1
  129. package/dist/query.d.ts.map +1 -1
  130. package/dist/questions.d.ts +2 -0
  131. package/dist/questions.d.ts.map +1 -1
  132. package/dist/questions.js +5 -1
  133. package/dist/queue.d.ts +40 -0
  134. package/dist/queue.d.ts.map +1 -0
  135. package/dist/queue.js +455 -0
  136. package/dist/reach.d.ts.map +1 -1
  137. package/dist/reach.js +28 -9
  138. package/dist/react.d.ts +15 -0
  139. package/dist/react.d.ts.map +1 -1
  140. package/dist/react.js +15 -1
  141. package/dist/readiness.d.ts +0 -1
  142. package/dist/readiness.d.ts.map +1 -1
  143. package/dist/reads.d.ts +4 -1
  144. package/dist/reads.d.ts.map +1 -1
  145. package/dist/reads.js +47 -5
  146. package/dist/refresh.js +2 -2
  147. package/dist/refusal.d.ts.map +1 -1
  148. package/dist/refusal.js +5 -4
  149. package/dist/replica.d.ts +5 -10
  150. package/dist/replica.d.ts.map +1 -1
  151. package/dist/replica.js +15 -16
  152. package/dist/retried.d.ts +30 -0
  153. package/dist/retried.d.ts.map +1 -0
  154. package/dist/retried.js +72 -0
  155. package/dist/room-draft.d.ts +4 -6
  156. package/dist/room-draft.d.ts.map +1 -1
  157. package/dist/room-draft.js +36 -52
  158. package/dist/room-drafts.d.ts.map +1 -1
  159. package/dist/room-drafts.js +17 -16
  160. package/dist/rows.d.ts.map +1 -1
  161. package/dist/rows.js +3 -4
  162. package/dist/runtime.d.ts +11 -17
  163. package/dist/runtime.d.ts.map +1 -1
  164. package/dist/save.d.ts +41 -86
  165. package/dist/save.d.ts.map +1 -1
  166. package/dist/save.js +232 -112
  167. package/dist/saves.d.ts +2 -40
  168. package/dist/saves.d.ts.map +1 -1
  169. package/dist/saves.js +107 -698
  170. package/dist/seed.d.ts.map +1 -1
  171. package/dist/seed.js +2 -3
  172. package/dist/shared.d.ts +1 -6
  173. package/dist/shared.d.ts.map +1 -1
  174. package/dist/shared.js +13 -95
  175. package/dist/shown.d.ts +4 -3
  176. package/dist/shown.d.ts.map +1 -1
  177. package/dist/shown.js +29 -33
  178. package/dist/socket/lanes.d.ts +2 -1
  179. package/dist/socket/lanes.d.ts.map +1 -1
  180. package/dist/socket/lanes.js +5 -2
  181. package/dist/socket/session.d.ts +0 -1
  182. package/dist/socket/session.d.ts.map +1 -1
  183. package/dist/socket.d.ts +2 -4
  184. package/dist/socket.d.ts.map +1 -1
  185. package/dist/socket.js +4 -8
  186. package/dist/source.d.ts.map +1 -1
  187. package/dist/source.js +5 -4
  188. package/dist/store/records.d.ts +4 -4
  189. package/dist/store/records.d.ts.map +1 -1
  190. package/dist/store/records.js +33 -11
  191. package/dist/store/space.d.ts +1 -1
  192. package/dist/store/space.d.ts.map +1 -1
  193. package/dist/store/space.js +0 -2
  194. package/dist/store/watchers.js +1 -2
  195. package/dist/store.d.ts +11 -6
  196. package/dist/store.d.ts.map +1 -1
  197. package/dist/store.js +38 -13
  198. package/dist/stored.d.ts +1 -1
  199. package/dist/stored.d.ts.map +1 -1
  200. package/dist/stored.js +22 -10
  201. package/dist/surface.d.ts +4 -2
  202. package/dist/surface.d.ts.map +1 -1
  203. package/dist/surface.js +20 -14
  204. package/dist/sync.d.ts +0 -1
  205. package/dist/sync.d.ts.map +1 -1
  206. package/dist/sync.js +91 -82
  207. package/dist/tables.d.ts +13 -8
  208. package/dist/tables.d.ts.map +1 -1
  209. package/dist/tables.js +25 -12
  210. package/dist/tallies.js +5 -5
  211. package/dist/validation.d.ts.map +1 -1
  212. package/dist/validation.js +2 -2
  213. package/dist/wire.d.ts +41 -4
  214. package/dist/wire.d.ts.map +1 -1
  215. package/dist/wire.js +13 -3
  216. package/package.json +3 -2
  217. package/dist/attempt.d.ts +0 -16
  218. package/dist/attempt.d.ts.map +0 -1
  219. package/dist/attempt.js +0 -70
  220. package/dist/job.d.ts +0 -33
  221. package/dist/job.d.ts.map +0 -1
  222. package/dist/job.js +0 -54
  223. package/dist/socket/rooms.d.ts +0 -43
  224. package/dist/socket/rooms.d.ts.map +0 -1
  225. package/dist/socket/rooms.js +0 -90
  226. package/dist/store/fences.d.ts +0 -16
  227. package/dist/store/fences.d.ts.map +0 -1
  228. package/dist/store/fences.js +0 -26
package/dist/saves.js CHANGED
@@ -1,436 +1,144 @@
1
1
  import { parseFrontmatter } from '@agentshouse/mdmodel';
2
2
  import { binaryRecord } from './binary.js';
3
- import { changeSet, mergeChangeSets } from './change-set.js';
4
- import { leaving as leavingIn, partnerOf, queuedFile, } from './change.js';
5
- import { available, composedOf } from './collection.js';
3
+ import { queuedFile } from './change.js';
4
+ import { available } from './collection.js';
6
5
  import { runtimeError } from './failure.js';
7
6
  import { attachedFile } from './file.js';
8
- import { roomDraftRevision } from './identity.js';
9
- import { confirmedAgain } from './install.js';
10
7
  import { compositionFor } from './listing.js';
8
+ import { scoped } from './models.js';
11
9
  import { absentBinary, attachAnswer, binaryAnswer, binaryLinkAnswer, failure, OFFLINE, UNCERTAIN, } from './outcome.js';
12
10
  import { sameBinary } from './overlay.js';
13
11
  import { ancestors } from './paths.js';
14
- import { connectionNow, prepare } from './preparation.js';
15
- import { entryAt, landed, movedBy, overlayOf, replaying, slicing, slicingWith, standing, storedRecord, touchedBy, } from './replica.js';
16
- import { binaryBase, changedParts, changesOf, held, heldFile, joinedDraft, moveTarget, readSavedParts, returnedBy, returnedInto, withdrawnDrafts, } from './room-draft.js';
12
+ import { connectionNow } from './preparation.js';
13
+ import { append, sliceAt, withdrawable } from './queue.js';
14
+ import { entryAt, replaying, slicing, } from './replica.js';
15
+ import { binaryBase, changesOf, held, heldFile, moveTarget, readSavedParts, } from './room-draft.js';
17
16
  import { refuse } from './refusal.js';
18
17
  import { visibleOf } from './room-drafts.js';
19
- import { documentKey } from './rows.js';
20
- import { buildBatch, buildMove, joined, limiting, queuedDocuments, requeuing, } from './save.js';
18
+ import { destination, planSave, } from './save.js';
21
19
  import { pendingShown } from './shown.js';
22
20
  import { paced, ROW_WEIGHT, stepped } from './turn.js';
23
21
  import { validatedNeighbourhood } from './validation.js';
24
- export function withdrawable(states) {
25
- return (batch) => {
26
- const state = states.get(batch.id);
27
- return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
28
- };
29
- }
30
- export async function sliceAt(store, record, view, queue, extra = [], moved = []) {
31
- const local = await store.open();
32
- const needed = new Set([...touchedBy(queue), ...extra]);
33
- const movedPaths = [...new Set([...movedBy(queue, record.locator), ...moved])];
34
- if (movedPaths.length > 0) {
35
- for (const { path } of await local.referrers(record.key, view, movedPaths)) {
36
- needed.add(path);
37
- }
38
- }
39
- const paths = [...needed];
40
- return stepped(slicing(await local.entries(record.key, view, paths), paths));
41
- }
42
- export async function landedWhole(store, locator, batch, confirmedLayer, current) {
43
- if (!landed(batch, confirmedLayer, current))
44
- return false;
45
- const partner = partnerOf(batch, locator);
46
- if (partner === null)
47
- return true;
48
- const other = await store.replica(partner);
49
- const half = other?.queue.find((one) => one.id === batch.id);
50
- if (other === undefined || half === undefined)
51
- return false;
52
- return landed(half, await sliceAt(store, other.record, 'confirmed', [half]));
53
- }
54
- async function carriedBack(store, locator, unlanded, written) {
55
- const back = new Map();
56
- const taken = [];
57
- for (const batch of unlanded) {
58
- const between = batch.between;
59
- if (between === undefined)
60
- continue;
61
- const source = between.source.room;
62
- const destination = between.destination.room;
63
- const local = await store.open();
64
- const arrived = (await local.pending(destination, batch.id)).state?.drafts ?? [];
65
- for (const { path: to } of arrived) {
66
- const record = await store.draft(destination, to);
67
- const path = between.moves.find((move) => move.to === to)?.path;
68
- if (record === undefined || path === undefined)
69
- continue;
70
- const [into, key] = source === locator ? [written, path] : [back, documentKey(source, path)];
71
- const existing = into.get(key) ?? (await store.draft(source, path));
72
- taken.push({ locator: destination, path: to });
73
- into.set(key, {
74
- locator: source,
75
- path,
76
- revision: roomDraftRevision(),
77
- parts: returnedInto(existing?.parts, record.parts),
78
- returned: existing?.returned ?? null,
79
- });
80
- }
81
- }
82
- const records = [...back.values()];
83
- const elsewhere = taken.filter((one) => one.locator !== locator);
84
- const rooms = new Set([...records, ...elsewhere].map((one) => one.locator));
85
- return {
86
- taken: taken.filter((one) => one.locator === locator).map(({ path }) => path),
87
- writes: [...rooms].map((room) => ({
88
- locator: room,
89
- roomDrafts: records.filter((record) => record.locator === room),
90
- consumedRoomDrafts: elsewhere.filter((one) => one.locator === room).map(({ path }) => path),
91
- })),
92
- changes: [...rooms].map((room) => changeSet(room, {
93
- drafts: [...records, ...elsewhere]
94
- .filter((one) => one.locator === room)
95
- .map(({ path }) => path),
96
- })),
97
- };
98
- }
99
- export function preparedAfter(runtime, locator, answer) {
100
- if (answer.indexed[locator] === false)
101
- prepare(runtime, [locator]);
102
- }
103
- export function replayed(store, record, confirmedLayer, queue) {
104
- return stepped(replaying(confirmedLayer, queue, record.locator, store.composed(record)));
105
- }
106
- export async function overlayDiff(record, before, visible, touched) {
107
- const kept = before !== undefined && before.record.key === record.key ? before.overlaid : new Set();
108
- return {
109
- overlay: {
110
- set: await stepped(overlayOf(visible, touched)),
111
- cleared: [...kept].filter((path) => !touched.has(path)),
112
- },
113
- touched,
114
- };
115
- }
116
- export async function settle(runtime, local, locator, work, record, queue, settling = {}) {
117
- return together(runtime, local, [
118
- await settlement(runtime, local, locator, work, record, queue, settling),
119
- ]);
120
- }
121
- async function unpairedIn(store, locator, batch) {
122
- const { state } = await (await store.open()).pending(locator, batch.id);
123
- if (state === undefined)
124
- return false;
125
- return !leavingIn(batch, locator) || withdrawable(new Map([[batch.id, state]]))(batch);
126
- }
127
- export async function together(runtime, local, settlements) {
128
- const settled = new Set(settlements.map(({ locator }) => locator));
129
- const all = [...settlements];
130
- for (let wave = settlements; wave.length > 0;) {
131
- const partnered = new Map();
132
- for (const { locator, left } of wave) {
133
- for (const batch of left) {
134
- const partner = partnerOf(batch, locator);
135
- if (partner === null ||
136
- settled.has(partner) ||
137
- !(await unpairedIn(runtime.store, partner, batch))) {
138
- continue;
139
- }
140
- partnered.set(partner, [...(partnered.get(partner) ?? []), batch]);
141
- }
142
- }
143
- const partners = [];
144
- for (const [partner, batches] of partnered) {
145
- settled.add(partner);
146
- partners.push(await unpairing(runtime, local, partner, batches));
147
- }
148
- all.push(...partners);
149
- wave = partners;
150
- }
151
- const answer = await local.commit({
152
- rooms: all.flatMap(({ commit }) => commit.rooms),
153
- writes: all.flatMap(({ commit }) => commit.writes),
154
- });
155
- return mergeChangeSets(all.map((one) => one.apply(answer)));
156
- }
157
- export async function settlement(runtime, local, locator, work, record, queue, settling = {}) {
158
- const { store, journal, localSaves } = runtime;
159
- const before = work.replica;
160
- const view = settling.view ?? 'confirmed';
161
- const previousQueue = work.queue;
162
- const overrides = settling.changes;
163
- const read = await sliceAt(store, record, view, queue, [
164
- ...(before !== undefined && before.record.key === record.key ? before.overlaid : []),
165
- ...(overrides?.set.map(({ path }) => path) ?? []),
166
- ...(overrides?.removed ?? []),
167
- ]);
168
- const confirmedLayer = overrides === undefined
169
- ? read
170
- : await stepped(slicingWith(read, overrides.set, overrides.removed));
171
- const composition = store.composed(record);
172
- const shown = queue.filter((batch) => {
173
- const held = store.landed.of(locator, batch.id);
174
- return (held !== undefined &&
175
- Object.entries(held).some(([path, revision]) => confirmedLayer.revisions[path] !== revision));
176
- });
177
- const unshown = queue.filter((batch) => !shown.includes(batch));
178
- const { withdrawn, kept } = await stepped(standing(confirmedLayer, unshown, withdrawable(work.states), localSaves, locator, composition));
179
- const founded = kept.filter((batch) => !unshown.includes(batch));
180
- const queueChanged = kept.length !== previousQueue.length || founded.length > 0;
181
- const unqueued = shown.map((batch) => ({ batch, landed: true }));
182
- for (const batch of withdrawn) {
183
- unqueued.push({ batch, landed: await landedWhole(store, locator, batch, confirmedLayer) });
184
- }
185
- const returning = [
186
- ...[...unqueued].reverse().map(({ batch, landed: held }) => ({
187
- batch,
188
- returned: localSaves || held ? null : returnedBy('withdrawn'),
189
- })),
190
- ...(settling.leaving ?? []),
191
- ];
192
- const written = new Map();
193
- for (const { batch, returned: reason } of returning) {
194
- if (reason === null || batch.between?.destination.room === locator)
195
- continue;
196
- for (const saved of work.states.get(batch.id)?.drafts ?? []) {
197
- const existing = written.get(saved.path) ?? (await store.draft(locator, saved.path));
198
- written.set(saved.path, {
199
- locator,
200
- path: saved.path,
201
- revision: roomDraftRevision(),
202
- parts: returnedInto(existing?.parts, saved.parts),
203
- returned: reason,
204
- });
205
- }
206
- }
207
- const carried = await carriedBack(store, locator, [
208
- ...[...unqueued].reverse().flatMap(({ batch, landed: held }) => (held ? [] : [batch])),
209
- ...(settling.leaving ?? []).flatMap(({ batch, returned }) => returned === null ? [] : [batch]),
210
- ], written);
211
- const replayedLayer = await replayed(store, record, confirmedLayer, kept);
212
- const { overlay } = await overlayDiff(record, before, replayedLayer.visible, replayedLayer.touched);
213
- const commit = {
214
- rooms: [
215
- {
216
- locator,
217
- key: record.key,
218
- record: storedRecord(record),
219
- roots: record.typedSelection?.roots ?? [],
220
- composed: composedOf(composition),
221
- ...settling.commit,
222
- ...(overrides === undefined
223
- ? {}
224
- : { confirmed: { set: overrides.set, removed: overrides.removed } }),
225
- overlay,
226
- },
227
- ],
228
- writes: [
229
- {
230
- locator,
231
- queue: kept,
232
- stored: previousQueue,
233
- founded,
234
- roomDrafts: [...written.values()],
235
- consumedRoomDrafts: carried.taken,
236
- },
237
- ...carried.writes,
238
- ],
239
- };
240
- const staying = new Set(kept.map((batch) => batch.id));
241
- return {
242
- locator,
243
- left: previousQueue.filter((batch) => !staying.has(batch.id)),
244
- commit,
245
- apply: (answer) => {
246
- for (const batch of previousQueue) {
247
- if (!staying.has(batch.id))
248
- store.landed.drop(locator, batch.id);
249
- }
250
- const drafts = [...written.keys(), ...carried.taken];
251
- for (const { batch, landed: held } of unqueued) {
252
- const outcome = localSaves ? 'dropped' : held ? 'landed' : 'withdrawn';
253
- journal.record(`outcome ${locator} ${batch.id} ${outcome}`);
254
- }
255
- const changed = answer.changed[locator];
256
- if (settling.commit?.published !== undefined ||
257
- settling.commit?.refreshed === true ||
258
- overrides !== undefined) {
259
- confirmedAgain(store, locator);
260
- }
261
- preparedAfter(runtime, locator, answer);
262
- return mergeChangeSets([
263
- changeSet(locator, {
264
- documents: changed === undefined ? [] : changed,
265
- room: settling.room,
266
- queue: queueChanged,
267
- drafts,
268
- }),
269
- ...carried.changes,
270
- ]);
271
- },
272
- };
273
- }
274
- export async function unpairing(runtime, local, locator, batches) {
275
- const { store, journal, localSaves, submitLater } = runtime;
276
- const ids = new Set(batches.map(({ id }) => id));
277
- const leaving = batches.map((batch) => ({
278
- batch,
279
- returned: !localSaves && leavingIn(batch, locator) ? returnedBy('withdrawn') : null,
280
- }));
281
- const outcome = localSaves ? 'dropped' : 'unpaired';
282
- const work = await store.work(locator);
283
- const current = work.replica;
284
- if (current !== undefined) {
285
- const settled = await settlement(runtime, local, locator, work, current.record, current.queue.filter((one) => !ids.has(one.id)), { leaving });
286
- return {
287
- ...settled,
288
- apply(answer) {
289
- for (const batch of batches)
290
- journal.record(`outcome ${locator} ${batch.id} ${outcome}`);
291
- const set = settled.apply(answer);
292
- submitLater(locator);
293
- return set;
294
- },
295
- };
296
- }
297
- const queue = work.queue.filter((one) => !ids.has(one.id));
298
- const written = new Map();
299
- for (const { batch, returned } of leaving) {
300
- if (returned === null)
301
- continue;
302
- for (const saved of work.states.get(batch.id)?.drafts ?? []) {
303
- written.set(saved.path, {
304
- locator,
305
- path: saved.path,
306
- revision: roomDraftRevision(),
307
- parts: returnedInto((written.get(saved.path) ?? (await store.draft(locator, saved.path)))?.parts, saved.parts),
308
- returned,
309
- });
310
- }
311
- }
312
- const carried = await carriedBack(store, locator, leaving.flatMap(({ batch, returned }) => (returned === null ? [] : [batch])), written);
313
- return {
314
- locator,
315
- left: batches,
316
- commit: {
317
- rooms: [],
318
- writes: [
319
- {
320
- locator,
321
- queue,
322
- stored: work.queue,
323
- roomDrafts: [...written.values()],
324
- consumedRoomDrafts: carried.taken,
325
- },
326
- ...carried.writes,
327
- ],
328
- },
329
- apply() {
330
- for (const batch of batches)
331
- journal.record(`outcome ${locator} ${batch.id} ${outcome}`);
332
- submitLater(locator);
333
- return mergeChangeSets([
334
- changeSet(locator, {
335
- queue: true,
336
- drafts: [...written.keys(), ...carried.taken],
337
- }),
338
- ...carried.changes,
339
- ]);
340
- },
341
- };
342
- }
343
- async function savedRoom(store, locator) {
344
- const local = await store.open();
22
+ const FULL = { outcome: 'refused', code: 'durable-write', retryable: true };
23
+ async function savedRoom({ store, presence }, locator) {
345
24
  const replica = await store.replica(locator);
346
- if (replica === undefined || store.fences.has(locator)) {
25
+ if (replica === undefined || presence.lost(locator)) {
347
26
  throw runtimeError('unknown-room', `Unknown Room ${locator}`);
348
27
  }
349
- return { local, replica };
28
+ return replica;
350
29
  }
351
- export async function saveRoomDrafts(runtime, locator, named, writtenBy) {
30
+ export function saveRoomDrafts(runtime, locator, named, writtenBy) {
352
31
  const { store } = runtime;
353
- const target = await savedRoom(store, locator);
354
- const drafts = new Map((await store.drafts(locator, named.map(({ path }) => path))).map((record) => [record.path, record]));
355
- const saved = [];
356
- for (const { path, revision } of named) {
357
- const record = drafts.get(path);
358
- if (record === undefined || record.revision !== revision) {
359
- return { outcome: 'refused', code: 'stale', retryable: false, file: path };
32
+ return store.exclusive(async () => {
33
+ if (store.space.full())
34
+ return FULL;
35
+ const replica = await savedRoom(runtime, locator);
36
+ const drafts = new Map((await store.drafts(locator, named.map(({ path }) => path))).map((record) => [record.path, record]));
37
+ const saved = [];
38
+ for (const { path, revision } of named) {
39
+ const record = drafts.get(path);
40
+ if (record === undefined || record.revision !== revision) {
41
+ return { outcome: 'refused', code: 'stale', retryable: false, file: path };
42
+ }
43
+ if (!saved.some((draft) => draft.path === path))
44
+ saved.push({ path, parts: record.parts });
360
45
  }
361
- if (!saved.some((draft) => draft.path === path))
362
- saved.push({ path, parts: record.parts });
363
- }
364
- return committed(runtime, locator, target, saved, new Set(saved.map(({ path }) => path)), writtenBy);
46
+ return loaded(runtime, locator, replica, saved, new Set(saved.map(({ path }) => path)), [], writtenBy);
47
+ });
365
48
  }
366
- export async function saveParts(runtime, locator, named, writtenBy) {
49
+ export function saveParts(runtime, locator, named, writtenBy) {
367
50
  const { store } = runtime;
368
- const read = readSavedParts(named);
369
- const target = await savedRoom(store, locator);
370
- const saved = [];
371
- const files = [];
372
- const filed = read.filter(({ parts }) => parts.file !== undefined).map(({ path }) => path);
373
- const visible = filed.length === 0 ? undefined : await visibleOf(store, locator, filed);
374
- for (const { path, parts } of read) {
375
- const file = parts.file === undefined
376
- ? undefined
377
- : await heldFile(parts.file.blob, parts.file.base === undefined ? binaryBase(visible, path) : parts.file.base);
378
- if (parts.file !== undefined && file !== undefined) {
379
- files.push({ key: file.localId, blob: parts.file.blob });
51
+ return store.exclusive(async () => {
52
+ const read = readSavedParts(named);
53
+ if (store.space.full())
54
+ return FULL;
55
+ const replica = await savedRoom(runtime, locator);
56
+ const saved = [];
57
+ const files = [];
58
+ const filed = read.filter(({ parts }) => parts.file !== undefined).map(({ path }) => path);
59
+ const visible = filed.length === 0 ? undefined : await visibleOf(store, locator, filed);
60
+ for (const { path, parts } of read) {
61
+ const file = parts.file === undefined
62
+ ? undefined
63
+ : await heldFile(parts.file.blob, parts.file.base === undefined ? binaryBase(visible, path) : parts.file.base);
64
+ if (parts.file !== undefined && file !== undefined) {
65
+ files.push({ key: file.localId, blob: parts.file.blob });
66
+ }
67
+ saved.push({ path, parts: held(undefined, parts, file) });
380
68
  }
381
- saved.push({ path, parts: held(undefined, parts, file) });
382
- }
383
- return committed(runtime, locator, target, saved, new Set(), writtenBy, files);
69
+ return loaded(runtime, locator, replica, saved, new Set(), files, writtenBy);
70
+ });
384
71
  }
385
- async function carriedDrafts(store, locator, saved, consumed) {
386
- if (!saved.some(({ parts }) => parts.move !== undefined))
387
- return new Map();
388
- const holding = new Map((await store.drafts(locator)).map((record) => [record.path, record]));
389
- return new Map(saved.flatMap(({ path, parts }) => {
390
- const record = holding.get(path);
391
- if (parts.move === undefined || record === undefined || consumed.has(path))
392
- return [];
393
- const to = moveTarget(parts.move);
394
- if (to === path || holding.has(to))
395
- return [];
396
- return [[path, { ...record, path: to, revision: roomDraftRevision() }]];
397
- }));
72
+ async function unmodeledIn(runtime, record) {
73
+ const composition = compositionFor(runtime, record);
74
+ if (available(composition))
75
+ return false;
76
+ const local = await runtime.store.open();
77
+ const roots = record.declaration.typedSelection?.roots ?? [];
78
+ return local.typedWithin(record.key, 'visible', roots, false);
398
79
  }
399
- async function savingIn(store, replica, changes, extra, leaving = []) {
400
- const record = replica.record;
80
+ async function savingRoom(runtime, replica, changes, extra, drafted) {
81
+ const { store } = runtime;
82
+ const { record, queue, states } = replica;
401
83
  const local = await store.open();
402
84
  const paths = [
403
85
  ...extra,
404
86
  ...changes.flatMap((change) => change.kind === 'move' ? [change.path, change.to] : [change.path]),
405
87
  ];
406
- const moved = [
407
- ...leaving,
408
- ...changes.flatMap((change) => (change.kind === 'move' ? [change.path] : [])),
409
- ];
88
+ const moved = changes.flatMap((change) => (change.kind === 'move' ? [change.path] : []));
410
89
  const removed = changes.flatMap((change) => (change.kind === 'remove' ? [change.path] : []));
411
90
  const referring = removed.length === 0
412
91
  ? []
413
92
  : (await local.referrers(record.key, 'confirmed', removed)).map(({ path }) => path);
414
- const confirmedLayer = await sliceAt(store, record, 'confirmed', replica.queue, [...paths, ...paths.flatMap(ancestors), ...referring], moved);
415
- const { visible } = await replayed(store, record, confirmedLayer, replica.queue);
93
+ const confirmed = await sliceAt(store, record, 'confirmed', queue, [...paths, ...paths.flatMap(ancestors), ...referring], moved);
416
94
  const composition = store.composed(record);
95
+ const changeable = withdrawable(states);
417
96
  return {
418
- record,
419
- confirmed: confirmedLayer,
420
- visible,
421
- folders: new Set(await local.folders(record.key, 'visible', paths)),
422
- *visibleOver(queue) {
423
- return (yield* replaying(confirmedLayer, queue, record.locator, composition)).visible;
97
+ held: { replica, confirmed },
98
+ room: {
99
+ record,
100
+ rule: runtime.writeRule(record.declaration),
101
+ composition: compositionFor(runtime, record),
102
+ queue,
103
+ changeable: new Set(queue.filter(changeable).map(({ id }) => id)),
104
+ saved: new Map([...states].map(([id, state]) => [id, state.drafts])),
105
+ *replay(over) {
106
+ return (yield* replaying(confirmed, over, record.locator, composition)).visible;
107
+ },
108
+ folders: new Set(await local.folders(record.key, paths)),
109
+ modelled: !(await unmodeledIn(runtime, record)),
110
+ drafts: new Map(drafted.length === 0
111
+ ? []
112
+ : (await store.drafts(record.locator, drafted)).map((held) => [held.path, held])),
113
+ validate: validatorOf(runtime, record, confirmed),
424
114
  },
425
115
  };
426
116
  }
427
- async function unmodeledIn(runtime, record) {
428
- const composition = compositionFor(runtime, record);
429
- if (available(composition))
430
- return false;
431
- const local = await runtime.store.open();
432
- const roots = record.typedSelection?.roots ?? [];
433
- return local.typedWithin(record.key, 'visible', roots, false);
117
+ function targetsOf(saved) {
118
+ return saved.flatMap(({ parts }) => (parts.move === undefined ? [] : [moveTarget(parts.move)]));
119
+ }
120
+ async function loaded(runtime, locator, replica, saved, consumed, files, writtenBy) {
121
+ const { store, localSaves } = runtime;
122
+ const into = destination(locator, saved);
123
+ const target = into === null ? undefined : await savedRoom(runtime, into);
124
+ const paths = saved.map(({ path }) => path);
125
+ const targets = targetsOf(saved);
126
+ const parts = saved.map(({ path, parts: held }) => changesOf(path, held));
127
+ const source = target === undefined
128
+ ? await savingRoom(runtime, replica, parts.flatMap(({ changes }) => changes), [...paths, ...parts.flatMap(({ bases }) => bases.map(({ path }) => path))], targets.length === 0 ? [] : [...paths, ...targets])
129
+ : await savingRoom(runtime, replica, paths.map((path) => ({ kind: 'remove', path })), paths, paths);
130
+ const arriving = target === undefined ? undefined : await savingRoom(runtime, target, [], targets, targets);
131
+ const { providerLimits } = await (await store.open()).facts();
132
+ const plan = await planSave({ saved, consumed, writtenBy, local: localSaves, providerLimits }, source.room, arriving?.room);
133
+ return append(runtime, {
134
+ plan,
135
+ files,
136
+ held: new Map([source, ...(arriving === undefined ? [] : [arriving])].map(({ room, held }) => [
137
+ room.record.locator,
138
+ held,
139
+ ])),
140
+ after: () => pendingShown(runtime),
141
+ });
434
142
  }
435
143
  function textDiffers(left, right) {
436
144
  return ((left?.content ?? undefined) !== (right?.content ?? undefined) ||
@@ -506,305 +214,6 @@ function referrersOf(local, key, view) {
506
214
  ...new Set((await local.referrers(key, view, targets)).map(({ path }) => path)),
507
215
  ];
508
216
  }
509
- async function sideOf(runtime, locator, replica, saving) {
510
- const record = replica.record;
511
- return {
512
- room: locator,
513
- handle: record.handle,
514
- baseRoomVersion: record.roomVersion,
515
- composition: compositionFor(runtime, record),
516
- operations: record.operations,
517
- providerOwnedPaths: record.providerOwnedPaths,
518
- visible: saving.visible,
519
- folders: saving.folders,
520
- queued: queuedDocuments(replica.queue),
521
- unmodeled: await unmodeledIn(runtime, record),
522
- validate: validatorOf(runtime, record, saving.confirmed),
523
- };
524
- }
525
- function movedInto(locator, saved) {
526
- const into = saved.find(({ parts }) => parts.move?.room !== undefined && parts.move.room !== locator);
527
- return into?.parts.move?.room ?? null;
528
- }
529
- async function saveCommit(store, local, files, rooms) {
530
- const commits = [];
531
- for (const { locator, saving, before, queue } of rooms) {
532
- const replayedLayer = await replayed(store, saving.record, saving.confirmed, queue);
533
- const diff = await overlayDiff(saving.record, before, replayedLayer.visible, replayedLayer.touched);
534
- commits.push({
535
- locator,
536
- key: saving.record.key,
537
- roots: saving.record.typedSelection?.roots ?? [],
538
- composed: composedOf(store.composed(saving.record)),
539
- overlay: diff.overlay,
540
- });
541
- }
542
- try {
543
- return await local.commit({
544
- rooms: commits,
545
- writes: rooms.map(({ write }) => write),
546
- files,
547
- whole: true,
548
- });
549
- }
550
- catch {
551
- return null;
552
- }
553
- }
554
- async function committedMove(runtime, locator, destination, { local, replica }, saved, consumed, writtenBy, files) {
555
- const { store, localSaves } = runtime;
556
- const mixed = saved.find(({ parts }) => Object.keys(parts).length !== 1 || parts.move?.room !== destination);
557
- if (mixed !== undefined) {
558
- return { outcome: 'refused', code: 'mixed-room-move', retryable: false, file: mixed.path };
559
- }
560
- const moves = saved.map(({ path, parts }) => ({ path, to: moveTarget(parts.move) }));
561
- const sourceSaving = await savingIn(store, replica, moves.map(({ path }) => ({ kind: 'remove', path })), saved.map(({ path }) => path), moves.map(({ path }) => path));
562
- const changed = saved.flatMap(({ path, parts }) => changedParts(path, parts, sourceSaving.visible));
563
- if (changed.length > 0)
564
- return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
565
- const target = await store.replica(destination);
566
- if (target === undefined || store.fences.has(destination)) {
567
- throw runtimeError('unknown-room', `Unknown Room ${destination}`);
568
- }
569
- const arriving = await savingIn(store, target, [], moves.map(({ to }) => to));
570
- const built = await buildMove({
571
- source: await sideOf(runtime, locator, replica, sourceSaving),
572
- destination: await sideOf(runtime, destination, target, arriving),
573
- moves,
574
- providerLimits: (await local.facts()).providerLimits,
575
- local: localSaves,
576
- ...(writtenBy === undefined ? {} : { writtenBy }),
577
- });
578
- if (built.kind === 'refused')
579
- return { outcome: 'refused', ...built.refusal };
580
- const id = built.leaving.id;
581
- const leavingDrafts = new Map((await store.drafts(locator, saved.map(({ path }) => path))).map((record) => [record.path, record]));
582
- const arrivingDrafts = new Set((await store.drafts(destination, moves.map(({ to }) => to))).map(({ path }) => path));
583
- const carrying = saved.flatMap(({ path, parts }) => {
584
- const record = leavingDrafts.get(path);
585
- const to = moveTarget(parts.move);
586
- if (record === undefined || consumed.has(path) || arrivingDrafts.has(to)) {
587
- return [];
588
- }
589
- const moved = {
590
- ...record,
591
- locator: destination,
592
- path: to,
593
- revision: roomDraftRevision(),
594
- };
595
- return [{ from: path, record: moved }];
596
- });
597
- const carried = carrying.map(({ record }) => record);
598
- const marked = carried.map(({ path }) => ({ path, parts: {} }));
599
- const left = [...consumed, ...carrying.map(({ from }) => from)];
600
- const leavingQueue = [...replica.queue, built.leaving];
601
- const arrivingQueue = [...target.queue, built.arriving];
602
- const answer = await saveCommit(store, local, files, [
603
- {
604
- locator,
605
- saving: sourceSaving,
606
- before: replica,
607
- queue: leavingQueue,
608
- write: {
609
- locator,
610
- queue: leavingQueue,
611
- stored: replica.queue,
612
- saved: new Map([[id, saved]]),
613
- consumedRoomDrafts: left,
614
- },
615
- },
616
- {
617
- locator: destination,
618
- saving: arriving,
619
- before: target,
620
- queue: arrivingQueue,
621
- write: {
622
- locator: destination,
623
- queue: arrivingQueue,
624
- stored: target.queue,
625
- saved: new Map([[id, marked]]),
626
- roomDrafts: carried,
627
- },
628
- },
629
- ]);
630
- if (answer === null)
631
- return { outcome: 'refused', code: 'durable-write', retryable: true };
632
- if (answer.fenced !== null) {
633
- throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
634
- }
635
- if (store.fences.has(locator) || store.fences.has(destination)) {
636
- throw runtimeError('unknown-room', `Unknown Room ${locator}`);
637
- }
638
- preparedAfter(runtime, locator, answer);
639
- preparedAfter(runtime, destination, answer);
640
- const shownNow = store.changed(mergeChangeSets([
641
- changeSet(locator, {
642
- documents: answer.changed[locator] ?? [],
643
- queue: true,
644
- drafts: left,
645
- }),
646
- changeSet(destination, {
647
- documents: answer.changed[destination] ?? [],
648
- queue: true,
649
- drafts: carried.map(({ path }) => path),
650
- }),
651
- ]));
652
- await Promise.all([shownNow, queuedSave(runtime, locator)]);
653
- return { outcome: 'saved', identifier: id };
654
- }
655
- function queuedSave(runtime, locator) {
656
- const shownNow = pendingShown(runtime);
657
- runtime.submitLater(locator);
658
- return shownNow;
659
- }
660
- async function committed(runtime, locator, target, saved, consumed, writtenBy, files = []) {
661
- const { store, localSaves } = runtime;
662
- if (store.space.full())
663
- return { outcome: 'refused', code: 'durable-write', retryable: true };
664
- const into = movedInto(locator, saved);
665
- if (into !== null) {
666
- return committedMove(runtime, locator, into, target, saved, consumed, writtenBy, files);
667
- }
668
- const { local, replica } = target;
669
- const { providerLimits } = await local.facts();
670
- const parts = saved.map(({ path, parts: held }) => changesOf(path, held));
671
- const changes = parts.flatMap(({ changes: made }) => made);
672
- const saving = await savingIn(store, replica, changes, [
673
- ...saved.map(({ path }) => path),
674
- ...parts.flatMap(({ bases }) => bases.map(({ path }) => path)),
675
- ]);
676
- const changed = saved.flatMap(({ path, parts: held }) => changedParts(path, held, saving.visible));
677
- if (changed.length > 0)
678
- return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
679
- const requeue = await stepped(requeuing(replica.queue, changes, saving.visible, withdrawable(replica.states), saving.visibleOver));
680
- if (requeue.kind === 'refused')
681
- return { outcome: 'refused', ...requeue.refusal };
682
- const foreign = [...requeue.withdrawn.keys()].some((origin) => {
683
- const writer = replica.queue.find((batch) => batch.id === origin)?.writtenBy;
684
- return writer?.app !== writtenBy?.app || writer?.release !== writtenBy?.release;
685
- });
686
- if (foreign) {
687
- return {
688
- outcome: 'refused',
689
- code: 'unknown-revision',
690
- retryable: false,
691
- file: [...requeue.queued].at(-1),
692
- };
693
- }
694
- const withdrawing = requeue.withdrawn.size > 0;
695
- const input = {
696
- local: localSaves,
697
- baseRoomVersion: replica.record.roomVersion,
698
- composition: compositionFor(runtime, replica.record),
699
- operations: replica.record.operations,
700
- providerOwnedPaths: replica.record.providerOwnedPaths,
701
- providerLimits,
702
- handle: replica.record.handle,
703
- visible: saving.visible,
704
- folders: saving.folders,
705
- changes,
706
- bases: parts.flatMap(({ bases }) => bases),
707
- queued: queuedDocuments(replica.queue),
708
- writtenBy,
709
- unmodeled: await unmodeledIn(runtime, replica.record),
710
- validate: validatorOf(runtime, replica.record, saving.confirmed),
711
- };
712
- const checked = withdrawing
713
- ? await buildBatch({ ...input, queued: new Set([...input.queued, ...requeue.queued]) })
714
- : null;
715
- if (checked?.kind === 'refused')
716
- return { outcome: 'refused', ...checked.refusal };
717
- const owners = [...requeue.owners].flatMap((origin) => {
718
- const batch = requeue.withdrawn.get(origin);
719
- return batch === undefined || batch === null ? [] : [{ origin, batch }];
720
- });
721
- if (owners.length > 1) {
722
- return {
723
- outcome: 'refused',
724
- code: 'unknown-revision',
725
- retryable: false,
726
- file: [...requeue.queued].at(-1),
727
- };
728
- }
729
- const built = !withdrawing
730
- ? await buildBatch(input)
731
- : requeue.changes.length === 0
732
- ? null
733
- : await buildBatch({
734
- ...input,
735
- visible: await stepped(saving.visibleOver(requeue.queue)),
736
- changes: requeue.changes,
737
- queued: queuedDocuments(requeue.queue),
738
- });
739
- if (built?.kind === 'refused')
740
- return { outcome: 'refused', ...built.refusal };
741
- const [owner] = owners;
742
- const join = built === null || owner === undefined ? null : joined(requeue.queue, owner.batch, built.batch);
743
- if (join?.kind === 'refused')
744
- return { outcome: 'refused', ...join.refusal };
745
- const withdrawn = new Map(requeue.withdrawn);
746
- if (join !== null && owner !== undefined)
747
- withdrawn.set(owner.origin, join.batch);
748
- const drafted = new Map();
749
- for (const [origin, replaced] of withdrawn) {
750
- if (replaced === null)
751
- continue;
752
- const carried = withdrawnDrafts(replica.states.get(origin)?.drafts ?? [], changes);
753
- drafted.set(replaced.id, replaced === join?.batch
754
- ? [
755
- ...carried.map((held) => joinedDraft(held, saved)),
756
- ...saved.filter((draft) => !carried.some((held) => held.path === draft.path)),
757
- ]
758
- : carried);
759
- }
760
- if (built !== null && join === null)
761
- drafted.set(built.batch.id, saved);
762
- const batches = join !== null ? join.queue : built === null ? requeue.queue : [...requeue.queue, built.batch];
763
- for (const replaced of withdrawn.values()) {
764
- if (replaced === null || !batches.includes(replaced))
765
- continue;
766
- const limited = await stepped(limiting(batches, replaced, providerLimits, saving.visibleOver));
767
- if (limited !== null)
768
- return { outcome: 'refused', ...limited };
769
- }
770
- const carried = await carriedDrafts(store, locator, saved, consumed);
771
- const left = [...consumed, ...carried.keys()];
772
- const arrived = [...carried.values()];
773
- const answer = await saveCommit(store, local, files, [
774
- {
775
- locator,
776
- saving,
777
- before: replica,
778
- queue: batches,
779
- write: {
780
- locator,
781
- queue: batches,
782
- stored: replica.queue,
783
- saved: drafted,
784
- consumedRoomDrafts: left,
785
- roomDrafts: arrived,
786
- },
787
- },
788
- ]);
789
- if (answer === null)
790
- return { outcome: 'refused', code: 'durable-write', retryable: true };
791
- if (answer.fenced !== null) {
792
- throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
793
- }
794
- if (store.fences.has(locator))
795
- throw runtimeError('unknown-room', `Unknown Room ${locator}`);
796
- preparedAfter(runtime, locator, answer);
797
- const shownNow = store.changed(changeSet(locator, {
798
- documents: answer.changed[locator] ?? [],
799
- queue: true,
800
- drafts: [...left, ...arrived.map(({ path }) => path)],
801
- }));
802
- await Promise.all([shownNow, queuedSave(runtime, locator)]);
803
- const identifier = join?.batch.id ??
804
- built?.batch.id ??
805
- [...requeue.withdrawn].map(([origin, replaced]) => replaced?.id ?? origin).at(-1);
806
- return { outcome: 'saved', identifier };
807
- }
808
217
  async function queuedFacts(store, locator, path, localId) {
809
218
  for (const batch of (await store.replica(locator))?.queue ?? []) {
810
219
  const change = batch.changes[queuedFile(batch, path, localId)];
@@ -821,7 +230,7 @@ export function batchFile(store, batch) {
821
230
  };
822
231
  }
823
232
  async function transferBinary(runtime, request, transfer, answer) {
824
- const { store, socket, journal } = runtime;
233
+ const { store, socket, presence, journal } = runtime;
825
234
  const { session } = socket;
826
235
  if (socket.closed() || session.now().refusal !== null)
827
236
  return UNCERTAIN;
@@ -837,18 +246,18 @@ async function transferBinary(runtime, request, transfer, answer) {
837
246
  const { closed, authorization, refusal } = session.now();
838
247
  if (closed || authorization === undefined || refusal !== null)
839
248
  return UNCERTAIN;
840
- const since = store.fences.count();
249
+ const since = presence.losses();
841
250
  journal.record(`transfer binary ${request.locator} ${request.path}`);
842
251
  try {
843
252
  const result = await transfer(authorization);
844
253
  const settled = result;
845
254
  journal.record(`outcome binary ${settled.kind}`);
846
- if (store.fences.since(request.locator, since))
255
+ if (presence.lostSince(request.locator, since))
847
256
  return failure({ kind: 'room-not-readable', locator: request.locator });
848
257
  if (settled.kind === 'scoped-refusal') {
849
258
  const room = await store.record(settled.locator);
850
259
  if (room !== undefined)
851
- await runtime.scoped(settled, room, await store.open());
260
+ await scoped(runtime, settled, room.locator, room.declaration);
852
261
  }
853
262
  if (settled.kind === 'revoked-authorization')
854
263
  await refuse(runtime, 'revoked-authorization');