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

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 (133) hide show
  1. package/AGENTS.md +232 -0
  2. package/README.md +44 -0
  3. package/dist/adapter.d.ts +207 -36
  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 +34 -0
  9. package/dist/attachment.d.ts +3 -0
  10. package/dist/attachment.d.ts.map +1 -0
  11. package/dist/attachment.js +8 -0
  12. package/dist/bootstrap.d.ts +7 -9
  13. package/dist/bootstrap.d.ts.map +1 -1
  14. package/dist/bootstrap.js +30 -28
  15. package/dist/change-set.d.ts +33 -0
  16. package/dist/change-set.d.ts.map +1 -0
  17. package/dist/change-set.js +83 -0
  18. package/dist/change.d.ts +9 -0
  19. package/dist/change.d.ts.map +1 -1
  20. package/dist/change.js +6 -5
  21. package/dist/client.d.ts +39 -32
  22. package/dist/client.d.ts.map +1 -1
  23. package/dist/client.js +114 -183
  24. package/dist/collection.d.ts +13 -16
  25. package/dist/collection.d.ts.map +1 -1
  26. package/dist/collection.js +45 -88
  27. package/dist/connection.d.ts +51 -46
  28. package/dist/connection.d.ts.map +1 -1
  29. package/dist/connection.js +1551 -1161
  30. package/dist/conversation.d.ts +57 -24
  31. package/dist/conversation.d.ts.map +1 -1
  32. package/dist/conversation.js +631 -330
  33. package/dist/durability.js +6 -6
  34. package/dist/file.d.ts +2 -2
  35. package/dist/file.d.ts.map +1 -1
  36. package/dist/file.js +48 -71
  37. package/dist/host.d.ts +12 -2
  38. package/dist/host.d.ts.map +1 -1
  39. package/dist/host.js +97 -119
  40. package/dist/identity.d.ts +1 -0
  41. package/dist/identity.d.ts.map +1 -1
  42. package/dist/identity.js +10 -9
  43. package/dist/index.d.ts +12 -11
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +1 -3
  46. package/dist/log.js +5 -6
  47. package/dist/memory.d.ts +16 -7
  48. package/dist/memory.d.ts.map +1 -1
  49. package/dist/memory.js +267 -220
  50. package/dist/model.d.ts +7 -29
  51. package/dist/model.d.ts.map +1 -1
  52. package/dist/model.js +19 -21
  53. package/dist/observation.d.ts +27 -0
  54. package/dist/observation.d.ts.map +1 -0
  55. package/dist/observation.js +95 -0
  56. package/dist/outcome.d.ts +10 -29
  57. package/dist/outcome.d.ts.map +1 -1
  58. package/dist/outcome.js +1 -23
  59. package/dist/overlay.d.ts +9 -2
  60. package/dist/overlay.d.ts.map +1 -1
  61. package/dist/overlay.js +187 -43
  62. package/dist/paths.d.ts +0 -2
  63. package/dist/paths.d.ts.map +1 -1
  64. package/dist/paths.js +6 -29
  65. package/dist/query.d.ts +215 -10
  66. package/dist/query.d.ts.map +1 -1
  67. package/dist/query.js +429 -15
  68. package/dist/reach.d.ts +40 -0
  69. package/dist/reach.d.ts.map +1 -0
  70. package/dist/reach.js +270 -0
  71. package/dist/react.d.ts +31 -0
  72. package/dist/react.d.ts.map +1 -0
  73. package/dist/react.js +62 -0
  74. package/dist/read-side.d.ts +18 -0
  75. package/dist/read-side.d.ts.map +1 -0
  76. package/dist/read-side.js +377 -0
  77. package/dist/refresh.d.ts +5 -5
  78. package/dist/refresh.d.ts.map +1 -1
  79. package/dist/refresh.js +49 -30
  80. package/dist/replica.d.ts +25 -5
  81. package/dist/replica.d.ts.map +1 -1
  82. package/dist/replica.js +227 -38
  83. package/dist/room-draft.d.ts +127 -0
  84. package/dist/room-draft.d.ts.map +1 -0
  85. package/dist/room-draft.js +329 -0
  86. package/dist/rows.d.ts +41 -0
  87. package/dist/rows.d.ts.map +1 -0
  88. package/dist/rows.js +97 -0
  89. package/dist/save.d.ts +7 -4
  90. package/dist/save.d.ts.map +1 -1
  91. package/dist/save.js +314 -356
  92. package/dist/search.d.ts +61 -0
  93. package/dist/search.d.ts.map +1 -0
  94. package/dist/search.js +125 -0
  95. package/dist/status.d.ts +4 -6
  96. package/dist/status.d.ts.map +1 -1
  97. package/dist/status.js +9 -13
  98. package/dist/store.d.ts +60 -32
  99. package/dist/store.d.ts.map +1 -1
  100. package/dist/store.js +338 -134
  101. package/dist/transfer.d.ts +5 -0
  102. package/dist/transfer.d.ts.map +1 -1
  103. package/dist/transfer.js +1 -5
  104. package/dist/wire.d.ts +52 -16
  105. package/dist/wire.d.ts.map +1 -1
  106. package/dist/wire.js +78 -108
  107. package/package.json +17 -11
  108. package/dist/adapter.js.map +0 -1
  109. package/dist/bootstrap.js.map +0 -1
  110. package/dist/change.js.map +0 -1
  111. package/dist/client.js.map +0 -1
  112. package/dist/collection.js.map +0 -1
  113. package/dist/connection.js.map +0 -1
  114. package/dist/conversation.js.map +0 -1
  115. package/dist/durability.js.map +0 -1
  116. package/dist/file.js.map +0 -1
  117. package/dist/host.js.map +0 -1
  118. package/dist/identity.js.map +0 -1
  119. package/dist/index.js.map +0 -1
  120. package/dist/log.js.map +0 -1
  121. package/dist/memory.js.map +0 -1
  122. package/dist/model.js.map +0 -1
  123. package/dist/outcome.js.map +0 -1
  124. package/dist/overlay.js.map +0 -1
  125. package/dist/paths.js.map +0 -1
  126. package/dist/query.js.map +0 -1
  127. package/dist/refresh.js.map +0 -1
  128. package/dist/replica.js.map +0 -1
  129. package/dist/save.js.map +0 -1
  130. package/dist/status.js.map +0 -1
  131. package/dist/store.js.map +0 -1
  132. package/dist/transfer.js.map +0 -1
  133. package/dist/wire.js.map +0 -1
package/dist/save.js CHANGED
@@ -1,350 +1,254 @@
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, } from './change.js';
4
+ import { governing, parseCollection, 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
+ import { covered, normalize } from './paths.js';
7
8
  function refuse(rule, file, location) {
8
9
  return {
9
10
  kind: 'refused',
10
11
  refusal: {
11
12
  rule,
12
- ...file === undefined ? {} : {
13
- file
14
- },
15
- ...location === undefined ? {} : {
16
- location
17
- }
18
- }
19
- };
20
- }
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)}`
13
+ ...(file === undefined ? {} : { file }),
14
+ ...(location === undefined ? {} : { location }),
15
+ },
36
16
  };
37
17
  }
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);
18
+ function repointed(document, definition, from, to, moves, settled) {
19
+ const parsed = parse(document.content, from);
20
+ if (!parsed.ok)
21
+ return [];
22
+ let next = parsed.document;
23
+ const names = new Set();
24
+ for (const { pointer, value } of definition === undefined
25
+ ? []
26
+ : references(document, definition)) {
27
+ const [name = ''] = pointerSegments(pointer);
28
+ const named = target(from, value);
29
+ if (named === null || settled.has(`${from} ${name}`))
43
30
  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);
31
+ const destination = moves.get(named) ?? named;
32
+ if (target(to, value) === destination)
67
33
  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;
34
+ const applied = apply(next, {
35
+ kind: 'reference',
36
+ pointer,
37
+ value: retarget(value, relative(to, destination)),
38
+ prior: value,
39
+ });
40
+ if (applied.outcome !== 'applied')
41
+ continue;
42
+ next = applied.document;
43
+ names.add(name);
98
44
  }
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;
45
+ return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
104
46
  }
105
47
  function edited(document, path, changes) {
106
48
  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;
49
+ for (const change of changes) {
50
+ if (change.path !== path)
51
+ continue;
52
+ if (change.kind !== 'section' && change.kind !== 'preamble')
53
+ continue;
54
+ const applied = apply(next, change.kind === 'preamble'
55
+ ? { kind: 'preamble', value: change.value, prior: next.preamble }
56
+ : {
57
+ kind: 'section',
58
+ name: change.name,
59
+ value: change.value,
60
+ prior: next.sections[change.name],
61
+ });
62
+ if (applied.outcome === 'applied')
63
+ next = applied.document;
121
64
  }
122
65
  return next;
123
66
  }
124
67
  function bodies(path, before, after) {
125
68
  const changes = [];
126
69
  if (after.preamble !== before.preamble) {
127
- changes.push({
128
- kind: 'preamble',
129
- path,
130
- value: after.preamble
131
- });
70
+ changes.push({ kind: 'preamble', path, value: after.preamble });
132
71
  }
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
- });
72
+ for (const [name, value] of Object.entries(after.sections)) {
73
+ if (before.sections[name] === value)
74
+ continue;
75
+ changes.push({ kind: 'section', path, name, value });
141
76
  }
142
77
  return changes;
143
78
  }
144
79
  function relinks(path, moved, before, moves) {
145
80
  let after = before;
146
- for (const link of links(before, path)){
81
+ for (const link of links(before, path)) {
147
82
  const destination = moves.get(link.target) ?? link.target;
148
- if (destination === link.target && moved === path) continue;
83
+ if (destination === link.target && moved === path)
84
+ continue;
149
85
  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
- });
86
+ if (to === relative(path, link.target))
87
+ continue;
88
+ const applied = apply(after, { kind: 'link', href: link.href, to });
156
89
  if (applied.outcome === 'refuse') {
157
90
  return {
158
91
  kind: 'refused',
159
92
  refusal: {
160
93
  rule: applied.refusal.rule,
161
94
  file: path,
162
- ...applied.refusal.location === undefined ? {} : {
163
- location: applied.refusal.location
164
- }
165
- }
95
+ ...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
96
+ },
166
97
  };
167
98
  }
168
99
  after = applied.document;
169
100
  }
170
- if (after === before) return {
171
- kind: 'changes',
172
- changes: []
173
- };
101
+ if (after === before)
102
+ return { kind: 'changes', changes: [] };
174
103
  if (before.type === undefined) {
175
- return {
176
- kind: 'changes',
177
- changes: [
178
- {
179
- kind: 'replace',
180
- path,
181
- content: serialize(after)
182
- }
183
- ]
184
- };
104
+ return { kind: 'changes', changes: [{ kind: 'replace', path, content: serialize(after) }] };
185
105
  }
186
- return {
187
- kind: 'changes',
188
- changes: bodies(path, before, after)
189
- };
106
+ return { kind: 'changes', changes: bodies(path, before, after) };
190
107
  }
191
108
  function subsumed(merged, path) {
192
- for(let index = merged.length - 1; index >= 0; index -= 1){
109
+ for (let index = merged.length - 1; index >= 0; index -= 1) {
193
110
  const item = merged[index];
194
- if (item.path !== path) continue;
195
- if (item.kind === 'section' || item.kind === 'preamble') merged.splice(index, 1);
111
+ if (item.path !== path)
112
+ continue;
113
+ if (item.kind === 'section' || item.kind === 'preamble')
114
+ merged.splice(index, 1);
196
115
  }
197
116
  }
198
117
  function rewrites(changes, visible) {
199
118
  const moves = new Map();
200
119
  const settled = new Set();
201
- for (const change of changes){
202
- if (change.kind === 'move') moves.set(change.path, change.to);
120
+ for (const change of changes) {
121
+ if (change.kind === 'move')
122
+ moves.set(change.path, change.to);
203
123
  if (change.kind === 'remove' || change.kind === 'replace' || change.kind === 'create') {
204
124
  settled.add(change.path);
205
125
  }
206
- if (change.kind === 'field') settled.add(`${change.path} ${change.name}`);
126
+ if (change.kind === 'field')
127
+ settled.add(`${change.path} ${change.name}`);
207
128
  }
208
- if (moves.size === 0) return {
209
- kind: 'changes',
210
- changes
211
- };
212
- const merged = [
213
- ...changes
214
- ];
129
+ if (moves.size === 0)
130
+ return { kind: 'changes', changes };
131
+ const merged = [...changes];
215
132
  const added = [];
216
- for (const [path, document] of Object.entries(visible.collection.documents)){
217
- if (settled.has(path)) continue;
133
+ for (const [path, document] of Object.entries(visible.collection.documents)) {
134
+ if (settled.has(path))
135
+ continue;
218
136
  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
- }
137
+ const definition = definitionOf(governing(visible.collection.composition, path), document.type);
138
+ added.push(...repointed(document, definition, path, moved, moves, settled));
231
139
  const relinked = relinks(path, moved, edited(document, path, merged), moves);
232
- if (relinked.kind === 'refused') return relinked;
233
- for (const change of relinked.changes){
140
+ if (relinked.kind === 'refused')
141
+ return relinked;
142
+ for (const change of relinked.changes) {
234
143
  if (change.kind === 'replace') {
235
144
  subsumed(merged, path);
236
145
  added.push(change);
237
146
  continue;
238
147
  }
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;
148
+ const index = merged.findIndex((item) => item.kind === change.kind &&
149
+ item.path === path &&
150
+ (change.kind !== 'section' || (item.kind === 'section' && item.name === change.name)));
151
+ if (index === -1)
152
+ added.push(change);
153
+ else
154
+ merged[index] = change;
242
155
  }
243
156
  }
157
+ return { kind: 'changes', changes: [...added, ...merged] };
158
+ }
159
+ function anticipated(changes, preconditions, rewritten, visible) {
160
+ const derived = rewritten.filter((change) => !changes.includes(change));
244
161
  return {
245
- kind: 'changes',
246
- changes: [
247
- ...added,
248
- ...merged
249
- ]
162
+ changes: rewritten,
163
+ preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
250
164
  };
251
165
  }
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;
166
+ export function providerRewritten(batch, visible) {
167
+ const rewritten = rewrites(batch.changes, visible);
168
+ if (rewritten.kind === 'refused')
169
+ return batch;
170
+ return {
171
+ ...batch,
172
+ ...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
173
+ };
263
174
  }
264
175
  function incoming(collection, removed) {
265
176
  const found = new Map();
266
- const note = (target, file)=>{
267
- if (!removed.has(target)) return;
177
+ const note = (target, file) => {
178
+ if (!removed.has(target))
179
+ return;
268
180
  const referrers = found.get(target) ?? new Set();
269
181
  referrers.add(file);
270
182
  found.set(target, referrers);
271
183
  };
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);
184
+ for (const [path, document] of Object.entries(collection.documents)) {
185
+ const definition = definitionOf(governing(collection.composition, path), document.type);
186
+ for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
187
+ if (to.includes(ATTACHMENT))
188
+ continue;
189
+ const named = target(path, value);
190
+ if (named !== null)
191
+ note(named, path);
277
192
  }
278
- for (const link of links(document, path))note(link.target, path);
193
+ for (const link of links(document, path))
194
+ note(link.target, path);
279
195
  }
280
- const [first] = [
281
- ...found.keys()
282
- ].sort();
283
- if (first === undefined) return null;
196
+ const [first] = [...found.keys()].sort();
197
+ if (first === undefined)
198
+ return null;
284
199
  const every = new Set();
285
- for (const referrers of found.values())for (const file of referrers)every.add(file);
200
+ for (const referrers of found.values())
201
+ for (const file of referrers)
202
+ every.add(file);
286
203
  return {
287
204
  kind: 'refused',
288
- refusal: {
289
- rule: 'referenced-missing-file',
290
- file: first,
291
- referrers: [
292
- ...every
293
- ].sort()
294
- }
205
+ refusal: { rule: 'referenced-missing-file', file: first, referrers: [...every].sort() },
295
206
  };
296
207
  }
297
208
  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;
209
+ return change.kind === 'move' ? [change.path, change.to] : [change.path];
316
210
  }
317
211
  function guard(changes, input) {
318
212
  const existing = new Set([
319
213
  ...Object.keys(input.visible.documents),
320
- ...input.visible.binaryTargets
214
+ ...input.visible.binaryTargets,
321
215
  ]);
322
- const definitions = definitionAddresses(input.composition);
323
- for (const change of changes){
324
- for (const path of affected(change)){
216
+ for (const change of changes) {
217
+ for (const path of affected(change)) {
325
218
  const normalized = normalize(path);
326
- if (normalized === null || normalized === '') return refuse('room-root', path);
327
- if (definitions.has(normalized)) return refuse('model-definition', normalized);
219
+ if (normalized === null || normalized === '')
220
+ return refuse('room-root', path);
328
221
  if (covered(input.providerOwnedPaths, normalized)) {
329
222
  return refuse('provider-owned-path', normalized);
330
223
  }
331
224
  const operation = change.kind === 'remove' ? 'delete' : 'write';
332
- if (!covered(input.operations[operation], normalized)) return refuse(`${operation}-grant`, normalized);
225
+ if (!covered(input.operations[operation], normalized))
226
+ return refuse(`${operation}-grant`, normalized);
333
227
  }
334
228
  if (change.kind === 'create') {
335
- if (existing.has(change.path)) return refuse('path-collision', change.path);
229
+ if (existing.has(change.path))
230
+ return refuse('path-collision', change.path);
336
231
  existing.add(change.path);
337
232
  continue;
338
233
  }
339
234
  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);
235
+ if (!existing.has(change.path))
236
+ return refuse('path-absent', change.path);
237
+ if (existing.has(change.to))
238
+ return refuse('path-collision', change.to);
239
+ if (unrevisionedDocument(input.visible, change.path)) {
240
+ return refuse('unknown-revision', change.path);
241
+ }
342
242
  existing.delete(change.path);
343
243
  existing.add(change.to);
344
244
  continue;
345
245
  }
346
- if (!existing.has(change.path)) return refuse('path-absent', change.path);
246
+ if (!existing.has(change.path))
247
+ return refuse('path-absent', change.path);
347
248
  if (change.kind === 'remove') {
249
+ if (unrevisionedDocument(input.visible, change.path)) {
250
+ return refuse('unknown-revision', change.path);
251
+ }
348
252
  existing.delete(change.path);
349
253
  continue;
350
254
  }
@@ -354,77 +258,71 @@ function guard(changes, input) {
354
258
  }
355
259
  continue;
356
260
  }
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);
261
+ if (input.visible.collection.documents[change.path] === undefined) {
262
+ return refuse('path-absent', change.path);
361
263
  }
362
264
  }
363
265
  return null;
364
266
  }
267
+ function unrevisionedDocument(visible, path) {
268
+ return visible.revisions[path] === undefined && !visible.binaryTargets.includes(path);
269
+ }
365
270
  function existence(visible, path) {
366
271
  const revision = visible.revisions[path];
367
- return revision === undefined ? {
368
- kind: 'present',
369
- path
370
- } : {
371
- kind: 'revision',
372
- path,
373
- revision
374
- };
272
+ return revision === undefined ? { kind: 'present', path } : { kind: 'revision', path, revision };
375
273
  }
376
- function preconditionsOf(changes, visible) {
274
+ function sameTarget(precondition, change) {
275
+ if (precondition.kind !== change.kind || precondition.path !== change.path)
276
+ return false;
277
+ return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
278
+ ((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
279
+ }
280
+ function preconditionsOf(changes, visible, bases) {
377
281
  const preconditions = [];
378
282
  const seen = new Set();
379
- const add = (precondition)=>{
283
+ const add = (precondition) => {
380
284
  const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
381
285
  const identity = `${precondition.kind} ${precondition.path} ${part}`;
382
- if (seen.has(identity)) return;
286
+ if (seen.has(identity))
287
+ return;
383
288
  seen.add(identity);
384
289
  preconditions.push(precondition);
385
290
  };
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
- });
405
- continue;
406
- }
407
- if (change.kind === 'preamble') {
291
+ for (const change of changes) {
292
+ if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
293
+ const based = bases.find((base) => sameTarget(base, change));
294
+ if (based !== undefined) {
295
+ add(based);
296
+ continue;
297
+ }
408
298
  const document = visible.collection.documents[change.path];
409
- add({
410
- kind: 'preamble',
411
- path: change.path,
412
- value: document.preamble
413
- });
299
+ if (change.kind === 'field') {
300
+ add({
301
+ kind: 'field',
302
+ path: change.path,
303
+ name: change.name,
304
+ value: document.fields[change.name],
305
+ });
306
+ }
307
+ else if (change.kind === 'section') {
308
+ add({
309
+ kind: 'section',
310
+ path: change.path,
311
+ name: change.name,
312
+ value: document.sections[change.name],
313
+ });
314
+ }
315
+ else
316
+ add({ kind: 'preamble', path: change.path, value: document.preamble });
414
317
  continue;
415
318
  }
416
319
  if (change.kind === 'create') {
417
- add({
418
- kind: 'absent',
419
- path: change.path
420
- });
320
+ add({ kind: 'absent', path: change.path });
421
321
  continue;
422
322
  }
423
323
  add(existence(visible, change.path));
424
- if (change.kind === 'move') add({
425
- kind: 'absent',
426
- path: change.to
427
- });
324
+ if (change.kind === 'move')
325
+ add({ kind: 'absent', path: change.to });
428
326
  }
429
327
  return preconditions;
430
328
  }
@@ -432,90 +330,149 @@ function depth(path) {
432
330
  return path.split('/').length;
433
331
  }
434
332
  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){
333
+ const written = changes.map((change) => (change.kind === 'move' ? change.to : change.path));
334
+ const created = changes
335
+ .filter((change) => change.kind === 'create' || change.kind === 'move')
336
+ .map((change) => (change.kind === 'move' ? change.to : change.path));
337
+ for (const limit of limits) {
438
338
  const value = limit.value;
439
- if (typeof value !== 'number') continue;
339
+ if (typeof value !== 'number')
340
+ continue;
440
341
  if (limit.name === 'change-batch-operations' && changes.length > value) {
441
342
  return refuse('provider-limit', undefined, limit.name);
442
343
  }
344
+ if (limit.name === 'change-documents' &&
345
+ new Set(changes.map((change) => change.path)).size > value) {
346
+ return refuse('provider-limit', undefined, limit.name);
347
+ }
443
348
  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);
349
+ const large = written.find((path) => (resulting.documents[path] ?? '').length > value);
350
+ if (large !== undefined)
351
+ return refuse('provider-limit', large, limit.name);
446
352
  }
447
353
  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);
354
+ const long = created.find((path) => path.length > value);
355
+ if (long !== undefined)
356
+ return refuse('provider-limit', long, limit.name);
450
357
  }
451
358
  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);
359
+ const deep = created.find((path) => depth(path) > value);
360
+ if (deep !== undefined)
361
+ return refuse('provider-limit', deep, limit.name);
454
362
  }
455
363
  }
456
364
  return null;
457
365
  }
366
+ function shellRelative(text, path, handle, definition) {
367
+ if (!text.includes(`/rooms/${handle}/`))
368
+ return null;
369
+ const parsed = parse(text, path);
370
+ if (!parsed.ok)
371
+ return null;
372
+ const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
373
+ return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
374
+ }
375
+ function relativeField(path, name, value, handle, definition) {
376
+ const opened = parse('---\n---\n');
377
+ if (definition === undefined ||
378
+ !opened.ok ||
379
+ !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
380
+ return value;
381
+ const held = apply(opened.document, { kind: 'field', name, value, prior: undefined });
382
+ if (held.outcome !== 'applied')
383
+ return value;
384
+ const result = relativize(held.document, path, handle, definition);
385
+ return result.outcome === 'applied' ? result.document.fields[name] : value;
386
+ }
387
+ function relativeChanges(input) {
388
+ const { handle, composition, visible } = input;
389
+ const typed = (path) => (type) => definitionOf(governing(composition, path), type);
390
+ const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
391
+ return input.changes.map((change) => {
392
+ const definition = typed(change.path);
393
+ if (change.kind === 'field') {
394
+ const type = visible.collection.documents[change.path]?.type;
395
+ const value = relativeField(change.path, change.name, change.value, handle, definition(type));
396
+ return value === change.value ? change : { ...change, value };
397
+ }
398
+ if (change.kind === 'section' || change.kind === 'preamble') {
399
+ const value = body(change.path, change.value);
400
+ return value === change.value ? change : { ...change, value };
401
+ }
402
+ if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
403
+ const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
404
+ return content === change.content ? change : { ...change, content };
405
+ }
406
+ if (change.kind !== 'create')
407
+ return change;
408
+ const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
409
+ const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
410
+ name,
411
+ relativeField(change.path, name, value, handle, definition(type)),
412
+ ]));
413
+ const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
414
+ return { ...change, fields, sections };
415
+ });
416
+ }
458
417
  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');
418
+ const changes = relativeChanges(input);
419
+ const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
420
+ if (structural !== undefined &&
421
+ Object.entries(input.visible.collection.documents).some(([path, document]) => document.type !== undefined &&
422
+ document.type !== 'any' &&
423
+ governing(input.composition, path).status === 'unavailable'))
424
+ return refuse('model-unavailable', structural.path);
425
+ if (changes.length === 0)
426
+ return refuse('no-change');
427
+ const rewritten = rewrites(changes, input.visible);
428
+ if (rewritten.kind === 'refused')
429
+ return rewritten;
467
430
  const refusal = guard(changes, input);
468
- if (refusal !== null) return refusal;
469
- const preconditions = preconditionsOf(changes, input.visible);
431
+ if (refusal !== null)
432
+ return refusal;
433
+ const preconditions = preconditionsOf(changes, input.visible, input.bases);
434
+ const predicted = anticipated(changes, preconditions, rewritten.changes, input.visible);
470
435
  const resulting = overlay(input.visible, [
471
- {
472
- id: '',
473
- baseRoomVersion: input.baseRoomVersion,
474
- changes,
475
- preconditions
476
- }
436
+ { id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
477
437
  ]);
478
- const removed = new Set(changes.filter((change)=>change.kind === 'remove').map((change)=>change.path));
438
+ const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
479
439
  if (removed.size > 0) {
480
440
  const referenced = incoming(parseCollection(resulting.documents, input.composition), removed);
481
- if (referenced !== null) return referenced;
441
+ if (referenced !== null)
442
+ return referenced;
482
443
  }
483
- const typed = (content)=>{
484
- if (content === undefined) return false;
444
+ const typed = (content) => {
445
+ if (content === undefined)
446
+ return false;
485
447
  const parsed = parse(content);
486
448
  return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
487
449
  };
488
- const typedPaths = [
489
- ...new Set(changes.flatMap(affected))
490
- ].filter((path)=>typed(input.visible.documents[path]) || typed(resulting.documents[path]));
450
+ const typedPaths = [...new Set(predicted.changes.flatMap(affected))].filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
491
451
  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);
452
+ const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
453
+ if (withoutModel !== undefined)
454
+ return refuse('model-unavailable', withoutModel);
455
+ const validationText = needsModel
456
+ ? resulting.documents
457
+ : Object.fromEntries(Object.entries(resulting.documents).filter(([path]) => predicted.changes.some((change) => affected(change).includes(path))));
458
+ const verdict = validateCollection(validationText, resulting.binaryTargets, input.composition);
498
459
  if (verdict.verdict === 'refuse') {
499
460
  return {
500
461
  kind: 'refused',
501
462
  refusal: {
502
- ...verdict.source === 'model' ? {
503
- source: 'model'
504
- } : {},
505
463
  rule: verdict.rule,
506
464
  file: verdict.file,
507
- ...verdict.location === undefined ? {} : {
508
- location: verdict.location
509
- },
510
- ...verdict.referrers === undefined ? {} : {
511
- referrers: verdict.referrers
512
- }
513
- }
465
+ ...(verdict.location === undefined ? {} : { location: verdict.location }),
466
+ ...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
467
+ },
514
468
  };
515
469
  }
516
470
  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');
471
+ if (limited !== null)
472
+ return limited;
473
+ const validated = typedPaths
474
+ .map((path) => governing(input.composition, path))
475
+ .find((model) => model.status !== 'unavailable');
519
476
  return {
520
477
  kind: 'batch',
521
478
  batch: immutable({
@@ -523,15 +480,16 @@ export function buildBatch(input) {
523
480
  baseRoomVersion: input.baseRoomVersion,
524
481
  changes,
525
482
  preconditions,
526
- ...validated === undefined ? {} : {
527
- validation: {
528
- identity: validated.snapshot.identity,
529
- version: validated.snapshot.version,
530
- source: validated.status
531
- }
532
- }
533
- })
483
+ ...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
484
+ ...(validated === undefined
485
+ ? {}
486
+ : {
487
+ validation: {
488
+ identity: validated.snapshot.identity,
489
+ version: validated.snapshot.version,
490
+ source: validated.status,
491
+ },
492
+ }),
493
+ }),
534
494
  };
535
495
  }
536
-
537
- //# sourceMappingURL=save.js.map