@agentshouse/mdruntime 0.1.0-cf04ede9fb796 → 0.1.0-cf0c43fc48aad

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 (220) hide show
  1. package/README.md +12 -12
  2. package/dist/acceptance.d.ts +0 -3
  3. package/dist/acceptance.d.ts.map +1 -1
  4. package/dist/acceptance.js +74 -164
  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 +27 -48
  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 -19
  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/client.d.ts +9 -18
  22. package/dist/client.d.ts.map +1 -1
  23. package/dist/client.js +32 -15
  24. package/dist/collection.d.ts +22 -4
  25. package/dist/collection.d.ts.map +1 -1
  26. package/dist/collection.js +12 -2
  27. package/dist/compile.d.ts +2 -0
  28. package/dist/compile.d.ts.map +1 -1
  29. package/dist/compile.js +31 -24
  30. package/dist/connection.d.ts.map +1 -1
  31. package/dist/connection.js +26 -21
  32. package/dist/conversation-live.d.ts +10 -6
  33. package/dist/conversation-live.d.ts.map +1 -1
  34. package/dist/conversation-live.js +8 -6
  35. package/dist/conversation.d.ts +28 -10
  36. package/dist/conversation.d.ts.map +1 -1
  37. package/dist/conversation.js +194 -141
  38. package/dist/declaration.d.ts +10 -0
  39. package/dist/declaration.d.ts.map +1 -0
  40. package/dist/declaration.js +24 -0
  41. package/dist/discovery.d.ts.map +1 -1
  42. package/dist/discovery.js +19 -44
  43. package/dist/durability.d.ts +3 -0
  44. package/dist/durability.d.ts.map +1 -1
  45. package/dist/entry.js +1 -1
  46. package/dist/expectation.d.ts +26 -13
  47. package/dist/expectation.d.ts.map +1 -1
  48. package/dist/expectation.js +72 -19
  49. package/dist/failure.d.ts +3 -0
  50. package/dist/failure.d.ts.map +1 -1
  51. package/dist/failure.js +6 -0
  52. package/dist/host.d.ts +17 -2
  53. package/dist/host.d.ts.map +1 -1
  54. package/dist/host.js +45 -11
  55. package/dist/index.d.ts +12 -8
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +10 -6
  58. package/dist/install.d.ts +2 -4
  59. package/dist/install.d.ts.map +1 -1
  60. package/dist/install.js +52 -78
  61. package/dist/lifecycle.d.ts +1 -1
  62. package/dist/lifecycle.d.ts.map +1 -1
  63. package/dist/lifecycle.js +40 -31
  64. package/dist/listing.d.ts +6 -11
  65. package/dist/listing.d.ts.map +1 -1
  66. package/dist/listing.js +25 -62
  67. package/dist/live.d.ts.map +1 -1
  68. package/dist/live.js +3 -3
  69. package/dist/locks.d.ts +2 -4
  70. package/dist/locks.d.ts.map +1 -1
  71. package/dist/locks.js +10 -39
  72. package/dist/lost.d.ts.map +1 -1
  73. package/dist/lost.js +10 -22
  74. package/dist/media.d.ts +1 -1
  75. package/dist/media.d.ts.map +1 -1
  76. package/dist/media.js +4 -4
  77. package/dist/memory.d.ts +4 -2
  78. package/dist/memory.d.ts.map +1 -1
  79. package/dist/memory.js +44 -23
  80. package/dist/messages.d.ts +9 -5
  81. package/dist/messages.d.ts.map +1 -1
  82. package/dist/messages.js +25 -35
  83. package/dist/model.d.ts +0 -1
  84. package/dist/model.d.ts.map +1 -1
  85. package/dist/model.js +0 -5
  86. package/dist/models.d.ts +4 -8
  87. package/dist/models.d.ts.map +1 -1
  88. package/dist/models.js +95 -137
  89. package/dist/observation.d.ts.map +1 -1
  90. package/dist/observation.js +12 -17
  91. package/dist/opened.d.ts +1 -1
  92. package/dist/opened.d.ts.map +1 -1
  93. package/dist/opened.js +5 -5
  94. package/dist/opening.d.ts.map +1 -1
  95. package/dist/opening.js +5 -9
  96. package/dist/outcome.d.ts +3 -0
  97. package/dist/outcome.d.ts.map +1 -1
  98. package/dist/outcome.js +15 -0
  99. package/dist/overlay.d.ts +5 -1
  100. package/dist/overlay.d.ts.map +1 -1
  101. package/dist/overlay.js +36 -21
  102. package/dist/page-metadata.d.ts +8 -0
  103. package/dist/page-metadata.d.ts.map +1 -0
  104. package/dist/page-metadata.js +19 -0
  105. package/dist/parts.d.ts +3 -59
  106. package/dist/parts.d.ts.map +1 -1
  107. package/dist/parts.js +10 -123
  108. package/dist/preparation.d.ts.map +1 -1
  109. package/dist/preparation.js +25 -24
  110. package/dist/presence.d.ts +52 -0
  111. package/dist/presence.d.ts.map +1 -0
  112. package/dist/presence.js +220 -0
  113. package/dist/queries.d.ts +1 -1
  114. package/dist/queries.d.ts.map +1 -1
  115. package/dist/queries.js +24 -14
  116. package/dist/query.d.ts +0 -1
  117. package/dist/query.d.ts.map +1 -1
  118. package/dist/questions.d.ts +0 -5
  119. package/dist/questions.d.ts.map +1 -1
  120. package/dist/questions.js +0 -7
  121. package/dist/queue.d.ts +40 -0
  122. package/dist/queue.d.ts.map +1 -0
  123. package/dist/queue.js +450 -0
  124. package/dist/reach.d.ts.map +1 -1
  125. package/dist/reach.js +67 -22
  126. package/dist/react.d.ts +5 -0
  127. package/dist/react.d.ts.map +1 -1
  128. package/dist/react.js +33 -1
  129. package/dist/reads.d.ts +3 -1
  130. package/dist/reads.d.ts.map +1 -1
  131. package/dist/reads.js +9 -2
  132. package/dist/recovery.d.ts +3 -1
  133. package/dist/recovery.d.ts.map +1 -1
  134. package/dist/recovery.js +29 -7
  135. package/dist/refresh.js +2 -2
  136. package/dist/refusal.d.ts.map +1 -1
  137. package/dist/refusal.js +5 -6
  138. package/dist/replica.d.ts +5 -11
  139. package/dist/replica.d.ts.map +1 -1
  140. package/dist/replica.js +13 -15
  141. package/dist/retried.d.ts +30 -0
  142. package/dist/retried.d.ts.map +1 -0
  143. package/dist/retried.js +72 -0
  144. package/dist/room-draft.d.ts +4 -6
  145. package/dist/room-draft.d.ts.map +1 -1
  146. package/dist/room-draft.js +46 -57
  147. package/dist/room-drafts.d.ts.map +1 -1
  148. package/dist/room-drafts.js +17 -16
  149. package/dist/rows.d.ts.map +1 -1
  150. package/dist/rows.js +3 -4
  151. package/dist/runtime.d.ts +9 -18
  152. package/dist/runtime.d.ts.map +1 -1
  153. package/dist/save.d.ts +42 -90
  154. package/dist/save.d.ts.map +1 -1
  155. package/dist/save.js +334 -167
  156. package/dist/saves.d.ts +2 -40
  157. package/dist/saves.d.ts.map +1 -1
  158. package/dist/saves.js +107 -705
  159. package/dist/seed.d.ts.map +1 -1
  160. package/dist/seed.js +2 -3
  161. package/dist/shared.d.ts +0 -5
  162. package/dist/shared.d.ts.map +1 -1
  163. package/dist/shared.js +4 -90
  164. package/dist/shown.d.ts +4 -3
  165. package/dist/shown.d.ts.map +1 -1
  166. package/dist/shown.js +26 -31
  167. package/dist/socket/lanes.d.ts +12 -4
  168. package/dist/socket/lanes.d.ts.map +1 -1
  169. package/dist/socket/lanes.js +34 -8
  170. package/dist/socket/session.d.ts +0 -1
  171. package/dist/socket/session.d.ts.map +1 -1
  172. package/dist/socket.d.ts +3 -6
  173. package/dist/socket.d.ts.map +1 -1
  174. package/dist/socket.js +5 -16
  175. package/dist/source.d.ts +0 -7
  176. package/dist/source.d.ts.map +1 -1
  177. package/dist/source.js +5 -4
  178. package/dist/store/records.d.ts +8 -6
  179. package/dist/store/records.d.ts.map +1 -1
  180. package/dist/store/records.js +13 -6
  181. package/dist/store/space.d.ts +1 -1
  182. package/dist/store/space.d.ts.map +1 -1
  183. package/dist/store/space.js +0 -2
  184. package/dist/store/watchers.js +1 -2
  185. package/dist/store.d.ts +11 -6
  186. package/dist/store.d.ts.map +1 -1
  187. package/dist/store.js +38 -13
  188. package/dist/stored.d.ts +2 -1
  189. package/dist/stored.d.ts.map +1 -1
  190. package/dist/stored.js +11 -11
  191. package/dist/surface.d.ts +1 -1
  192. package/dist/surface.d.ts.map +1 -1
  193. package/dist/surface.js +13 -16
  194. package/dist/sync.d.ts.map +1 -1
  195. package/dist/sync.js +88 -85
  196. package/dist/tables.d.ts +6 -8
  197. package/dist/tables.d.ts.map +1 -1
  198. package/dist/tables.js +41 -20
  199. package/dist/tallies.js +5 -5
  200. package/dist/validation.d.ts.map +1 -1
  201. package/dist/validation.js +2 -2
  202. package/dist/wire.d.ts +51 -7
  203. package/dist/wire.d.ts.map +1 -1
  204. package/dist/wire.js +15 -4
  205. package/package.json +3 -2
  206. package/dist/attempt.d.ts +0 -16
  207. package/dist/attempt.d.ts.map +0 -1
  208. package/dist/attempt.js +0 -70
  209. package/dist/job.d.ts +0 -33
  210. package/dist/job.d.ts.map +0 -1
  211. package/dist/job.js +0 -54
  212. package/dist/log.d.ts +0 -6
  213. package/dist/log.d.ts.map +0 -1
  214. package/dist/log.js +0 -14
  215. package/dist/socket/rooms.d.ts +0 -44
  216. package/dist/socket/rooms.d.ts.map +0 -1
  217. package/dist/socket/rooms.js +0 -90
  218. package/dist/store/fences.d.ts +0 -16
  219. package/dist/store/fences.d.ts.map +0 -1
  220. package/dist/store/fences.js +0 -26
package/dist/save.js CHANGED
@@ -1,14 +1,14 @@
1
- import { definitionOf } from './model.js';
2
- import { apply, ARRAY_INDEX, ATTACHMENT, links, parse, pointerSegments, references, relative, relativize, retarget, roomReferences, serialize, target, } from '@agentshouse/mdmodel';
1
+ import { apply, ARRAY_INDEX, ATTACHMENT, create, equalValue, links, parse, pointerSegments, references, relative, relativize, retarget, roomReferences, serialize, target, } from '@agentshouse/mdmodel';
3
2
  import { immutable, leaving, queuedFile, departure, } from './change.js';
4
- import { collectionOf, documentsOf, governing, } from './collection.js';
5
- import { changeBatchIdentity } from './identity.js';
6
- import { overlaying } from './overlay.js';
7
- import { ancestors, authoredAt, covered, normalize } from './paths.js';
3
+ import { collectionOf, definitionsAt, documentsOf, governing, } from './collection.js';
4
+ import { changeBatchIdentity, roomDraftRevision } from './identity.js';
5
+ import { composed, overlaying } from './overlay.js';
6
+ import { ancestors } from './paths.js';
8
7
  import { entriesIn, sorting } from './shared.js';
8
+ import { changedParts, changesOf, moveTarget, withdrawnDrafts, } from './room-draft.js';
9
9
  import { paced, ROW_WEIGHT, stepped } from './turn.js';
10
10
  import { validatedCollection } from './validation.js';
11
- function refuse(code, file, location) {
11
+ function refuse(code, file, location, line) {
12
12
  return {
13
13
  kind: 'refused',
14
14
  refusal: {
@@ -16,13 +16,14 @@ function refuse(code, file, location) {
16
16
  retryable: false,
17
17
  ...(file === undefined ? {} : { file }),
18
18
  ...(location === undefined ? {} : { location }),
19
+ ...(line === undefined ? {} : { line }),
19
20
  },
20
21
  };
21
22
  }
23
+ function refusedBy(refusal, file) {
24
+ return refuse(refusal.rule, file, refusal.location, refusal.line);
25
+ }
22
26
  function repointed(document, definition, from, to, moves, settled) {
23
- const parsed = parse(document.content, from);
24
- if (!parsed.ok)
25
- return [];
26
27
  const changes = [];
27
28
  const names = new Set();
28
29
  for (const { pointer, value } of definition === undefined
@@ -45,15 +46,22 @@ function repointed(document, definition, from, to, moves, settled) {
45
46
  }
46
47
  if (changes.length === 0)
47
48
  return [];
48
- const applied = apply(parsed.document, changes);
49
+ const applied = apply(document, changes, definition);
49
50
  if (applied.outcome !== 'applied')
50
51
  return [];
51
52
  const next = applied.document;
52
53
  return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
53
54
  }
54
- function edited(document, changes) {
55
+ function edited(document, changes, definition) {
55
56
  const parts = new Map();
56
57
  for (const change of changes) {
58
+ if (change.kind === 'field')
59
+ parts.set(`field ${change.name}`, {
60
+ kind: 'field',
61
+ name: change.name,
62
+ value: change.value,
63
+ prior: document.fields[change.name],
64
+ });
57
65
  if (change.kind === 'preamble')
58
66
  parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
59
67
  if (change.kind === 'section')
@@ -66,11 +74,16 @@ function edited(document, changes) {
66
74
  }
67
75
  if (parts.size === 0)
68
76
  return document;
69
- const applied = apply(document, [...parts.values()]);
77
+ const applied = apply(document, [...parts.values()], definition);
70
78
  return applied.outcome === 'applied' ? applied.document : document;
71
79
  }
72
80
  function bodies(path, before, after) {
73
81
  const changes = [];
82
+ for (const [name, value] of Object.entries(after.fields)) {
83
+ if (equalValue(before.fields[name], value))
84
+ continue;
85
+ changes.push({ kind: 'field', path, name, value });
86
+ }
74
87
  if (after.preamble !== before.preamble) {
75
88
  changes.push({ kind: 'preamble', path, value: after.preamble });
76
89
  }
@@ -81,7 +94,7 @@ function bodies(path, before, after) {
81
94
  }
82
95
  return changes;
83
96
  }
84
- function relinks(path, moved, before, moves) {
97
+ function relinks(path, moved, before, moves, definition) {
85
98
  const changes = [];
86
99
  for (const link of links(before, path)) {
87
100
  const destination = moves.get(link.target) ?? link.target;
@@ -94,18 +107,9 @@ function relinks(path, moved, before, moves) {
94
107
  }
95
108
  if (changes.length === 0)
96
109
  return { kind: 'changes', changes: [] };
97
- const applied = apply(before, changes);
98
- if (applied.outcome === 'refuse') {
99
- return {
100
- kind: 'refused',
101
- refusal: {
102
- code: applied.refusal.rule,
103
- retryable: false,
104
- file: path,
105
- ...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
106
- },
107
- };
108
- }
110
+ const applied = apply(before, changes, definition);
111
+ if (applied.outcome === 'refuse')
112
+ return refusedBy(applied.refusal, path);
109
113
  const after = applied.document;
110
114
  if (after === before)
111
115
  return { kind: 'changes', changes: [] };
@@ -115,7 +119,18 @@ function relinks(path, moved, before, moves) {
115
119
  return { kind: 'changes', changes: bodies(path, before, after) };
116
120
  }
117
121
  function partKey(change) {
118
- return change.kind === 'section' ? `section ${change.name}` : change.kind;
122
+ return change.kind === 'section' || change.kind === 'field'
123
+ ? `${change.kind} ${change.name}`
124
+ : change.kind;
125
+ }
126
+ const NAMES_A_SKIP_TESTS = 64;
127
+ const ENCODED = /[%&\\]/u;
128
+ function movedNames(moves) {
129
+ const names = new Set([...moves.keys()].map((path) => path.slice(path.lastIndexOf('/') + 1)));
130
+ return names.size > NAMES_A_SKIP_TESTS ? null : [...names];
131
+ }
132
+ function cannotLink(text, names) {
133
+ return names !== null && !ENCODED.test(text) && !names.some((name) => text.includes(name));
119
134
  }
120
135
  function* rewrites(changes, visible) {
121
136
  const moves = new Map();
@@ -142,17 +157,20 @@ function* rewrites(changes, visible) {
142
157
  }
143
158
  if (moves.size === 0)
144
159
  return { kind: 'changes', changes };
160
+ const names = movedNames(moves);
145
161
  const merged = [...changes];
146
162
  const added = [];
147
163
  for (const [path, document] of yield* documentsOf(visible.collection)) {
148
164
  if (settled.has(path))
149
165
  continue;
150
166
  const moved = moves.get(path) ?? path;
151
- const definition = definitionOf(governing(visible.collection.composition, path), document.type);
167
+ const definition = definitionsAt(visible.collection.composition, path)(document.type);
152
168
  const repointing = repointed(document, definition, path, moved, moves, settled);
153
169
  added.push(...repointing);
154
170
  const own = parts.get(path) ?? [];
155
- const relinked = relinks(path, moved, edited(document, own.map((index) => merged[index])), moves);
171
+ const relinked = moved === path && own.length === 0 && cannotLink(document.content, names)
172
+ ? { kind: 'changes', changes: [] }
173
+ : relinks(path, moved, edited(document, own.map((index) => merged[index]), definition), moves, definition);
156
174
  if (relinked.kind === 'refused')
157
175
  return relinked;
158
176
  const keyed = new Map();
@@ -165,7 +183,7 @@ function* rewrites(changes, visible) {
165
183
  if (change.kind === 'replace') {
166
184
  for (const index of own) {
167
185
  const kind = merged[index]?.kind;
168
- if (kind === 'section' || kind === 'preamble')
186
+ if (kind === 'field' || kind === 'section' || kind === 'preamble')
169
187
  merged[index] = null;
170
188
  }
171
189
  added.push(change);
@@ -221,25 +239,21 @@ function crossed(document, definition, path, to) {
221
239
  }
222
240
  return changes;
223
241
  }
224
- function rewritten(path, document, changes) {
242
+ function rewritten(path, document, changes, definition) {
225
243
  if (changes.length === 0)
226
244
  return [];
227
- const applied = apply(document, changes);
245
+ const applied = apply(document, changes, definition);
228
246
  if (applied.outcome !== 'applied')
229
247
  return [];
230
248
  const after = applied.document;
231
249
  if (document.type === undefined)
232
250
  return [{ kind: 'replace', path, content: serialize(after) }];
233
- const names = new Set(changes.flatMap((change) => change.kind === 'reference' ? [pointerSegments(change.pointer)[0] ?? ''] : []));
234
- return [
235
- ...[...names].map((name) => ({ kind: 'field', path, name, value: after.fields[name] })),
236
- ...bodies(path, document, after),
237
- ];
251
+ return bodies(path, document, after);
238
252
  }
239
253
  function roomPathOf(handle, path) {
240
254
  return `/rooms/${handle}/${path}`;
241
255
  }
242
- export function* leavingRewritten(batch, visible) {
256
+ function* leavingRewritten(batch, visible) {
243
257
  const between = batch.between;
244
258
  if (between === undefined)
245
259
  return batch;
@@ -248,8 +262,8 @@ export function* leavingRewritten(batch, visible) {
248
262
  for (const [path, document] of yield* documentsOf(visible.collection)) {
249
263
  if (moved.has(path))
250
264
  continue;
251
- const definition = definitionOf(governing(visible.collection.composition, path), document.type);
252
- derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named))));
265
+ const definition = definitionsAt(visible.collection.composition, path)(document.type);
266
+ derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named)), definition));
253
267
  yield ROW_WEIGHT + visible.collection.text[path].length;
254
268
  }
255
269
  if (derived.length === 0)
@@ -291,7 +305,7 @@ function* incoming(collection, removed, candidates) {
291
305
  for (const [path, document] of candidates === null
292
306
  ? yield* documentsOf(collection)
293
307
  : read(candidates)) {
294
- const definition = definitionOf(governing(collection.composition, path), document.type);
308
+ const definition = definitionsAt(collection.composition, path)(document.type);
295
309
  for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
296
310
  if (to.includes(ATTACHMENT))
297
311
  continue;
@@ -334,18 +348,9 @@ function guard(changes, input, removing) {
334
348
  const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
335
349
  for (const [index, change] of changes.entries()) {
336
350
  for (const path of affected(change)) {
337
- const normalized = normalize(path);
338
- if (normalized === null || normalized === '')
339
- return refuse('room-root', path);
340
- if (covered(input.providerOwnedPaths, normalized) ||
341
- (input.authoredPaths !== undefined && !authoredAt(input.authoredPaths, normalized))) {
342
- return refuse('provider-owned-path', normalized);
343
- }
344
- if (!input.pathRule(normalized))
345
- return refuse('invalid-path', normalized);
346
- for (const operation of change.kind === 'remove' ? removing : WRITE)
347
- if (!input.local && !covered(input.operations[operation], normalized))
348
- return refuse(`${operation}-grant`, normalized);
351
+ const refused = input.rule(path, change.kind === 'remove' ? removing : WRITE);
352
+ if (refused !== null)
353
+ return refuse(refused.code, refused.path);
349
354
  }
350
355
  if (change.kind === 'create' || change.kind === 'arrival') {
351
356
  const taken = existing.has(change.path) ? change.path : occupied(change.path);
@@ -406,9 +411,9 @@ function guard(changes, input, removing) {
406
411
  return null;
407
412
  }
408
413
  function unrevised(input, path) {
409
- return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
414
+ return !input.local && !input.queued.has(path) && input.visible.revisions[path] === undefined;
410
415
  }
411
- export function queuedDocuments(queue) {
416
+ function queuedDocuments(queue) {
412
417
  const paths = new Set();
413
418
  for (const batch of queue) {
414
419
  for (const change of batch.changes) {
@@ -544,56 +549,69 @@ function* exceeding(limits, changes, effective, resulting) {
544
549
  function shellRelative(text, path, handle, definition) {
545
550
  if (!text.includes(`/rooms/${handle}/`))
546
551
  return null;
547
- const parsed = parse(text, path);
552
+ const parsed = parse(text, path, definition);
548
553
  if (!parsed.ok)
549
554
  return null;
550
555
  const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
551
556
  return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
552
557
  }
553
558
  function relativeField(path, name, value, handle, definition) {
554
- const opened = parse('---\n---\n');
555
- if (definition === undefined ||
556
- !opened.ok ||
557
- !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
559
+ if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
558
560
  return value;
559
- const held = apply(opened.document, [{ kind: 'field', name, value, prior: undefined }]);
561
+ const held = apply(create(definition.name, definition), [{ kind: 'field', name, value, prior: undefined }], definition);
560
562
  if (held.outcome !== 'applied')
561
563
  return value;
562
564
  const result = relativize(held.document, path, handle, definition);
563
565
  return result.outcome === 'applied' ? result.document.fields[name] : value;
564
566
  }
565
567
  function relativeChanges(input) {
568
+ const changes = [];
569
+ for (const change of input.changes) {
570
+ const next = relativeChange(change, input);
571
+ if ('refusal' in next)
572
+ return next;
573
+ changes.push(next.change);
574
+ }
575
+ return { kind: 'changes', changes };
576
+ }
577
+ function relativeChange(change, input) {
566
578
  const { handle, composition, visible } = input;
567
- const typed = (path) => (type) => definitionOf(governing(composition, path), type);
568
579
  const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
569
- return input.changes.map((change) => {
570
- const definition = typed(change.path);
571
- if (change.kind === 'field') {
572
- const type = visible.collection.document(change.path)?.type;
573
- const value = relativeField(change.path, change.name, change.value, handle, definition(type));
574
- return value === change.value ? change : { ...change, value };
575
- }
576
- if (change.kind === 'section' || change.kind === 'preamble') {
577
- const value = body(change.path, change.value);
578
- return value === change.value ? change : { ...change, value };
579
- }
580
- if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
581
- const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
582
- return content === change.content ? change : { ...change, content };
583
- }
584
- if (change.kind !== 'create')
585
- return change;
586
- const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
587
- const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
588
- name,
589
- relativeField(change.path, name, value, handle, definition(type)),
590
- ]));
591
- const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
592
- return { ...change, fields, sections };
593
- });
580
+ const definition = definitionsAt(composition, change.path);
581
+ if (change.kind === 'field') {
582
+ const type = visible.collection.document(change.path)?.type;
583
+ const value = relativeField(change.path, change.name, change.value, handle, definition(type));
584
+ return { change: value === change.value ? change : { ...change, value } };
585
+ }
586
+ if (change.kind === 'section' || change.kind === 'preamble') {
587
+ const value = body(change.path, change.value);
588
+ return { change: value === change.value ? change : { ...change, value } };
589
+ }
590
+ if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
591
+ const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
592
+ return { change: content === change.content ? change : { ...change, content } };
593
+ }
594
+ if (change.kind !== 'create')
595
+ return { change };
596
+ const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
597
+ const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
598
+ name,
599
+ relativeField(change.path, name, value, handle, definition(type)),
600
+ ]));
601
+ const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
602
+ const written = composed(change.path, fields, sections, composition);
603
+ if (written.outcome === 'refuse')
604
+ return refusedBy(written.refusal, change.path);
605
+ if (written.outcome === 'stale')
606
+ throw new Error(`the creation of ${change.path} is stale`);
607
+ if (Object.keys(sections).length === 0)
608
+ return { change: { ...change, fields, sections } };
609
+ return {
610
+ change: { kind: 'create', path: change.path, content: serialize(written.document) },
611
+ };
594
612
  }
595
613
  function unqueueable(file) {
596
- return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
614
+ return refuse('unknown-revision', file);
597
615
  }
598
616
  function isPart(change) {
599
617
  return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
@@ -601,8 +619,29 @@ function isPart(change) {
601
619
  function touched(change) {
602
620
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
603
621
  }
604
- function partsApplied(content, path, changes) {
605
- const parsed = parse(content, path);
622
+ function partsApplied(content, path, changes, composition) {
623
+ const applied = partsResult(content, path, changes, composition);
624
+ return applied?.outcome === 'applied' ? serialize(applied.document) : null;
625
+ }
626
+ function unapplied(changes, visible, composition) {
627
+ const parted = new Map();
628
+ for (const change of changes) {
629
+ if (isPart(change))
630
+ parted.set(change.path, [...(parted.get(change.path) ?? []), change]);
631
+ }
632
+ for (const [path, parts] of parted) {
633
+ const content = visible.documents[path];
634
+ if (content === undefined)
635
+ continue;
636
+ const applied = partsResult(content, path, parts, composition);
637
+ if (applied?.outcome === 'refuse')
638
+ return refusedBy(applied.refusal, path);
639
+ }
640
+ return null;
641
+ }
642
+ function partsResult(content, path, changes, composition) {
643
+ const definitions = definitionsAt(composition, path);
644
+ const parsed = parse(content, path, definitions);
606
645
  if (!parsed.ok)
607
646
  return null;
608
647
  const document = parsed.document;
@@ -625,12 +664,11 @@ function partsApplied(content, path, changes) {
625
664
  else
626
665
  parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
627
666
  }
628
- const applied = apply(document, [...parts.values()]);
629
- return applied.outcome === 'applied' ? serialize(applied.document) : null;
667
+ return apply(document, [...parts.values()], definitions(document.type));
630
668
  }
631
- function settledParts(changes, deferred) {
669
+ function settledParts(changes, deferred, composition) {
632
670
  for (const [held, parts] of deferred) {
633
- const content = partsApplied(held.content, held.path, parts);
671
+ const content = partsApplied(held.content, held.path, parts, composition);
634
672
  if (content === null)
635
673
  return held.path;
636
674
  changes[changes.indexOf(held)] = { ...held, content };
@@ -705,7 +743,9 @@ function* relinked(batch, move, before, after, appending) {
705
743
  for (const change of derived.changes) {
706
744
  if (change === move)
707
745
  continue;
708
- const unsettled = isPart(change) ? null : settledParts(changes, deferred);
746
+ const unsettled = isPart(change)
747
+ ? null
748
+ : settledParts(changes, deferred, after.collection.composition);
709
749
  if (unsettled !== null)
710
750
  return unqueueable(unsettled);
711
751
  const outcome = folded(changes, change, appending, deferred);
@@ -716,7 +756,7 @@ function* relinked(batch, move, before, after, appending) {
716
756
  if (outcome !== 'skipped')
717
757
  changed = true;
718
758
  }
719
- const unsettled = settledParts(changes, deferred);
759
+ const unsettled = settledParts(changes, deferred, after.collection.composition);
720
760
  if (unsettled !== null)
721
761
  return unqueueable(unsettled);
722
762
  if (!changed)
@@ -777,11 +817,10 @@ function withdrawnFile(batch, change, at) {
777
817
  ];
778
818
  return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
779
819
  }
780
- export function* requeuing(queue, changes, visible, changeable, state) {
820
+ function* requeuing(queue, changes, visible, changeable, state) {
781
821
  const next = [...queue];
782
822
  const origins = new Map();
783
823
  const withdrawn = new Map();
784
- const owners = new Set();
785
824
  const queued = new Set();
786
825
  const kept = [];
787
826
  const originOf = (batch) => origins.get(batch) ?? batch.id;
@@ -827,7 +866,6 @@ export function* requeuing(queue, changes, visible, changeable, state) {
827
866
  if (replaced !== null)
828
867
  origins.set(replaced, origin);
829
868
  }
830
- owners.add(originOf(owner));
831
869
  next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
832
870
  queued.add(change.path);
833
871
  }
@@ -837,56 +875,13 @@ export function* requeuing(queue, changes, visible, changeable, state) {
837
875
  changes: kept,
838
876
  queued,
839
877
  withdrawn,
840
- owners,
841
878
  };
842
879
  }
843
- function covering(precondition) {
844
- return precondition.kind === 'field' ||
845
- precondition.kind === 'section' ||
846
- precondition.kind === 'preamble'
847
- ? identity(precondition)
848
- : `path ${precondition.path}`;
849
- }
850
- export function joined(queue, owner, extra) {
851
- const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
852
- const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
853
- if (clash !== undefined)
854
- return unqueueable(clash);
855
- const changes = [...owner.changes];
856
- const deferred = new Map();
857
- for (const change of extra.changes) {
858
- const unsettled = isPart(change) ? null : settledParts(changes, deferred);
859
- if (unsettled !== null)
860
- return unqueueable(unsettled);
861
- if (folded(changes, change, true, deferred) === 'refused')
862
- return unqueueable(change.path);
863
- }
864
- const unsettled = settledParts(changes, deferred);
865
- if (unsettled !== null)
866
- return unqueueable(unsettled);
867
- const held = new Set(owner.preconditions.map(covering));
868
- const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
869
- const preconditions = [
870
- ...owner.preconditions,
871
- ...extra.preconditions.filter((precondition) => {
872
- const key = covering(precondition);
873
- return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
874
- }),
875
- ];
876
- const batch = immutable({
877
- ...owner,
878
- ...(extra.validation === undefined ? {} : { validation: extra.validation }),
879
- id: changeBatchIdentity(),
880
- changes,
881
- preconditions,
882
- });
883
- return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
884
- }
885
- export function* limiting(queue, batch, limits, state) {
880
+ function* limiting(queue, batch, limits, state) {
886
881
  const at = queue.indexOf(batch);
887
882
  const rewritten = yield* providerRewritten(batch, yield* state(queue.slice(0, at)));
888
883
  const limited = yield* exceeding(limits, batch.changes, rewritten.changes, yield* state(queue.slice(0, at + 1)));
889
- return limited?.kind === 'refused' ? limited.refusal : null;
884
+ return limited?.refusal ?? null;
890
885
  }
891
886
  function typedText(content) {
892
887
  if (content === undefined)
@@ -912,7 +907,7 @@ function touchedLibrary(resulting, touched, composition) {
912
907
  if (binaries.has(path))
913
908
  known.set(path, { type: undefined, binary: true });
914
909
  else if (content !== undefined && content !== null) {
915
- const read = parse(content, path);
910
+ const read = parse(content, path, definitionsAt(composition, path));
916
911
  if (read.ok)
917
912
  known.set(path, { type: read.document.type ?? 'any', binary: false });
918
913
  await pace(content.length + ROW_WEIGHT);
@@ -950,6 +945,7 @@ async function judging(visible, resulting, composition, validate, removed, touch
950
945
  retryable: false,
951
946
  file: verdict.file,
952
947
  ...(verdict.location === undefined ? {} : { location: verdict.location }),
948
+ ...(verdict.line === undefined ? {} : { line: verdict.line }),
953
949
  ...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
954
950
  },
955
951
  };
@@ -965,8 +961,6 @@ function placed(reference) {
965
961
  const key = roomPathOf(reference.handle, reference.path);
966
962
  if (reference.kind === 'link')
967
963
  return `link ${key}`;
968
- if (reference.kind === 'item')
969
- return `section ${reference.section} ${key}`;
970
964
  const pointer = reference.pointer
971
965
  .split('/')
972
966
  .map((segment) => (ARRAY_INDEX.test(segment) ? '-' : segment))
@@ -976,10 +970,11 @@ function placed(reference) {
976
970
  function roomReferencesIn(path, content, composition) {
977
971
  if (content === undefined)
978
972
  return [];
979
- const parsed = parse(content, path);
973
+ const definitions = definitionsAt(composition, path);
974
+ const parsed = parse(content, path, definitions);
980
975
  if (!parsed.ok)
981
976
  return [];
982
- return roomReferences(parsed.document, definitionOf(governing(composition, path), parsed.document.type));
977
+ return roomReferences(parsed.document, definitions(parsed.document.type));
983
978
  }
984
979
  function* admittedRooms(changes, visible, resulting, composition) {
985
980
  const from = new Map();
@@ -997,7 +992,7 @@ function* admittedRooms(changes, visible, resulting, composition) {
997
992
  continue;
998
993
  if (held.has(placed(reference)))
999
994
  continue;
1000
- return refuse('unresolved-reference', path, reference.kind === 'field' ? reference.pointer : reference.section);
995
+ return refuse('unresolved-reference', path, reference.pointer);
1001
996
  }
1002
997
  yield ROW_WEIGHT + now.length;
1003
998
  }
@@ -1015,7 +1010,10 @@ function validationOf(validated) {
1015
1010
  };
1016
1011
  }
1017
1012
  async function batching(input) {
1018
- const changes = relativeChanges(input);
1013
+ const relative = relativeChanges(input);
1014
+ if (relative.kind === 'refused')
1015
+ return relative;
1016
+ const { changes } = relative;
1019
1017
  const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
1020
1018
  if (structural !== undefined && input.unmodeled) {
1021
1019
  return refuse('model-unavailable', structural.path);
@@ -1025,12 +1023,12 @@ async function batching(input) {
1025
1023
  const rewritten = await stepped(rewrites(changes, input.visible));
1026
1024
  if (rewritten.kind === 'refused')
1027
1025
  return rewritten;
1028
- const refusal = guard(changes, input, WRITE);
1026
+ const refusal = guard(changes, input, WRITE) ?? unapplied(changes, input.visible, input.composition);
1029
1027
  if (refusal !== null)
1030
1028
  return refusal;
1031
1029
  const preconditions = await stepped(preconditionsOf(changes, input.visible, input.bases));
1032
1030
  const predicted = await stepped(anticipated(changes, preconditions, rewritten.changes, input.visible));
1033
- const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }]));
1031
+ const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }], input.composition));
1034
1032
  const judged = await judging(input.visible, resulting, input.composition, input.validate, new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path)), [...new Set(predicted.changes.flatMap(affected))]);
1035
1033
  if (judged.kind === 'refused')
1036
1034
  return judged;
@@ -1052,7 +1050,7 @@ async function batching(input) {
1052
1050
  }),
1053
1051
  };
1054
1052
  }
1055
- export const MOVE_DOCUMENTS = 'move-documents';
1053
+ const MOVE_DOCUMENTS = 'move-documents';
1056
1054
  function heldAt(visible, path) {
1057
1055
  return visible.documents[path] !== undefined || visible.binaries[path] !== undefined;
1058
1056
  }
@@ -1061,7 +1059,7 @@ function arrivingText(path, to, source, moved) {
1061
1059
  const document = source.visible.collection.document(path);
1062
1060
  if (document === undefined)
1063
1061
  return content;
1064
- const definition = definitionOf(governing(source.composition, path), document.type);
1062
+ const definition = definitionsAt(source.composition, path)(document.type);
1065
1063
  const changes = crossed(document, definition, path, (named, href) => {
1066
1064
  const at = moved.get(named);
1067
1065
  if (at !== undefined)
@@ -1070,7 +1068,7 @@ function arrivingText(path, to, source, moved) {
1070
1068
  });
1071
1069
  if (changes.length === 0)
1072
1070
  return content;
1073
- const applied = apply(document, changes);
1071
+ const applied = apply(document, changes, definition);
1074
1072
  return applied.outcome === 'applied' ? serialize(applied.document) : content;
1075
1073
  }
1076
1074
  function arrivalOf({ path, to }, source, moved) {
@@ -1103,11 +1101,9 @@ function arrivalOf({ path, to }, source, moved) {
1103
1101
  };
1104
1102
  }
1105
1103
  async function moving(input) {
1106
- const { source, destination, moves, pathRule } = input;
1107
- if (moves.length === 0)
1108
- return refuse('no-change');
1104
+ const { source, destination, moves } = input;
1109
1105
  const removals = moves.map(({ path }) => ({ kind: 'remove', path }));
1110
- const leaves = guard(removals, { ...source, local: input.local, bases: [], pathRule }, MOVE_OUT);
1106
+ const leaves = guard(removals, { ...source, local: input.local, bases: [] }, MOVE_OUT);
1111
1107
  if (leaves !== null)
1112
1108
  return leaves;
1113
1109
  const uploading = moves.find(({ path }) => source.visible.binaries[path]?.state === 'pending');
@@ -1115,7 +1111,7 @@ async function moving(input) {
1115
1111
  return refuse('unknown-revision', uploading.path);
1116
1112
  const moved = new Map(moves.map(({ path, to }) => [path, to]));
1117
1113
  const arrivals = moves.map((move) => arrivalOf(move, source, moved));
1118
- const arrives = guard(arrivals, { ...destination, local: input.local, bases: [], pathRule }, WRITE);
1114
+ const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, WRITE);
1119
1115
  if (arrives !== null)
1120
1116
  return arrives;
1121
1117
  const bound = input.providerLimits.find((limit) => limit.name === MOVE_DOCUMENTS)?.value;
@@ -1144,7 +1140,7 @@ async function moving(input) {
1144
1140
  between,
1145
1141
  };
1146
1142
  const effective = await stepped(leavingRewritten(leavingBatch, source.visible));
1147
- const left = await stepped(overlaying(source.visible, [effective]));
1143
+ const left = await stepped(overlaying(source.visible, [effective], source.composition));
1148
1144
  const sourceJudged = await judging(source.visible, left, source.composition, source.validate, new Set(moves.map(({ path }) => path)), [...new Set(effective.changes.map((change) => change.path))]);
1149
1145
  if (sourceJudged.kind === 'refused')
1150
1146
  return sourceJudged;
@@ -1156,7 +1152,7 @@ async function moving(input) {
1156
1152
  ...written,
1157
1153
  between,
1158
1154
  };
1159
- const arrived = await stepped(overlaying(destination.visible, [arrivingBatch]));
1155
+ const arrived = await stepped(overlaying(destination.visible, [arrivingBatch], destination.composition));
1160
1156
  const destinationJudged = await judging(destination.visible, arrived, destination.composition, destination.validate, new Set(), moves.map(({ to }) => to));
1161
1157
  if (destinationJudged.kind === 'refused')
1162
1158
  return destinationJudged;
@@ -1166,9 +1162,180 @@ async function moving(input) {
1166
1162
  return limited;
1167
1163
  return { kind: 'moved', leaving: immutable(leavingBatch), arriving: immutable(arrivingBatch) };
1168
1164
  }
1169
- export function buildMove(input) {
1170
- return moving(input);
1165
+ function unreadable(saved, visible, composition) {
1166
+ const path = saved.find(({ path: at, parts }) => (parts.fields !== undefined ||
1167
+ parts.sections !== undefined ||
1168
+ parts.preamble !== undefined) &&
1169
+ typedText(visible.documents[at]) &&
1170
+ governing(composition, at).status === 'unavailable')?.path;
1171
+ return path === undefined ? null : refuse('model-unavailable', path);
1172
+ }
1173
+ function changedUnderneath(saved, visible) {
1174
+ const parts = saved.flatMap(({ path, parts: held }) => changedParts(path, held, visible));
1175
+ return parts.length === 0
1176
+ ? null
1177
+ : { kind: 'refused', refusal: { code: 'changed-underneath', retryable: false, parts } };
1178
+ }
1179
+ function sideOf(room, visible) {
1180
+ const { record } = room;
1181
+ return {
1182
+ room: record.locator,
1183
+ handle: record.declaration.handle,
1184
+ baseRoomVersion: record.roomVersion,
1185
+ composition: room.composition,
1186
+ rule: room.rule,
1187
+ visible,
1188
+ folders: room.folders,
1189
+ queued: queuedDocuments(room.queue),
1190
+ unmodeled: !room.modelled,
1191
+ validate: room.validate,
1192
+ };
1193
+ }
1194
+ function carriedDrafts(ask, from, into) {
1195
+ return new Map(ask.saved.flatMap(({ path, parts }) => {
1196
+ const record = from.drafts.get(path);
1197
+ if (parts.move === undefined || record === undefined || ask.consumed.has(path))
1198
+ return [];
1199
+ const to = moveTarget(parts.move);
1200
+ if (into.drafts.has(to))
1201
+ return [];
1202
+ const locator = into.record.locator;
1203
+ return [[path, { ...record, locator, path: to, revision: roomDraftRevision() }]];
1204
+ }));
1205
+ }
1206
+ async function plannedMove(ask, source, into) {
1207
+ const destination = into.record.locator;
1208
+ const mixed = ask.saved.find(({ parts }) => Object.keys(parts).length !== 1 || parts.move?.room !== destination);
1209
+ if (mixed !== undefined)
1210
+ return refuse('mixed-room-move', mixed.path);
1211
+ const leavingVisible = await stepped(source.replay(source.queue));
1212
+ const changed = changedUnderneath(ask.saved, leavingVisible);
1213
+ if (changed !== null)
1214
+ return changed;
1215
+ const moves = ask.saved.map(({ path, parts }) => ({ path, to: moveTarget(parts.move) }));
1216
+ const built = await moving({
1217
+ source: sideOf(source, leavingVisible),
1218
+ destination: sideOf(into, await stepped(into.replay(into.queue))),
1219
+ moves,
1220
+ providerLimits: ask.providerLimits,
1221
+ local: ask.local,
1222
+ ...(ask.writtenBy === undefined ? {} : { writtenBy: ask.writtenBy }),
1223
+ });
1224
+ if (built.kind === 'refused')
1225
+ return built;
1226
+ const { id } = built.leaving;
1227
+ const carried = carriedDrafts(ask, source, into);
1228
+ return {
1229
+ kind: 'planned',
1230
+ identifier: id,
1231
+ rooms: [
1232
+ {
1233
+ locator: source.record.locator,
1234
+ queue: [...source.queue, built.leaving],
1235
+ saved: new Map([[id, ask.saved]]),
1236
+ consumed: [...ask.consumed, ...carried.keys()],
1237
+ carried: [],
1238
+ },
1239
+ {
1240
+ locator: destination,
1241
+ queue: [...into.queue, built.arriving],
1242
+ saved: new Map([
1243
+ [id, [...carried.values()].map(({ path }) => ({ path, parts: {} }))],
1244
+ ]),
1245
+ consumed: [],
1246
+ carried: [...carried.values()],
1247
+ },
1248
+ ],
1249
+ };
1250
+ }
1251
+ async function plannedWithin(ask, room) {
1252
+ const { record } = room;
1253
+ const visible = await stepped(room.replay(room.queue));
1254
+ const changed = unreadable(ask.saved, visible, room.composition) ?? changedUnderneath(ask.saved, visible);
1255
+ if (changed !== null)
1256
+ return changed;
1257
+ const parts = ask.saved.map(({ path, parts: held }) => changesOf(path, held));
1258
+ const changes = parts.flatMap(({ changes: made }) => made);
1259
+ const requeue = await stepped(requeuing(room.queue, changes, visible, (batch) => room.changeable.has(batch.id), room.replay));
1260
+ if (requeue.kind === 'refused')
1261
+ return requeue;
1262
+ const foreign = [...requeue.withdrawn.keys()].some((origin) => {
1263
+ const writer = room.queue.find((batch) => batch.id === origin)?.writtenBy;
1264
+ return writer?.app !== ask.writtenBy?.app || writer?.release !== ask.writtenBy?.release;
1265
+ });
1266
+ if (foreign)
1267
+ return unqueueable([...requeue.queued].at(-1));
1268
+ const input = {
1269
+ local: ask.local,
1270
+ baseRoomVersion: record.roomVersion,
1271
+ composition: room.composition,
1272
+ rule: room.rule,
1273
+ providerLimits: ask.providerLimits,
1274
+ handle: record.declaration.handle,
1275
+ visible,
1276
+ folders: room.folders,
1277
+ changes,
1278
+ bases: parts.flatMap(({ bases }) => bases),
1279
+ queued: queuedDocuments(room.queue),
1280
+ writtenBy: ask.writtenBy,
1281
+ unmodeled: !room.modelled,
1282
+ validate: room.validate,
1283
+ };
1284
+ const rewritten = requeue.withdrawn.size > 0;
1285
+ if (rewritten) {
1286
+ const whole = await batching({
1287
+ ...input,
1288
+ providerLimits: [],
1289
+ queued: new Set([...input.queued, ...requeue.queued]),
1290
+ });
1291
+ if (whole.kind === 'refused')
1292
+ return whole;
1293
+ }
1294
+ const own = !rewritten
1295
+ ? await batching(input)
1296
+ : requeue.changes.length === 0
1297
+ ? null
1298
+ : await batching({
1299
+ ...input,
1300
+ visible: await stepped(room.replay(requeue.queue)),
1301
+ changes: requeue.changes,
1302
+ queued: queuedDocuments(requeue.queue),
1303
+ });
1304
+ if (own?.kind === 'refused')
1305
+ return own;
1306
+ const queue = own === null ? requeue.queue : [...requeue.queue, own.batch];
1307
+ const saved = new Map();
1308
+ for (const [origin, rebuilt] of requeue.withdrawn) {
1309
+ if (rebuilt === null)
1310
+ continue;
1311
+ const limited = await stepped(limiting(queue, rebuilt, ask.providerLimits, room.replay));
1312
+ if (limited !== null)
1313
+ return { kind: 'refused', refusal: limited };
1314
+ saved.set(rebuilt.id, withdrawnDrafts(room.saved.get(origin) ?? [], changes));
1315
+ }
1316
+ if (own !== null) {
1317
+ saved.set(own.batch.id, ask.saved.filter(({ path }) => !requeue.queued.has(path)));
1318
+ }
1319
+ const carried = carriedDrafts(ask, room, room);
1320
+ return {
1321
+ kind: 'planned',
1322
+ identifier: own?.batch.id ??
1323
+ [...requeue.withdrawn].map(([origin, rebuilt]) => rebuilt?.id ?? origin).at(-1),
1324
+ rooms: [
1325
+ {
1326
+ locator: record.locator,
1327
+ queue,
1328
+ saved,
1329
+ consumed: [...ask.consumed, ...carried.keys()],
1330
+ carried: [...carried.values()],
1331
+ },
1332
+ ],
1333
+ };
1334
+ }
1335
+ export function destination(locator, saved) {
1336
+ const into = saved.find(({ parts }) => parts.move?.room !== undefined && parts.move.room !== locator);
1337
+ return into?.parts.move?.room ?? null;
1171
1338
  }
1172
- export function buildBatch(input) {
1173
- return batching(input);
1339
+ export function planSave(ask, room, into) {
1340
+ return into === undefined ? plannedWithin(ask, room) : plannedMove(ask, room, into);
1174
1341
  }