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

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