@agentshouse/mdruntime 0.1.0-alpha.9 → 0.1.0-c0105dbd22806

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 (214) hide show
  1. package/AGENTS.md +299 -0
  2. package/README.md +54 -0
  3. package/dist/adapter.d.ts +246 -40
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/adapter.js +1 -10
  6. package/dist/attached-model.d.ts +11 -0
  7. package/dist/attached-model.d.ts.map +1 -0
  8. package/dist/attached-model.js +47 -0
  9. package/dist/attempt.d.ts +16 -0
  10. package/dist/attempt.d.ts.map +1 -0
  11. package/dist/attempt.js +70 -0
  12. package/dist/binary.d.ts +23 -0
  13. package/dist/binary.d.ts.map +1 -0
  14. package/dist/binary.js +8 -0
  15. package/dist/bootstrap.d.ts +17 -7
  16. package/dist/bootstrap.d.ts.map +1 -1
  17. package/dist/bootstrap.js +97 -41
  18. package/dist/change-set.d.ts +33 -0
  19. package/dist/change-set.d.ts.map +1 -0
  20. package/dist/change-set.js +90 -0
  21. package/dist/change.d.ts +24 -0
  22. package/dist/change.d.ts.map +1 -1
  23. package/dist/change.js +31 -5
  24. package/dist/chat/acts.d.ts +34 -0
  25. package/dist/chat/acts.d.ts.map +1 -0
  26. package/dist/chat/acts.js +36 -0
  27. package/dist/chat/context.d.ts +3 -0
  28. package/dist/chat/context.d.ts.map +1 -0
  29. package/dist/chat/context.js +10 -0
  30. package/dist/chat/input.d.ts +9 -0
  31. package/dist/chat/input.d.ts.map +1 -0
  32. package/dist/chat/input.js +99 -0
  33. package/dist/chat/kit.d.ts +60 -0
  34. package/dist/chat/kit.d.ts.map +1 -0
  35. package/dist/chat/kit.js +123 -0
  36. package/dist/chat/lasting.d.ts +6 -0
  37. package/dist/chat/lasting.d.ts.map +1 -0
  38. package/dist/chat/lasting.js +19 -0
  39. package/dist/chat/list.d.ts +26 -0
  40. package/dist/chat/list.d.ts.map +1 -0
  41. package/dist/chat/list.js +62 -0
  42. package/dist/chat/markdown.d.ts +20 -0
  43. package/dist/chat/markdown.d.ts.map +1 -0
  44. package/dist/chat/markdown.js +188 -0
  45. package/dist/chat/message.d.ts +6 -0
  46. package/dist/chat/message.d.ts.map +1 -0
  47. package/dist/chat/message.js +192 -0
  48. package/dist/chat/messages.d.ts +21 -0
  49. package/dist/chat/messages.d.ts.map +1 -0
  50. package/dist/chat/messages.js +151 -0
  51. package/dist/chat/name.d.ts +6 -0
  52. package/dist/chat/name.d.ts.map +1 -0
  53. package/dist/chat/name.js +37 -0
  54. package/dist/chat/opened.d.ts +11 -0
  55. package/dist/chat/opened.d.ts.map +1 -0
  56. package/dist/chat/opened.js +17 -0
  57. package/dist/chat/overlay.d.ts +31 -0
  58. package/dist/chat/overlay.d.ts.map +1 -0
  59. package/dist/chat/overlay.js +97 -0
  60. package/dist/chat/parts.d.ts +57 -0
  61. package/dist/chat/parts.d.ts.map +1 -0
  62. package/dist/chat/parts.js +119 -0
  63. package/dist/chat/process.d.ts +20 -0
  64. package/dist/chat/process.d.ts.map +1 -0
  65. package/dist/chat/process.js +32 -0
  66. package/dist/chat/provider.d.ts +63 -0
  67. package/dist/chat/provider.d.ts.map +1 -0
  68. package/dist/chat/provider.js +645 -0
  69. package/dist/chat/question.d.ts +9 -0
  70. package/dist/chat/question.d.ts.map +1 -0
  71. package/dist/chat/question.js +80 -0
  72. package/dist/chat/questions.d.ts +31 -0
  73. package/dist/chat/questions.d.ts.map +1 -0
  74. package/dist/chat/questions.js +111 -0
  75. package/dist/chat/read.d.ts +5 -0
  76. package/dist/chat/read.d.ts.map +1 -0
  77. package/dist/chat/read.js +13 -0
  78. package/dist/chat/ready.d.ts +33 -0
  79. package/dist/chat/ready.d.ts.map +1 -0
  80. package/dist/chat/ready.js +91 -0
  81. package/dist/chat/style.d.ts +4 -0
  82. package/dist/chat/style.d.ts.map +1 -0
  83. package/dist/chat/style.js +302 -0
  84. package/dist/chat.d.ts +14 -0
  85. package/dist/chat.d.ts.map +1 -0
  86. package/dist/chat.js +8 -0
  87. package/dist/client.d.ts +49 -33
  88. package/dist/client.d.ts.map +1 -1
  89. package/dist/client.js +164 -183
  90. package/dist/collection.d.ts +9 -17
  91. package/dist/collection.d.ts.map +1 -1
  92. package/dist/collection.js +83 -89
  93. package/dist/connection.d.ts +66 -48
  94. package/dist/connection.d.ts.map +1 -1
  95. package/dist/connection.js +1964 -1173
  96. package/dist/conversation.d.ts +65 -24
  97. package/dist/conversation.d.ts.map +1 -1
  98. package/dist/conversation.js +675 -334
  99. package/dist/durability.js +6 -6
  100. package/dist/entry.d.ts +17 -0
  101. package/dist/entry.d.ts.map +1 -0
  102. package/dist/entry.js +156 -0
  103. package/dist/expectation.d.ts +21 -0
  104. package/dist/expectation.d.ts.map +1 -0
  105. package/dist/expectation.js +70 -0
  106. package/dist/failure.d.ts +13 -0
  107. package/dist/failure.d.ts.map +1 -0
  108. package/dist/failure.js +19 -0
  109. package/dist/fence.d.ts +6 -0
  110. package/dist/fence.d.ts.map +1 -0
  111. package/dist/fence.js +10 -0
  112. package/dist/file.d.ts +2 -2
  113. package/dist/file.d.ts.map +1 -1
  114. package/dist/file.js +48 -71
  115. package/dist/host.d.ts +15 -3
  116. package/dist/host.d.ts.map +1 -1
  117. package/dist/host.js +109 -127
  118. package/dist/identity.d.ts +4 -0
  119. package/dist/identity.d.ts.map +1 -1
  120. package/dist/identity.js +22 -9
  121. package/dist/index.d.ts +17 -11
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +4 -3
  124. package/dist/log.js +5 -6
  125. package/dist/memory.d.ts +19 -8
  126. package/dist/memory.d.ts.map +1 -1
  127. package/dist/memory.js +304 -216
  128. package/dist/model.d.ts +16 -30
  129. package/dist/model.d.ts.map +1 -1
  130. package/dist/model.js +21 -21
  131. package/dist/observation.d.ts +27 -0
  132. package/dist/observation.d.ts.map +1 -0
  133. package/dist/observation.js +95 -0
  134. package/dist/open.d.ts +2 -0
  135. package/dist/open.d.ts.map +1 -0
  136. package/dist/open.js +1 -0
  137. package/dist/outcome.d.ts +37 -31
  138. package/dist/outcome.d.ts.map +1 -1
  139. package/dist/outcome.js +36 -18
  140. package/dist/overlay.d.ts +22 -4
  141. package/dist/overlay.d.ts.map +1 -1
  142. package/dist/overlay.js +236 -54
  143. package/dist/paths.d.ts +0 -2
  144. package/dist/paths.d.ts.map +1 -1
  145. package/dist/paths.js +6 -29
  146. package/dist/query.d.ts +219 -10
  147. package/dist/query.d.ts.map +1 -1
  148. package/dist/query.js +462 -15
  149. package/dist/reach.d.ts +42 -0
  150. package/dist/reach.d.ts.map +1 -0
  151. package/dist/reach.js +294 -0
  152. package/dist/react.d.ts +41 -0
  153. package/dist/react.d.ts.map +1 -0
  154. package/dist/react.js +64 -0
  155. package/dist/read-side.d.ts +18 -0
  156. package/dist/read-side.d.ts.map +1 -0
  157. package/dist/read-side.js +440 -0
  158. package/dist/refresh.d.ts +15 -5
  159. package/dist/refresh.d.ts.map +1 -1
  160. package/dist/refresh.js +64 -41
  161. package/dist/replica.d.ts +28 -5
  162. package/dist/replica.d.ts.map +1 -1
  163. package/dist/replica.js +249 -40
  164. package/dist/room-draft.d.ts +144 -0
  165. package/dist/room-draft.d.ts.map +1 -0
  166. package/dist/room-draft.js +438 -0
  167. package/dist/rows.d.ts +46 -0
  168. package/dist/rows.d.ts.map +1 -0
  169. package/dist/rows.js +145 -0
  170. package/dist/save.d.ts +33 -5
  171. package/dist/save.d.ts.map +1 -1
  172. package/dist/save.js +630 -366
  173. package/dist/search.d.ts +62 -0
  174. package/dist/search.d.ts.map +1 -0
  175. package/dist/search.js +125 -0
  176. package/dist/status.d.ts +6 -8
  177. package/dist/status.d.ts.map +1 -1
  178. package/dist/status.js +8 -20
  179. package/dist/store.d.ts +89 -34
  180. package/dist/store.d.ts.map +1 -1
  181. package/dist/store.js +513 -132
  182. package/dist/transfer.d.ts +5 -0
  183. package/dist/transfer.d.ts.map +1 -1
  184. package/dist/transfer.js +1 -5
  185. package/dist/wire.d.ts +59 -19
  186. package/dist/wire.d.ts.map +1 -1
  187. package/dist/wire.js +88 -105
  188. package/package.json +34 -16
  189. package/dist/adapter.js.map +0 -1
  190. package/dist/bootstrap.js.map +0 -1
  191. package/dist/change.js.map +0 -1
  192. package/dist/client.js.map +0 -1
  193. package/dist/collection.js.map +0 -1
  194. package/dist/connection.js.map +0 -1
  195. package/dist/conversation.js.map +0 -1
  196. package/dist/durability.js.map +0 -1
  197. package/dist/file.js.map +0 -1
  198. package/dist/host.js.map +0 -1
  199. package/dist/identity.js.map +0 -1
  200. package/dist/index.js.map +0 -1
  201. package/dist/log.js.map +0 -1
  202. package/dist/memory.js.map +0 -1
  203. package/dist/model.js.map +0 -1
  204. package/dist/outcome.js.map +0 -1
  205. package/dist/overlay.js.map +0 -1
  206. package/dist/paths.js.map +0 -1
  207. package/dist/query.js.map +0 -1
  208. package/dist/refresh.js.map +0 -1
  209. package/dist/replica.js.map +0 -1
  210. package/dist/save.js.map +0 -1
  211. package/dist/status.js.map +0 -1
  212. package/dist/store.js.map +0 -1
  213. package/dist/transfer.js.map +0 -1
  214. package/dist/wire.js.map +0 -1
package/dist/save.js CHANGED
@@ -1,521 +1,784 @@
1
- import { apply, links, parse, serialize } from '@agentshouse/mdmodel';
2
- import { immutable } from './change.js';
3
- import { governing, parseCollection, typesAt, validateCollection } from './collection.js';
1
+ import { definitionOf } from './model.js';
2
+ import { apply, ATTACHMENT, links, parse, pointerSegments, references, relative, relativize, retarget, serialize, target, } from '@agentshouse/mdmodel';
3
+ import { immutable, queuedFile, departure, } from './change.js';
4
+ import { collectionOf, documentsOf, governing, validateCollection, } from './collection.js';
4
5
  import { changeBatchIdentity } from './identity.js';
5
6
  import { overlay } from './overlay.js';
6
- import { beneath, covered, normalize, relative, resolve } from './paths.js';
7
- function refuse(rule, file, location) {
7
+ import { covered, normalize } from './paths.js';
8
+ function refuse(code, file, location) {
8
9
  return {
9
10
  kind: 'refused',
10
11
  refusal: {
11
- rule,
12
- ...file === undefined ? {} : {
13
- file
14
- },
15
- ...location === undefined ? {} : {
16
- location
17
- }
18
- }
12
+ code,
13
+ retryable: false,
14
+ ...(file === undefined ? {} : { file }),
15
+ ...(location === undefined ? {} : { location }),
16
+ },
19
17
  };
20
18
  }
21
- function members(visible, prefix) {
22
- return [
23
- ...Object.keys(visible.documents),
24
- ...visible.binaryTargets
25
- ].filter((path)=>beneath(prefix, path)).sort();
26
- }
27
- function spread(change, path, member) {
28
- if (change.kind === 'remove') return {
29
- kind: 'remove',
30
- path: member
31
- };
32
- return {
33
- kind: 'move',
34
- path: member,
35
- to: `${change.to}${member.slice(path.length)}`
36
- };
37
- }
38
- function expand(changes, visible) {
39
- const expanded = [];
40
- for (const change of changes){
41
- if (change.kind !== 'move' && change.kind !== 'remove') {
42
- expanded.push(change);
19
+ function repointed(document, definition, from, to, moves, settled) {
20
+ const parsed = parse(document.content, from);
21
+ if (!parsed.ok)
22
+ return [];
23
+ let next = parsed.document;
24
+ const names = new Set();
25
+ for (const { pointer, value } of definition === undefined
26
+ ? []
27
+ : references(document, definition)) {
28
+ const [name = ''] = pointerSegments(pointer);
29
+ const named = target(from, value);
30
+ if (named === null || settled.has(`${from} ${name}`))
43
31
  continue;
44
- }
45
- const path = normalize(change.path);
46
- const to = change.kind === 'move' ? normalize(change.to) : '';
47
- if (path === null || path === '' || to === null || change.kind === 'move' && to === '') {
48
- return {
49
- kind: 'refused',
50
- refusal: {
51
- rule: 'room-root',
52
- file: change.path
53
- }
54
- };
55
- }
56
- const structural = change.kind === 'move' ? {
57
- ...change,
58
- path,
59
- to
60
- } : {
61
- ...change,
62
- path
63
- };
64
- const present = visible.documents[path] !== undefined || visible.binaryTargets.includes(path);
65
- if (present) {
66
- expanded.push(structural);
32
+ const destination = moves.get(named) ?? named;
33
+ if (target(to, value) === destination)
67
34
  continue;
68
- }
69
- const beneathIt = members(visible, path);
70
- if (beneathIt.length === 0) {
71
- return {
72
- kind: 'refused',
73
- refusal: {
74
- rule: 'path-absent',
75
- file: change.path
76
- }
77
- };
78
- }
79
- for (const member of beneathIt)expanded.push(spread(structural, path, member));
80
- }
81
- return {
82
- kind: 'changes',
83
- changes: expanded
84
- };
85
- }
86
- function referenceField(spec) {
87
- if (typeof spec !== 'object' || spec === null) return false;
88
- const declared = spec;
89
- return declared.kind === 'reference' || declared.kind === 'list' && declared.of === 'reference';
90
- }
91
- function definitionOf(types, type) {
92
- return types.find((declared)=>declared.name === type);
93
- }
94
- function retarget(value, from, to, moves) {
95
- if (Array.isArray(value)) {
96
- const next = value.map((item)=>retarget(item, from, to, moves) ?? item);
97
- return next.some((item, index)=>item !== value[index]) ? next : undefined;
35
+ const applied = apply(next, {
36
+ kind: 'reference',
37
+ pointer,
38
+ value: retarget(value, relative(to, destination)),
39
+ prior: value,
40
+ });
41
+ if (applied.outcome !== 'applied')
42
+ continue;
43
+ next = applied.document;
44
+ names.add(name);
98
45
  }
99
- if (typeof value !== 'string') return undefined;
100
- const target = resolve(from, value);
101
- if (target === null) return undefined;
102
- const next = relative(to, moves.get(target) ?? target);
103
- return next === value ? undefined : next;
46
+ return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
104
47
  }
105
48
  function edited(document, path, changes) {
106
49
  let next = document;
107
- for (const change of changes){
108
- if (change.path !== path) continue;
109
- if (change.kind !== 'section' && change.kind !== 'preamble') continue;
110
- const applied = apply(next, change.kind === 'preamble' ? {
111
- kind: 'preamble',
112
- value: change.value,
113
- prior: next.preamble
114
- } : {
115
- kind: 'section',
116
- name: change.name,
117
- value: change.value,
118
- prior: next.sections[change.name]
119
- });
120
- if (applied.outcome === 'applied') next = applied.document;
50
+ for (const change of changes) {
51
+ if (change.path !== path)
52
+ continue;
53
+ if (change.kind !== 'section' && change.kind !== 'preamble')
54
+ continue;
55
+ const applied = apply(next, change.kind === 'preamble'
56
+ ? { kind: 'preamble', value: change.value, prior: next.preamble }
57
+ : {
58
+ kind: 'section',
59
+ name: change.name,
60
+ value: change.value,
61
+ prior: next.sections[change.name],
62
+ });
63
+ if (applied.outcome === 'applied')
64
+ next = applied.document;
121
65
  }
122
66
  return next;
123
67
  }
124
68
  function bodies(path, before, after) {
125
69
  const changes = [];
126
70
  if (after.preamble !== before.preamble) {
127
- changes.push({
128
- kind: 'preamble',
129
- path,
130
- value: after.preamble
131
- });
71
+ changes.push({ kind: 'preamble', path, value: after.preamble });
132
72
  }
133
- for (const [name, value] of Object.entries(after.sections)){
134
- if (before.sections[name] === value) continue;
135
- changes.push({
136
- kind: 'section',
137
- path,
138
- name,
139
- value
140
- });
73
+ for (const [name, value] of Object.entries(after.sections)) {
74
+ if (before.sections[name] === value)
75
+ continue;
76
+ changes.push({ kind: 'section', path, name, value });
141
77
  }
142
78
  return changes;
143
79
  }
144
80
  function relinks(path, moved, before, moves) {
145
81
  let after = before;
146
- for (const link of links(before, path)){
82
+ for (const link of links(before, path)) {
147
83
  const destination = moves.get(link.target) ?? link.target;
148
- if (destination === link.target && moved === path) continue;
84
+ if (destination === link.target && moved === path)
85
+ continue;
149
86
  const to = relative(moved, destination);
150
- if (to === relative(path, link.target)) continue;
151
- const applied = apply(after, {
152
- kind: 'link',
153
- href: link.href,
154
- to
155
- });
87
+ if (to === relative(path, link.target))
88
+ continue;
89
+ const applied = apply(after, { kind: 'link', href: link.href, to });
156
90
  if (applied.outcome === 'refuse') {
157
91
  return {
158
92
  kind: 'refused',
159
93
  refusal: {
160
- rule: applied.refusal.rule,
94
+ code: applied.refusal.rule,
95
+ retryable: false,
161
96
  file: path,
162
- ...applied.refusal.location === undefined ? {} : {
163
- location: applied.refusal.location
164
- }
165
- }
97
+ ...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
98
+ },
166
99
  };
167
100
  }
168
101
  after = applied.document;
169
102
  }
170
- if (after === before) return {
171
- kind: 'changes',
172
- changes: []
173
- };
103
+ if (after === before)
104
+ return { kind: 'changes', changes: [] };
174
105
  if (before.type === undefined) {
175
- return {
176
- kind: 'changes',
177
- changes: [
178
- {
179
- kind: 'replace',
180
- path,
181
- content: serialize(after)
182
- }
183
- ]
184
- };
106
+ return { kind: 'changes', changes: [{ kind: 'replace', path, content: serialize(after) }] };
185
107
  }
186
- return {
187
- kind: 'changes',
188
- changes: bodies(path, before, after)
189
- };
108
+ return { kind: 'changes', changes: bodies(path, before, after) };
190
109
  }
191
110
  function subsumed(merged, path) {
192
- for(let index = merged.length - 1; index >= 0; index -= 1){
111
+ for (let index = merged.length - 1; index >= 0; index -= 1) {
193
112
  const item = merged[index];
194
- if (item.path !== path) continue;
195
- if (item.kind === 'section' || item.kind === 'preamble') merged.splice(index, 1);
113
+ if (item.path !== path)
114
+ continue;
115
+ if (item.kind === 'section' || item.kind === 'preamble')
116
+ merged.splice(index, 1);
196
117
  }
197
118
  }
198
119
  function rewrites(changes, visible) {
199
120
  const moves = new Map();
200
121
  const settled = new Set();
201
- for (const change of changes){
202
- if (change.kind === 'move') moves.set(change.path, change.to);
203
- if (change.kind === 'remove' || change.kind === 'replace' || change.kind === 'create') {
122
+ for (const change of changes) {
123
+ if (change.kind === 'move')
124
+ moves.set(change.path, change.to);
125
+ if (change.kind === 'remove' ||
126
+ change.kind === 'replace' ||
127
+ change.kind === 'create' ||
128
+ change.kind === 'binary') {
204
129
  settled.add(change.path);
205
130
  }
206
- if (change.kind === 'field') settled.add(`${change.path} ${change.name}`);
131
+ if (change.kind === 'field')
132
+ settled.add(`${change.path} ${change.name}`);
207
133
  }
208
- if (moves.size === 0) return {
209
- kind: 'changes',
210
- changes
211
- };
212
- const merged = [
213
- ...changes
214
- ];
134
+ if (moves.size === 0)
135
+ return { kind: 'changes', changes };
136
+ const merged = [...changes];
215
137
  const added = [];
216
- for (const [path, document] of Object.entries(visible.collection.documents)){
217
- if (settled.has(path)) continue;
138
+ for (const [path, document] of documentsOf(visible.collection)) {
139
+ if (settled.has(path))
140
+ continue;
218
141
  const moved = moves.get(path) ?? path;
219
- const definition = definitionOf(typesAt(visible.collection.composition, path), document.type);
220
- for (const [name, spec] of Object.entries(definition?.fields ?? {})){
221
- if (!referenceField(spec) || settled.has(`${path} ${name}`)) continue;
222
- const value = retarget(document.fields[name], path, moved, moves);
223
- if (value === undefined) continue;
224
- added.push({
225
- kind: 'field',
226
- path,
227
- name,
228
- value
229
- });
230
- }
142
+ const definition = definitionOf(governing(visible.collection.composition, path), document.type);
143
+ added.push(...repointed(document, definition, path, moved, moves, settled));
231
144
  const relinked = relinks(path, moved, edited(document, path, merged), moves);
232
- if (relinked.kind === 'refused') return relinked;
233
- for (const change of relinked.changes){
145
+ if (relinked.kind === 'refused')
146
+ return relinked;
147
+ for (const change of relinked.changes) {
234
148
  if (change.kind === 'replace') {
235
149
  subsumed(merged, path);
236
150
  added.push(change);
237
151
  continue;
238
152
  }
239
- const index = merged.findIndex((item)=>item.kind === change.kind && item.path === path && (change.kind !== 'section' || item.kind === 'section' && item.name === change.name));
240
- if (index === -1) added.push(change);
241
- else merged[index] = change;
153
+ const index = merged.findIndex((item) => item.kind === change.kind &&
154
+ item.path === path &&
155
+ (change.kind !== 'section' || (item.kind === 'section' && item.name === change.name)));
156
+ if (index === -1)
157
+ added.push(change);
158
+ else
159
+ merged[index] = change;
242
160
  }
243
161
  }
162
+ return { kind: 'changes', changes: [...added, ...merged] };
163
+ }
164
+ function anticipated(changes, preconditions, rewritten, visible) {
165
+ const derived = rewritten.filter((change) => !changes.includes(change));
244
166
  return {
245
- kind: 'changes',
246
- changes: [
247
- ...added,
248
- ...merged
249
- ]
167
+ changes: rewritten,
168
+ preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
250
169
  };
251
170
  }
252
- function targets(value, from) {
253
- const values = Array.isArray(value) ? value : [
254
- value
255
- ];
256
- const resolved = [];
257
- for (const item of values){
258
- if (typeof item !== 'string') continue;
259
- const target = resolve(from, item);
260
- if (target !== null) resolved.push(target);
261
- }
262
- return resolved;
171
+ export function providerRewritten(batch, visible) {
172
+ const rewritten = rewrites(batch.changes, visible);
173
+ if (rewritten.kind === 'refused')
174
+ return batch;
175
+ return {
176
+ ...batch,
177
+ ...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
178
+ };
263
179
  }
264
180
  function incoming(collection, removed) {
265
181
  const found = new Map();
266
- const note = (target, file)=>{
267
- if (!removed.has(target)) return;
182
+ const note = (target, file) => {
183
+ if (!removed.has(target))
184
+ return;
268
185
  const referrers = found.get(target) ?? new Set();
269
186
  referrers.add(file);
270
187
  found.set(target, referrers);
271
188
  };
272
- for (const [path, document] of Object.entries(collection.documents)){
273
- const definition = definitionOf(typesAt(collection.composition, path), document.type);
274
- for (const [name, spec] of Object.entries(definition?.fields ?? {})){
275
- if (!referenceField(spec)) continue;
276
- for (const target of targets(document.fields[name], path))note(target, path);
189
+ for (const [path, document] of documentsOf(collection)) {
190
+ const definition = definitionOf(governing(collection.composition, path), document.type);
191
+ for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
192
+ if (to.includes(ATTACHMENT))
193
+ continue;
194
+ const named = target(path, value);
195
+ if (named !== null)
196
+ note(named, path);
277
197
  }
278
- for (const link of links(document, path))note(link.target, path);
279
198
  }
280
- const [first] = [
281
- ...found.keys()
282
- ].sort();
283
- if (first === undefined) return null;
199
+ const [first] = [...found.keys()].sort();
200
+ if (first === undefined)
201
+ return null;
284
202
  const every = new Set();
285
- for (const referrers of found.values())for (const file of referrers)every.add(file);
203
+ for (const referrers of found.values())
204
+ for (const file of referrers)
205
+ every.add(file);
286
206
  return {
287
207
  kind: 'refused',
288
208
  refusal: {
289
- rule: 'referenced-missing-file',
209
+ code: 'referenced-missing-file',
210
+ retryable: false,
290
211
  file: first,
291
- referrers: [
292
- ...every
293
- ].sort()
294
- }
212
+ referrers: [...every].sort(),
213
+ },
295
214
  };
296
215
  }
297
216
  function affected(change) {
298
- return change.kind === 'move' ? [
299
- change.path,
300
- change.to
301
- ] : [
302
- change.path
303
- ];
304
- }
305
- function definitionAddresses(composition) {
306
- const addresses = new Set();
307
- if (composition.model.status !== 'unavailable') {
308
- for (const path of Object.keys(composition.model.snapshot.definitions))addresses.add(path);
309
- }
310
- if (composition.selection !== null && composition.selection.model.status !== 'unavailable') {
311
- for (const path of Object.keys(composition.selection.model.snapshot.definitions)){
312
- addresses.add(path);
313
- }
314
- }
315
- return addresses;
217
+ return change.kind === 'move' ? [change.path, change.to] : [change.path];
316
218
  }
317
219
  function guard(changes, input) {
318
220
  const existing = new Set([
319
221
  ...Object.keys(input.visible.documents),
320
- ...input.visible.binaryTargets
222
+ ...Object.keys(input.visible.binaries),
321
223
  ]);
322
- const definitions = definitionAddresses(input.composition);
323
- for (const change of changes){
324
- for (const path of affected(change)){
224
+ for (const [index, change] of changes.entries()) {
225
+ for (const path of affected(change)) {
325
226
  const normalized = normalize(path);
326
- if (normalized === null || normalized === '') return refuse('room-root', path);
327
- if (definitions.has(normalized)) return refuse('model-definition', normalized);
227
+ if (normalized === null || normalized === '')
228
+ return refuse('room-root', path);
328
229
  if (covered(input.providerOwnedPaths, normalized)) {
329
230
  return refuse('provider-owned-path', normalized);
330
231
  }
331
232
  const operation = change.kind === 'remove' ? 'delete' : 'write';
332
- if (!covered(input.operations[operation], normalized)) return refuse(`${operation}-grant`, normalized);
233
+ if (!input.local && !covered(input.operations[operation], normalized))
234
+ return refuse(`${operation}-grant`, normalized);
333
235
  }
334
236
  if (change.kind === 'create') {
335
- if (existing.has(change.path)) return refuse('path-collision', change.path);
237
+ if (existing.has(change.path))
238
+ return refuse('path-collision', change.path);
239
+ existing.add(change.path);
240
+ continue;
241
+ }
242
+ if (change.kind === 'binary') {
243
+ if (input.visible.documents[change.path] !== undefined ||
244
+ (existing.has(change.path) &&
245
+ input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
246
+ return refuse('path-collision', change.path);
247
+ }
248
+ if (!input.local &&
249
+ existing.has(change.path) &&
250
+ input.visible.revisions[departure(changes, index)] === undefined) {
251
+ return refuse('unknown-revision', change.path);
252
+ }
336
253
  existing.add(change.path);
337
254
  continue;
338
255
  }
339
256
  if (change.kind === 'move') {
340
- if (!existing.has(change.path)) return refuse('path-absent', change.path);
341
- if (existing.has(change.to)) return refuse('path-collision', change.to);
257
+ if (!existing.has(change.path))
258
+ return refuse('path-absent', change.path);
259
+ if (existing.has(change.to))
260
+ return refuse('path-collision', change.to);
261
+ if (unrevised(input, change.path)) {
262
+ return refuse('unknown-revision', change.path);
263
+ }
342
264
  existing.delete(change.path);
343
265
  existing.add(change.to);
344
266
  continue;
345
267
  }
346
- if (!existing.has(change.path)) return refuse('path-absent', change.path);
268
+ if (!existing.has(change.path))
269
+ return refuse('path-absent', change.path);
347
270
  if (change.kind === 'remove') {
271
+ if (unrevised(input, change.path)) {
272
+ return refuse('unknown-revision', change.path);
273
+ }
348
274
  existing.delete(change.path);
349
275
  continue;
350
276
  }
351
277
  if (change.kind === 'replace') {
352
- if (input.visible.revisions[change.path] === undefined) {
278
+ if (!input.local && input.visible.revisions[change.path] === undefined) {
353
279
  return refuse('unknown-revision', change.path);
354
280
  }
355
281
  continue;
356
282
  }
357
- const document = input.visible.collection.documents[change.path];
358
- if (document === undefined) return refuse('path-absent', change.path);
359
- if (change.kind === 'section' && document.sections[change.name] === undefined) {
360
- return refuse('required-section', change.path, change.name);
283
+ if (input.visible.collection.document(change.path) === undefined) {
284
+ return refuse('path-absent', change.path);
361
285
  }
362
286
  }
363
287
  return null;
364
288
  }
289
+ function unrevised(input, path) {
290
+ return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
291
+ }
292
+ function identity(precondition) {
293
+ const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
294
+ return `${precondition.kind} ${precondition.path} ${part}`;
295
+ }
365
296
  function existence(visible, path) {
366
297
  const revision = visible.revisions[path];
367
- return revision === undefined ? {
368
- kind: 'present',
369
- path
370
- } : {
371
- kind: 'revision',
372
- path,
373
- revision
374
- };
298
+ if (revision !== undefined)
299
+ return { kind: 'revision', path, revision };
300
+ const content = visible.documents[path];
301
+ return content === undefined ? { kind: 'present', path } : { kind: 'content', path, content };
375
302
  }
376
- function preconditionsOf(changes, visible) {
303
+ function sameTarget(precondition, change) {
304
+ if (precondition.kind !== change.kind || precondition.path !== change.path)
305
+ return false;
306
+ return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
307
+ ((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
308
+ }
309
+ function preconditionsOf(changes, visible, bases) {
377
310
  const preconditions = [];
378
311
  const seen = new Set();
379
- const add = (precondition)=>{
380
- const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
381
- const identity = `${precondition.kind} ${precondition.path} ${part}`;
382
- if (seen.has(identity)) return;
383
- seen.add(identity);
312
+ const add = (precondition) => {
313
+ const held = identity(precondition);
314
+ if (seen.has(held))
315
+ return;
316
+ seen.add(held);
384
317
  preconditions.push(precondition);
385
318
  };
386
- for (const change of changes){
387
- if (change.kind === 'field') {
388
- const document = visible.collection.documents[change.path];
389
- add({
390
- kind: 'field',
391
- path: change.path,
392
- name: change.name,
393
- value: document.fields[change.name]
394
- });
395
- continue;
396
- }
397
- if (change.kind === 'section') {
398
- const document = visible.collection.documents[change.path];
399
- add({
400
- kind: 'section',
401
- path: change.path,
402
- name: change.name,
403
- value: document.sections[change.name]
404
- });
319
+ for (const change of changes) {
320
+ if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
321
+ const based = bases.find((base) => sameTarget(base, change));
322
+ if (based !== undefined) {
323
+ add(based);
324
+ continue;
325
+ }
326
+ const document = visible.collection.document(change.path);
327
+ if (change.kind === 'field') {
328
+ add({
329
+ kind: 'field',
330
+ path: change.path,
331
+ name: change.name,
332
+ value: document.fields[change.name],
333
+ });
334
+ }
335
+ else if (change.kind === 'section') {
336
+ add({
337
+ kind: 'section',
338
+ path: change.path,
339
+ name: change.name,
340
+ value: document.sections[change.name],
341
+ });
342
+ }
343
+ else
344
+ add({ kind: 'preamble', path: change.path, value: document.preamble });
405
345
  continue;
406
346
  }
407
- if (change.kind === 'preamble') {
408
- const document = visible.collection.documents[change.path];
409
- add({
410
- kind: 'preamble',
411
- path: change.path,
412
- value: document.preamble
413
- });
347
+ if (change.kind === 'create') {
348
+ add({ kind: 'absent', path: change.path });
414
349
  continue;
415
350
  }
416
- if (change.kind === 'create') {
417
- add({
418
- kind: 'absent',
419
- path: change.path
420
- });
351
+ if (change.kind === 'binary') {
352
+ const revision = visible.revisions[change.path];
353
+ add(visible.binaries[change.path] === undefined || revision === undefined
354
+ ? { kind: 'absent', path: change.path }
355
+ : { kind: 'revision', path: change.path, revision });
421
356
  continue;
422
357
  }
423
358
  add(existence(visible, change.path));
424
- if (change.kind === 'move') add({
425
- kind: 'absent',
426
- path: change.to
427
- });
359
+ if (change.kind === 'move')
360
+ add({ kind: 'absent', path: change.to });
428
361
  }
429
362
  return preconditions;
430
363
  }
431
364
  function depth(path) {
432
365
  return path.split('/').length;
433
366
  }
434
- function exceeded(limits, changes, resulting) {
435
- const written = changes.map((change)=>change.kind === 'move' ? change.to : change.path);
436
- const created = changes.filter((change)=>change.kind === 'create' || change.kind === 'move').map((change)=>change.kind === 'move' ? change.to : change.path);
437
- for (const limit of limits){
367
+ function exceeded(limits, changes, effective, resulting) {
368
+ const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
369
+ const created = changes
370
+ .filter((change) => change.kind === 'create' || change.kind === 'move' || change.kind === 'binary')
371
+ .map((change) => (change.kind === 'move' ? change.to : change.path));
372
+ for (const limit of limits) {
438
373
  const value = limit.value;
439
- if (typeof value !== 'number') continue;
374
+ if (typeof value !== 'number')
375
+ continue;
440
376
  if (limit.name === 'change-batch-operations' && changes.length > value) {
441
377
  return refuse('provider-limit', undefined, limit.name);
442
378
  }
379
+ if (limit.name === 'change-documents' &&
380
+ new Set(changes.map((change) => change.path)).size > value) {
381
+ return refuse('provider-limit', undefined, limit.name);
382
+ }
443
383
  if (limit.name === 'document-size') {
444
- const large = written.find((path)=>(resulting.documents[path] ?? '').length > value);
445
- if (large !== undefined) return refuse('provider-limit', large, limit.name);
384
+ const large = written.find((path) => new TextEncoder().encode(resulting.documents[path] ?? '').byteLength > value);
385
+ if (large !== undefined)
386
+ return refuse('provider-limit', large, limit.name);
446
387
  }
447
388
  if (limit.name === 'path-length') {
448
- const long = created.find((path)=>path.length > value);
449
- if (long !== undefined) return refuse('provider-limit', long, limit.name);
389
+ const long = created.find((path) => path.length > value);
390
+ if (long !== undefined)
391
+ return refuse('provider-limit', long, limit.name);
450
392
  }
451
393
  if (limit.name === 'path-depth') {
452
- const deep = created.find((path)=>depth(path) > value);
453
- if (deep !== undefined) return refuse('provider-limit', deep, limit.name);
394
+ const deep = created.find((path) => depth(path) > value);
395
+ if (deep !== undefined)
396
+ return refuse('provider-limit', deep, limit.name);
454
397
  }
455
398
  }
456
399
  return null;
457
400
  }
401
+ function shellRelative(text, path, handle, definition) {
402
+ if (!text.includes(`/rooms/${handle}/`))
403
+ return null;
404
+ const parsed = parse(text, path);
405
+ if (!parsed.ok)
406
+ return null;
407
+ const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
408
+ return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
409
+ }
410
+ function relativeField(path, name, value, handle, definition) {
411
+ const opened = parse('---\n---\n');
412
+ if (definition === undefined ||
413
+ !opened.ok ||
414
+ !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
415
+ return value;
416
+ const held = apply(opened.document, { kind: 'field', name, value, prior: undefined });
417
+ if (held.outcome !== 'applied')
418
+ return value;
419
+ const result = relativize(held.document, path, handle, definition);
420
+ return result.outcome === 'applied' ? result.document.fields[name] : value;
421
+ }
422
+ function relativeChanges(input) {
423
+ const { handle, composition, visible } = input;
424
+ const typed = (path) => (type) => definitionOf(governing(composition, path), type);
425
+ const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
426
+ return input.changes.map((change) => {
427
+ const definition = typed(change.path);
428
+ if (change.kind === 'field') {
429
+ const type = visible.collection.document(change.path)?.type;
430
+ const value = relativeField(change.path, change.name, change.value, handle, definition(type));
431
+ return value === change.value ? change : { ...change, value };
432
+ }
433
+ if (change.kind === 'section' || change.kind === 'preamble') {
434
+ const value = body(change.path, change.value);
435
+ return value === change.value ? change : { ...change, value };
436
+ }
437
+ if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
438
+ const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
439
+ return content === change.content ? change : { ...change, content };
440
+ }
441
+ if (change.kind !== 'create')
442
+ return change;
443
+ const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
444
+ const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
445
+ name,
446
+ relativeField(change.path, name, value, handle, definition(type)),
447
+ ]));
448
+ const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
449
+ return { ...change, fields, sections };
450
+ });
451
+ }
452
+ function unmodeledType(collection, composition) {
453
+ for (const [path, document] of documentsOf(collection)) {
454
+ if (document.type !== undefined &&
455
+ document.type !== 'any' &&
456
+ governing(composition, path).status === 'unavailable')
457
+ return true;
458
+ }
459
+ return false;
460
+ }
461
+ function unqueueable(file) {
462
+ return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
463
+ }
464
+ function isPart(change) {
465
+ return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
466
+ }
467
+ function touched(change) {
468
+ return change.kind === 'move' ? [change.path, change.to] : [change.path];
469
+ }
470
+ function partApplied(content, path, change) {
471
+ const parsed = parse(content, path);
472
+ if (!parsed.ok)
473
+ return null;
474
+ const document = parsed.document;
475
+ const applied = apply(document, change.kind === 'field'
476
+ ? {
477
+ kind: 'field',
478
+ name: change.name,
479
+ value: change.value,
480
+ prior: document.fields[change.name],
481
+ }
482
+ : change.kind === 'section'
483
+ ? {
484
+ kind: 'section',
485
+ name: change.name,
486
+ value: change.value,
487
+ prior: document.sections[change.name],
488
+ }
489
+ : { kind: 'preamble', value: change.value, prior: document.preamble });
490
+ return applied.outcome === 'applied' ? serialize(applied.document) : null;
491
+ }
492
+ function folded(changes, change, appending) {
493
+ const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
494
+ const whole = held === undefined ? -1 : changes.indexOf(held);
495
+ const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
496
+ if (isPart(change)) {
497
+ const same = changes.findIndex((item) => item.kind === change.kind &&
498
+ item.path === change.path &&
499
+ (change.kind === 'preamble' || ('name' in item && item.name === change.name)));
500
+ if (same !== -1) {
501
+ changes[same] = change;
502
+ return 'folded';
503
+ }
504
+ if (held === undefined) {
505
+ if (!appending)
506
+ return 'skipped';
507
+ if (occupied)
508
+ return 'refused';
509
+ changes.push(change);
510
+ return 'appended';
511
+ }
512
+ if ('content' in held) {
513
+ const content = partApplied(held.content, held.path, change);
514
+ if (content === null)
515
+ return 'refused';
516
+ changes[whole] = { ...held, content };
517
+ return 'folded';
518
+ }
519
+ if (change.kind === 'preamble')
520
+ return 'refused';
521
+ changes[whole] =
522
+ change.kind === 'field'
523
+ ? { ...held, fields: { ...held.fields, [change.name]: change.value } }
524
+ : { ...held, sections: { ...held.sections, [change.name]: change.value } };
525
+ return 'folded';
526
+ }
527
+ if (change.kind === 'replace') {
528
+ if (held !== undefined) {
529
+ if (!('content' in held))
530
+ return 'refused';
531
+ changes[whole] = { ...held, content: change.content };
532
+ return 'folded';
533
+ }
534
+ const parts = changes.filter((item) => isPart(item) && item.path === change.path);
535
+ if (parts.length === 0 && !appending)
536
+ return 'skipped';
537
+ if (occupied)
538
+ return 'refused';
539
+ changes.splice(0, changes.length, ...changes.filter((item) => !parts.includes(item)), change);
540
+ return 'appended';
541
+ }
542
+ if (changes.some((item) => touched(change).some((path) => touched(item).includes(path))))
543
+ return 'refused';
544
+ changes.push(change);
545
+ return 'appended';
546
+ }
547
+ function relinked(batch, move, before, after, appending) {
548
+ const derived = rewrites([move], after);
549
+ if (derived.kind === 'refused')
550
+ return derived;
551
+ const changes = [...batch.changes];
552
+ const appended = [];
553
+ let changed = false;
554
+ for (const change of derived.changes) {
555
+ if (change === move)
556
+ continue;
557
+ const outcome = folded(changes, change, appending);
558
+ if (outcome === 'refused')
559
+ return unqueueable(change.path);
560
+ if (outcome === 'appended')
561
+ appended.push(change);
562
+ if (outcome !== 'skipped')
563
+ changed = true;
564
+ }
565
+ if (!changed)
566
+ return { kind: 'relinked', batch };
567
+ const held = new Set(batch.preconditions.map(identity));
568
+ return {
569
+ kind: 'relinked',
570
+ batch: immutable({
571
+ ...batch,
572
+ id: changeBatchIdentity(),
573
+ changes,
574
+ preconditions: [
575
+ ...batch.preconditions,
576
+ ...preconditionsOf(appended, before, []).filter((precondition) => !held.has(identity(precondition))),
577
+ ],
578
+ }),
579
+ };
580
+ }
581
+ function leading(changes, at) {
582
+ const from = departure(changes, at);
583
+ return new Set(changes.filter((change, index) => index < at && change.kind === 'move' && departure(changes, index) === from));
584
+ }
585
+ function created(batch, at) {
586
+ const file = batch.changes[at];
587
+ return (leading(batch.changes, at).size === 0 &&
588
+ batch.preconditions.some((precondition) => precondition.kind === 'absent' && precondition.path === file.path));
589
+ }
590
+ function withdrawnFile(batch, change, at) {
591
+ const file = batch.changes[at];
592
+ const from = departure(batch.changes, at);
593
+ const moves = leading(batch.changes, at);
594
+ const fresh = created(batch, at);
595
+ const vacated = new Set([...moves].map((move) => move.to));
596
+ if (fresh)
597
+ vacated.add(file.path);
598
+ const replaced = fresh
599
+ ? change.kind === 'remove'
600
+ ? []
601
+ : [{ ...file, path: change.to }]
602
+ : change.kind === 'remove'
603
+ ? [{ kind: 'remove', path: from }]
604
+ : change.to === from
605
+ ? [{ ...file, path: from }]
606
+ : [
607
+ { kind: 'move', path: from, to: change.to },
608
+ { ...file, path: change.to },
609
+ ];
610
+ const added = change.kind === 'move' && (fresh || change.to !== from)
611
+ ? [{ kind: 'absent', path: change.to }]
612
+ : [];
613
+ const changes = batch.changes.flatMap((item) => item === file ? replaced : item.kind === 'move' && moves.has(item) ? [] : [item]);
614
+ if (changes.length === 0)
615
+ return null;
616
+ const preconditions = [
617
+ ...added,
618
+ ...batch.preconditions.filter((precondition) => precondition.kind !== 'absent' || !vacated.has(precondition.path)),
619
+ ];
620
+ return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
621
+ }
622
+ export function requeued(queue, changes, visible, changeable, state) {
623
+ const next = [...queue];
624
+ const origins = new Map();
625
+ const withdrawn = new Map();
626
+ const owners = new Set();
627
+ const queued = new Set();
628
+ const kept = [];
629
+ const originOf = (batch) => origins.get(batch) ?? batch.id;
630
+ const open = (batch) => origins.has(batch) || changeable(batch);
631
+ for (const change of changes) {
632
+ const held = visible.binaries[change.path];
633
+ if ((change.kind !== 'move' && change.kind !== 'remove') || held?.state !== 'pending') {
634
+ kept.push(change);
635
+ continue;
636
+ }
637
+ let index = next.length - 1;
638
+ while (index >= 0 && queuedFile(next[index], change.path, held.localId) === -1)
639
+ index -= 1;
640
+ const owner = next[index];
641
+ if (owner === undefined || !open(owner))
642
+ return unqueueable(change.path);
643
+ const following = next.slice(index);
644
+ const rebuilt = [];
645
+ let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
646
+ for (const [offset, batch] of following.entries()) {
647
+ let current = batch;
648
+ if (change.kind === 'move') {
649
+ const after = state(next.slice(0, index + offset + 1));
650
+ const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
651
+ const relinking = relinked(batch, change, before, after, appending);
652
+ if (relinking.kind === 'refused')
653
+ return relinking;
654
+ current = relinking.batch;
655
+ before = after;
656
+ }
657
+ if (current !== batch && !open(batch))
658
+ return unqueueable(change.path);
659
+ rebuilt.push(offset === 0
660
+ ? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
661
+ : current);
662
+ }
663
+ for (const [offset, batch] of following.entries()) {
664
+ const replaced = rebuilt[offset];
665
+ if (replaced === batch)
666
+ continue;
667
+ const origin = originOf(batch);
668
+ withdrawn.set(origin, replaced);
669
+ if (replaced !== null)
670
+ origins.set(replaced, origin);
671
+ }
672
+ owners.add(originOf(owner));
673
+ next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
674
+ queued.add(change.path);
675
+ }
676
+ return {
677
+ kind: 'requeued',
678
+ queue: withdrawn.size === 0 ? queue : next,
679
+ changes: kept,
680
+ queued,
681
+ withdrawn,
682
+ owners,
683
+ };
684
+ }
685
+ function covering(precondition) {
686
+ return precondition.kind === 'field' ||
687
+ precondition.kind === 'section' ||
688
+ precondition.kind === 'preamble'
689
+ ? identity(precondition)
690
+ : `path ${precondition.path}`;
691
+ }
692
+ export function joined(queue, owner, extra) {
693
+ const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
694
+ const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
695
+ if (clash !== undefined)
696
+ return unqueueable(clash);
697
+ const changes = [...owner.changes];
698
+ for (const change of extra.changes) {
699
+ if (folded(changes, change, true) === 'refused')
700
+ return unqueueable(change.path);
701
+ }
702
+ const held = new Set(owner.preconditions.map(covering));
703
+ const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
704
+ const preconditions = [
705
+ ...owner.preconditions,
706
+ ...extra.preconditions.filter((precondition) => {
707
+ const key = covering(precondition);
708
+ return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
709
+ }),
710
+ ];
711
+ const batch = immutable({
712
+ ...owner,
713
+ ...(extra.validation === undefined ? {} : { validation: extra.validation }),
714
+ id: changeBatchIdentity(),
715
+ changes,
716
+ preconditions,
717
+ });
718
+ return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
719
+ }
720
+ export function limitedIn(queue, batch, limits, state) {
721
+ const at = queue.indexOf(batch);
722
+ const limited = exceeded(limits, batch.changes, providerRewritten(batch, state(queue.slice(0, at))).changes, state(queue.slice(0, at + 1)));
723
+ return limited?.kind === 'refused' ? limited.refusal : null;
724
+ }
458
725
  export function buildBatch(input) {
459
- const structural = input.changes.find((change)=>change.kind === 'move' || change.kind === 'remove');
460
- if (structural !== undefined && Object.entries(input.visible.collection.documents).some(([path, document])=>document.type !== undefined && document.type !== 'any' && governing(input.composition, path).status === 'unavailable')) return refuse('model-unavailable', structural.path);
461
- const expanded = expand(input.changes, input.visible);
462
- if (expanded.kind === 'refused') return expanded;
463
- const rewritten = rewrites(expanded.changes, input.visible);
464
- if (rewritten.kind === 'refused') return rewritten;
465
- const changes = rewritten.changes;
466
- if (changes.length === 0) return refuse('no-change');
726
+ const changes = relativeChanges(input);
727
+ const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
728
+ if (structural !== undefined && unmodeledType(input.visible.collection, input.composition))
729
+ return refuse('model-unavailable', structural.path);
730
+ if (changes.length === 0)
731
+ return refuse('no-change');
732
+ const rewritten = rewrites(changes, input.visible);
733
+ if (rewritten.kind === 'refused')
734
+ return rewritten;
467
735
  const refusal = guard(changes, input);
468
- if (refusal !== null) return refusal;
469
- const preconditions = preconditionsOf(changes, input.visible);
736
+ if (refusal !== null)
737
+ return refusal;
738
+ const preconditions = preconditionsOf(changes, input.visible, input.bases);
739
+ const predicted = anticipated(changes, preconditions, rewritten.changes, input.visible);
470
740
  const resulting = overlay(input.visible, [
471
- {
472
- id: '',
473
- baseRoomVersion: input.baseRoomVersion,
474
- changes,
475
- preconditions
476
- }
741
+ { id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
477
742
  ]);
478
- const removed = new Set(changes.filter((change)=>change.kind === 'remove').map((change)=>change.path));
743
+ const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
479
744
  if (removed.size > 0) {
480
- const referenced = incoming(parseCollection(resulting.documents, input.composition), removed);
481
- if (referenced !== null) return referenced;
745
+ const referenced = incoming(collectionOf(resulting.documents, input.composition), removed);
746
+ if (referenced !== null)
747
+ return referenced;
482
748
  }
483
- const typed = (content)=>{
484
- if (content === undefined) return false;
749
+ const typed = (content) => {
750
+ if (content === undefined)
751
+ return false;
485
752
  const parsed = parse(content);
486
753
  return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
487
754
  };
488
- const typedPaths = [
489
- ...new Set(changes.flatMap(affected))
490
- ].filter((path)=>typed(input.visible.documents[path]) || typed(resulting.documents[path]));
755
+ const typedPaths = [...new Set(predicted.changes.flatMap(affected))].filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
491
756
  const needsModel = typedPaths.length > 0;
492
- const withoutModel = typedPaths.find((path)=>governing(input.composition, path).status === 'unavailable');
493
- if (needsModel && (withoutModel !== undefined || input.binding === null)) {
494
- return refuse('model-unavailable', withoutModel ?? typedPaths[0]);
495
- }
496
- const validationText = needsModel ? resulting.documents : Object.fromEntries(Object.entries(resulting.documents).filter(([path])=>changes.some((change)=>affected(change).includes(path))));
497
- const verdict = validateCollection(validationText, resulting.binaryTargets, input.composition, input.binding);
757
+ const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
758
+ if (withoutModel !== undefined)
759
+ return refuse('model-unavailable', withoutModel);
760
+ const validationText = needsModel
761
+ ? resulting.documents
762
+ : Object.fromEntries(Object.entries(resulting.documents).filter(([path]) => predicted.changes.some((change) => affected(change).includes(path))));
763
+ const verdict = validateCollection(validationText, Object.keys(resulting.binaries), input.composition);
498
764
  if (verdict.verdict === 'refuse') {
499
765
  return {
500
766
  kind: 'refused',
501
767
  refusal: {
502
- ...verdict.source === 'model' ? {
503
- source: 'model'
504
- } : {},
505
- rule: verdict.rule,
768
+ code: verdict.rule,
769
+ retryable: false,
506
770
  file: verdict.file,
507
- ...verdict.location === undefined ? {} : {
508
- location: verdict.location
509
- },
510
- ...verdict.referrers === undefined ? {} : {
511
- referrers: verdict.referrers
512
- }
513
- }
771
+ ...(verdict.location === undefined ? {} : { location: verdict.location }),
772
+ ...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
773
+ },
514
774
  };
515
775
  }
516
- const limited = exceeded(input.providerLimits, changes, resulting);
517
- if (limited !== null) return limited;
518
- const validated = typedPaths.map((path)=>governing(input.composition, path)).find((model)=>model.status !== 'unavailable');
776
+ const limited = exceeded(input.providerLimits, changes, predicted.changes, resulting);
777
+ if (limited !== null)
778
+ return limited;
779
+ const validated = typedPaths
780
+ .map((path) => governing(input.composition, path))
781
+ .find((model) => model.status !== 'unavailable');
519
782
  return {
520
783
  kind: 'batch',
521
784
  batch: immutable({
@@ -523,15 +786,16 @@ export function buildBatch(input) {
523
786
  baseRoomVersion: input.baseRoomVersion,
524
787
  changes,
525
788
  preconditions,
526
- ...validated === undefined ? {} : {
527
- validation: {
528
- identity: validated.snapshot.identity,
529
- version: validated.snapshot.version,
530
- source: validated.status
531
- }
532
- }
533
- })
789
+ ...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
790
+ ...(validated === undefined
791
+ ? {}
792
+ : {
793
+ validation: {
794
+ identity: validated.snapshot.identity,
795
+ version: validated.snapshot.version,
796
+ source: validated.status,
797
+ },
798
+ }),
799
+ }),
534
800
  };
535
801
  }
536
-
537
- //# sourceMappingURL=save.js.map