@agentshouse/mdruntime 0.1.0-cba6f2439bb25 → 0.1.0-cbbae1fff01c6

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 (303) hide show
  1. package/README.md +10 -10
  2. package/dist/acceptance.d.ts +3 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +308 -0
  5. package/dist/actions.d.ts +20 -0
  6. package/dist/actions.d.ts.map +1 -0
  7. package/dist/actions.js +251 -0
  8. package/dist/adapter.d.ts +95 -103
  9. package/dist/adapter.d.ts.map +1 -1
  10. package/dist/attached-model.d.ts +3 -10
  11. package/dist/attached-model.d.ts.map +1 -1
  12. package/dist/attached-model.js +21 -56
  13. package/dist/bootstrap.d.ts +12 -11
  14. package/dist/bootstrap.d.ts.map +1 -1
  15. package/dist/bootstrap.js +213 -89
  16. package/dist/cell.d.ts +6 -0
  17. package/dist/cell.d.ts.map +1 -0
  18. package/dist/cell.js +9 -0
  19. package/dist/chain.d.ts +2 -1
  20. package/dist/chain.d.ts.map +1 -1
  21. package/dist/change-set.d.ts +4 -0
  22. package/dist/change-set.d.ts.map +1 -1
  23. package/dist/change-set.js +6 -0
  24. package/dist/change.d.ts +1 -0
  25. package/dist/change.d.ts.map +1 -1
  26. package/dist/change.js +4 -2
  27. package/dist/chat-types.d.ts +5 -0
  28. package/dist/chat-types.d.ts.map +1 -0
  29. package/dist/chat-types.js +13 -0
  30. package/dist/client.d.ts +24 -7
  31. package/dist/client.d.ts.map +1 -1
  32. package/dist/client.js +9 -5
  33. package/dist/collection.d.ts +23 -7
  34. package/dist/collection.d.ts.map +1 -1
  35. package/dist/collection.js +13 -19
  36. package/dist/compile.d.ts +6 -6
  37. package/dist/compile.d.ts.map +1 -1
  38. package/dist/compile.js +272 -332
  39. package/dist/connection.d.ts +5 -143
  40. package/dist/connection.d.ts.map +1 -1
  41. package/dist/connection.js +105 -5253
  42. package/dist/context.d.ts +2 -0
  43. package/dist/context.d.ts.map +1 -0
  44. package/dist/context.js +7 -0
  45. package/dist/conversation-live.d.ts +135 -0
  46. package/dist/conversation-live.d.ts.map +1 -0
  47. package/dist/conversation-live.js +247 -0
  48. package/dist/conversation-row.d.ts +33 -0
  49. package/dist/conversation-row.d.ts.map +1 -0
  50. package/dist/conversation-row.js +53 -0
  51. package/dist/conversation.d.ts +41 -47
  52. package/dist/conversation.d.ts.map +1 -1
  53. package/dist/conversation.js +874 -911
  54. package/dist/declaration.d.ts +10 -0
  55. package/dist/declaration.d.ts.map +1 -0
  56. package/dist/declaration.js +24 -0
  57. package/dist/discovery.d.ts +10 -0
  58. package/dist/discovery.d.ts.map +1 -0
  59. package/dist/discovery.js +148 -0
  60. package/dist/durability.d.ts +15 -1
  61. package/dist/durability.d.ts.map +1 -1
  62. package/dist/entry.d.ts +2 -2
  63. package/dist/entry.d.ts.map +1 -1
  64. package/dist/entry.js +17 -7
  65. package/dist/expectation.d.ts +4 -2
  66. package/dist/expectation.d.ts.map +1 -1
  67. package/dist/expectation.js +33 -11
  68. package/dist/failure.d.ts +1 -0
  69. package/dist/failure.d.ts.map +1 -1
  70. package/dist/failure.js +3 -0
  71. package/dist/figures.d.ts +49 -0
  72. package/dist/figures.d.ts.map +1 -0
  73. package/dist/figures.js +197 -0
  74. package/dist/holdings.d.ts +29 -0
  75. package/dist/holdings.d.ts.map +1 -0
  76. package/dist/holdings.js +95 -0
  77. package/dist/host.d.ts +20 -8
  78. package/dist/host.d.ts.map +1 -1
  79. package/dist/host.js +44 -34
  80. package/dist/identity.d.ts +0 -1
  81. package/dist/identity.d.ts.map +1 -1
  82. package/dist/identity.js +0 -3
  83. package/dist/index.d.ts +28 -8
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +10 -1
  86. package/dist/install.d.ts +54 -0
  87. package/dist/install.d.ts.map +1 -0
  88. package/dist/install.js +538 -0
  89. package/dist/job.d.ts +5 -0
  90. package/dist/job.d.ts.map +1 -0
  91. package/dist/job.js +7 -0
  92. package/dist/lifecycle.d.ts +7 -0
  93. package/dist/lifecycle.d.ts.map +1 -0
  94. package/dist/lifecycle.js +272 -0
  95. package/dist/listing.d.ts +48 -0
  96. package/dist/listing.d.ts.map +1 -0
  97. package/dist/listing.js +139 -0
  98. package/dist/live.d.ts +5 -4
  99. package/dist/live.d.ts.map +1 -1
  100. package/dist/live.js +64 -23
  101. package/dist/locks.d.ts +7 -0
  102. package/dist/locks.d.ts.map +1 -0
  103. package/dist/locks.js +33 -0
  104. package/dist/log.d.ts +1 -0
  105. package/dist/log.d.ts.map +1 -1
  106. package/dist/lost.d.ts +5 -0
  107. package/dist/lost.d.ts.map +1 -0
  108. package/dist/lost.js +50 -0
  109. package/dist/media.d.ts +2 -2
  110. package/dist/media.d.ts.map +1 -1
  111. package/dist/media.js +22 -12
  112. package/dist/memory.d.ts +14 -5
  113. package/dist/memory.d.ts.map +1 -1
  114. package/dist/memory.js +177 -85
  115. package/dist/messages.d.ts +24 -3
  116. package/dist/messages.d.ts.map +1 -1
  117. package/dist/messages.js +89 -31
  118. package/dist/model.d.ts +2 -11
  119. package/dist/model.d.ts.map +1 -1
  120. package/dist/model.js +0 -23
  121. package/dist/models.d.ts +10 -0
  122. package/dist/models.d.ts.map +1 -0
  123. package/dist/models.js +156 -0
  124. package/dist/observation.d.ts +2 -2
  125. package/dist/observation.d.ts.map +1 -1
  126. package/dist/observation.js +14 -14
  127. package/dist/opened.d.ts +1 -1
  128. package/dist/opened.d.ts.map +1 -1
  129. package/dist/opened.js +5 -5
  130. package/dist/opening.d.ts +4 -0
  131. package/dist/opening.d.ts.map +1 -0
  132. package/dist/opening.js +59 -0
  133. package/dist/outcome.d.ts +4 -4
  134. package/dist/outcome.d.ts.map +1 -1
  135. package/dist/outcome.js +3 -0
  136. package/dist/overlay.d.ts +3 -1
  137. package/dist/overlay.d.ts.map +1 -1
  138. package/dist/overlay.js +43 -21
  139. package/dist/parts.d.ts +11 -0
  140. package/dist/parts.d.ts.map +1 -0
  141. package/dist/parts.js +21 -0
  142. package/dist/paths.d.ts +1 -0
  143. package/dist/paths.d.ts.map +1 -1
  144. package/dist/paths.js +22 -0
  145. package/dist/preparation.d.ts +20 -0
  146. package/dist/preparation.d.ts.map +1 -0
  147. package/dist/preparation.js +233 -0
  148. package/dist/presence.d.ts +52 -0
  149. package/dist/presence.d.ts.map +1 -0
  150. package/dist/presence.js +220 -0
  151. package/dist/queries.d.ts +16 -0
  152. package/dist/queries.d.ts.map +1 -0
  153. package/dist/queries.js +270 -0
  154. package/dist/query.d.ts +14 -210
  155. package/dist/query.d.ts.map +1 -1
  156. package/dist/query.js +3 -505
  157. package/dist/questions.d.ts +34 -0
  158. package/dist/questions.d.ts.map +1 -0
  159. package/dist/questions.js +122 -0
  160. package/dist/queue.d.ts +40 -0
  161. package/dist/queue.d.ts.map +1 -0
  162. package/dist/queue.js +455 -0
  163. package/dist/reach.d.ts +15 -13
  164. package/dist/reach.d.ts.map +1 -1
  165. package/dist/reach.js +187 -140
  166. package/dist/react.d.ts +14 -2
  167. package/dist/react.d.ts.map +1 -1
  168. package/dist/react.js +10 -1
  169. package/dist/read.d.ts +7 -0
  170. package/dist/read.d.ts.map +1 -0
  171. package/dist/read.js +17 -0
  172. package/dist/readiness.d.ts +2 -16
  173. package/dist/readiness.d.ts.map +1 -1
  174. package/dist/readiness.js +4 -10
  175. package/dist/reads.d.ts +36 -10
  176. package/dist/reads.d.ts.map +1 -1
  177. package/dist/reads.js +160 -77
  178. package/dist/recovery.d.ts +1 -0
  179. package/dist/recovery.d.ts.map +1 -1
  180. package/dist/recovery.js +35 -1
  181. package/dist/refresh.d.ts +7 -5
  182. package/dist/refresh.d.ts.map +1 -1
  183. package/dist/refresh.js +35 -7
  184. package/dist/refusal.d.ts +5 -0
  185. package/dist/refusal.d.ts.map +1 -0
  186. package/dist/refusal.js +57 -0
  187. package/dist/replica.d.ts +11 -17
  188. package/dist/replica.d.ts.map +1 -1
  189. package/dist/replica.js +64 -34
  190. package/dist/room-draft.d.ts +7 -8
  191. package/dist/room-draft.d.ts.map +1 -1
  192. package/dist/room-draft.js +100 -103
  193. package/dist/room-drafts.d.ts +24 -0
  194. package/dist/room-drafts.d.ts.map +1 -0
  195. package/dist/room-drafts.js +217 -0
  196. package/dist/rows.d.ts +14 -21
  197. package/dist/rows.d.ts.map +1 -1
  198. package/dist/rows.js +49 -186
  199. package/dist/runtime.d.ts +34 -0
  200. package/dist/runtime.d.ts.map +1 -0
  201. package/dist/runtime.js +1 -0
  202. package/dist/save.d.ts +41 -86
  203. package/dist/save.d.ts.map +1 -1
  204. package/dist/save.js +239 -116
  205. package/dist/saves.d.ts +32 -0
  206. package/dist/saves.d.ts.map +1 -0
  207. package/dist/saves.js +413 -0
  208. package/dist/search.d.ts +12 -63
  209. package/dist/search.d.ts.map +1 -1
  210. package/dist/search.js +9 -52
  211. package/dist/seed.d.ts +11 -2
  212. package/dist/seed.d.ts.map +1 -1
  213. package/dist/seed.js +71 -1
  214. package/dist/shared.d.ts +1 -7
  215. package/dist/shared.d.ts.map +1 -1
  216. package/dist/shared.js +13 -105
  217. package/dist/shown.d.ts +32 -0
  218. package/dist/shown.d.ts.map +1 -0
  219. package/dist/shown.js +290 -0
  220. package/dist/socket/admission.d.ts +7 -0
  221. package/dist/socket/admission.d.ts.map +1 -0
  222. package/dist/socket/admission.js +39 -0
  223. package/dist/socket/answers.d.ts +6 -0
  224. package/dist/socket/answers.d.ts.map +1 -0
  225. package/dist/socket/answers.js +20 -0
  226. package/dist/socket/lanes.d.ts +20 -0
  227. package/dist/socket/lanes.d.ts.map +1 -0
  228. package/dist/socket/lanes.js +77 -0
  229. package/dist/socket/reads.d.ts +10 -0
  230. package/dist/socket/reads.d.ts.map +1 -0
  231. package/dist/socket/reads.js +31 -0
  232. package/dist/socket/session.d.ts +38 -0
  233. package/dist/socket/session.d.ts.map +1 -0
  234. package/dist/socket/session.js +23 -0
  235. package/dist/socket.d.ts +67 -0
  236. package/dist/socket.d.ts.map +1 -0
  237. package/dist/socket.js +170 -0
  238. package/dist/source.d.ts +61 -0
  239. package/dist/source.d.ts.map +1 -0
  240. package/dist/source.js +82 -0
  241. package/dist/status.d.ts +0 -6
  242. package/dist/status.d.ts.map +1 -1
  243. package/dist/status.js +0 -6
  244. package/dist/store/installs.d.ts +30 -0
  245. package/dist/store/installs.d.ts.map +1 -0
  246. package/dist/store/installs.js +34 -0
  247. package/dist/store/readiness.d.ts +10 -0
  248. package/dist/store/readiness.d.ts.map +1 -0
  249. package/dist/store/readiness.js +52 -0
  250. package/dist/store/records.d.ts +210 -0
  251. package/dist/store/records.d.ts.map +1 -0
  252. package/dist/store/records.js +428 -0
  253. package/dist/store/room-held.d.ts +8 -0
  254. package/dist/store/room-held.d.ts.map +1 -0
  255. package/dist/store/room-held.js +17 -0
  256. package/dist/store/space.d.ts +11 -0
  257. package/dist/store/space.d.ts.map +1 -0
  258. package/dist/store/space.js +41 -0
  259. package/dist/store/watchers.d.ts +17 -0
  260. package/dist/store/watchers.d.ts.map +1 -0
  261. package/dist/store/watchers.js +56 -0
  262. package/dist/store/writes.d.ts +24 -0
  263. package/dist/store/writes.d.ts.map +1 -0
  264. package/dist/store/writes.js +125 -0
  265. package/dist/store.d.ts +110 -157
  266. package/dist/store.d.ts.map +1 -1
  267. package/dist/store.js +162 -394
  268. package/dist/stored.d.ts +59 -38
  269. package/dist/stored.d.ts.map +1 -1
  270. package/dist/stored.js +287 -206
  271. package/dist/surface.d.ts +88 -0
  272. package/dist/surface.d.ts.map +1 -0
  273. package/dist/surface.js +223 -0
  274. package/dist/sync.d.ts +19 -0
  275. package/dist/sync.d.ts.map +1 -0
  276. package/dist/sync.js +521 -0
  277. package/dist/tables.d.ts +116 -56
  278. package/dist/tables.d.ts.map +1 -1
  279. package/dist/tables.js +214 -98
  280. package/dist/tallies.d.ts +18 -0
  281. package/dist/tallies.d.ts.map +1 -0
  282. package/dist/tallies.js +129 -0
  283. package/dist/transfer.d.ts +0 -5
  284. package/dist/transfer.d.ts.map +1 -1
  285. package/dist/validation.d.ts +3 -4
  286. package/dist/validation.d.ts.map +1 -1
  287. package/dist/validation.js +42 -100
  288. package/dist/windows.d.ts +1 -12
  289. package/dist/windows.d.ts.map +1 -1
  290. package/dist/windows.js +2 -75
  291. package/dist/wire.d.ts +21 -9
  292. package/dist/wire.d.ts.map +1 -1
  293. package/dist/wire.js +10 -4
  294. package/package.json +3 -2
  295. package/dist/fence.d.ts +0 -6
  296. package/dist/fence.d.ts.map +0 -1
  297. package/dist/fence.js +0 -10
  298. package/dist/fulltext.d.ts +0 -27
  299. package/dist/fulltext.d.ts.map +0 -1
  300. package/dist/fulltext.js +0 -370
  301. package/dist/words.d.ts +0 -19
  302. package/dist/words.d.ts.map +0 -1
  303. package/dist/words.js +0 -156
package/dist/save.js CHANGED
@@ -1,11 +1,11 @@
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, 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';
3
+ import { collectionOf, definitionsAt, documentsOf, governing, } from './collection.js';
4
+ import { changeBatchIdentity, roomDraftRevision } from './identity.js';
6
5
  import { overlaying } from './overlay.js';
7
- import { ancestors, covered, normalize } from './paths.js';
8
- import { sorting } from './shared.js';
6
+ import { ancestors } from './paths.js';
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
11
  function refuse(code, file, location) {
@@ -20,9 +20,6 @@ function refuse(code, file, location) {
20
20
  };
21
21
  }
22
22
  function repointed(document, definition, from, to, moves, settled) {
23
- const parsed = parse(document.content, from);
24
- if (!parsed.ok)
25
- return [];
26
23
  const changes = [];
27
24
  const names = new Set();
28
25
  for (const { pointer, value } of definition === undefined
@@ -45,13 +42,13 @@ function repointed(document, definition, from, to, moves, settled) {
45
42
  }
46
43
  if (changes.length === 0)
47
44
  return [];
48
- const applied = apply(parsed.document, changes);
45
+ const applied = apply(document, changes, definition);
49
46
  if (applied.outcome !== 'applied')
50
47
  return [];
51
48
  const next = applied.document;
52
49
  return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
53
50
  }
54
- function edited(document, changes) {
51
+ function edited(document, changes, definition) {
55
52
  const parts = new Map();
56
53
  for (const change of changes) {
57
54
  if (change.kind === 'preamble')
@@ -66,7 +63,7 @@ function edited(document, changes) {
66
63
  }
67
64
  if (parts.size === 0)
68
65
  return document;
69
- const applied = apply(document, [...parts.values()]);
66
+ const applied = apply(document, [...parts.values()], definition);
70
67
  return applied.outcome === 'applied' ? applied.document : document;
71
68
  }
72
69
  function bodies(path, before, after) {
@@ -81,7 +78,7 @@ function bodies(path, before, after) {
81
78
  }
82
79
  return changes;
83
80
  }
84
- function relinks(path, moved, before, moves) {
81
+ function relinks(path, moved, before, moves, definition) {
85
82
  const changes = [];
86
83
  for (const link of links(before, path)) {
87
84
  const destination = moves.get(link.target) ?? link.target;
@@ -94,7 +91,7 @@ function relinks(path, moved, before, moves) {
94
91
  }
95
92
  if (changes.length === 0)
96
93
  return { kind: 'changes', changes: [] };
97
- const applied = apply(before, changes);
94
+ const applied = apply(before, changes, definition);
98
95
  if (applied.outcome === 'refuse') {
99
96
  return {
100
97
  kind: 'refused',
@@ -117,6 +114,15 @@ function relinks(path, moved, before, moves) {
117
114
  function partKey(change) {
118
115
  return change.kind === 'section' ? `section ${change.name}` : change.kind;
119
116
  }
117
+ const NAMES_A_SKIP_TESTS = 64;
118
+ const ENCODED = /[%&\\]/u;
119
+ function movedNames(moves) {
120
+ const names = new Set([...moves.keys()].map((path) => path.slice(path.lastIndexOf('/') + 1)));
121
+ return names.size > NAMES_A_SKIP_TESTS ? null : [...names];
122
+ }
123
+ function cannotLink(text, names) {
124
+ return names !== null && !ENCODED.test(text) && !names.some((name) => text.includes(name));
125
+ }
120
126
  function* rewrites(changes, visible) {
121
127
  const moves = new Map();
122
128
  const settled = new Set();
@@ -142,17 +148,20 @@ function* rewrites(changes, visible) {
142
148
  }
143
149
  if (moves.size === 0)
144
150
  return { kind: 'changes', changes };
151
+ const names = movedNames(moves);
145
152
  const merged = [...changes];
146
153
  const added = [];
147
154
  for (const [path, document] of yield* documentsOf(visible.collection)) {
148
155
  if (settled.has(path))
149
156
  continue;
150
157
  const moved = moves.get(path) ?? path;
151
- const definition = definitionOf(governing(visible.collection.composition, path), document.type);
158
+ const definition = definitionsAt(visible.collection.composition, path)(document.type);
152
159
  const repointing = repointed(document, definition, path, moved, moves, settled);
153
160
  added.push(...repointing);
154
161
  const own = parts.get(path) ?? [];
155
- const relinked = relinks(path, moved, edited(document, own.map((index) => merged[index])), moves);
162
+ const relinked = moved === path && own.length === 0 && cannotLink(document.content, names)
163
+ ? { kind: 'changes', changes: [] }
164
+ : relinks(path, moved, edited(document, own.map((index) => merged[index]), definition), moves, definition);
156
165
  if (relinked.kind === 'refused')
157
166
  return relinked;
158
167
  const keyed = new Map();
@@ -221,10 +230,10 @@ function crossed(document, definition, path, to) {
221
230
  }
222
231
  return changes;
223
232
  }
224
- function rewritten(path, document, changes) {
233
+ function rewritten(path, document, changes, definition) {
225
234
  if (changes.length === 0)
226
235
  return [];
227
- const applied = apply(document, changes);
236
+ const applied = apply(document, changes, definition);
228
237
  if (applied.outcome !== 'applied')
229
238
  return [];
230
239
  const after = applied.document;
@@ -239,7 +248,7 @@ function rewritten(path, document, changes) {
239
248
  function roomPathOf(handle, path) {
240
249
  return `/rooms/${handle}/${path}`;
241
250
  }
242
- export function* leavingRewritten(batch, visible) {
251
+ function* leavingRewritten(batch, visible) {
243
252
  const between = batch.between;
244
253
  if (between === undefined)
245
254
  return batch;
@@ -248,8 +257,8 @@ export function* leavingRewritten(batch, visible) {
248
257
  for (const [path, document] of yield* documentsOf(visible.collection)) {
249
258
  if (moved.has(path))
250
259
  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))));
260
+ const definition = definitionsAt(visible.collection.composition, path)(document.type);
261
+ derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named)), definition));
253
262
  yield ROW_WEIGHT + visible.collection.text[path].length;
254
263
  }
255
264
  if (derived.length === 0)
@@ -291,7 +300,7 @@ function* incoming(collection, removed, candidates) {
291
300
  for (const [path, document] of candidates === null
292
301
  ? yield* documentsOf(collection)
293
302
  : read(candidates)) {
294
- const definition = definitionOf(governing(collection.composition, path), document.type);
303
+ const definition = definitionsAt(collection.composition, path)(document.type);
295
304
  for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
296
305
  if (to.includes(ATTACHMENT))
297
306
  continue;
@@ -321,6 +330,8 @@ function* incoming(collection, removed, candidates) {
321
330
  function affected(change) {
322
331
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
323
332
  }
333
+ const WRITE = ['write'];
334
+ const MOVE_OUT = ['write', 'erase'];
324
335
  function guard(changes, input, removing) {
325
336
  const overridden = new Map();
326
337
  const existing = {
@@ -332,15 +343,9 @@ function guard(changes, input, removing) {
332
343
  const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
333
344
  for (const [index, change] of changes.entries()) {
334
345
  for (const path of affected(change)) {
335
- const normalized = normalize(path);
336
- if (normalized === null || normalized === '')
337
- return refuse('room-root', path);
338
- if (covered(input.providerOwnedPaths, normalized)) {
339
- return refuse('provider-owned-path', normalized);
340
- }
341
- const operation = change.kind === 'remove' ? removing : 'write';
342
- if (!input.local && !covered(input.operations[operation], normalized))
343
- return refuse(`${operation}-grant`, normalized);
346
+ const refused = input.rule(path, change.kind === 'remove' ? removing : WRITE);
347
+ if (refused !== null)
348
+ return refuse(refused.code, refused.path);
344
349
  }
345
350
  if (change.kind === 'create' || change.kind === 'arrival') {
346
351
  const taken = existing.has(change.path) ? change.path : occupied(change.path);
@@ -401,9 +406,9 @@ function guard(changes, input, removing) {
401
406
  return null;
402
407
  }
403
408
  function unrevised(input, path) {
404
- return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
409
+ return !input.local && !input.queued.has(path) && input.visible.revisions[path] === undefined;
405
410
  }
406
- export function queuedDocuments(queue) {
411
+ function queuedDocuments(queue) {
407
412
  const paths = new Set();
408
413
  for (const batch of queue) {
409
414
  for (const change of batch.changes) {
@@ -539,19 +544,16 @@ function* exceeding(limits, changes, effective, resulting) {
539
544
  function shellRelative(text, path, handle, definition) {
540
545
  if (!text.includes(`/rooms/${handle}/`))
541
546
  return null;
542
- const parsed = parse(text, path);
547
+ const parsed = parse(text, path, definition);
543
548
  if (!parsed.ok)
544
549
  return null;
545
550
  const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
546
551
  return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
547
552
  }
548
553
  function relativeField(path, name, value, handle, definition) {
549
- const opened = parse('---\n---\n');
550
- if (definition === undefined ||
551
- !opened.ok ||
552
- !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
554
+ if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
553
555
  return value;
554
- const held = apply(opened.document, [{ kind: 'field', name, value, prior: undefined }]);
556
+ const held = apply(create(definition.name, definition), [{ kind: 'field', name, value, prior: undefined }], definition);
555
557
  if (held.outcome !== 'applied')
556
558
  return value;
557
559
  const result = relativize(held.document, path, handle, definition);
@@ -559,7 +561,7 @@ function relativeField(path, name, value, handle, definition) {
559
561
  }
560
562
  function relativeChanges(input) {
561
563
  const { handle, composition, visible } = input;
562
- const typed = (path) => (type) => definitionOf(governing(composition, path), type);
564
+ const typed = (path) => definitionsAt(composition, path);
563
565
  const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
564
566
  return input.changes.map((change) => {
565
567
  const definition = typed(change.path);
@@ -588,7 +590,7 @@ function relativeChanges(input) {
588
590
  });
589
591
  }
590
592
  function unqueueable(file) {
591
- return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
593
+ return refuse('unknown-revision', file);
592
594
  }
593
595
  function isPart(change) {
594
596
  return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
@@ -596,8 +598,9 @@ function isPart(change) {
596
598
  function touched(change) {
597
599
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
598
600
  }
599
- function partsApplied(content, path, changes) {
600
- const parsed = parse(content, path);
601
+ function partsApplied(content, path, changes, composition) {
602
+ const definitions = definitionsAt(composition, path);
603
+ const parsed = parse(content, path, definitions);
601
604
  if (!parsed.ok)
602
605
  return null;
603
606
  const document = parsed.document;
@@ -620,12 +623,12 @@ function partsApplied(content, path, changes) {
620
623
  else
621
624
  parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
622
625
  }
623
- const applied = apply(document, [...parts.values()]);
626
+ const applied = apply(document, [...parts.values()], definitions(document.type));
624
627
  return applied.outcome === 'applied' ? serialize(applied.document) : null;
625
628
  }
626
- function settledParts(changes, deferred) {
629
+ function settledParts(changes, deferred, composition) {
627
630
  for (const [held, parts] of deferred) {
628
- const content = partsApplied(held.content, held.path, parts);
631
+ const content = partsApplied(held.content, held.path, parts, composition);
629
632
  if (content === null)
630
633
  return held.path;
631
634
  changes[changes.indexOf(held)] = { ...held, content };
@@ -700,7 +703,9 @@ function* relinked(batch, move, before, after, appending) {
700
703
  for (const change of derived.changes) {
701
704
  if (change === move)
702
705
  continue;
703
- const unsettled = isPart(change) ? null : settledParts(changes, deferred);
706
+ const unsettled = isPart(change)
707
+ ? null
708
+ : settledParts(changes, deferred, after.collection.composition);
704
709
  if (unsettled !== null)
705
710
  return unqueueable(unsettled);
706
711
  const outcome = folded(changes, change, appending, deferred);
@@ -711,7 +716,7 @@ function* relinked(batch, move, before, after, appending) {
711
716
  if (outcome !== 'skipped')
712
717
  changed = true;
713
718
  }
714
- const unsettled = settledParts(changes, deferred);
719
+ const unsettled = settledParts(changes, deferred, after.collection.composition);
715
720
  if (unsettled !== null)
716
721
  return unqueueable(unsettled);
717
722
  if (!changed)
@@ -772,11 +777,10 @@ function withdrawnFile(batch, change, at) {
772
777
  ];
773
778
  return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
774
779
  }
775
- export function* requeuing(queue, changes, visible, changeable, state) {
780
+ function* requeuing(queue, changes, visible, changeable, state) {
776
781
  const next = [...queue];
777
782
  const origins = new Map();
778
783
  const withdrawn = new Map();
779
- const owners = new Set();
780
784
  const queued = new Set();
781
785
  const kept = [];
782
786
  const originOf = (batch) => origins.get(batch) ?? batch.id;
@@ -822,7 +826,6 @@ export function* requeuing(queue, changes, visible, changeable, state) {
822
826
  if (replaced !== null)
823
827
  origins.set(replaced, origin);
824
828
  }
825
- owners.add(originOf(owner));
826
829
  next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
827
830
  queued.add(change.path);
828
831
  }
@@ -832,56 +835,13 @@ export function* requeuing(queue, changes, visible, changeable, state) {
832
835
  changes: kept,
833
836
  queued,
834
837
  withdrawn,
835
- owners,
836
838
  };
837
839
  }
838
- function covering(precondition) {
839
- return precondition.kind === 'field' ||
840
- precondition.kind === 'section' ||
841
- precondition.kind === 'preamble'
842
- ? identity(precondition)
843
- : `path ${precondition.path}`;
844
- }
845
- export function joined(queue, owner, extra) {
846
- const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
847
- const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
848
- if (clash !== undefined)
849
- return unqueueable(clash);
850
- const changes = [...owner.changes];
851
- const deferred = new Map();
852
- for (const change of extra.changes) {
853
- const unsettled = isPart(change) ? null : settledParts(changes, deferred);
854
- if (unsettled !== null)
855
- return unqueueable(unsettled);
856
- if (folded(changes, change, true, deferred) === 'refused')
857
- return unqueueable(change.path);
858
- }
859
- const unsettled = settledParts(changes, deferred);
860
- if (unsettled !== null)
861
- return unqueueable(unsettled);
862
- const held = new Set(owner.preconditions.map(covering));
863
- const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
864
- const preconditions = [
865
- ...owner.preconditions,
866
- ...extra.preconditions.filter((precondition) => {
867
- const key = covering(precondition);
868
- return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
869
- }),
870
- ];
871
- const batch = immutable({
872
- ...owner,
873
- ...(extra.validation === undefined ? {} : { validation: extra.validation }),
874
- id: changeBatchIdentity(),
875
- changes,
876
- preconditions,
877
- });
878
- return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
879
- }
880
- export function* limiting(queue, batch, limits, state) {
840
+ function* limiting(queue, batch, limits, state) {
881
841
  const at = queue.indexOf(batch);
882
842
  const rewritten = yield* providerRewritten(batch, yield* state(queue.slice(0, at)));
883
843
  const limited = yield* exceeding(limits, batch.changes, rewritten.changes, yield* state(queue.slice(0, at + 1)));
884
- return limited?.kind === 'refused' ? limited.refusal : null;
844
+ return limited?.refusal ?? null;
885
845
  }
886
846
  function typedText(content) {
887
847
  if (content === undefined)
@@ -898,7 +858,7 @@ function touchedLibrary(resulting, touched, composition) {
898
858
  })
899
859
  .sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
900
860
  const contents = new Map(entries.map(({ path, content }) => [path, content]));
901
- const binaries = new Set(Object.keys(resulting.binaries).filter((path) => resulting.binaries[path] !== undefined));
861
+ const binaries = new Set(Array.from(entriesIn(resulting.binaries), ([path]) => path));
902
862
  return validatedCollection(() => Promise.resolve({ entries, next: null }), async (paths) => {
903
863
  const known = new Map();
904
864
  const pace = paced();
@@ -907,7 +867,7 @@ function touchedLibrary(resulting, touched, composition) {
907
867
  if (binaries.has(path))
908
868
  known.set(path, { type: undefined, binary: true });
909
869
  else if (content !== undefined && content !== null) {
910
- const read = parse(content, path);
870
+ const read = parse(content, path, definitionsAt(composition, path));
911
871
  if (read.ok)
912
872
  known.set(path, { type: read.document.type ?? 'any', binary: false });
913
873
  await pace(content.length + ROW_WEIGHT);
@@ -971,10 +931,11 @@ function placed(reference) {
971
931
  function roomReferencesIn(path, content, composition) {
972
932
  if (content === undefined)
973
933
  return [];
974
- const parsed = parse(content, path);
934
+ const definitions = definitionsAt(composition, path);
935
+ const parsed = parse(content, path, definitions);
975
936
  if (!parsed.ok)
976
937
  return [];
977
- return roomReferences(path, content, definitionOf(governing(composition, path), parsed.document.type));
938
+ return roomReferences(parsed.document, definitions(parsed.document.type));
978
939
  }
979
940
  function* admittedRooms(changes, visible, resulting, composition) {
980
941
  const from = new Map();
@@ -1020,12 +981,12 @@ async function batching(input) {
1020
981
  const rewritten = await stepped(rewrites(changes, input.visible));
1021
982
  if (rewritten.kind === 'refused')
1022
983
  return rewritten;
1023
- const refusal = guard(changes, input, 'delete');
984
+ const refusal = guard(changes, input, WRITE);
1024
985
  if (refusal !== null)
1025
986
  return refusal;
1026
987
  const preconditions = await stepped(preconditionsOf(changes, input.visible, input.bases));
1027
988
  const predicted = await stepped(anticipated(changes, preconditions, rewritten.changes, input.visible));
1028
- const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }]));
989
+ const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }], input.composition));
1029
990
  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))]);
1030
991
  if (judged.kind === 'refused')
1031
992
  return judged;
@@ -1047,7 +1008,7 @@ async function batching(input) {
1047
1008
  }),
1048
1009
  };
1049
1010
  }
1050
- export const MOVE_DOCUMENTS = 'move-documents';
1011
+ const MOVE_DOCUMENTS = 'move-documents';
1051
1012
  function heldAt(visible, path) {
1052
1013
  return visible.documents[path] !== undefined || visible.binaries[path] !== undefined;
1053
1014
  }
@@ -1056,7 +1017,7 @@ function arrivingText(path, to, source, moved) {
1056
1017
  const document = source.visible.collection.document(path);
1057
1018
  if (document === undefined)
1058
1019
  return content;
1059
- const definition = definitionOf(governing(source.composition, path), document.type);
1020
+ const definition = definitionsAt(source.composition, path)(document.type);
1060
1021
  const changes = crossed(document, definition, path, (named, href) => {
1061
1022
  const at = moved.get(named);
1062
1023
  if (at !== undefined)
@@ -1065,13 +1026,14 @@ function arrivingText(path, to, source, moved) {
1065
1026
  });
1066
1027
  if (changes.length === 0)
1067
1028
  return content;
1068
- const applied = apply(document, changes);
1029
+ const applied = apply(document, changes, definition);
1069
1030
  return applied.outcome === 'applied' ? serialize(applied.document) : content;
1070
1031
  }
1071
1032
  function arrivalOf({ path, to }, source, moved) {
1072
- const { documents, revisions, fileIds, changes, binaries } = source.visible;
1033
+ const { documents, revisions, fileIds, created, changes, binaries } = source.visible;
1073
1034
  const kept = {
1074
1035
  ...(fileIds[path] === undefined ? {} : { fileId: fileIds[path] }),
1036
+ ...(created[path] === undefined ? {} : { created: created[path] }),
1075
1037
  ...(changes[path] === undefined ? {} : { change: changes[path] }),
1076
1038
  };
1077
1039
  const heads = {
@@ -1098,10 +1060,8 @@ function arrivalOf({ path, to }, source, moved) {
1098
1060
  }
1099
1061
  async function moving(input) {
1100
1062
  const { source, destination, moves } = input;
1101
- if (moves.length === 0)
1102
- return refuse('no-change');
1103
1063
  const removals = moves.map(({ path }) => ({ kind: 'remove', path }));
1104
- const leaves = guard(removals, { ...source, local: input.local, bases: [] }, 'write');
1064
+ const leaves = guard(removals, { ...source, local: input.local, bases: [] }, MOVE_OUT);
1105
1065
  if (leaves !== null)
1106
1066
  return leaves;
1107
1067
  const uploading = moves.find(({ path }) => source.visible.binaries[path]?.state === 'pending');
@@ -1109,7 +1069,7 @@ async function moving(input) {
1109
1069
  return refuse('unknown-revision', uploading.path);
1110
1070
  const moved = new Map(moves.map(({ path, to }) => [path, to]));
1111
1071
  const arrivals = moves.map((move) => arrivalOf(move, source, moved));
1112
- const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, 'write');
1072
+ const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, WRITE);
1113
1073
  if (arrives !== null)
1114
1074
  return arrives;
1115
1075
  const bound = input.providerLimits.find((limit) => limit.name === MOVE_DOCUMENTS)?.value;
@@ -1138,7 +1098,7 @@ async function moving(input) {
1138
1098
  between,
1139
1099
  };
1140
1100
  const effective = await stepped(leavingRewritten(leavingBatch, source.visible));
1141
- const left = await stepped(overlaying(source.visible, [effective]));
1101
+ const left = await stepped(overlaying(source.visible, [effective], source.composition));
1142
1102
  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))]);
1143
1103
  if (sourceJudged.kind === 'refused')
1144
1104
  return sourceJudged;
@@ -1150,7 +1110,7 @@ async function moving(input) {
1150
1110
  ...written,
1151
1111
  between,
1152
1112
  };
1153
- const arrived = await stepped(overlaying(destination.visible, [arrivingBatch]));
1113
+ const arrived = await stepped(overlaying(destination.visible, [arrivingBatch], destination.composition));
1154
1114
  const destinationJudged = await judging(destination.visible, arrived, destination.composition, destination.validate, new Set(), moves.map(({ to }) => to));
1155
1115
  if (destinationJudged.kind === 'refused')
1156
1116
  return destinationJudged;
@@ -1160,9 +1120,172 @@ async function moving(input) {
1160
1120
  return limited;
1161
1121
  return { kind: 'moved', leaving: immutable(leavingBatch), arriving: immutable(arrivingBatch) };
1162
1122
  }
1163
- export function buildMove(input) {
1164
- return moving(input);
1123
+ function changedUnderneath(saved, visible) {
1124
+ const parts = saved.flatMap(({ path, parts: held }) => changedParts(path, held, visible));
1125
+ return parts.length === 0
1126
+ ? null
1127
+ : { kind: 'refused', refusal: { code: 'changed-underneath', retryable: false, parts } };
1128
+ }
1129
+ function sideOf(room, visible) {
1130
+ const { record } = room;
1131
+ return {
1132
+ room: record.locator,
1133
+ handle: record.declaration.handle,
1134
+ baseRoomVersion: record.roomVersion,
1135
+ composition: room.composition,
1136
+ rule: room.rule,
1137
+ visible,
1138
+ folders: room.folders,
1139
+ queued: queuedDocuments(room.queue),
1140
+ unmodeled: !room.modelled,
1141
+ validate: room.validate,
1142
+ };
1143
+ }
1144
+ function carriedDrafts(ask, from, into) {
1145
+ return new Map(ask.saved.flatMap(({ path, parts }) => {
1146
+ const record = from.drafts.get(path);
1147
+ if (parts.move === undefined || record === undefined || ask.consumed.has(path))
1148
+ return [];
1149
+ const to = moveTarget(parts.move);
1150
+ if (into.drafts.has(to))
1151
+ return [];
1152
+ const locator = into.record.locator;
1153
+ return [[path, { ...record, locator, path: to, revision: roomDraftRevision() }]];
1154
+ }));
1155
+ }
1156
+ async function plannedMove(ask, source, into) {
1157
+ const destination = into.record.locator;
1158
+ const mixed = ask.saved.find(({ parts }) => Object.keys(parts).length !== 1 || parts.move?.room !== destination);
1159
+ if (mixed !== undefined)
1160
+ return refuse('mixed-room-move', mixed.path);
1161
+ const leavingVisible = await stepped(source.replay(source.queue));
1162
+ const changed = changedUnderneath(ask.saved, leavingVisible);
1163
+ if (changed !== null)
1164
+ return changed;
1165
+ const moves = ask.saved.map(({ path, parts }) => ({ path, to: moveTarget(parts.move) }));
1166
+ const built = await moving({
1167
+ source: sideOf(source, leavingVisible),
1168
+ destination: sideOf(into, await stepped(into.replay(into.queue))),
1169
+ moves,
1170
+ providerLimits: ask.providerLimits,
1171
+ local: ask.local,
1172
+ ...(ask.writtenBy === undefined ? {} : { writtenBy: ask.writtenBy }),
1173
+ });
1174
+ if (built.kind === 'refused')
1175
+ return built;
1176
+ const { id } = built.leaving;
1177
+ const carried = carriedDrafts(ask, source, into);
1178
+ return {
1179
+ kind: 'planned',
1180
+ identifier: id,
1181
+ rooms: [
1182
+ {
1183
+ locator: source.record.locator,
1184
+ queue: [...source.queue, built.leaving],
1185
+ saved: new Map([[id, ask.saved]]),
1186
+ consumed: [...ask.consumed, ...carried.keys()],
1187
+ carried: [],
1188
+ },
1189
+ {
1190
+ locator: destination,
1191
+ queue: [...into.queue, built.arriving],
1192
+ saved: new Map([
1193
+ [id, [...carried.values()].map(({ path }) => ({ path, parts: {} }))],
1194
+ ]),
1195
+ consumed: [],
1196
+ carried: [...carried.values()],
1197
+ },
1198
+ ],
1199
+ };
1200
+ }
1201
+ async function plannedWithin(ask, room) {
1202
+ const { record } = room;
1203
+ const visible = await stepped(room.replay(room.queue));
1204
+ const changed = changedUnderneath(ask.saved, visible);
1205
+ if (changed !== null)
1206
+ return changed;
1207
+ const parts = ask.saved.map(({ path, parts: held }) => changesOf(path, held));
1208
+ const changes = parts.flatMap(({ changes: made }) => made);
1209
+ const requeue = await stepped(requeuing(room.queue, changes, visible, (batch) => room.changeable.has(batch.id), room.replay));
1210
+ if (requeue.kind === 'refused')
1211
+ return requeue;
1212
+ const foreign = [...requeue.withdrawn.keys()].some((origin) => {
1213
+ const writer = room.queue.find((batch) => batch.id === origin)?.writtenBy;
1214
+ return writer?.app !== ask.writtenBy?.app || writer?.release !== ask.writtenBy?.release;
1215
+ });
1216
+ if (foreign)
1217
+ return unqueueable([...requeue.queued].at(-1));
1218
+ const input = {
1219
+ local: ask.local,
1220
+ baseRoomVersion: record.roomVersion,
1221
+ composition: room.composition,
1222
+ rule: room.rule,
1223
+ providerLimits: ask.providerLimits,
1224
+ handle: record.declaration.handle,
1225
+ visible,
1226
+ folders: room.folders,
1227
+ changes,
1228
+ bases: parts.flatMap(({ bases }) => bases),
1229
+ queued: queuedDocuments(room.queue),
1230
+ writtenBy: ask.writtenBy,
1231
+ unmodeled: !room.modelled,
1232
+ validate: room.validate,
1233
+ };
1234
+ const rewritten = requeue.withdrawn.size > 0;
1235
+ if (rewritten) {
1236
+ const whole = await batching({
1237
+ ...input,
1238
+ providerLimits: [],
1239
+ queued: new Set([...input.queued, ...requeue.queued]),
1240
+ });
1241
+ if (whole.kind === 'refused')
1242
+ return whole;
1243
+ }
1244
+ const own = !rewritten
1245
+ ? await batching(input)
1246
+ : requeue.changes.length === 0
1247
+ ? null
1248
+ : await batching({
1249
+ ...input,
1250
+ visible: await stepped(room.replay(requeue.queue)),
1251
+ changes: requeue.changes,
1252
+ queued: queuedDocuments(requeue.queue),
1253
+ });
1254
+ if (own?.kind === 'refused')
1255
+ return own;
1256
+ const queue = own === null ? requeue.queue : [...requeue.queue, own.batch];
1257
+ const saved = new Map();
1258
+ for (const [origin, rebuilt] of requeue.withdrawn) {
1259
+ if (rebuilt === null)
1260
+ continue;
1261
+ const limited = await stepped(limiting(queue, rebuilt, ask.providerLimits, room.replay));
1262
+ if (limited !== null)
1263
+ return { kind: 'refused', refusal: limited };
1264
+ saved.set(rebuilt.id, withdrawnDrafts(room.saved.get(origin) ?? [], changes));
1265
+ }
1266
+ if (own !== null) {
1267
+ saved.set(own.batch.id, ask.saved.filter(({ path }) => !requeue.queued.has(path)));
1268
+ }
1269
+ const carried = carriedDrafts(ask, room, room);
1270
+ return {
1271
+ kind: 'planned',
1272
+ identifier: own?.batch.id ??
1273
+ [...requeue.withdrawn].map(([origin, rebuilt]) => rebuilt?.id ?? origin).at(-1),
1274
+ rooms: [
1275
+ {
1276
+ locator: record.locator,
1277
+ queue,
1278
+ saved,
1279
+ consumed: [...ask.consumed, ...carried.keys()],
1280
+ carried: [...carried.values()],
1281
+ },
1282
+ ],
1283
+ };
1284
+ }
1285
+ export function destination(locator, saved) {
1286
+ const into = saved.find(({ parts }) => parts.move?.room !== undefined && parts.move.room !== locator);
1287
+ return into?.parts.move?.room ?? null;
1165
1288
  }
1166
- export function buildBatch(input) {
1167
- return batching(input);
1289
+ export function planSave(ask, room, into) {
1290
+ return into === undefined ? plannedWithin(ask, room) : plannedMove(ask, room, into);
1168
1291
  }