@agentshouse/mdruntime 0.1.0-c069136dcb4e8 → 0.1.0-c076b00ebf42a

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