@agentshouse/mdruntime 0.1.0-alpha.8 → 0.1.0-c009335e08279

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 (121) hide show
  1. package/AGENTS.md +208 -0
  2. package/README.md +44 -0
  3. package/dist/adapter.d.ts +215 -25
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/adapter.js +2 -3
  6. package/dist/attachment.d.ts +3 -0
  7. package/dist/attachment.d.ts.map +1 -0
  8. package/dist/attachment.js +8 -0
  9. package/dist/bootstrap.d.ts +6 -7
  10. package/dist/bootstrap.d.ts.map +1 -1
  11. package/dist/bootstrap.js +31 -28
  12. package/dist/change-set.d.ts +33 -0
  13. package/dist/change-set.d.ts.map +1 -0
  14. package/dist/change-set.js +83 -0
  15. package/dist/change.d.ts +9 -0
  16. package/dist/change.d.ts.map +1 -1
  17. package/dist/change.js +6 -5
  18. package/dist/client.d.ts +69 -0
  19. package/dist/client.d.ts.map +1 -0
  20. package/dist/client.js +165 -0
  21. package/dist/collection.d.ts +22 -15
  22. package/dist/collection.d.ts.map +1 -1
  23. package/dist/collection.js +60 -78
  24. package/dist/connection.d.ts +68 -40
  25. package/dist/connection.d.ts.map +1 -1
  26. package/dist/connection.js +1597 -961
  27. package/dist/conversation.d.ts +79 -0
  28. package/dist/conversation.d.ts.map +1 -0
  29. package/dist/conversation.js +557 -0
  30. package/dist/durability.d.ts +8 -3
  31. package/dist/durability.d.ts.map +1 -1
  32. package/dist/durability.js +7 -61
  33. package/dist/file.d.ts +20 -0
  34. package/dist/file.d.ts.map +1 -0
  35. package/dist/file.js +126 -0
  36. package/dist/host.d.ts +13 -0
  37. package/dist/host.d.ts.map +1 -0
  38. package/dist/host.js +175 -0
  39. package/dist/identity.d.ts +1 -0
  40. package/dist/identity.d.ts.map +1 -1
  41. package/dist/identity.js +10 -9
  42. package/dist/index.d.ts +14 -10
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +1 -3
  45. package/dist/log.js +5 -6
  46. package/dist/memory.d.ts +17 -2
  47. package/dist/memory.d.ts.map +1 -1
  48. package/dist/memory.js +268 -113
  49. package/dist/model.d.ts +10 -25
  50. package/dist/model.d.ts.map +1 -1
  51. package/dist/model.js +18 -21
  52. package/dist/observation.d.ts +25 -0
  53. package/dist/observation.d.ts.map +1 -0
  54. package/dist/observation.js +85 -0
  55. package/dist/outcome.d.ts +12 -24
  56. package/dist/outcome.d.ts.map +1 -1
  57. package/dist/outcome.js +1 -13
  58. package/dist/overlay.d.ts +9 -2
  59. package/dist/overlay.d.ts.map +1 -1
  60. package/dist/overlay.js +187 -43
  61. package/dist/paths.d.ts +0 -1
  62. package/dist/paths.d.ts.map +1 -1
  63. package/dist/paths.js +12 -19
  64. package/dist/query.d.ts +210 -9
  65. package/dist/query.d.ts.map +1 -1
  66. package/dist/query.js +420 -15
  67. package/dist/read-side.d.ts +14 -0
  68. package/dist/read-side.d.ts.map +1 -0
  69. package/dist/read-side.js +371 -0
  70. package/dist/refresh.d.ts +4 -5
  71. package/dist/refresh.d.ts.map +1 -1
  72. package/dist/refresh.js +45 -30
  73. package/dist/replica.d.ts +27 -6
  74. package/dist/replica.d.ts.map +1 -1
  75. package/dist/replica.js +233 -30
  76. package/dist/room-draft.d.ts +124 -0
  77. package/dist/room-draft.d.ts.map +1 -0
  78. package/dist/room-draft.js +327 -0
  79. package/dist/rows.d.ts +41 -0
  80. package/dist/rows.d.ts.map +1 -0
  81. package/dist/rows.js +100 -0
  82. package/dist/save.d.ts +10 -6
  83. package/dist/save.d.ts.map +1 -1
  84. package/dist/save.js +266 -333
  85. package/dist/search.d.ts +61 -0
  86. package/dist/search.d.ts.map +1 -0
  87. package/dist/search.js +125 -0
  88. package/dist/status.d.ts +9 -2
  89. package/dist/status.d.ts.map +1 -1
  90. package/dist/status.js +11 -14
  91. package/dist/store.d.ts +75 -18
  92. package/dist/store.d.ts.map +1 -1
  93. package/dist/store.js +358 -97
  94. package/dist/transfer.d.ts +5 -0
  95. package/dist/transfer.d.ts.map +1 -1
  96. package/dist/transfer.js +1 -5
  97. package/dist/wire.d.ts +64 -0
  98. package/dist/wire.d.ts.map +1 -0
  99. package/dist/wire.js +118 -0
  100. package/package.json +15 -13
  101. package/dist/adapter.js.map +0 -1
  102. package/dist/bootstrap.js.map +0 -1
  103. package/dist/change.js.map +0 -1
  104. package/dist/collection.js.map +0 -1
  105. package/dist/connection.js.map +0 -1
  106. package/dist/durability.js.map +0 -1
  107. package/dist/identity.js.map +0 -1
  108. package/dist/index.js.map +0 -1
  109. package/dist/log.js.map +0 -1
  110. package/dist/memory.js.map +0 -1
  111. package/dist/model.js.map +0 -1
  112. package/dist/outcome.js.map +0 -1
  113. package/dist/overlay.js.map +0 -1
  114. package/dist/paths.js.map +0 -1
  115. package/dist/query.js.map +0 -1
  116. package/dist/refresh.js.map +0 -1
  117. package/dist/replica.js.map +0 -1
  118. package/dist/save.js.map +0 -1
  119. package/dist/status.js.map +0 -1
  120. package/dist/store.js.map +0 -1
  121. package/dist/transfer.js.map +0 -1
package/dist/save.js CHANGED
@@ -1,336 +1,259 @@
1
- import { apply, links, parse, serialize } from '@agentshouse/mdmodel';
2
- import { immutable } from './change.js';
3
- import { parseCollection, 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';
90
- }
91
- function definitionOf(types, type) {
92
- return types.find((declared)=>declared.name === type);
19
+ return spec.kind === 'reference' || (spec.kind === 'list' && spec.of.kind === 'reference');
93
20
  }
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(visible.collection.types, 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(collection.types, 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
- ];
214
+ return change.kind === 'move' ? [change.path, change.to] : [change.path];
304
215
  }
305
216
  function guard(changes, input) {
306
217
  const existing = new Set([
307
218
  ...Object.keys(input.visible.documents),
308
- ...input.visible.binaryTargets
219
+ ...input.visible.binaryTargets,
309
220
  ]);
310
- for (const change of changes){
311
- for (const path of affected(change)){
221
+ for (const change of changes) {
222
+ for (const path of affected(change)) {
312
223
  const normalized = normalize(path);
313
- if (normalized === null || normalized === '') return refuse('room-root', path);
224
+ if (normalized === null || normalized === '')
225
+ return refuse('room-root', path);
314
226
  if (covered(input.providerOwnedPaths, normalized)) {
315
227
  return refuse('provider-owned-path', normalized);
316
228
  }
317
229
  const operation = change.kind === 'remove' ? 'delete' : 'write';
318
- if (!covered(input.operations[operation], normalized)) return refuse(`${operation}-grant`, normalized);
230
+ if (!covered(input.operations[operation], normalized))
231
+ return refuse(`${operation}-grant`, normalized);
319
232
  }
320
233
  if (change.kind === 'create') {
321
- if (existing.has(change.path)) return refuse('path-collision', change.path);
234
+ if (existing.has(change.path))
235
+ return refuse('path-collision', change.path);
322
236
  existing.add(change.path);
323
237
  continue;
324
238
  }
325
239
  if (change.kind === 'move') {
326
- if (!existing.has(change.path)) return refuse('path-absent', change.path);
327
- 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
+ }
328
247
  existing.delete(change.path);
329
248
  existing.add(change.to);
330
249
  continue;
331
250
  }
332
- if (!existing.has(change.path)) return refuse('path-absent', change.path);
251
+ if (!existing.has(change.path))
252
+ return refuse('path-absent', change.path);
333
253
  if (change.kind === 'remove') {
254
+ if (unrevisionedDocument(input.visible, change.path)) {
255
+ return refuse('unknown-revision', change.path);
256
+ }
334
257
  existing.delete(change.path);
335
258
  continue;
336
259
  }
@@ -340,77 +263,71 @@ function guard(changes, input) {
340
263
  }
341
264
  continue;
342
265
  }
343
- const document = input.visible.collection.documents[change.path];
344
- if (document === undefined) return refuse('path-absent', change.path);
345
- if (change.kind === 'section' && document.sections[change.name] === undefined) {
346
- return refuse('required-section', change.path, change.name);
266
+ if (input.visible.collection.documents[change.path] === undefined) {
267
+ return refuse('path-absent', change.path);
347
268
  }
348
269
  }
349
270
  return null;
350
271
  }
272
+ function unrevisionedDocument(visible, path) {
273
+ return visible.revisions[path] === undefined && !visible.binaryTargets.includes(path);
274
+ }
351
275
  function existence(visible, path) {
352
276
  const revision = visible.revisions[path];
353
- return revision === undefined ? {
354
- kind: 'present',
355
- path
356
- } : {
357
- kind: 'revision',
358
- path,
359
- revision
360
- };
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));
361
284
  }
362
- function preconditionsOf(changes, visible) {
285
+ function preconditionsOf(changes, visible, bases) {
363
286
  const preconditions = [];
364
287
  const seen = new Set();
365
- const add = (precondition)=>{
288
+ const add = (precondition) => {
366
289
  const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
367
290
  const identity = `${precondition.kind} ${precondition.path} ${part}`;
368
- if (seen.has(identity)) return;
291
+ if (seen.has(identity))
292
+ return;
369
293
  seen.add(identity);
370
294
  preconditions.push(precondition);
371
295
  };
372
- for (const change of changes){
373
- if (change.kind === 'field') {
374
- const document = visible.collection.documents[change.path];
375
- add({
376
- kind: 'field',
377
- path: change.path,
378
- name: change.name,
379
- value: document.fields[change.name]
380
- });
381
- continue;
382
- }
383
- if (change.kind === 'section') {
384
- const document = visible.collection.documents[change.path];
385
- add({
386
- kind: 'section',
387
- path: change.path,
388
- name: change.name,
389
- value: document.sections[change.name]
390
- });
391
- continue;
392
- }
393
- 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
+ }
394
303
  const document = visible.collection.documents[change.path];
395
- add({
396
- kind: 'preamble',
397
- path: change.path,
398
- value: document.preamble
399
- });
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 });
400
322
  continue;
401
323
  }
402
324
  if (change.kind === 'create') {
403
- add({
404
- kind: 'absent',
405
- path: change.path
406
- });
325
+ add({ kind: 'absent', path: change.path });
407
326
  continue;
408
327
  }
409
328
  add(existence(visible, change.path));
410
- if (change.kind === 'move') add({
411
- kind: 'absent',
412
- path: change.to
413
- });
329
+ if (change.kind === 'move')
330
+ add({ kind: 'absent', path: change.to });
414
331
  }
415
332
  return preconditions;
416
333
  }
@@ -418,85 +335,100 @@ function depth(path) {
418
335
  return path.split('/').length;
419
336
  }
420
337
  function exceeded(limits, changes, resulting) {
421
- const written = changes.map((change)=>change.kind === 'move' ? change.to : change.path);
422
- const created = changes.filter((change)=>change.kind === 'create' || change.kind === 'move').map((change)=>change.kind === 'move' ? change.to : change.path);
423
- 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) {
424
343
  const value = limit.value;
425
- if (typeof value !== 'number') continue;
344
+ if (typeof value !== 'number')
345
+ continue;
426
346
  if (limit.name === 'change-batch-operations' && changes.length > value) {
427
347
  return refuse('provider-limit', undefined, limit.name);
428
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
+ }
429
353
  if (limit.name === 'document-size') {
430
- const large = written.find((path)=>(resulting.documents[path] ?? '').length > value);
431
- 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);
432
357
  }
433
358
  if (limit.name === 'path-length') {
434
- const long = created.find((path)=>path.length > value);
435
- 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);
436
362
  }
437
363
  if (limit.name === 'path-depth') {
438
- const deep = created.find((path)=>depth(path) > value);
439
- 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);
440
367
  }
441
368
  }
442
369
  return null;
443
370
  }
444
371
  export function buildBatch(input) {
445
- const structural = input.changes.find((change)=>change.kind === 'move' || change.kind === 'remove');
446
- if (structural !== undefined && input.model.status === 'unavailable' && Object.values(input.visible.collection.documents).some((document)=>document.type !== undefined && document.type !== 'any')) return refuse('model-unavailable', structural.path);
447
- const expanded = expand(input.changes, input.visible);
448
- if (expanded.kind === 'refused') return expanded;
449
- const rewritten = rewrites(expanded.changes, input.visible);
450
- if (rewritten.kind === 'refused') return rewritten;
451
- const changes = rewritten.changes;
452
- 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.rewriting === 'provider' ? input.changes : rewritten.changes;
453
384
  const refusal = guard(changes, input);
454
- if (refusal !== null) return refusal;
455
- 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 = input.rewriting === 'provider'
389
+ ? anticipated(changes, preconditions, rewritten.changes, input.visible)
390
+ : { changes, preconditions };
456
391
  const resulting = overlay(input.visible, [
457
- {
458
- id: '',
459
- baseRoomVersion: input.baseRoomVersion,
460
- changes,
461
- preconditions
462
- }
392
+ { id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
463
393
  ]);
464
- const removed = new Set(changes.filter((change)=>change.kind === 'remove').map((change)=>change.path));
394
+ const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
465
395
  if (removed.size > 0) {
466
- const referenced = incoming(parseCollection(resulting.documents, input.model), removed);
467
- if (referenced !== null) return referenced;
396
+ const referenced = incoming(parseCollection(resulting.documents, input.composition), removed);
397
+ if (referenced !== null)
398
+ return referenced;
468
399
  }
469
- const typed = (content)=>{
470
- if (content === undefined) return false;
400
+ const typed = (content) => {
401
+ if (content === undefined)
402
+ return false;
471
403
  const parsed = parse(content);
472
404
  return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
473
405
  };
474
- const needsModel = changes.some((change)=>affected(change).some((path)=>typed(input.visible.documents[path]) || typed(resulting.documents[path])));
475
- if (needsModel && (input.model.status === 'unavailable' || input.binding === null)) {
476
- return refuse('model-unavailable', changes.find((change)=>affected(change).some((path)=>typed(input.visible.documents[path]) || typed(resulting.documents[path])))?.path);
477
- }
478
- const validationText = needsModel ? resulting.documents : Object.fromEntries(Object.entries(resulting.documents).filter(([path])=>changes.some((change)=>affected(change).includes(path))));
479
- const verdict = validateCollection(validationText, resulting.binaryTargets, input.collectionRoot, input.model, input.binding);
406
+ const typedPaths = [...new Set(predicted.changes.flatMap(affected))].filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
407
+ const needsModel = typedPaths.length > 0;
408
+ const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
409
+ if (withoutModel !== undefined)
410
+ return refuse('model-unavailable', withoutModel);
411
+ const validationText = needsModel
412
+ ? resulting.documents
413
+ : Object.fromEntries(Object.entries(resulting.documents).filter(([path]) => predicted.changes.some((change) => affected(change).includes(path))));
414
+ const verdict = validateCollection(validationText, resulting.binaryTargets, input.composition);
480
415
  if (verdict.verdict === 'refuse') {
481
416
  return {
482
417
  kind: 'refused',
483
418
  refusal: {
484
- ...verdict.source === 'model' ? {
485
- source: 'model'
486
- } : {},
487
419
  rule: verdict.rule,
488
420
  file: verdict.file,
489
- ...verdict.location === undefined ? {} : {
490
- location: verdict.location
491
- },
492
- ...verdict.referrers === undefined ? {} : {
493
- referrers: verdict.referrers
494
- }
495
- }
421
+ ...(verdict.location === undefined ? {} : { location: verdict.location }),
422
+ ...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
423
+ },
496
424
  };
497
425
  }
498
426
  const limited = exceeded(input.providerLimits, changes, resulting);
499
- if (limited !== null) return limited;
427
+ if (limited !== null)
428
+ return limited;
429
+ const validated = typedPaths
430
+ .map((path) => governing(input.composition, path))
431
+ .find((model) => model.status !== 'unavailable');
500
432
  return {
501
433
  kind: 'batch',
502
434
  batch: immutable({
@@ -504,15 +436,16 @@ export function buildBatch(input) {
504
436
  baseRoomVersion: input.baseRoomVersion,
505
437
  changes,
506
438
  preconditions,
507
- ...needsModel && input.model.status !== 'unavailable' ? {
508
- validation: {
509
- identity: input.model.snapshot.identity,
510
- version: input.model.snapshot.version,
511
- source: input.model.status
512
- }
513
- } : {}
514
- })
439
+ ...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
440
+ ...(validated === undefined
441
+ ? {}
442
+ : {
443
+ validation: {
444
+ identity: validated.snapshot.identity,
445
+ version: validated.snapshot.version,
446
+ source: validated.status,
447
+ },
448
+ }),
449
+ }),
515
450
  };
516
451
  }
517
-
518
- //# sourceMappingURL=save.js.map