@agentshouse/mdruntime 0.1.0-cb1f56e16291b → 0.1.0-cb3bb3b233fa1

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 (327) hide show
  1. package/README.md +21 -19
  2. package/dist/acceptance.d.ts +3 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +299 -0
  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 +26 -0
  9. package/dist/actions.d.ts.map +1 -0
  10. package/dist/actions.js +302 -0
  11. package/dist/adapter.d.ts +113 -81
  12. package/dist/adapter.d.ts.map +1 -1
  13. package/dist/attached-model.d.ts +3 -11
  14. package/dist/attached-model.d.ts.map +1 -1
  15. package/dist/attached-model.js +20 -60
  16. package/dist/authored.d.ts +88 -0
  17. package/dist/authored.d.ts.map +1 -0
  18. package/dist/authored.js +345 -0
  19. package/dist/bootstrap.d.ts +9 -5
  20. package/dist/bootstrap.d.ts.map +1 -1
  21. package/dist/bootstrap.js +204 -85
  22. package/dist/cell.d.ts +6 -0
  23. package/dist/cell.d.ts.map +1 -0
  24. package/dist/cell.js +9 -0
  25. package/dist/chain.d.ts +2 -1
  26. package/dist/chain.d.ts.map +1 -1
  27. package/dist/change-set.d.ts +4 -0
  28. package/dist/change-set.d.ts.map +1 -1
  29. package/dist/change-set.js +6 -0
  30. package/dist/change.d.ts +5 -0
  31. package/dist/change.d.ts.map +1 -1
  32. package/dist/change.js +7 -2
  33. package/dist/chat-types.d.ts +3 -0
  34. package/dist/chat-types.d.ts.map +1 -0
  35. package/dist/chat-types.js +11 -0
  36. package/dist/client.d.ts +30 -22
  37. package/dist/client.d.ts.map +1 -1
  38. package/dist/client.js +39 -20
  39. package/dist/collection.d.ts +26 -4
  40. package/dist/collection.d.ts.map +1 -1
  41. package/dist/collection.js +19 -2
  42. package/dist/column.d.ts +5 -0
  43. package/dist/column.d.ts.map +1 -0
  44. package/dist/column.js +9 -0
  45. package/dist/compile.d.ts +9 -7
  46. package/dist/compile.d.ts.map +1 -1
  47. package/dist/compile.js +252 -318
  48. package/dist/connection.d.ts +5 -141
  49. package/dist/connection.d.ts.map +1 -1
  50. package/dist/connection.js +111 -5282
  51. package/dist/context.d.ts.map +1 -1
  52. package/dist/context.js +1 -1
  53. package/dist/conversation-live.d.ts +150 -0
  54. package/dist/conversation-live.d.ts.map +1 -0
  55. package/dist/conversation-live.js +259 -0
  56. package/dist/conversation-row.d.ts +40 -0
  57. package/dist/conversation-row.d.ts.map +1 -0
  58. package/dist/conversation-row.js +66 -0
  59. package/dist/conversation.d.ts +62 -43
  60. package/dist/conversation.d.ts.map +1 -1
  61. package/dist/conversation.js +987 -902
  62. package/dist/declaration.d.ts +10 -0
  63. package/dist/declaration.d.ts.map +1 -0
  64. package/dist/declaration.js +24 -0
  65. package/dist/discovery.d.ts +10 -0
  66. package/dist/discovery.d.ts.map +1 -0
  67. package/dist/discovery.js +150 -0
  68. package/dist/durability.d.ts +19 -0
  69. package/dist/durability.d.ts.map +1 -1
  70. package/dist/durability.js +4 -0
  71. package/dist/entry.d.ts +2 -2
  72. package/dist/entry.d.ts.map +1 -1
  73. package/dist/entry.js +18 -9
  74. package/dist/expectation.d.ts +26 -13
  75. package/dist/expectation.d.ts.map +1 -1
  76. package/dist/expectation.js +72 -19
  77. package/dist/failure.d.ts +3 -0
  78. package/dist/failure.d.ts.map +1 -1
  79. package/dist/failure.js +6 -0
  80. package/dist/figures.d.ts +49 -0
  81. package/dist/figures.d.ts.map +1 -0
  82. package/dist/figures.js +199 -0
  83. package/dist/file.d.ts +1 -1
  84. package/dist/file.d.ts.map +1 -1
  85. package/dist/file.js +3 -2
  86. package/dist/holdings.d.ts +29 -0
  87. package/dist/holdings.d.ts.map +1 -0
  88. package/dist/holdings.js +95 -0
  89. package/dist/host.d.ts +20 -4
  90. package/dist/host.d.ts.map +1 -1
  91. package/dist/host.js +72 -28
  92. package/dist/identity.d.ts +0 -1
  93. package/dist/identity.d.ts.map +1 -1
  94. package/dist/identity.js +0 -3
  95. package/dist/index.d.ts +32 -12
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +14 -5
  98. package/dist/install.d.ts +60 -0
  99. package/dist/install.d.ts.map +1 -0
  100. package/dist/install.js +537 -0
  101. package/dist/lifecycle.d.ts +7 -0
  102. package/dist/lifecycle.d.ts.map +1 -0
  103. package/dist/lifecycle.js +282 -0
  104. package/dist/listing.d.ts +52 -0
  105. package/dist/listing.d.ts.map +1 -0
  106. package/dist/listing.js +152 -0
  107. package/dist/live.d.ts +8 -6
  108. package/dist/live.d.ts.map +1 -1
  109. package/dist/live.js +110 -31
  110. package/dist/loaded.d.ts +5 -0
  111. package/dist/loaded.d.ts.map +1 -0
  112. package/dist/loaded.js +40 -0
  113. package/dist/locks.d.ts +7 -0
  114. package/dist/locks.d.ts.map +1 -0
  115. package/dist/locks.js +32 -0
  116. package/dist/lost.d.ts +5 -0
  117. package/dist/lost.d.ts.map +1 -0
  118. package/dist/lost.js +49 -0
  119. package/dist/media.d.ts +6 -1
  120. package/dist/media.d.ts.map +1 -1
  121. package/dist/media.js +68 -17
  122. package/dist/memory.d.ts +11 -3
  123. package/dist/memory.d.ts.map +1 -1
  124. package/dist/memory.js +132 -33
  125. package/dist/messages.d.ts +33 -3
  126. package/dist/messages.d.ts.map +1 -1
  127. package/dist/messages.js +98 -27
  128. package/dist/model.d.ts +2 -10
  129. package/dist/model.d.ts.map +1 -1
  130. package/dist/model.js +0 -14
  131. package/dist/models.d.ts +10 -0
  132. package/dist/models.d.ts.map +1 -0
  133. package/dist/models.js +153 -0
  134. package/dist/observation.d.ts +27 -12
  135. package/dist/observation.d.ts.map +1 -1
  136. package/dist/observation.js +184 -85
  137. package/dist/opened.d.ts +1 -1
  138. package/dist/opened.d.ts.map +1 -1
  139. package/dist/opened.js +5 -5
  140. package/dist/opening.d.ts +4 -0
  141. package/dist/opening.d.ts.map +1 -0
  142. package/dist/opening.js +53 -0
  143. package/dist/outcome.d.ts +17 -2
  144. package/dist/outcome.d.ts.map +1 -1
  145. package/dist/outcome.js +35 -2
  146. package/dist/overlay.d.ts +7 -2
  147. package/dist/overlay.d.ts.map +1 -1
  148. package/dist/overlay.js +48 -24
  149. package/dist/page-metadata.d.ts +8 -0
  150. package/dist/page-metadata.d.ts.map +1 -0
  151. package/dist/page-metadata.js +19 -0
  152. package/dist/parts.d.ts +4 -0
  153. package/dist/parts.d.ts.map +1 -0
  154. package/dist/parts.js +13 -0
  155. package/dist/paths.d.ts +1 -0
  156. package/dist/paths.d.ts.map +1 -1
  157. package/dist/paths.js +22 -0
  158. package/dist/preparation.d.ts +41 -0
  159. package/dist/preparation.d.ts.map +1 -0
  160. package/dist/preparation.js +314 -0
  161. package/dist/presence.d.ts +58 -0
  162. package/dist/presence.d.ts.map +1 -0
  163. package/dist/presence.js +231 -0
  164. package/dist/queries.d.ts +16 -0
  165. package/dist/queries.d.ts.map +1 -0
  166. package/dist/queries.js +308 -0
  167. package/dist/query.d.ts +13 -213
  168. package/dist/query.d.ts.map +1 -1
  169. package/dist/query.js +3 -524
  170. package/dist/questions.d.ts +30 -0
  171. package/dist/questions.d.ts.map +1 -0
  172. package/dist/questions.js +116 -0
  173. package/dist/queue.d.ts +40 -0
  174. package/dist/queue.d.ts.map +1 -0
  175. package/dist/queue.js +480 -0
  176. package/dist/reach.d.ts +10 -10
  177. package/dist/reach.d.ts.map +1 -1
  178. package/dist/reach.js +235 -120
  179. package/dist/react.d.ts +17 -3
  180. package/dist/react.d.ts.map +1 -1
  181. package/dist/react.js +33 -1
  182. package/dist/read.d.ts +7 -0
  183. package/dist/read.d.ts.map +1 -0
  184. package/dist/read.js +17 -0
  185. package/dist/readiness.d.ts +9 -4
  186. package/dist/readiness.d.ts.map +1 -1
  187. package/dist/readiness.js +3 -5
  188. package/dist/reads.d.ts +39 -0
  189. package/dist/reads.d.ts.map +1 -0
  190. package/dist/reads.js +178 -0
  191. package/dist/recovery.d.ts +3 -1
  192. package/dist/recovery.d.ts.map +1 -1
  193. package/dist/recovery.js +38 -8
  194. package/dist/refresh.d.ts +1 -1
  195. package/dist/refresh.d.ts.map +1 -1
  196. package/dist/refresh.js +10 -22
  197. package/dist/refusal.d.ts +5 -0
  198. package/dist/refusal.d.ts.map +1 -0
  199. package/dist/refusal.js +55 -0
  200. package/dist/replica.d.ts +11 -16
  201. package/dist/replica.d.ts.map +1 -1
  202. package/dist/replica.js +66 -35
  203. package/dist/retried.d.ts +30 -0
  204. package/dist/retried.d.ts.map +1 -0
  205. package/dist/retried.js +72 -0
  206. package/dist/room-draft.d.ts +32 -9
  207. package/dist/room-draft.d.ts.map +1 -1
  208. package/dist/room-draft.js +273 -66
  209. package/dist/room-drafts.d.ts +24 -0
  210. package/dist/room-drafts.d.ts.map +1 -0
  211. package/dist/room-drafts.js +220 -0
  212. package/dist/room-list.d.ts +10 -0
  213. package/dist/room-list.d.ts.map +1 -0
  214. package/dist/room-list.js +45 -0
  215. package/dist/rows.d.ts +16 -23
  216. package/dist/rows.d.ts.map +1 -1
  217. package/dist/rows.js +49 -191
  218. package/dist/runtime.d.ts +34 -0
  219. package/dist/runtime.d.ts.map +1 -0
  220. package/dist/runtime.js +1 -0
  221. package/dist/save.d.ts +42 -86
  222. package/dist/save.d.ts.map +1 -1
  223. package/dist/save.js +332 -228
  224. package/dist/saves.d.ts +34 -0
  225. package/dist/saves.d.ts.map +1 -0
  226. package/dist/saves.js +437 -0
  227. package/dist/search.d.ts.map +1 -1
  228. package/dist/search.js +1 -1
  229. package/dist/seed.d.ts +11 -2
  230. package/dist/seed.d.ts.map +1 -1
  231. package/dist/seed.js +71 -1
  232. package/dist/shared.d.ts +1 -6
  233. package/dist/shared.d.ts.map +1 -1
  234. package/dist/shared.js +13 -95
  235. package/dist/shown.d.ts +33 -0
  236. package/dist/shown.d.ts.map +1 -0
  237. package/dist/shown.js +309 -0
  238. package/dist/socket/admission.d.ts +7 -0
  239. package/dist/socket/admission.d.ts.map +1 -0
  240. package/dist/socket/admission.js +39 -0
  241. package/dist/socket/answers.d.ts +6 -0
  242. package/dist/socket/answers.d.ts.map +1 -0
  243. package/dist/socket/answers.js +20 -0
  244. package/dist/socket/lanes.d.ts +28 -0
  245. package/dist/socket/lanes.d.ts.map +1 -0
  246. package/dist/socket/lanes.js +103 -0
  247. package/dist/socket/reads.d.ts +10 -0
  248. package/dist/socket/reads.d.ts.map +1 -0
  249. package/dist/socket/reads.js +31 -0
  250. package/dist/socket/session.d.ts +38 -0
  251. package/dist/socket/session.d.ts.map +1 -0
  252. package/dist/socket/session.js +23 -0
  253. package/dist/socket.d.ts +67 -0
  254. package/dist/socket.d.ts.map +1 -0
  255. package/dist/socket.js +164 -0
  256. package/dist/source.d.ts +59 -0
  257. package/dist/source.d.ts.map +1 -0
  258. package/dist/source.js +93 -0
  259. package/dist/status.d.ts +0 -2
  260. package/dist/status.d.ts.map +1 -1
  261. package/dist/status.js +0 -6
  262. package/dist/store/installs.d.ts +30 -0
  263. package/dist/store/installs.d.ts.map +1 -0
  264. package/dist/store/installs.js +40 -0
  265. package/dist/store/readiness.d.ts +11 -0
  266. package/dist/store/readiness.d.ts.map +1 -0
  267. package/dist/store/readiness.js +68 -0
  268. package/dist/store/records.d.ts +223 -0
  269. package/dist/store/records.d.ts.map +1 -0
  270. package/dist/store/records.js +465 -0
  271. package/dist/store/room-held.d.ts +8 -0
  272. package/dist/store/room-held.d.ts.map +1 -0
  273. package/dist/store/room-held.js +17 -0
  274. package/dist/store/rooms-shown.d.ts +8 -0
  275. package/dist/store/rooms-shown.d.ts.map +1 -0
  276. package/dist/store/rooms-shown.js +13 -0
  277. package/dist/store/space.d.ts +11 -0
  278. package/dist/store/space.d.ts.map +1 -0
  279. package/dist/store/space.js +41 -0
  280. package/dist/store/watchers.d.ts +17 -0
  281. package/dist/store/watchers.d.ts.map +1 -0
  282. package/dist/store/watchers.js +56 -0
  283. package/dist/store/writes.d.ts +24 -0
  284. package/dist/store/writes.d.ts.map +1 -0
  285. package/dist/store/writes.js +125 -0
  286. package/dist/store.d.ts +115 -152
  287. package/dist/store.d.ts.map +1 -1
  288. package/dist/store.js +171 -395
  289. package/dist/stored.d.ts +72 -67
  290. package/dist/stored.d.ts.map +1 -1
  291. package/dist/stored.js +317 -166
  292. package/dist/surface.d.ts +90 -0
  293. package/dist/surface.d.ts.map +1 -0
  294. package/dist/surface.js +225 -0
  295. package/dist/sync.d.ts +19 -0
  296. package/dist/sync.d.ts.map +1 -0
  297. package/dist/sync.js +536 -0
  298. package/dist/tables.d.ts +112 -89
  299. package/dist/tables.d.ts.map +1 -1
  300. package/dist/tables.js +197 -283
  301. package/dist/tallies.d.ts +18 -0
  302. package/dist/tallies.d.ts.map +1 -0
  303. package/dist/tallies.js +128 -0
  304. package/dist/validation.d.ts.map +1 -1
  305. package/dist/validation.js +9 -12
  306. package/dist/windows.d.ts +1 -12
  307. package/dist/windows.d.ts.map +1 -1
  308. package/dist/windows.js +2 -75
  309. package/dist/wire.d.ts +68 -7
  310. package/dist/wire.d.ts.map +1 -1
  311. package/dist/wire.js +23 -4
  312. package/package.json +3 -2
  313. package/dist/attempt.d.ts +0 -16
  314. package/dist/attempt.d.ts.map +0 -1
  315. package/dist/attempt.js +0 -70
  316. package/dist/fence.d.ts +0 -6
  317. package/dist/fence.d.ts.map +0 -1
  318. package/dist/fence.js +0 -10
  319. package/dist/job.d.ts +0 -14
  320. package/dist/job.d.ts.map +0 -1
  321. package/dist/job.js +0 -28
  322. package/dist/log.d.ts +0 -5
  323. package/dist/log.d.ts.map +0 -1
  324. package/dist/log.js +0 -14
  325. package/dist/phrase.d.ts +0 -3
  326. package/dist/phrase.d.ts.map +0 -1
  327. package/dist/phrase.js +0 -8
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';
3
- 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, covered, normalize } from './paths.js';
8
- import { sorting } from './shared.js';
1
+ import { apply, ARRAY_INDEX, ATTACHMENT, create, links, parse, pointerSegments, references, relative, relativize, retarget, roomReferences, serialize, target, } from '@agentshouse/mdmodel';
2
+ import { immutable, isPart, leaving, queuedFile, departure, } from './change.js';
3
+ import { collectionOf, definitionsAt, documentsOf, governing, textType, typedText, } from './collection.js';
4
+ import { changeBatchIdentity, roomDraftRevision } from './identity.js';
5
+ import { composed, overlaying } from './overlay.js';
6
+ import { ancestors } from './paths.js';
7
+ import { entriesIn, sorting } from './shared.js';
8
+ import { applyParts, changedParts, changesOf, moveTarget, partsBetween, 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,43 +46,13 @@ 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
- const parts = new Map();
56
- for (const change of changes) {
57
- if (change.kind === 'preamble')
58
- parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
59
- if (change.kind === 'section')
60
- parts.set(`section ${change.name}`, {
61
- kind: 'section',
62
- name: change.name,
63
- value: change.value,
64
- prior: document.sections[change.name] ?? '',
65
- });
66
- }
67
- if (parts.size === 0)
68
- return document;
69
- const applied = apply(document, [...parts.values()]);
70
- return applied.outcome === 'applied' ? applied.document : document;
71
- }
72
- function bodies(path, before, after) {
73
- const changes = [];
74
- if (after.preamble !== before.preamble) {
75
- changes.push({ kind: 'preamble', path, value: after.preamble });
76
- }
77
- for (const [name, value] of Object.entries(after.sections)) {
78
- if (before.sections[name] === value)
79
- continue;
80
- changes.push({ kind: 'section', path, name, value });
81
- }
82
- return changes;
83
- }
84
- function relinks(path, moved, before, moves) {
55
+ function relinks(path, moved, before, moves, definition) {
85
56
  const changes = [];
86
57
  for (const link of links(before, path)) {
87
58
  const destination = moves.get(link.target) ?? link.target;
@@ -94,28 +65,30 @@ function relinks(path, moved, before, moves) {
94
65
  }
95
66
  if (changes.length === 0)
96
67
  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
- }
68
+ const applied = apply(before, changes, definition);
69
+ if (applied.outcome === 'refuse')
70
+ return refusedBy(applied.refusal, path);
109
71
  const after = applied.document;
110
72
  if (after === before)
111
73
  return { kind: 'changes', changes: [] };
112
74
  if (before.type === undefined) {
113
75
  return { kind: 'changes', changes: [{ kind: 'replace', path, content: serialize(after) }] };
114
76
  }
115
- return { kind: 'changes', changes: bodies(path, before, after) };
77
+ return { kind: 'changes', changes: partsBetween(path, before, after) };
116
78
  }
117
79
  function partKey(change) {
118
- return change.kind === 'section' ? `section ${change.name}` : change.kind;
80
+ return change.kind === 'section' || change.kind === 'field'
81
+ ? `${change.kind} ${change.name}`
82
+ : change.kind;
83
+ }
84
+ const NAMES_A_SKIP_TESTS = 64;
85
+ const ENCODED = /[%&\\]/u;
86
+ function movedNames(moves) {
87
+ const names = new Set([...moves.keys()].map((path) => path.slice(path.lastIndexOf('/') + 1)));
88
+ return names.size > NAMES_A_SKIP_TESTS ? null : [...names];
89
+ }
90
+ function cannotLink(text, names) {
91
+ return names !== null && !ENCODED.test(text) && !names.some((name) => text.includes(name));
119
92
  }
120
93
  function* rewrites(changes, visible) {
121
94
  const moves = new Map();
@@ -142,17 +115,21 @@ function* rewrites(changes, visible) {
142
115
  }
143
116
  if (moves.size === 0)
144
117
  return { kind: 'changes', changes };
118
+ const names = movedNames(moves);
145
119
  const merged = [...changes];
146
120
  const added = [];
147
121
  for (const [path, document] of yield* documentsOf(visible.collection)) {
148
122
  if (settled.has(path))
149
123
  continue;
150
124
  const moved = moves.get(path) ?? path;
151
- const definition = definitionOf(governing(visible.collection.composition, path), document.type);
125
+ const definition = definitionsAt(visible.collection.composition, path)(document.type);
152
126
  const repointing = repointed(document, definition, path, moved, moves, settled);
153
127
  added.push(...repointing);
154
128
  const own = parts.get(path) ?? [];
155
- const relinked = relinks(path, moved, edited(document, own.map((index) => merged[index])), moves);
129
+ const edited = applyParts(document, own.map((index) => merged[index]), definition);
130
+ const relinked = moved === path && own.length === 0 && cannotLink(document.content, names)
131
+ ? { kind: 'changes', changes: [] }
132
+ : relinks(path, moved, edited.outcome === 'applied' ? edited.document : document, moves, definition);
156
133
  if (relinked.kind === 'refused')
157
134
  return relinked;
158
135
  const keyed = new Map();
@@ -165,7 +142,7 @@ function* rewrites(changes, visible) {
165
142
  if (change.kind === 'replace') {
166
143
  for (const index of own) {
167
144
  const kind = merged[index]?.kind;
168
- if (kind === 'section' || kind === 'preamble')
145
+ if (kind === 'field' || kind === 'section' || kind === 'preamble')
169
146
  merged[index] = null;
170
147
  }
171
148
  added.push(change);
@@ -221,25 +198,21 @@ function crossed(document, definition, path, to) {
221
198
  }
222
199
  return changes;
223
200
  }
224
- function rewritten(path, document, changes) {
201
+ function rewritten(path, document, changes, definition) {
225
202
  if (changes.length === 0)
226
203
  return [];
227
- const applied = apply(document, changes);
204
+ const applied = apply(document, changes, definition);
228
205
  if (applied.outcome !== 'applied')
229
206
  return [];
230
207
  const after = applied.document;
231
208
  if (document.type === undefined)
232
209
  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
- ];
210
+ return partsBetween(path, document, after);
238
211
  }
239
212
  function roomPathOf(handle, path) {
240
213
  return `/rooms/${handle}/${path}`;
241
214
  }
242
- export function* leavingRewritten(batch, visible) {
215
+ function* leavingRewritten(batch, visible) {
243
216
  const between = batch.between;
244
217
  if (between === undefined)
245
218
  return batch;
@@ -248,8 +221,8 @@ export function* leavingRewritten(batch, visible) {
248
221
  for (const [path, document] of yield* documentsOf(visible.collection)) {
249
222
  if (moved.has(path))
250
223
  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))));
224
+ const definition = definitionsAt(visible.collection.composition, path)(document.type);
225
+ derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named)), definition));
253
226
  yield ROW_WEIGHT + visible.collection.text[path].length;
254
227
  }
255
228
  if (derived.length === 0)
@@ -291,7 +264,7 @@ function* incoming(collection, removed, candidates) {
291
264
  for (const [path, document] of candidates === null
292
265
  ? yield* documentsOf(collection)
293
266
  : read(candidates)) {
294
- const definition = definitionOf(governing(collection.composition, path), document.type);
267
+ const definition = definitionsAt(collection.composition, path)(document.type);
295
268
  for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
296
269
  if (to.includes(ATTACHMENT))
297
270
  continue;
@@ -321,6 +294,8 @@ function* incoming(collection, removed, candidates) {
321
294
  function affected(change) {
322
295
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
323
296
  }
297
+ const WRITE = ['write'];
298
+ const MOVE_OUT = ['write', 'erase'];
324
299
  function guard(changes, input, removing) {
325
300
  const overridden = new Map();
326
301
  const existing = {
@@ -332,15 +307,9 @@ function guard(changes, input, removing) {
332
307
  const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
333
308
  for (const [index, change] of changes.entries()) {
334
309
  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);
310
+ const refused = input.rule(path, change.kind === 'remove' ? removing : WRITE);
311
+ if (refused !== null)
312
+ return refuse(refused.code, refused.path);
344
313
  }
345
314
  if (change.kind === 'create' || change.kind === 'arrival') {
346
315
  const taken = existing.has(change.path) ? change.path : occupied(change.path);
@@ -401,9 +370,9 @@ function guard(changes, input, removing) {
401
370
  return null;
402
371
  }
403
372
  function unrevised(input, path) {
404
- return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
373
+ return !input.local && !input.queued.has(path) && input.visible.revisions[path] === undefined;
405
374
  }
406
- export function queuedDocuments(queue) {
375
+ function queuedDocuments(queue) {
407
376
  const paths = new Set();
408
377
  for (const batch of queue) {
409
378
  for (const change of batch.changes) {
@@ -448,7 +417,7 @@ function* preconditionsOf(changes, visible, bases) {
448
417
  };
449
418
  for (const change of changes) {
450
419
  yield ROW_WEIGHT;
451
- if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
420
+ if (isPart(change)) {
452
421
  const based = bases.find((base) => sameTarget(base, change));
453
422
  if (based !== undefined) {
454
423
  add(based);
@@ -539,93 +508,101 @@ function* exceeding(limits, changes, effective, resulting) {
539
508
  function shellRelative(text, path, handle, definition) {
540
509
  if (!text.includes(`/rooms/${handle}/`))
541
510
  return null;
542
- const parsed = parse(text, path);
511
+ const parsed = parse(text, path, definition);
543
512
  if (!parsed.ok)
544
513
  return null;
545
514
  const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
546
515
  return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
547
516
  }
548
517
  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}/`))
518
+ if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
553
519
  return value;
554
- const held = apply(opened.document, [{ kind: 'field', name, value, prior: undefined }]);
520
+ const held = apply(create(definition.name, definition), [{ kind: 'field', name, value, prior: undefined }], definition);
555
521
  if (held.outcome !== 'applied')
556
522
  return value;
557
523
  const result = relativize(held.document, path, handle, definition);
558
524
  return result.outcome === 'applied' ? result.document.fields[name] : value;
559
525
  }
560
526
  function relativeChanges(input) {
527
+ const changes = [];
528
+ for (const change of input.changes) {
529
+ const next = relativeChange(change, input);
530
+ if ('refusal' in next)
531
+ return next;
532
+ changes.push(next.change);
533
+ }
534
+ return { kind: 'changes', changes };
535
+ }
536
+ function relativeChange(change, input) {
561
537
  const { handle, composition, visible } = input;
562
- const typed = (path) => (type) => definitionOf(governing(composition, path), type);
563
538
  const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
564
- return input.changes.map((change) => {
565
- const definition = typed(change.path);
566
- if (change.kind === 'field') {
567
- const type = visible.collection.document(change.path)?.type;
568
- const value = relativeField(change.path, change.name, change.value, handle, definition(type));
569
- return value === change.value ? change : { ...change, value };
570
- }
571
- if (change.kind === 'section' || change.kind === 'preamble') {
572
- const value = body(change.path, change.value);
573
- return value === change.value ? change : { ...change, value };
574
- }
575
- if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
576
- const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
577
- return content === change.content ? change : { ...change, content };
578
- }
579
- if (change.kind !== 'create')
580
- return change;
581
- const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
582
- const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
583
- name,
584
- relativeField(change.path, name, value, handle, definition(type)),
585
- ]));
586
- const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
587
- return { ...change, fields, sections };
588
- });
539
+ const definition = definitionsAt(composition, change.path);
540
+ if (change.kind === 'field') {
541
+ const type = visible.collection.document(change.path)?.type;
542
+ const value = relativeField(change.path, change.name, change.value, handle, definition(type));
543
+ return { change: value === change.value ? change : { ...change, value } };
544
+ }
545
+ if (change.kind === 'section' || change.kind === 'preamble') {
546
+ const value = body(change.path, change.value);
547
+ return { change: value === change.value ? change : { ...change, value } };
548
+ }
549
+ if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
550
+ const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
551
+ return { change: content === change.content ? change : { ...change, content } };
552
+ }
553
+ if (change.kind !== 'create')
554
+ return { change };
555
+ const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
556
+ const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
557
+ name,
558
+ relativeField(change.path, name, value, handle, definition(type)),
559
+ ]));
560
+ const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
561
+ const written = composed(change.path, fields, sections, composition);
562
+ if (written.outcome === 'refuse')
563
+ return refusedBy(written.refusal, change.path);
564
+ if (written.outcome === 'stale')
565
+ throw new Error(`the creation of ${change.path} is stale`);
566
+ if (Object.keys(sections).length === 0)
567
+ return { change: { ...change, fields, sections } };
568
+ return {
569
+ change: { kind: 'create', path: change.path, content: serialize(written.document) },
570
+ };
589
571
  }
590
572
  function unqueueable(file) {
591
- return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
592
- }
593
- function isPart(change) {
594
- return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
573
+ return refuse('unknown-revision', file);
595
574
  }
596
575
  function touched(change) {
597
576
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
598
577
  }
599
- function partsApplied(content, path, changes) {
600
- const parsed = parse(content, path);
601
- if (!parsed.ok)
602
- return null;
603
- const document = parsed.document;
604
- const parts = new Map();
578
+ function partsApplied(content, path, changes, composition) {
579
+ const applied = partsResult(content, path, changes, composition);
580
+ return applied?.outcome === 'applied' ? serialize(applied.document) : null;
581
+ }
582
+ function unapplied(changes, visible, composition) {
583
+ const parted = new Map();
605
584
  for (const change of changes) {
606
- if (change.kind === 'field')
607
- parts.set(`field ${change.name}`, {
608
- kind: 'field',
609
- name: change.name,
610
- value: change.value,
611
- prior: document.fields[change.name],
612
- });
613
- else if (change.kind === 'section')
614
- parts.set(`section ${change.name}`, {
615
- kind: 'section',
616
- name: change.name,
617
- value: change.value,
618
- prior: document.sections[change.name] ?? '',
619
- });
620
- else
621
- parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
585
+ if (isPart(change))
586
+ parted.set(change.path, [...(parted.get(change.path) ?? []), change]);
622
587
  }
623
- const applied = apply(document, [...parts.values()]);
624
- return applied.outcome === 'applied' ? serialize(applied.document) : null;
588
+ for (const [path, parts] of parted) {
589
+ const content = visible.documents[path];
590
+ if (content === undefined)
591
+ continue;
592
+ const applied = partsResult(content, path, parts, composition);
593
+ if (applied?.outcome === 'refuse')
594
+ return refusedBy(applied.refusal, path);
595
+ }
596
+ return null;
625
597
  }
626
- function settledParts(changes, deferred) {
598
+ function partsResult(content, path, changes, composition) {
599
+ const definitions = definitionsAt(composition, path);
600
+ const parsed = parse(content, path, definitions);
601
+ return parsed.ok ? applyParts(parsed.document, changes, definitions(parsed.document.type)) : null;
602
+ }
603
+ function settledParts(changes, deferred, composition) {
627
604
  for (const [held, parts] of deferred) {
628
- const content = partsApplied(held.content, held.path, parts);
605
+ const content = partsApplied(held.content, held.path, parts, composition);
629
606
  if (content === null)
630
607
  return held.path;
631
608
  changes[changes.indexOf(held)] = { ...held, content };
@@ -700,7 +677,9 @@ function* relinked(batch, move, before, after, appending) {
700
677
  for (const change of derived.changes) {
701
678
  if (change === move)
702
679
  continue;
703
- const unsettled = isPart(change) ? null : settledParts(changes, deferred);
680
+ const unsettled = isPart(change)
681
+ ? null
682
+ : settledParts(changes, deferred, after.collection.composition);
704
683
  if (unsettled !== null)
705
684
  return unqueueable(unsettled);
706
685
  const outcome = folded(changes, change, appending, deferred);
@@ -711,7 +690,7 @@ function* relinked(batch, move, before, after, appending) {
711
690
  if (outcome !== 'skipped')
712
691
  changed = true;
713
692
  }
714
- const unsettled = settledParts(changes, deferred);
693
+ const unsettled = settledParts(changes, deferred, after.collection.composition);
715
694
  if (unsettled !== null)
716
695
  return unqueueable(unsettled);
717
696
  if (!changed)
@@ -772,11 +751,10 @@ function withdrawnFile(batch, change, at) {
772
751
  ];
773
752
  return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
774
753
  }
775
- export function* requeuing(queue, changes, visible, changeable, state) {
754
+ function* requeuing(queue, changes, visible, changeable, state) {
776
755
  const next = [...queue];
777
756
  const origins = new Map();
778
757
  const withdrawn = new Map();
779
- const owners = new Set();
780
758
  const queued = new Set();
781
759
  const kept = [];
782
760
  const originOf = (batch) => origins.get(batch) ?? batch.id;
@@ -822,7 +800,6 @@ export function* requeuing(queue, changes, visible, changeable, state) {
822
800
  if (replaced !== null)
823
801
  origins.set(replaced, origin);
824
802
  }
825
- owners.add(originOf(owner));
826
803
  next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
827
804
  queued.add(change.path);
828
805
  }
@@ -832,62 +809,16 @@ export function* requeuing(queue, changes, visible, changeable, state) {
832
809
  changes: kept,
833
810
  queued,
834
811
  withdrawn,
835
- owners,
836
812
  };
837
813
  }
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) {
814
+ function* limiting(queue, batch, limits, state) {
881
815
  const at = queue.indexOf(batch);
882
816
  const rewritten = yield* providerRewritten(batch, yield* state(queue.slice(0, at)));
883
817
  const limited = yield* exceeding(limits, batch.changes, rewritten.changes, yield* state(queue.slice(0, at + 1)));
884
- return limited?.kind === 'refused' ? limited.refusal : null;
818
+ return limited?.refusal ?? null;
885
819
  }
886
- function typedText(content) {
887
- if (content === undefined)
888
- return false;
889
- const parsed = parse(content);
890
- return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
820
+ function typedContent(content) {
821
+ return content !== undefined && typedText(parse(content));
891
822
  }
892
823
  function touchedLibrary(resulting, touched, composition) {
893
824
  const entries = touched
@@ -898,7 +829,7 @@ function touchedLibrary(resulting, touched, composition) {
898
829
  })
899
830
  .sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
900
831
  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));
832
+ const binaries = new Set(Array.from(entriesIn(resulting.binaries), ([path]) => path));
902
833
  return validatedCollection(() => Promise.resolve({ entries, next: null }), async (paths) => {
903
834
  const known = new Map();
904
835
  const pace = paced();
@@ -907,9 +838,9 @@ function touchedLibrary(resulting, touched, composition) {
907
838
  if (binaries.has(path))
908
839
  known.set(path, { type: undefined, binary: true });
909
840
  else if (content !== undefined && content !== null) {
910
- const read = parse(content, path);
911
- if (read.ok)
912
- known.set(path, { type: read.document.type ?? 'any', binary: false });
841
+ const type = textType(parse(content, path, definitionsAt(composition, path)));
842
+ if (type !== undefined)
843
+ known.set(path, { type, binary: false });
913
844
  await pace(content.length + ROW_WEIGHT);
914
845
  }
915
846
  }
@@ -927,7 +858,7 @@ async function judging(visible, resulting, composition, validate, removed, touch
927
858
  for (const path of touched) {
928
859
  const before = visible.documents[path];
929
860
  const after = resulting.documents[path];
930
- if (typedText(before) || typedText(after))
861
+ if (typedContent(before) || typedContent(after))
931
862
  typedPaths.push(path);
932
863
  await pace((before?.length ?? 0) + (after?.length ?? 0) + ROW_WEIGHT);
933
864
  }
@@ -945,6 +876,7 @@ async function judging(visible, resulting, composition, validate, removed, touch
945
876
  retryable: false,
946
877
  file: verdict.file,
947
878
  ...(verdict.location === undefined ? {} : { location: verdict.location }),
879
+ ...(verdict.line === undefined ? {} : { line: verdict.line }),
948
880
  ...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
949
881
  },
950
882
  };
@@ -960,8 +892,6 @@ function placed(reference) {
960
892
  const key = roomPathOf(reference.handle, reference.path);
961
893
  if (reference.kind === 'link')
962
894
  return `link ${key}`;
963
- if (reference.kind === 'item')
964
- return `section ${reference.section} ${key}`;
965
895
  const pointer = reference.pointer
966
896
  .split('/')
967
897
  .map((segment) => (ARRAY_INDEX.test(segment) ? '-' : segment))
@@ -971,10 +901,11 @@ function placed(reference) {
971
901
  function roomReferencesIn(path, content, composition) {
972
902
  if (content === undefined)
973
903
  return [];
974
- const parsed = parse(content, path);
904
+ const definitions = definitionsAt(composition, path);
905
+ const parsed = parse(content, path, definitions);
975
906
  if (!parsed.ok)
976
907
  return [];
977
- return roomReferences(path, content, definitionOf(governing(composition, path), parsed.document.type));
908
+ return roomReferences(parsed.document, definitions(parsed.document.type));
978
909
  }
979
910
  function* admittedRooms(changes, visible, resulting, composition) {
980
911
  const from = new Map();
@@ -992,7 +923,7 @@ function* admittedRooms(changes, visible, resulting, composition) {
992
923
  continue;
993
924
  if (held.has(placed(reference)))
994
925
  continue;
995
- return refuse('unresolved-reference', path, reference.kind === 'field' ? reference.pointer : reference.section);
926
+ return refuse('unresolved-reference', path, reference.pointer);
996
927
  }
997
928
  yield ROW_WEIGHT + now.length;
998
929
  }
@@ -1010,7 +941,10 @@ function validationOf(validated) {
1010
941
  };
1011
942
  }
1012
943
  async function batching(input) {
1013
- const changes = relativeChanges(input);
944
+ const relative = relativeChanges(input);
945
+ if (relative.kind === 'refused')
946
+ return relative;
947
+ const { changes } = relative;
1014
948
  const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
1015
949
  if (structural !== undefined && input.unmodeled) {
1016
950
  return refuse('model-unavailable', structural.path);
@@ -1020,12 +954,12 @@ async function batching(input) {
1020
954
  const rewritten = await stepped(rewrites(changes, input.visible));
1021
955
  if (rewritten.kind === 'refused')
1022
956
  return rewritten;
1023
- const refusal = guard(changes, input, 'delete');
957
+ const refusal = guard(changes, input, WRITE) ?? unapplied(changes, input.visible, input.composition);
1024
958
  if (refusal !== null)
1025
959
  return refusal;
1026
960
  const preconditions = await stepped(preconditionsOf(changes, input.visible, input.bases));
1027
961
  const predicted = await stepped(anticipated(changes, preconditions, rewritten.changes, input.visible));
1028
- const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }]));
962
+ const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }], input.composition));
1029
963
  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
964
  if (judged.kind === 'refused')
1031
965
  return judged;
@@ -1047,7 +981,7 @@ async function batching(input) {
1047
981
  }),
1048
982
  };
1049
983
  }
1050
- export const MOVE_DOCUMENTS = 'move-documents';
984
+ const MOVE_DOCUMENTS = 'move-documents';
1051
985
  function heldAt(visible, path) {
1052
986
  return visible.documents[path] !== undefined || visible.binaries[path] !== undefined;
1053
987
  }
@@ -1056,7 +990,7 @@ function arrivingText(path, to, source, moved) {
1056
990
  const document = source.visible.collection.document(path);
1057
991
  if (document === undefined)
1058
992
  return content;
1059
- const definition = definitionOf(governing(source.composition, path), document.type);
993
+ const definition = definitionsAt(source.composition, path)(document.type);
1060
994
  const changes = crossed(document, definition, path, (named, href) => {
1061
995
  const at = moved.get(named);
1062
996
  if (at !== undefined)
@@ -1065,13 +999,14 @@ function arrivingText(path, to, source, moved) {
1065
999
  });
1066
1000
  if (changes.length === 0)
1067
1001
  return content;
1068
- const applied = apply(document, changes);
1002
+ const applied = apply(document, changes, definition);
1069
1003
  return applied.outcome === 'applied' ? serialize(applied.document) : content;
1070
1004
  }
1071
1005
  function arrivalOf({ path, to }, source, moved) {
1072
- const { documents, revisions, fileIds, changes, binaries } = source.visible;
1006
+ const { documents, revisions, fileIds, created, changes, binaries } = source.visible;
1073
1007
  const kept = {
1074
1008
  ...(fileIds[path] === undefined ? {} : { fileId: fileIds[path] }),
1009
+ ...(created[path] === undefined ? {} : { created: created[path] }),
1075
1010
  ...(changes[path] === undefined ? {} : { change: changes[path] }),
1076
1011
  };
1077
1012
  const heads = {
@@ -1098,10 +1033,8 @@ function arrivalOf({ path, to }, source, moved) {
1098
1033
  }
1099
1034
  async function moving(input) {
1100
1035
  const { source, destination, moves } = input;
1101
- if (moves.length === 0)
1102
- return refuse('no-change');
1103
1036
  const removals = moves.map(({ path }) => ({ kind: 'remove', path }));
1104
- const leaves = guard(removals, { ...source, local: input.local, bases: [] }, 'write');
1037
+ const leaves = guard(removals, { ...source, local: input.local, bases: [] }, MOVE_OUT);
1105
1038
  if (leaves !== null)
1106
1039
  return leaves;
1107
1040
  const uploading = moves.find(({ path }) => source.visible.binaries[path]?.state === 'pending');
@@ -1109,7 +1042,7 @@ async function moving(input) {
1109
1042
  return refuse('unknown-revision', uploading.path);
1110
1043
  const moved = new Map(moves.map(({ path, to }) => [path, to]));
1111
1044
  const arrivals = moves.map((move) => arrivalOf(move, source, moved));
1112
- const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, 'write');
1045
+ const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, WRITE);
1113
1046
  if (arrives !== null)
1114
1047
  return arrives;
1115
1048
  const bound = input.providerLimits.find((limit) => limit.name === MOVE_DOCUMENTS)?.value;
@@ -1138,7 +1071,7 @@ async function moving(input) {
1138
1071
  between,
1139
1072
  };
1140
1073
  const effective = await stepped(leavingRewritten(leavingBatch, source.visible));
1141
- const left = await stepped(overlaying(source.visible, [effective]));
1074
+ const left = await stepped(overlaying(source.visible, [effective], source.composition));
1142
1075
  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
1076
  if (sourceJudged.kind === 'refused')
1144
1077
  return sourceJudged;
@@ -1150,7 +1083,7 @@ async function moving(input) {
1150
1083
  ...written,
1151
1084
  between,
1152
1085
  };
1153
- const arrived = await stepped(overlaying(destination.visible, [arrivingBatch]));
1086
+ const arrived = await stepped(overlaying(destination.visible, [arrivingBatch], destination.composition));
1154
1087
  const destinationJudged = await judging(destination.visible, arrived, destination.composition, destination.validate, new Set(), moves.map(({ to }) => to));
1155
1088
  if (destinationJudged.kind === 'refused')
1156
1089
  return destinationJudged;
@@ -1160,9 +1093,180 @@ async function moving(input) {
1160
1093
  return limited;
1161
1094
  return { kind: 'moved', leaving: immutable(leavingBatch), arriving: immutable(arrivingBatch) };
1162
1095
  }
1163
- export function buildMove(input) {
1164
- return moving(input);
1096
+ function unreadable(saved, visible, composition) {
1097
+ const path = saved.find(({ path: at, parts }) => (parts.fields !== undefined ||
1098
+ parts.sections !== undefined ||
1099
+ parts.preamble !== undefined) &&
1100
+ typedContent(visible.documents[at]) &&
1101
+ governing(composition, at).status === 'unavailable')?.path;
1102
+ return path === undefined ? null : refuse('model-unavailable', path);
1103
+ }
1104
+ function changedUnderneath(saved, visible) {
1105
+ const parts = saved.flatMap(({ path, parts: held }) => changedParts(path, held, visible));
1106
+ return parts.length === 0
1107
+ ? null
1108
+ : { kind: 'refused', refusal: { code: 'changed-underneath', retryable: false, parts } };
1109
+ }
1110
+ function sideOf(room, visible) {
1111
+ const { record } = room;
1112
+ return {
1113
+ room: record.locator,
1114
+ handle: record.declaration.handle,
1115
+ baseRoomVersion: record.roomVersion,
1116
+ composition: room.composition,
1117
+ rule: room.rule,
1118
+ visible,
1119
+ folders: room.folders,
1120
+ queued: queuedDocuments(room.queue),
1121
+ unmodeled: !room.modelled,
1122
+ validate: room.validate,
1123
+ };
1124
+ }
1125
+ function carriedDrafts(ask, from, into) {
1126
+ return new Map(ask.saved.flatMap(({ path, parts }) => {
1127
+ const record = from.drafts.get(path);
1128
+ if (parts.move === undefined || record === undefined || ask.consumed.has(path))
1129
+ return [];
1130
+ const to = moveTarget(parts.move);
1131
+ if (into.drafts.has(to))
1132
+ return [];
1133
+ const locator = into.record.locator;
1134
+ return [[path, { ...record, locator, path: to, revision: roomDraftRevision() }]];
1135
+ }));
1136
+ }
1137
+ async function plannedMove(ask, source, into) {
1138
+ const destination = into.record.locator;
1139
+ const mixed = ask.saved.find(({ parts }) => Object.keys(parts).length !== 1 || parts.move?.room !== destination);
1140
+ if (mixed !== undefined)
1141
+ return refuse('mixed-room-move', mixed.path);
1142
+ const leavingVisible = await stepped(source.replay(source.queue));
1143
+ const changed = changedUnderneath(ask.saved, leavingVisible);
1144
+ if (changed !== null)
1145
+ return changed;
1146
+ const moves = ask.saved.map(({ path, parts }) => ({ path, to: moveTarget(parts.move) }));
1147
+ const built = await moving({
1148
+ source: sideOf(source, leavingVisible),
1149
+ destination: sideOf(into, await stepped(into.replay(into.queue))),
1150
+ moves,
1151
+ providerLimits: ask.providerLimits,
1152
+ local: ask.local,
1153
+ ...(ask.writtenBy === undefined ? {} : { writtenBy: ask.writtenBy }),
1154
+ });
1155
+ if (built.kind === 'refused')
1156
+ return built;
1157
+ const { id } = built.leaving;
1158
+ const carried = carriedDrafts(ask, source, into);
1159
+ return {
1160
+ kind: 'planned',
1161
+ identifier: id,
1162
+ rooms: [
1163
+ {
1164
+ locator: source.record.locator,
1165
+ queue: [...source.queue, built.leaving],
1166
+ saved: new Map([[id, ask.saved]]),
1167
+ consumed: [...ask.consumed, ...carried.keys()],
1168
+ carried: [],
1169
+ },
1170
+ {
1171
+ locator: destination,
1172
+ queue: [...into.queue, built.arriving],
1173
+ saved: new Map([
1174
+ [id, [...carried.values()].map(({ path }) => ({ path, parts: {} }))],
1175
+ ]),
1176
+ consumed: [],
1177
+ carried: [...carried.values()],
1178
+ },
1179
+ ],
1180
+ };
1181
+ }
1182
+ async function plannedWithin(ask, room) {
1183
+ const { record } = room;
1184
+ const visible = await stepped(room.replay(room.queue));
1185
+ const changed = unreadable(ask.saved, visible, room.composition) ?? changedUnderneath(ask.saved, visible);
1186
+ if (changed !== null)
1187
+ return changed;
1188
+ const parts = ask.saved.map(({ path, parts: held }) => changesOf(path, held));
1189
+ const changes = parts.flatMap(({ changes: made }) => made);
1190
+ const requeue = await stepped(requeuing(room.queue, changes, visible, (batch) => room.changeable.has(batch.id), room.replay));
1191
+ if (requeue.kind === 'refused')
1192
+ return requeue;
1193
+ const foreign = [...requeue.withdrawn.keys()].some((origin) => {
1194
+ const writer = room.queue.find((batch) => batch.id === origin)?.writtenBy;
1195
+ return writer?.app !== ask.writtenBy?.app || writer?.release !== ask.writtenBy?.release;
1196
+ });
1197
+ if (foreign)
1198
+ return unqueueable([...requeue.queued].at(-1));
1199
+ const input = {
1200
+ local: ask.local,
1201
+ baseRoomVersion: record.roomVersion,
1202
+ composition: room.composition,
1203
+ rule: room.rule,
1204
+ providerLimits: ask.providerLimits,
1205
+ handle: record.declaration.handle,
1206
+ visible,
1207
+ folders: room.folders,
1208
+ changes,
1209
+ bases: parts.flatMap(({ bases }) => bases),
1210
+ queued: queuedDocuments(room.queue),
1211
+ writtenBy: ask.writtenBy,
1212
+ unmodeled: !room.modelled,
1213
+ validate: room.validate,
1214
+ };
1215
+ const rewritten = requeue.withdrawn.size > 0;
1216
+ if (rewritten) {
1217
+ const whole = await batching({
1218
+ ...input,
1219
+ providerLimits: [],
1220
+ queued: new Set([...input.queued, ...requeue.queued]),
1221
+ });
1222
+ if (whole.kind === 'refused')
1223
+ return whole;
1224
+ }
1225
+ const own = !rewritten
1226
+ ? await batching(input)
1227
+ : requeue.changes.length === 0
1228
+ ? null
1229
+ : await batching({
1230
+ ...input,
1231
+ visible: await stepped(room.replay(requeue.queue)),
1232
+ changes: requeue.changes,
1233
+ queued: queuedDocuments(requeue.queue),
1234
+ });
1235
+ if (own?.kind === 'refused')
1236
+ return own;
1237
+ const queue = own === null ? requeue.queue : [...requeue.queue, own.batch];
1238
+ const saved = new Map();
1239
+ for (const [origin, rebuilt] of requeue.withdrawn) {
1240
+ if (rebuilt === null)
1241
+ continue;
1242
+ const limited = await stepped(limiting(queue, rebuilt, ask.providerLimits, room.replay));
1243
+ if (limited !== null)
1244
+ return { kind: 'refused', refusal: limited };
1245
+ saved.set(rebuilt.id, withdrawnDrafts(room.saved.get(origin) ?? [], changes));
1246
+ }
1247
+ if (own !== null) {
1248
+ saved.set(own.batch.id, ask.saved.filter(({ path }) => !requeue.queued.has(path)));
1249
+ }
1250
+ const carried = carriedDrafts(ask, room, room);
1251
+ return {
1252
+ kind: 'planned',
1253
+ identifier: own?.batch.id ??
1254
+ [...requeue.withdrawn].map(([origin, rebuilt]) => rebuilt?.id ?? origin).at(-1),
1255
+ rooms: [
1256
+ {
1257
+ locator: record.locator,
1258
+ queue,
1259
+ saved,
1260
+ consumed: [...ask.consumed, ...carried.keys()],
1261
+ carried: [...carried.values()],
1262
+ },
1263
+ ],
1264
+ };
1265
+ }
1266
+ export function destination(locator, saved) {
1267
+ const into = saved.find(({ parts }) => parts.move?.room !== undefined && parts.move.room !== locator);
1268
+ return into?.parts.move?.room ?? null;
1165
1269
  }
1166
- export function buildBatch(input) {
1167
- return batching(input);
1270
+ export function planSave(ask, room, into) {
1271
+ return into === undefined ? plannedWithin(ask, room) : plannedMove(ask, room, into);
1168
1272
  }