@agentshouse/mdruntime 0.1.0-cd507e16e5fc0 → 0.1.0-cd574a610e9c8

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 (269) hide show
  1. package/README.md +21 -19
  2. package/dist/acceptance.d.ts +0 -3
  3. package/dist/acceptance.d.ts.map +1 -1
  4. package/dist/acceptance.js +75 -165
  5. package/dist/act-lanes.d.ts +30 -0
  6. package/dist/act-lanes.d.ts.map +1 -0
  7. package/dist/act-lanes.js +225 -0
  8. package/dist/actions.d.ts +8 -2
  9. package/dist/actions.d.ts.map +1 -1
  10. package/dist/actions.js +118 -67
  11. package/dist/adapter.d.ts +90 -58
  12. package/dist/adapter.d.ts.map +1 -1
  13. package/dist/attached-model.d.ts +0 -1
  14. package/dist/attached-model.d.ts.map +1 -1
  15. package/dist/attached-model.js +0 -5
  16. package/dist/authored.d.ts +88 -0
  17. package/dist/authored.d.ts.map +1 -0
  18. package/dist/authored.js +345 -0
  19. package/dist/bootstrap.d.ts +1 -1
  20. package/dist/bootstrap.d.ts.map +1 -1
  21. package/dist/bootstrap.js +6 -19
  22. package/dist/change-set.d.ts +4 -0
  23. package/dist/change-set.d.ts.map +1 -1
  24. package/dist/change-set.js +6 -0
  25. package/dist/change.d.ts +4 -0
  26. package/dist/change.d.ts.map +1 -1
  27. package/dist/change.js +7 -2
  28. package/dist/chat-types.d.ts +0 -2
  29. package/dist/chat-types.d.ts.map +1 -1
  30. package/dist/chat-types.js +0 -2
  31. package/dist/client.d.ts +13 -20
  32. package/dist/client.d.ts.map +1 -1
  33. package/dist/client.js +36 -20
  34. package/dist/collection.d.ts +25 -4
  35. package/dist/collection.d.ts.map +1 -1
  36. package/dist/collection.js +19 -2
  37. package/dist/column.d.ts +5 -0
  38. package/dist/column.d.ts.map +1 -0
  39. package/dist/column.js +9 -0
  40. package/dist/compile.d.ts +2 -0
  41. package/dist/compile.d.ts.map +1 -1
  42. package/dist/compile.js +31 -25
  43. package/dist/connection.d.ts.map +1 -1
  44. package/dist/connection.js +31 -24
  45. package/dist/context.d.ts.map +1 -1
  46. package/dist/context.js +1 -1
  47. package/dist/conversation-live.d.ts +26 -12
  48. package/dist/conversation-live.d.ts.map +1 -1
  49. package/dist/conversation-live.js +23 -12
  50. package/dist/conversation-row.d.ts +8 -1
  51. package/dist/conversation-row.d.ts.map +1 -1
  52. package/dist/conversation-row.js +20 -7
  53. package/dist/conversation.d.ts +37 -12
  54. package/dist/conversation.d.ts.map +1 -1
  55. package/dist/conversation.js +320 -234
  56. package/dist/declaration.d.ts +10 -0
  57. package/dist/declaration.d.ts.map +1 -0
  58. package/dist/declaration.js +24 -0
  59. package/dist/discovery.d.ts.map +1 -1
  60. package/dist/discovery.js +19 -44
  61. package/dist/durability.d.ts +4 -0
  62. package/dist/durability.d.ts.map +1 -1
  63. package/dist/durability.js +4 -0
  64. package/dist/entry.js +1 -1
  65. package/dist/expectation.d.ts +26 -13
  66. package/dist/expectation.d.ts.map +1 -1
  67. package/dist/expectation.js +72 -19
  68. package/dist/failure.d.ts +3 -0
  69. package/dist/failure.d.ts.map +1 -1
  70. package/dist/failure.js +6 -0
  71. package/dist/figures.d.ts.map +1 -1
  72. package/dist/figures.js +4 -2
  73. package/dist/file.d.ts +1 -1
  74. package/dist/file.d.ts.map +1 -1
  75. package/dist/file.js +3 -2
  76. package/dist/host.d.ts +18 -2
  77. package/dist/host.d.ts.map +1 -1
  78. package/dist/host.js +59 -15
  79. package/dist/index.d.ts +16 -11
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +12 -7
  82. package/dist/install.d.ts +10 -6
  83. package/dist/install.d.ts.map +1 -1
  84. package/dist/install.js +92 -111
  85. package/dist/lifecycle.d.ts +1 -1
  86. package/dist/lifecycle.d.ts.map +1 -1
  87. package/dist/lifecycle.js +54 -40
  88. package/dist/listing.d.ts +11 -12
  89. package/dist/listing.d.ts.map +1 -1
  90. package/dist/listing.js +48 -72
  91. package/dist/live.d.ts +5 -3
  92. package/dist/live.d.ts.map +1 -1
  93. package/dist/live.js +74 -33
  94. package/dist/loaded.d.ts +5 -0
  95. package/dist/loaded.d.ts.map +1 -0
  96. package/dist/loaded.js +40 -0
  97. package/dist/locks.d.ts +2 -4
  98. package/dist/locks.d.ts.map +1 -1
  99. package/dist/locks.js +10 -39
  100. package/dist/lost.d.ts.map +1 -1
  101. package/dist/lost.js +11 -23
  102. package/dist/media.d.ts +6 -1
  103. package/dist/media.d.ts.map +1 -1
  104. package/dist/media.js +68 -17
  105. package/dist/memory.d.ts +8 -3
  106. package/dist/memory.d.ts.map +1 -1
  107. package/dist/memory.js +92 -33
  108. package/dist/messages.d.ts +12 -6
  109. package/dist/messages.d.ts.map +1 -1
  110. package/dist/messages.js +30 -38
  111. package/dist/model.d.ts +0 -1
  112. package/dist/model.d.ts.map +1 -1
  113. package/dist/model.js +0 -5
  114. package/dist/models.d.ts +4 -8
  115. package/dist/models.d.ts.map +1 -1
  116. package/dist/models.js +97 -139
  117. package/dist/observation.d.ts +27 -12
  118. package/dist/observation.d.ts.map +1 -1
  119. package/dist/observation.js +184 -85
  120. package/dist/opened.d.ts +1 -1
  121. package/dist/opened.d.ts.map +1 -1
  122. package/dist/opened.js +5 -5
  123. package/dist/opening.d.ts.map +1 -1
  124. package/dist/opening.js +5 -9
  125. package/dist/outcome.d.ts +13 -1
  126. package/dist/outcome.d.ts.map +1 -1
  127. package/dist/outcome.js +32 -2
  128. package/dist/overlay.d.ts +6 -2
  129. package/dist/overlay.d.ts.map +1 -1
  130. package/dist/overlay.js +37 -24
  131. package/dist/page-metadata.d.ts +8 -0
  132. package/dist/page-metadata.d.ts.map +1 -0
  133. package/dist/page-metadata.js +19 -0
  134. package/dist/parts.d.ts +3 -58
  135. package/dist/parts.d.ts.map +1 -1
  136. package/dist/parts.js +10 -115
  137. package/dist/preparation.d.ts +27 -6
  138. package/dist/preparation.d.ts.map +1 -1
  139. package/dist/preparation.js +156 -68
  140. package/dist/presence.d.ts +58 -0
  141. package/dist/presence.d.ts.map +1 -0
  142. package/dist/presence.js +231 -0
  143. package/dist/queries.d.ts +2 -2
  144. package/dist/queries.d.ts.map +1 -1
  145. package/dist/queries.js +67 -31
  146. package/dist/query.d.ts +0 -1
  147. package/dist/query.d.ts.map +1 -1
  148. package/dist/questions.d.ts +1 -5
  149. package/dist/questions.d.ts.map +1 -1
  150. package/dist/questions.js +1 -7
  151. package/dist/queue.d.ts +40 -0
  152. package/dist/queue.d.ts.map +1 -0
  153. package/dist/queue.js +480 -0
  154. package/dist/reach.d.ts +2 -3
  155. package/dist/reach.d.ts.map +1 -1
  156. package/dist/reach.js +88 -26
  157. package/dist/react.d.ts +8 -1
  158. package/dist/react.d.ts.map +1 -1
  159. package/dist/react.js +33 -1
  160. package/dist/readiness.d.ts +9 -3
  161. package/dist/readiness.d.ts.map +1 -1
  162. package/dist/readiness.js +3 -5
  163. package/dist/reads.d.ts +4 -2
  164. package/dist/reads.d.ts.map +1 -1
  165. package/dist/reads.js +11 -4
  166. package/dist/recovery.d.ts +3 -1
  167. package/dist/recovery.d.ts.map +1 -1
  168. package/dist/recovery.js +29 -7
  169. package/dist/refresh.js +2 -2
  170. package/dist/refusal.d.ts.map +1 -1
  171. package/dist/refusal.js +5 -6
  172. package/dist/replica.d.ts +5 -11
  173. package/dist/replica.d.ts.map +1 -1
  174. package/dist/replica.js +15 -17
  175. package/dist/retried.d.ts +30 -0
  176. package/dist/retried.d.ts.map +1 -0
  177. package/dist/retried.js +72 -0
  178. package/dist/room-draft.d.ts +31 -8
  179. package/dist/room-draft.d.ts.map +1 -1
  180. package/dist/room-draft.js +273 -66
  181. package/dist/room-drafts.d.ts.map +1 -1
  182. package/dist/room-drafts.js +22 -18
  183. package/dist/room-list.d.ts +10 -0
  184. package/dist/room-list.d.ts.map +1 -0
  185. package/dist/room-list.js +45 -0
  186. package/dist/rows.d.ts +2 -1
  187. package/dist/rows.d.ts.map +1 -1
  188. package/dist/rows.js +9 -10
  189. package/dist/runtime.d.ts +9 -18
  190. package/dist/runtime.d.ts.map +1 -1
  191. package/dist/save.d.ts +42 -90
  192. package/dist/save.d.ts.map +1 -1
  193. package/dist/save.js +327 -229
  194. package/dist/saves.d.ts +4 -40
  195. package/dist/saves.d.ts.map +1 -1
  196. package/dist/saves.js +143 -714
  197. package/dist/seed.d.ts.map +1 -1
  198. package/dist/seed.js +4 -5
  199. package/dist/shared.d.ts +0 -5
  200. package/dist/shared.d.ts.map +1 -1
  201. package/dist/shared.js +4 -90
  202. package/dist/shown.d.ts +7 -5
  203. package/dist/shown.d.ts.map +1 -1
  204. package/dist/shown.js +77 -63
  205. package/dist/socket/lanes.d.ts +12 -4
  206. package/dist/socket/lanes.d.ts.map +1 -1
  207. package/dist/socket/lanes.js +34 -8
  208. package/dist/socket/session.d.ts +0 -1
  209. package/dist/socket/session.d.ts.map +1 -1
  210. package/dist/socket.d.ts +3 -6
  211. package/dist/socket.d.ts.map +1 -1
  212. package/dist/socket.js +5 -16
  213. package/dist/source.d.ts +5 -7
  214. package/dist/source.d.ts.map +1 -1
  215. package/dist/source.js +18 -6
  216. package/dist/store/installs.d.ts +2 -2
  217. package/dist/store/installs.d.ts.map +1 -1
  218. package/dist/store/installs.js +10 -4
  219. package/dist/store/readiness.d.ts +5 -4
  220. package/dist/store/readiness.d.ts.map +1 -1
  221. package/dist/store/readiness.js +28 -12
  222. package/dist/store/records.d.ts +18 -7
  223. package/dist/store/records.d.ts.map +1 -1
  224. package/dist/store/records.js +58 -24
  225. package/dist/store/rooms-shown.d.ts +8 -0
  226. package/dist/store/rooms-shown.d.ts.map +1 -0
  227. package/dist/store/rooms-shown.js +13 -0
  228. package/dist/store/space.d.ts +1 -1
  229. package/dist/store/space.d.ts.map +1 -1
  230. package/dist/store/space.js +0 -2
  231. package/dist/store/watchers.js +1 -2
  232. package/dist/store.d.ts +23 -14
  233. package/dist/store.d.ts.map +1 -1
  234. package/dist/store.js +46 -17
  235. package/dist/stored.d.ts +25 -14
  236. package/dist/stored.d.ts.map +1 -1
  237. package/dist/stored.js +83 -32
  238. package/dist/surface.d.ts +5 -3
  239. package/dist/surface.d.ts.map +1 -1
  240. package/dist/surface.js +25 -23
  241. package/dist/sync.d.ts +1 -1
  242. package/dist/sync.d.ts.map +1 -1
  243. package/dist/sync.js +96 -92
  244. package/dist/tables.d.ts +16 -57
  245. package/dist/tables.d.ts.map +1 -1
  246. package/dist/tables.js +91 -332
  247. package/dist/tallies.d.ts.map +1 -1
  248. package/dist/tallies.js +6 -7
  249. package/dist/validation.d.ts.map +1 -1
  250. package/dist/validation.js +4 -10
  251. package/dist/wire.d.ts +53 -7
  252. package/dist/wire.d.ts.map +1 -1
  253. package/dist/wire.js +17 -4
  254. package/package.json +3 -2
  255. package/dist/attempt.d.ts +0 -16
  256. package/dist/attempt.d.ts.map +0 -1
  257. package/dist/attempt.js +0 -70
  258. package/dist/job.d.ts +0 -33
  259. package/dist/job.d.ts.map +0 -1
  260. package/dist/job.js +0 -54
  261. package/dist/log.d.ts +0 -6
  262. package/dist/log.d.ts.map +0 -1
  263. package/dist/log.js +0 -14
  264. package/dist/socket/rooms.d.ts +0 -44
  265. package/dist/socket/rooms.d.ts.map +0 -1
  266. package/dist/socket/rooms.js +0 -90
  267. package/dist/store/fences.d.ts +0 -16
  268. package/dist/store/fences.d.ts.map +0 -1
  269. package/dist/store/fences.js +0 -26
package/dist/saves.js CHANGED
@@ -1,436 +1,161 @@
1
- import { parseFrontmatter } from '@agentshouse/mdmodel';
1
+ import { isImageType, parse } 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 { changeSet } from './change-set.js';
4
+ import { queuedFile } from './change.js';
5
+ import { available, textType } from './collection.js';
6
6
  import { runtimeError } from './failure.js';
7
7
  import { attachedFile } from './file.js';
8
8
  import { roomDraftRevision } from './identity.js';
9
- import { confirmedAgain } from './install.js';
10
9
  import { compositionFor } from './listing.js';
11
- import { absentBinary, attachAnswer, binaryAnswer, binaryLinkAnswer, failure, OFFLINE, UNCERTAIN, } from './outcome.js';
10
+ import { scoped } from './models.js';
11
+ import { absentBinary, attachAnswer, binaryAnswer, binaryLinkAnswer, failure, linkedAnswer, OFFLINE, UNCERTAIN, } from './outcome.js';
12
12
  import { sameBinary } from './overlay.js';
13
13
  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';
14
+ import { connectionNow } from './preparation.js';
15
+ import { append, sliceAt, withdrawable } from './queue.js';
16
+ import { entryAt, replaying, slicing, } from './replica.js';
17
+ import { binaryBase, changesOf, held, heldFile, joinedOver, moveTarget, readSavedParts, } from './room-draft.js';
17
18
  import { refuse } from './refusal.js';
18
19
  import { visibleOf } from './room-drafts.js';
19
- import { documentKey } from './rows.js';
20
- import { buildBatch, buildMove, joined, limiting, queuedDocuments, requeuing, } from './save.js';
20
+ import { destination, planSave, } from './save.js';
21
21
  import { pendingShown } from './shown.js';
22
22
  import { paced, ROW_WEIGHT, stepped } from './turn.js';
23
23
  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
- }
24
+ const FULL = { outcome: 'refused', code: 'durable-write', retryable: true };
25
+ async function savedRoom({ store, presence }, locator) {
26
+ const replica = await store.replica(locator);
27
+ if (replica === undefined || presence.lost(locator)) {
28
+ throw runtimeError('unknown-room', `Unknown Room ${locator}`);
81
29
  }
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)));
30
+ return replica;
105
31
  }
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]);
32
+ export function saveRoomDrafts(runtime, locator, named, writtenBy) {
33
+ const { store } = runtime;
34
+ return store.exclusive(async () => {
35
+ if (store.space.full())
36
+ return FULL;
37
+ const replica = await savedRoom(runtime, locator);
38
+ const drafts = new Map((await store.drafts(locator, named.map(({ path }) => path))).map((record) => [record.path, record]));
39
+ const saved = [];
40
+ for (const { path, revision } of named) {
41
+ const record = drafts.get(path);
42
+ if (record === undefined || record.revision !== revision) {
43
+ return { outcome: 'refused', code: 'stale', retryable: false, file: path };
141
44
  }
45
+ if (!saved.some((draft) => draft.path === path))
46
+ saved.push({ path, parts: record.parts });
142
47
  }
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),
48
+ const result = await loaded(runtime, locator, replica, saved, new Set(saved.map(({ path }) => path)), [], writtenBy);
49
+ if (result.outcome !== 'refused' || result.code !== 'changed-underneath')
50
+ return result;
51
+ return joinedUnderneath(runtime, locator, saved.map(({ path }) => drafts.get(path)), result);
154
52
  });
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
53
  }
343
- async function savedRoom(store, locator) {
344
- const local = await store.open();
345
- const replica = await store.replica(locator);
346
- if (replica === undefined || store.fences.has(locator)) {
347
- throw runtimeError('unknown-room', `Unknown Room ${locator}`);
348
- }
349
- return { local, replica };
54
+ async function joinedUnderneath({ store }, locator, records, refused) {
55
+ const visible = (await visibleOf(store, locator, records.map(({ path }) => path)));
56
+ const joined = records.flatMap((record) => {
57
+ const join = joinedOver(record.parts, record.marks, record.path, visible);
58
+ return join === null ? [] : [{ ...record, ...join, revision: roomDraftRevision() }];
59
+ });
60
+ if (joined.length === 0)
61
+ return refused;
62
+ await (await store.open()).commit({ rooms: [], writes: [{ locator, roomDrafts: joined }] });
63
+ await store.changed(changeSet(locator, { drafts: joined.map(({ path }) => path) }));
64
+ return refused;
350
65
  }
351
- export async function saveRoomDrafts(runtime, locator, named, writtenBy) {
66
+ export function saveParts(runtime, locator, named, writtenBy) {
352
67
  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 };
68
+ return store.exclusive(async () => {
69
+ const read = readSavedParts(named);
70
+ if (store.space.full())
71
+ return FULL;
72
+ const replica = await savedRoom(runtime, locator);
73
+ const saved = [];
74
+ const files = [];
75
+ const filed = read.filter(({ parts }) => parts.file !== undefined).map(({ path }) => path);
76
+ const visible = filed.length === 0 ? undefined : await visibleOf(store, locator, filed);
77
+ for (const { path, parts } of read) {
78
+ const file = parts.file === undefined
79
+ ? undefined
80
+ : await heldFile(parts.file.blob, parts.file.base === undefined ? binaryBase(visible, path) : parts.file.base);
81
+ if (parts.file !== undefined && file !== undefined) {
82
+ files.push({ key: file.localId, blob: parts.file.blob });
83
+ }
84
+ saved.push({ path, parts: held(undefined, parts, file) });
360
85
  }
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);
86
+ return loaded(runtime, locator, replica, saved, new Set(), files, writtenBy);
87
+ });
365
88
  }
366
- export async function saveParts(runtime, locator, named, writtenBy) {
89
+ async function unmodeledIn(runtime, record) {
90
+ const composition = compositionFor(runtime, record);
91
+ if (available(composition))
92
+ return false;
93
+ const local = await runtime.store.open();
94
+ const roots = record.declaration.typedSelection?.roots ?? [];
95
+ return local.typedWithin(record.key, 'visible', roots, false);
96
+ }
97
+ async function savingRoom(runtime, replica, changes, extra, drafted) {
367
98
  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 });
380
- }
381
- saved.push({ path, parts: held(undefined, parts, file) });
382
- }
383
- return committed(runtime, locator, target, saved, new Set(), writtenBy, files);
384
- }
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
- }));
398
- }
399
- async function savingIn(store, replica, changes, extra, leaving = []) {
400
- const record = replica.record;
99
+ const { record, queue, states } = replica;
401
100
  const local = await store.open();
402
101
  const paths = [
403
102
  ...extra,
404
103
  ...changes.flatMap((change) => change.kind === 'move' ? [change.path, change.to] : [change.path]),
405
104
  ];
406
- const moved = [
407
- ...leaving,
408
- ...changes.flatMap((change) => (change.kind === 'move' ? [change.path] : [])),
409
- ];
105
+ const moved = changes.flatMap((change) => (change.kind === 'move' ? [change.path] : []));
410
106
  const removed = changes.flatMap((change) => (change.kind === 'remove' ? [change.path] : []));
411
107
  const referring = removed.length === 0
412
108
  ? []
413
109
  : (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);
110
+ const confirmed = await sliceAt(store, record, 'confirmed', queue, [...paths, ...paths.flatMap(ancestors), ...referring], moved);
416
111
  const composition = store.composed(record);
112
+ const changeable = withdrawable(states);
417
113
  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;
114
+ held: { replica, confirmed },
115
+ room: {
116
+ record,
117
+ rule: runtime.writeRule(record.declaration),
118
+ composition: compositionFor(runtime, record),
119
+ queue,
120
+ changeable: new Set(queue.filter(changeable).map(({ id }) => id)),
121
+ saved: new Map([...states].map(([id, state]) => [id, state.drafts])),
122
+ *replay(over) {
123
+ return (yield* replaying(confirmed, over, record.locator, composition)).visible;
124
+ },
125
+ folders: new Set(await local.folders(record.key, paths)),
126
+ modelled: !(await unmodeledIn(runtime, record)),
127
+ drafts: new Map(drafted.length === 0
128
+ ? []
129
+ : (await store.drafts(record.locator, drafted)).map((held) => [held.path, held])),
130
+ validate: validatorOf(runtime, record, confirmed),
424
131
  },
425
132
  };
426
133
  }
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);
134
+ function targetsOf(saved) {
135
+ return saved.flatMap(({ parts }) => (parts.move === undefined ? [] : [moveTarget(parts.move)]));
136
+ }
137
+ async function loaded(runtime, locator, replica, saved, consumed, files, writtenBy) {
138
+ const { store, localSaves } = runtime;
139
+ const into = destination(locator, saved);
140
+ const target = into === null ? undefined : await savedRoom(runtime, into);
141
+ const paths = saved.map(({ path }) => path);
142
+ const targets = targetsOf(saved);
143
+ const parts = saved.map(({ path, parts: held }) => changesOf(path, held));
144
+ const source = target === undefined
145
+ ? await savingRoom(runtime, replica, parts.flatMap(({ changes }) => changes), [...paths, ...parts.flatMap(({ bases }) => bases.map(({ path }) => path))], targets.length === 0 ? [] : [...paths, ...targets])
146
+ : await savingRoom(runtime, replica, paths.map((path) => ({ kind: 'remove', path })), paths, paths);
147
+ const arriving = target === undefined ? undefined : await savingRoom(runtime, target, [], targets, targets);
148
+ const { providerLimits } = await (await store.open()).facts();
149
+ const plan = await planSave({ saved, consumed, writtenBy, local: localSaves, providerLimits }, source.room, arriving?.room);
150
+ return append(runtime, {
151
+ plan,
152
+ files,
153
+ held: new Map([source, ...(arriving === undefined ? [] : [arriving])].map(({ room, held }) => [
154
+ room.record.locator,
155
+ held,
156
+ ])),
157
+ after: () => pendingShown(runtime),
158
+ });
434
159
  }
435
160
  function textDiffers(left, right) {
436
161
  return ((left?.content ?? undefined) !== (right?.content ?? undefined) ||
@@ -454,8 +179,7 @@ function lookupOf(local, key, view) {
454
179
  function declaredOf(entry) {
455
180
  if (entry.binary !== null)
456
181
  return { type: undefined, binary: true };
457
- const read = parseFrontmatter(entry.content ?? '');
458
- return { type: read.ok ? (read.frontmatter.type ?? 'any') : undefined, binary: false };
182
+ return { type: textType(parse(entry.content ?? '')), binary: false };
459
183
  }
460
184
  function overriddenLookup(lookup, overrides) {
461
185
  return async (paths) => {
@@ -506,309 +230,6 @@ function referrersOf(local, key, view) {
506
230
  ...new Set((await local.referrers(key, view, targets)).map(({ path }) => path)),
507
231
  ];
508
232
  }
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
- authoredPaths: record.authoredPaths,
519
- visible: saving.visible,
520
- folders: saving.folders,
521
- queued: queuedDocuments(replica.queue),
522
- unmodeled: await unmodeledIn(runtime, record),
523
- validate: validatorOf(runtime, record, saving.confirmed),
524
- };
525
- }
526
- function movedInto(locator, saved) {
527
- const into = saved.find(({ parts }) => parts.move?.room !== undefined && parts.move.room !== locator);
528
- return into?.parts.move?.room ?? null;
529
- }
530
- async function saveCommit(store, local, files, rooms) {
531
- const commits = [];
532
- for (const { locator, saving, before, queue } of rooms) {
533
- const replayedLayer = await replayed(store, saving.record, saving.confirmed, queue);
534
- const diff = await overlayDiff(saving.record, before, replayedLayer.visible, replayedLayer.touched);
535
- commits.push({
536
- locator,
537
- key: saving.record.key,
538
- roots: saving.record.typedSelection?.roots ?? [],
539
- composed: composedOf(store.composed(saving.record)),
540
- overlay: diff.overlay,
541
- });
542
- }
543
- try {
544
- return await local.commit({
545
- rooms: commits,
546
- writes: rooms.map(({ write }) => write),
547
- files,
548
- whole: true,
549
- });
550
- }
551
- catch {
552
- return null;
553
- }
554
- }
555
- async function committedMove(runtime, locator, destination, { local, replica }, saved, consumed, writtenBy, files) {
556
- const { store, localSaves } = runtime;
557
- const mixed = saved.find(({ parts }) => Object.keys(parts).length !== 1 || parts.move?.room !== destination);
558
- if (mixed !== undefined) {
559
- return { outcome: 'refused', code: 'mixed-room-move', retryable: false, file: mixed.path };
560
- }
561
- const moves = saved.map(({ path, parts }) => ({ path, to: moveTarget(parts.move) }));
562
- const sourceSaving = await savingIn(store, replica, moves.map(({ path }) => ({ kind: 'remove', path })), saved.map(({ path }) => path), moves.map(({ path }) => path));
563
- const changed = saved.flatMap(({ path, parts }) => changedParts(path, parts, sourceSaving.visible));
564
- if (changed.length > 0)
565
- return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
566
- const target = await store.replica(destination);
567
- if (target === undefined || store.fences.has(destination)) {
568
- throw runtimeError('unknown-room', `Unknown Room ${destination}`);
569
- }
570
- const arriving = await savingIn(store, target, [], moves.map(({ to }) => to));
571
- const built = await buildMove({
572
- source: await sideOf(runtime, locator, replica, sourceSaving),
573
- destination: await sideOf(runtime, destination, target, arriving),
574
- moves,
575
- providerLimits: (await local.facts()).providerLimits,
576
- pathRule: runtime.pathRule,
577
- local: localSaves,
578
- ...(writtenBy === undefined ? {} : { writtenBy }),
579
- });
580
- if (built.kind === 'refused')
581
- return { outcome: 'refused', ...built.refusal };
582
- const id = built.leaving.id;
583
- const leavingDrafts = new Map((await store.drafts(locator, saved.map(({ path }) => path))).map((record) => [record.path, record]));
584
- const arrivingDrafts = new Set((await store.drafts(destination, moves.map(({ to }) => to))).map(({ path }) => path));
585
- const carrying = saved.flatMap(({ path, parts }) => {
586
- const record = leavingDrafts.get(path);
587
- const to = moveTarget(parts.move);
588
- if (record === undefined || consumed.has(path) || arrivingDrafts.has(to)) {
589
- return [];
590
- }
591
- const moved = {
592
- ...record,
593
- locator: destination,
594
- path: to,
595
- revision: roomDraftRevision(),
596
- };
597
- return [{ from: path, record: moved }];
598
- });
599
- const carried = carrying.map(({ record }) => record);
600
- const marked = carried.map(({ path }) => ({ path, parts: {} }));
601
- const left = [...consumed, ...carrying.map(({ from }) => from)];
602
- const leavingQueue = [...replica.queue, built.leaving];
603
- const arrivingQueue = [...target.queue, built.arriving];
604
- const answer = await saveCommit(store, local, files, [
605
- {
606
- locator,
607
- saving: sourceSaving,
608
- before: replica,
609
- queue: leavingQueue,
610
- write: {
611
- locator,
612
- queue: leavingQueue,
613
- stored: replica.queue,
614
- saved: new Map([[id, saved]]),
615
- consumedRoomDrafts: left,
616
- },
617
- },
618
- {
619
- locator: destination,
620
- saving: arriving,
621
- before: target,
622
- queue: arrivingQueue,
623
- write: {
624
- locator: destination,
625
- queue: arrivingQueue,
626
- stored: target.queue,
627
- saved: new Map([[id, marked]]),
628
- roomDrafts: carried,
629
- },
630
- },
631
- ]);
632
- if (answer === null)
633
- return { outcome: 'refused', code: 'durable-write', retryable: true };
634
- if (answer.fenced !== null) {
635
- throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
636
- }
637
- if (store.fences.has(locator) || store.fences.has(destination)) {
638
- throw runtimeError('unknown-room', `Unknown Room ${locator}`);
639
- }
640
- preparedAfter(runtime, locator, answer);
641
- preparedAfter(runtime, destination, answer);
642
- const shownNow = store.changed(mergeChangeSets([
643
- changeSet(locator, {
644
- documents: answer.changed[locator] ?? [],
645
- queue: true,
646
- drafts: left,
647
- }),
648
- changeSet(destination, {
649
- documents: answer.changed[destination] ?? [],
650
- queue: true,
651
- drafts: carried.map(({ path }) => path),
652
- }),
653
- ]));
654
- await Promise.all([shownNow, queuedSave(runtime, locator)]);
655
- return { outcome: 'saved', identifier: id };
656
- }
657
- function queuedSave(runtime, locator) {
658
- const shownNow = pendingShown(runtime);
659
- runtime.submitLater(locator);
660
- return shownNow;
661
- }
662
- async function committed(runtime, locator, target, saved, consumed, writtenBy, files = []) {
663
- const { store, localSaves } = runtime;
664
- if (store.space.full())
665
- return { outcome: 'refused', code: 'durable-write', retryable: true };
666
- const into = movedInto(locator, saved);
667
- if (into !== null) {
668
- return committedMove(runtime, locator, into, target, saved, consumed, writtenBy, files);
669
- }
670
- const { local, replica } = target;
671
- const { providerLimits } = await local.facts();
672
- const parts = saved.map(({ path, parts: held }) => changesOf(path, held));
673
- const changes = parts.flatMap(({ changes: made }) => made);
674
- const saving = await savingIn(store, replica, changes, [
675
- ...saved.map(({ path }) => path),
676
- ...parts.flatMap(({ bases }) => bases.map(({ path }) => path)),
677
- ]);
678
- const changed = saved.flatMap(({ path, parts: held }) => changedParts(path, held, saving.visible));
679
- if (changed.length > 0)
680
- return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
681
- const requeue = await stepped(requeuing(replica.queue, changes, saving.visible, withdrawable(replica.states), saving.visibleOver));
682
- if (requeue.kind === 'refused')
683
- return { outcome: 'refused', ...requeue.refusal };
684
- const foreign = [...requeue.withdrawn.keys()].some((origin) => {
685
- const writer = replica.queue.find((batch) => batch.id === origin)?.writtenBy;
686
- return writer?.app !== writtenBy?.app || writer?.release !== writtenBy?.release;
687
- });
688
- if (foreign) {
689
- return {
690
- outcome: 'refused',
691
- code: 'unknown-revision',
692
- retryable: false,
693
- file: [...requeue.queued].at(-1),
694
- };
695
- }
696
- const withdrawing = requeue.withdrawn.size > 0;
697
- const input = {
698
- local: localSaves,
699
- baseRoomVersion: replica.record.roomVersion,
700
- composition: compositionFor(runtime, replica.record),
701
- operations: replica.record.operations,
702
- providerOwnedPaths: replica.record.providerOwnedPaths,
703
- authoredPaths: replica.record.authoredPaths,
704
- providerLimits,
705
- pathRule: runtime.pathRule,
706
- handle: replica.record.handle,
707
- visible: saving.visible,
708
- folders: saving.folders,
709
- changes,
710
- bases: parts.flatMap(({ bases }) => bases),
711
- queued: queuedDocuments(replica.queue),
712
- writtenBy,
713
- unmodeled: await unmodeledIn(runtime, replica.record),
714
- validate: validatorOf(runtime, replica.record, saving.confirmed),
715
- };
716
- const checked = withdrawing
717
- ? await buildBatch({ ...input, queued: new Set([...input.queued, ...requeue.queued]) })
718
- : null;
719
- if (checked?.kind === 'refused')
720
- return { outcome: 'refused', ...checked.refusal };
721
- const owners = [...requeue.owners].flatMap((origin) => {
722
- const batch = requeue.withdrawn.get(origin);
723
- return batch === undefined || batch === null ? [] : [{ origin, batch }];
724
- });
725
- if (owners.length > 1) {
726
- return {
727
- outcome: 'refused',
728
- code: 'unknown-revision',
729
- retryable: false,
730
- file: [...requeue.queued].at(-1),
731
- };
732
- }
733
- const built = !withdrawing
734
- ? await buildBatch(input)
735
- : requeue.changes.length === 0
736
- ? null
737
- : await buildBatch({
738
- ...input,
739
- visible: await stepped(saving.visibleOver(requeue.queue)),
740
- changes: requeue.changes,
741
- queued: queuedDocuments(requeue.queue),
742
- });
743
- if (built?.kind === 'refused')
744
- return { outcome: 'refused', ...built.refusal };
745
- const [owner] = owners;
746
- const join = built === null || owner === undefined ? null : joined(requeue.queue, owner.batch, built.batch);
747
- if (join?.kind === 'refused')
748
- return { outcome: 'refused', ...join.refusal };
749
- const withdrawn = new Map(requeue.withdrawn);
750
- if (join !== null && owner !== undefined)
751
- withdrawn.set(owner.origin, join.batch);
752
- const drafted = new Map();
753
- for (const [origin, replaced] of withdrawn) {
754
- if (replaced === null)
755
- continue;
756
- const carried = withdrawnDrafts(replica.states.get(origin)?.drafts ?? [], changes);
757
- drafted.set(replaced.id, replaced === join?.batch
758
- ? [
759
- ...carried.map((held) => joinedDraft(held, saved)),
760
- ...saved.filter((draft) => !carried.some((held) => held.path === draft.path)),
761
- ]
762
- : carried);
763
- }
764
- if (built !== null && join === null)
765
- drafted.set(built.batch.id, saved);
766
- const batches = join !== null ? join.queue : built === null ? requeue.queue : [...requeue.queue, built.batch];
767
- for (const replaced of withdrawn.values()) {
768
- if (replaced === null || !batches.includes(replaced))
769
- continue;
770
- const limited = await stepped(limiting(batches, replaced, providerLimits, saving.visibleOver));
771
- if (limited !== null)
772
- return { outcome: 'refused', ...limited };
773
- }
774
- const carried = await carriedDrafts(store, locator, saved, consumed);
775
- const left = [...consumed, ...carried.keys()];
776
- const arrived = [...carried.values()];
777
- const answer = await saveCommit(store, local, files, [
778
- {
779
- locator,
780
- saving,
781
- before: replica,
782
- queue: batches,
783
- write: {
784
- locator,
785
- queue: batches,
786
- stored: replica.queue,
787
- saved: drafted,
788
- consumedRoomDrafts: left,
789
- roomDrafts: arrived,
790
- },
791
- },
792
- ]);
793
- if (answer === null)
794
- return { outcome: 'refused', code: 'durable-write', retryable: true };
795
- if (answer.fenced !== null) {
796
- throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
797
- }
798
- if (store.fences.has(locator))
799
- throw runtimeError('unknown-room', `Unknown Room ${locator}`);
800
- preparedAfter(runtime, locator, answer);
801
- const shownNow = store.changed(changeSet(locator, {
802
- documents: answer.changed[locator] ?? [],
803
- queue: true,
804
- drafts: [...left, ...arrived.map(({ path }) => path)],
805
- }));
806
- await Promise.all([shownNow, queuedSave(runtime, locator)]);
807
- const identifier = join?.batch.id ??
808
- built?.batch.id ??
809
- [...requeue.withdrawn].map(([origin, replaced]) => replaced?.id ?? origin).at(-1);
810
- return { outcome: 'saved', identifier };
811
- }
812
233
  async function queuedFacts(store, locator, path, localId) {
813
234
  for (const batch of (await store.replica(locator))?.queue ?? []) {
814
235
  const change = batch.changes[queuedFile(batch, path, localId)];
@@ -825,7 +246,7 @@ export function batchFile(store, batch) {
825
246
  };
826
247
  }
827
248
  async function transferBinary(runtime, request, transfer, answer) {
828
- const { store, socket, journal } = runtime;
249
+ const { store, socket, presence } = runtime;
829
250
  const { session } = socket;
830
251
  if (socket.closed() || session.now().refusal !== null)
831
252
  return UNCERTAIN;
@@ -841,25 +262,22 @@ async function transferBinary(runtime, request, transfer, answer) {
841
262
  const { closed, authorization, refusal } = session.now();
842
263
  if (closed || authorization === undefined || refusal !== null)
843
264
  return UNCERTAIN;
844
- const since = store.fences.count();
845
- journal.record(`transfer binary ${request.locator} ${request.path}`);
265
+ const since = presence.losses();
846
266
  try {
847
267
  const result = await transfer(authorization);
848
268
  const settled = result;
849
- journal.record(`outcome binary ${settled.kind}`);
850
- if (store.fences.since(request.locator, since))
269
+ if (request.locator !== null && presence.lostSince(request.locator, since))
851
270
  return failure({ kind: 'room-not-readable', locator: request.locator });
852
271
  if (settled.kind === 'scoped-refusal') {
853
272
  const room = await store.record(settled.locator);
854
273
  if (room !== undefined)
855
- await runtime.scoped(settled, room, await store.open());
274
+ await scoped(runtime, settled, room.locator, room.declaration);
856
275
  }
857
276
  if (settled.kind === 'revoked-authorization')
858
277
  await refuse(runtime, 'revoked-authorization');
859
278
  return answer(result);
860
279
  }
861
280
  catch {
862
- journal.record('outcome binary transport-uncertainty');
863
281
  return UNCERTAIN;
864
282
  }
865
283
  }
@@ -972,11 +390,11 @@ async function showBinaryPath(runtime, locator, path, revision) {
972
390
  if (kept !== null)
973
391
  return kept;
974
392
  const mediaType = await heldMediaType(store, locator, path);
975
- if (mediaType?.startsWith('image/') === true)
393
+ if (mediaType !== undefined && isImageType(mediaType))
976
394
  return fetchBinary(runtime, asked, name);
977
395
  return linkOf(runtime, asked, revision === undefined ? mediaType : undefined);
978
396
  }
979
- async function attachBinary(runtime, locator, file) {
397
+ async function attachFile(runtime, locator, file, answer) {
980
398
  const { store, socket } = runtime;
981
399
  await socket.ready;
982
400
  await socket.firstDiscovery;
@@ -986,16 +404,27 @@ async function attachBinary(runtime, locator, file) {
986
404
  socket.session.now().refusal !== null)
987
405
  return OFFLINE;
988
406
  const request = await attachedFile(locator, file);
989
- return transferBinary(runtime, { locator, path: file.name }, async (held) => {
407
+ return transferBinary(runtime, { locator }, async (held) => {
990
408
  const result = await socket.attach(held, request);
991
- if (result.kind === 'attached') {
409
+ if (result.kind === 'attached' && locator !== null) {
992
410
  store.attached.hold(locator, result.path, {
993
411
  revision: result.revision,
994
412
  mediaType: request.mediaType,
995
413
  });
996
414
  }
997
415
  return result;
998
- }, attachAnswer);
416
+ }, answer);
417
+ }
418
+ async function keyedAttachment(runtime, link) {
419
+ await runtime.socket.ready;
420
+ const asked = { kind: 'key', link };
421
+ return transferBinary(runtime, { locator: null }, (authorization) => runtime.socket.link(authorization, asked), (result) => binaryLinkAnswer(result));
422
+ }
423
+ export function conversationFiles(runtime) {
424
+ return {
425
+ attach: (file) => attachFile(runtime, null, file, linkedAnswer),
426
+ attachment: (link) => keyedAttachment(runtime, link),
427
+ };
999
428
  }
1000
429
  export function binaryFileSurface(runtime) {
1001
430
  return {
@@ -1003,6 +432,6 @@ export function binaryFileSurface(runtime) {
1003
432
  read: (room, path, revision) => readBinaryPath(runtime, room, path, revision),
1004
433
  link: (room, path, revision) => linkBinaryPath(runtime, room, path, revision),
1005
434
  show: (room, path, revision) => showBinaryPath(runtime, room, path, revision),
1006
- attach: (room, file) => attachBinary(runtime, room, file),
435
+ attach: (room, file) => attachFile(runtime, room, file, attachAnswer),
1007
436
  };
1008
437
  }