@agentshouse/mdmodel 0.1.0-cbce4f1da228f → 0.1.0-cc58fdf3541b7

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 (63) hide show
  1. package/API.md +152 -142
  2. package/README.md +16 -19
  3. package/dist/blocks.d.ts.map +1 -1
  4. package/dist/blocks.js +29 -7
  5. package/dist/condition.d.ts +42 -10
  6. package/dist/condition.d.ts.map +1 -1
  7. package/dist/condition.js +220 -87
  8. package/dist/definition.d.ts +3 -1
  9. package/dist/definition.d.ts.map +1 -1
  10. package/dist/definition.js +105 -21
  11. package/dist/divide.js +9 -9
  12. package/dist/document.d.ts +6 -5
  13. package/dist/document.d.ts.map +1 -1
  14. package/dist/document.js +309 -187
  15. package/dist/index.d.ts +9 -7
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +5 -5
  18. package/dist/links.d.ts +1 -0
  19. package/dist/links.d.ts.map +1 -1
  20. package/dist/links.js +85 -39
  21. package/dist/paths.d.ts +9 -1
  22. package/dist/paths.d.ts.map +1 -1
  23. package/dist/paths.js +28 -2
  24. package/dist/pattern.d.ts +10 -0
  25. package/dist/pattern.d.ts.map +1 -0
  26. package/dist/pattern.js +355 -0
  27. package/dist/project.d.ts.map +1 -1
  28. package/dist/project.js +7 -7
  29. package/dist/references.d.ts.map +1 -1
  30. package/dist/references.js +6 -1
  31. package/dist/schema.d.ts +1 -0
  32. package/dist/schema.d.ts.map +1 -1
  33. package/dist/schema.js +55 -4
  34. package/dist/types.d.ts +32 -3
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/validate.d.ts +27 -4
  37. package/dist/validate.d.ts.map +1 -1
  38. package/dist/validate.js +225 -70
  39. package/dist/yaml.d.ts.map +1 -1
  40. package/dist/yaml.js +36 -3
  41. package/fixtures/README.md +9 -4
  42. package/fixtures/cases/conditional-section/context.json +1 -0
  43. package/fixtures/cases/conditional-section/expected.json +6 -0
  44. package/fixtures/cases/conditional-section/library/questions/hosting.md +9 -0
  45. package/fixtures/cases/conditional-section/library/questions/payments.md +12 -0
  46. package/fixtures/cases/conditional-section/schema.yaml +14 -0
  47. package/fixtures/cases/cross-room-link/context.json +1 -0
  48. package/fixtures/cases/cross-room-link/expected.json +1 -0
  49. package/fixtures/cases/cross-room-link/library/clients/acme.md +6 -0
  50. package/fixtures/cases/cross-room-link/schema.yaml +3 -0
  51. package/fixtures/cases/cross-room-reference/context.json +1 -0
  52. package/fixtures/cases/cross-room-reference/expected.json +1 -0
  53. package/fixtures/cases/cross-room-reference/library/clients/acme.md +9 -0
  54. package/fixtures/cases/cross-room-reference/schema.yaml +9 -0
  55. package/fixtures/cases/member-on-an-untyped-document/context.json +1 -0
  56. package/fixtures/cases/member-on-an-untyped-document/expected.json +6 -0
  57. package/fixtures/cases/member-on-an-untyped-document/library/note.md +4 -0
  58. package/fixtures/cases/member-on-another-type/context.json +1 -0
  59. package/fixtures/cases/member-on-another-type/expected.json +6 -0
  60. package/fixtures/cases/member-on-another-type/library/note.md +4 -0
  61. package/fixtures/cases/member-on-another-type/schema.yaml +7 -0
  62. package/fixtures/division.json +26 -26
  63. package/package.json +7 -2
package/dist/document.js CHANGED
@@ -1,24 +1,35 @@
1
1
  import { ARRAY_INDEX, pointerSegments } from './definition.js';
2
- import { hrefOccurrences } from './links.js';
3
- import { destinationPath, relative, retarget, SHELL_ROOMS, shellTarget, target } from './paths.js';
2
+ import { destinations, hrefOccurrences, linkInventory, } from './links.js';
3
+ import { destinationPath, relative, retarget, roomPath, roomTarget, SHELL_ROOMS, shellTarget, target, } from './paths.js';
4
4
  import { references } from './references.js';
5
5
  import { headingSpans, sectionsRecord } from './sections.js';
6
6
  import { refusal } from './refuse.js';
7
- import { bodyStartAfterYaml, bodyStartOf, equalValue, extractFrontmatter, frontmatterValue, jsValue, lineEnding, mapEntry, parseYamlMapping, serializeJs, serializeKey, YamlError, } from './yaml.js';
8
- const internals = new WeakMap();
7
+ import { bodyAfterFrontmatter, bodyStartAfterYaml, bodyStartOf, equalValue, extractFrontmatter, frontmatterValue, jsValue, lineEnding, mapEntry, parseYamlMapping, serializeJs, serializeKey, YamlError, } from './yaml.js';
8
+ const HELD = Symbol('held');
9
+ function heldOf(document) {
10
+ return document[HELD];
11
+ }
12
+ export function inventoryOf(document) {
13
+ const held = heldOf(document);
14
+ if (held?.inventory !== undefined)
15
+ return held.inventory;
16
+ const inventory = linkInventory(document.content.slice(bodyStartOf(document.content)));
17
+ if (held !== undefined)
18
+ held.inventory = inventory;
19
+ return inventory;
20
+ }
9
21
  function refuse(file, rule, location) {
10
22
  return location === undefined ? refusal(file, rule) : refusal(file, rule, { location });
11
23
  }
12
24
  function freezeDocument(content, type, fields, preamble, sections, internal) {
13
- const document = Object.freeze({
25
+ const held = { internal, inventory: undefined };
26
+ return Object.freeze(Object.defineProperty({
14
27
  content,
15
28
  type,
16
29
  fields: Object.freeze(fields),
17
30
  preamble,
18
31
  sections: Object.freeze(sections),
19
- });
20
- internals.set(document, internal);
21
- return document;
32
+ }, HELD, { value: held }));
22
33
  }
23
34
  function declaredType(fields) {
24
35
  const value = fields.type;
@@ -61,6 +72,15 @@ export function parseFrontmatter(content, file = '') {
61
72
  }),
62
73
  };
63
74
  }
75
+ export function outline(content, file) {
76
+ const read = parseFrontmatter(content, file);
77
+ const targets = bodyLinks(bodyAfterFrontmatter(content), file).map((link) => link.target);
78
+ return {
79
+ type: read.ok ? (read.frontmatter.type ?? 'any') : null,
80
+ fields: read.ok ? read.frontmatter.fields : null,
81
+ links: [...new Set(targets)],
82
+ };
83
+ }
64
84
  export function parse(content, file = '') {
65
85
  const read = opened(content, file);
66
86
  if (!read.ok)
@@ -72,86 +92,6 @@ export function parse(content, file = '') {
72
92
  document: freezeDocument(content, declaredType(read.fields), read.fields, content.slice(bodyStart, spans[0]?.headingStart), sectionsRecord(spans, content), { root: read.root, yamlEnd: read.yamlEnd, bodyStart }),
73
93
  };
74
94
  }
75
- function splice(content, start, end, insert) {
76
- return content.slice(0, start) + insert + content.slice(end);
77
- }
78
- function applyField(document, name, value, prior) {
79
- const internal = internals.get(document);
80
- if (internal === undefined || internal.root === null) {
81
- return { outcome: 'refuse', refusal: refuse('', 'frontmatter-fence', name) };
82
- }
83
- if (!equalValue(document.fields[name], prior)) {
84
- return { outcome: 'stale', document };
85
- }
86
- if (!frontmatterValue(value)) {
87
- return { outcome: 'refuse', refusal: refuse('', 'frontmatter-fence', name) };
88
- }
89
- let nextContent;
90
- const existing = mapEntry(internal.root, name);
91
- if (existing !== undefined) {
92
- const held = existing.value;
93
- const inline = held.start > existing.after &&
94
- !/[\r\n]/.test(document.content.slice(existing.after, held.start));
95
- nextContent = inline
96
- ? splice(document.content, held.start, held.end, serializeJs(value))
97
- : splice(document.content, existing.after, held.end, ` ${serializeJs(value)}`);
98
- }
99
- else if (document.content[internal.root.start] === '{') {
100
- nextContent = splice(document.content, internal.root.end - 1, internal.root.end - 1, `${internal.root.entries.length === 0 ? '' : ', '}${serializeKey(name)}: ${serializeJs(value)}`);
101
- }
102
- else {
103
- const nl = lineEnding(document.content);
104
- const lineStart = document.content.lastIndexOf('\n', internal.root.start - 1) + 1;
105
- const indent = document.content.slice(lineStart, internal.root.start);
106
- const line = `${indent}${serializeKey(name)}: ${serializeJs(value)}${nl}`;
107
- const before = document.content.slice(0, internal.yamlEnd);
108
- const prefix = before.endsWith('\n') || before.endsWith('\r\n') ? '' : nl;
109
- nextContent = splice(document.content, internal.yamlEnd, internal.yamlEnd, `${prefix}${line}`);
110
- }
111
- const parsed = parse(nextContent);
112
- if (!parsed.ok)
113
- return { outcome: 'refuse', refusal: parsed.refusal };
114
- return { outcome: 'applied', document: parsed.document };
115
- }
116
- function applySection(document, name, value, prior) {
117
- const internal = internals.get(document);
118
- if (internal === undefined)
119
- return { outcome: 'refuse', refusal: refuse('', 'required-section', name) };
120
- const spans = headingSpans(document.content, internal.bodyStart);
121
- const span = spans.find((item) => item.name === name);
122
- if (span === undefined)
123
- return { outcome: 'refuse', refusal: refuse('', 'required-section', name) };
124
- const current = document.content.slice(span.bodyStart, span.bodyEnd);
125
- if (current !== prior)
126
- return { outcome: 'stale', document };
127
- return replaced(document, internal.bodyStart, span.bodyStart, span.bodyEnd, value);
128
- }
129
- function applyPreamble(document, value, prior) {
130
- if (document.preamble !== prior)
131
- return { outcome: 'stale', document };
132
- const start = bodyStartOf(document.content);
133
- return replaced(document, start, start, start + document.preamble.length, value);
134
- }
135
- function firstHeadings(content, bodyStart) {
136
- const first = new Map();
137
- for (const span of headingSpans(content, bodyStart)) {
138
- if (!first.has(span.name))
139
- first.set(span.name, span.headingStart);
140
- }
141
- return first;
142
- }
143
- function replaced(document, bodyStart, start, end, value) {
144
- const content = splice(document.content, start, end, value);
145
- const after = firstHeadings(content, bodyStartOf(content));
146
- for (const [name, at] of firstHeadings(document.content, bodyStart)) {
147
- if (after.get(name) !== (at < start ? at : at + value.length - (end - start)))
148
- return { outcome: 'refuse', refusal: refuse('', 'overlapping-section', name) };
149
- }
150
- const parsed = parse(content);
151
- if (!parsed.ok)
152
- return { outcome: 'refuse', refusal: parsed.refusal };
153
- return { outcome: 'applied', document: parsed.document };
154
- }
155
95
  function fitsBare(to) {
156
96
  if (to === '' || to.startsWith('<'))
157
97
  return false;
@@ -179,16 +119,17 @@ function destinationText(to, bracketed) {
179
119
  return `<${to}>`;
180
120
  return null;
181
121
  }
182
- function relinked(content, bodyStart, occurrences, written) {
183
- const unwritable = new Set();
184
- for (const match of occurrences) {
185
- const to = written.get(match.href);
186
- if (to !== undefined && destinationText(to, match.bracketed) === null)
187
- unwritable.add(match.href);
188
- }
189
- for (const [href, to] of written) {
190
- if (unwritable.has(href))
122
+ function shellRelinked(body, file, handle) {
123
+ const occurrences = hrefOccurrences(body);
124
+ const written = new Map();
125
+ for (const href of new Set(occurrences.map((occurrence) => occurrence.href))) {
126
+ const path = shellTarget(href, handle);
127
+ if (path === null)
128
+ continue;
129
+ const to = retarget(href, relative(file, path));
130
+ if (destinationText(to, false) === null)
191
131
  return refuse('', 'unwritable-link', to);
132
+ written.set(href, to);
192
133
  }
193
134
  let rewritten = '';
194
135
  let cursor = 0;
@@ -196,45 +137,16 @@ function relinked(content, bodyStart, occurrences, written) {
196
137
  const to = written.get(match.href);
197
138
  if (to === undefined)
198
139
  continue;
199
- rewritten += content.slice(cursor, bodyStart + match.start);
200
- rewritten += destinationText(to, match.bracketed);
201
- cursor = bodyStart + match.end;
202
- }
203
- return rewritten + content.slice(cursor);
204
- }
205
- function shellRelinked(content, bodyStart, file, handle) {
206
- const occurrences = hrefOccurrences(content.slice(bodyStart));
207
- const written = new Map();
208
- for (const href of new Set(occurrences.map((occurrence) => occurrence.href))) {
209
- const path = shellTarget(href, handle);
210
- if (path !== null)
211
- written.set(href, retarget(href, relative(file, path)));
140
+ rewritten += body.slice(cursor, match.start) + destinationText(to, match.bracketed);
141
+ cursor = match.end;
212
142
  }
213
- return relinked(content, bodyStart, occurrences, written);
214
- }
215
- function reparsed(document, content) {
216
- if (typeof content !== 'string')
217
- return { outcome: 'refuse', refusal: content };
218
- if (content === document.content)
219
- return { outcome: 'applied', document };
220
- const parsed = parse(content);
221
- if (!parsed.ok)
222
- return { outcome: 'refuse', refusal: parsed.refusal };
223
- return { outcome: 'applied', document: parsed.document };
143
+ return rewritten + body.slice(cursor);
224
144
  }
225
- function applyLink(document, href, to) {
226
- const content = document.content;
227
- const bodyStart = bodyStartOf(content);
228
- const occurrences = hrefOccurrences(content.slice(bodyStart));
229
- if (!occurrences.some((occurrence) => occurrence.href === href))
230
- return { outcome: 'stale', document };
231
- return reparsed(document, relinked(content, bodyStart, occurrences, new Map([[href, retarget(href, to)]])));
232
- }
233
- function nodeAt(root, pointer) {
145
+ function nodeAt(root, pointer, entryOf = mapEntry) {
234
146
  let node = root;
235
147
  for (const key of pointerSegments(pointer)) {
236
148
  if (node?.kind === 'map')
237
- node = mapEntry(node, key)?.value;
149
+ node = entryOf(node, key)?.value;
238
150
  else if (node?.kind === 'seq' && ARRAY_INDEX.test(key))
239
151
  node = node.items[Number(key)];
240
152
  else
@@ -249,37 +161,36 @@ function scalarText(source, value) {
249
161
  return JSON.stringify(value);
250
162
  return serializeJs(value);
251
163
  }
252
- function applyReference(document, pointer, value, prior) {
253
- const root = internals.get(document)?.root;
254
- const node = root === null || root === undefined ? undefined : nodeAt(root, pointer);
255
- if (node?.kind !== 'scalar' || node.value !== prior)
256
- return { outcome: 'stale', document };
257
- const text = scalarText(document.content[node.start], value);
258
- const parsed = parse(splice(document.content, node.start, node.end, text));
259
- if (!parsed.ok)
260
- return { outcome: 'refuse', refusal: parsed.refusal };
261
- return { outcome: 'applied', document: parsed.document };
262
- }
263
- export function relativize(document, file, handle, definition) {
264
- let held = document;
164
+ export function relativize(document, file, handle, definition, kept = new Set()) {
165
+ const relativized = (href) => {
166
+ const path = shellTarget(href, handle);
167
+ return path === null || kept.has(`${SHELL_ROOMS}${handle}/${path}`) ? null : path;
168
+ };
169
+ const changes = [];
265
170
  for (const { pointer, value } of definition === undefined
266
171
  ? []
267
172
  : references(document, definition)) {
268
- const path = shellTarget(value, handle);
269
- if (path === null)
270
- continue;
271
- const result = applyReference(held, pointer, retarget(value, relative(file, path)), value);
272
- if (result.outcome !== 'applied')
273
- return result;
274
- held = result.document;
173
+ const path = relativized(value);
174
+ if (path !== null)
175
+ changes.push({
176
+ kind: 'reference',
177
+ pointer,
178
+ value: retarget(value, relative(file, path)),
179
+ prior: value,
180
+ });
181
+ }
182
+ for (const href of new Set(destinations(inventoryOf(document)).map(({ href }) => href))) {
183
+ const path = relativized(href);
184
+ if (path !== null)
185
+ changes.push({ kind: 'link', href, to: relative(file, path) });
275
186
  }
276
- return reparsed(held, shellRelinked(held.content, bodyStartOf(held.content), file, handle));
187
+ return apply(document, changes);
277
188
  }
278
189
  export function relativizeBody(body, file, handle) {
279
- const relinkedBody = shellRelinked(body, 0, file, handle);
190
+ const relinkedBody = shellRelinked(body, file, handle);
280
191
  if (typeof relinkedBody !== 'string')
281
192
  return { outcome: 'refuse', refusal: relinkedBody };
282
- const elsewhere = hrefOccurrences(relinkedBody).find(({ href }) => destinationPath(href)?.startsWith(SHELL_ROOMS) === true);
193
+ const elsewhere = hrefOccurrences(relinkedBody).find(({ href }) => destinationPath(href)?.startsWith(SHELL_ROOMS) === true && roomTarget(href) === null);
283
194
  if (elsewhere !== undefined)
284
195
  return { outcome: 'refuse', refusal: refuse(file, 'unresolved-link', elsewhere.href) };
285
196
  return { outcome: 'applied', body: relinkedBody };
@@ -288,11 +199,11 @@ export function relinkBody(body, file, moves) {
288
199
  let rewritten = '';
289
200
  let cursor = 0;
290
201
  for (const match of hrefOccurrences(body)) {
291
- const from = target(file, match.href);
202
+ const from = target(file, match.href) ?? roomPath(match.href);
292
203
  const to = from === null ? undefined : moves.get(from);
293
204
  if (to === undefined)
294
205
  continue;
295
- const written = retarget(match.href, relative(file, to));
206
+ const written = retarget(match.href, from.startsWith(SHELL_ROOMS) || to.startsWith(SHELL_ROOMS) ? to : relative(file, to));
296
207
  const text = destinationText(written, match.bracketed);
297
208
  if (text === null)
298
209
  return { outcome: 'refuse', refusal: refuse(file, 'unwritable-link', written) };
@@ -301,54 +212,265 @@ export function relinkBody(body, file, moves) {
301
212
  }
302
213
  return { outcome: 'applied', body: rewritten + body.slice(cursor) };
303
214
  }
304
- function occurrencesIn(text, file, offset) {
305
- const found = [];
306
- for (const occurrence of hrefOccurrences(text)) {
307
- const resolved = target(file, occurrence.href);
308
- if (resolved !== null)
309
- found.push({ href: occurrence.href, target: resolved, start: offset + occurrence.start });
310
- }
311
- return found;
312
- }
313
- function linksIn(text, file) {
215
+ function linksIn(occurrences, file) {
314
216
  const found = [];
315
217
  const seen = new Set();
316
- for (const occurrence of occurrencesIn(text, file, 0)) {
218
+ for (const occurrence of occurrences) {
317
219
  if (seen.has(occurrence.href))
318
220
  continue;
221
+ const resolved = target(file, occurrence.href);
222
+ if (resolved === null)
223
+ continue;
319
224
  seen.add(occurrence.href);
320
- found.push({ href: occurrence.href, target: occurrence.target });
225
+ found.push({ href: occurrence.href, target: resolved });
321
226
  }
322
227
  return found;
323
228
  }
324
229
  export function links(document, file) {
325
- return linksIn(document.content.slice(bodyStartOf(document.content)), file);
326
- }
327
- export function linkOccurrences(document, file) {
328
- const bodyStart = bodyStartOf(document.content);
329
- return occurrencesIn(document.content.slice(bodyStart), file, bodyStart);
230
+ return linksIn(destinations(inventoryOf(document)), file);
330
231
  }
331
232
  export function bodyLinks(body, file) {
332
- return linksIn(body, file);
233
+ return linksIn(hrefOccurrences(body), file);
333
234
  }
334
- export function apply(document, change) {
235
+ function internalOf(document) {
236
+ const held = heldOf(document);
237
+ if (held !== undefined)
238
+ return held.internal;
239
+ const read = opened(document.content, '');
240
+ if (!read.ok)
241
+ return read.refusal;
242
+ return {
243
+ root: read.root,
244
+ yamlEnd: read.yamlEnd,
245
+ bodyStart: read.root === null ? 0 : bodyStartAfterYaml(document.content, read.yamlEnd),
246
+ };
247
+ }
248
+ function partOf(change) {
249
+ if (change.kind === 'field' || change.kind === 'section')
250
+ return `${change.kind} ${change.name}`;
335
251
  if (change.kind === 'link')
336
- return applyLink(document, change.href, change.to);
252
+ return `link ${change.href}`;
337
253
  if (change.kind === 'reference')
338
- return applyReference(document, change.pointer, change.value, change.prior);
339
- if (change.kind === 'preamble')
340
- return applyPreamble(document, change.value, change.prior);
341
- if (change.kind === 'field')
342
- return applyField(document, change.name, change.value, change.prior);
343
- return applySection(document, change.name, change.value, change.prior);
254
+ return `reference ${change.pointer}`;
255
+ return 'preamble';
256
+ }
257
+ function planner(document, internal) {
258
+ const content = document.content;
259
+ const keyed = new Map();
260
+ let spans;
261
+ let occurrences;
262
+ let ending;
263
+ let added = 0;
264
+ const entryOf = (map, key) => {
265
+ let index = keyed.get(map);
266
+ if (index === undefined) {
267
+ index = new Map();
268
+ for (const entry of map.entries)
269
+ if (!index.has(entry.key))
270
+ index.set(entry.key, entry);
271
+ keyed.set(map, index);
272
+ }
273
+ return index.get(key);
274
+ };
275
+ const field = (name, value, prior, at) => {
276
+ const root = internal.root;
277
+ if (root === null)
278
+ return refuse('', 'frontmatter-fence', name);
279
+ if (!equalValue(document.fields[name], prior))
280
+ return 'stale';
281
+ if (!frontmatterValue(value))
282
+ return refuse('', 'frontmatter-fence', name);
283
+ const existing = entryOf(root, name);
284
+ if (existing !== undefined) {
285
+ const held = existing.value;
286
+ const inline = held.start > existing.after && !/[\r\n]/.test(content.slice(existing.after, held.start));
287
+ return inline
288
+ ? [{ start: held.start, end: held.end, text: serializeJs(value), at }]
289
+ : [{ start: existing.after, end: held.end, text: ` ${serializeJs(value)}`, at }];
290
+ }
291
+ const entry = `${serializeKey(name)}: ${serializeJs(value)}`;
292
+ added += 1;
293
+ if (content[root.start] === '{') {
294
+ const separator = root.entries.length === 0 && added === 1 ? '' : ', ';
295
+ return [{ start: root.end - 1, end: root.end - 1, text: `${separator}${entry}`, at }];
296
+ }
297
+ const lineStart = content.lastIndexOf('\n', root.start - 1) + 1;
298
+ ending ??= lineEnding(content);
299
+ const line = `${content.slice(lineStart, root.start)}${entry}${ending}`;
300
+ return [{ start: internal.yamlEnd, end: internal.yamlEnd, text: line, at, entry: true }];
301
+ };
302
+ const section = (name, value, prior, at) => {
303
+ if (spans === undefined) {
304
+ spans = new Map();
305
+ for (const span of headingSpans(content, internal.bodyStart))
306
+ if (!spans.has(span.name))
307
+ spans.set(span.name, span);
308
+ }
309
+ const span = spans.get(name);
310
+ if (span === undefined)
311
+ return refuse('', 'required-section', name);
312
+ if (content.slice(span.bodyStart, span.bodyEnd) !== prior)
313
+ return 'stale';
314
+ return [{ start: span.bodyStart, end: span.bodyEnd, text: value, at }];
315
+ };
316
+ const link = (href, to, at) => {
317
+ if (occurrences === undefined) {
318
+ occurrences = new Map();
319
+ for (const occurrence of destinations(inventoryOf(document))) {
320
+ const same = occurrences.get(occurrence.href);
321
+ if (same === undefined)
322
+ occurrences.set(occurrence.href, [occurrence]);
323
+ else
324
+ same.push(occurrence);
325
+ }
326
+ }
327
+ const found = occurrences.get(href);
328
+ if (found === undefined)
329
+ return 'stale';
330
+ const written = retarget(href, to);
331
+ if (destinationText(written, false) === null)
332
+ return refuse('', 'unwritable-link', written);
333
+ return found.map((occurrence) => ({
334
+ start: internal.bodyStart + occurrence.start,
335
+ end: internal.bodyStart + occurrence.end,
336
+ text: destinationText(written, occurrence.bracketed),
337
+ at,
338
+ }));
339
+ };
340
+ const reference = (pointer, value, prior, at) => {
341
+ const node = internal.root === null ? undefined : nodeAt(internal.root, pointer, entryOf);
342
+ if (node?.kind !== 'scalar' || node.value !== prior)
343
+ return 'stale';
344
+ return [
345
+ { start: node.start, end: node.end, text: scalarText(content[node.start], value), at },
346
+ ];
347
+ };
348
+ return (change, at) => {
349
+ if (change.kind === 'field')
350
+ return field(change.name, change.value, change.prior, at);
351
+ if (change.kind === 'section')
352
+ return section(change.name, change.value, change.prior, at);
353
+ if (change.kind === 'link')
354
+ return link(change.href, change.to, at);
355
+ if (change.kind === 'reference')
356
+ return reference(change.pointer, change.value, change.prior, at);
357
+ if (document.preamble !== change.prior)
358
+ return 'stale';
359
+ const start = internal.bodyStart;
360
+ return [{ start, end: start + document.preamble.length, text: change.value, at }];
361
+ };
362
+ }
363
+ function overlapping(edits, stale) {
364
+ let reach = -1;
365
+ let owner = -1;
366
+ for (const edit of edits) {
367
+ if (edit.start < reach)
368
+ stale.add(Math.max(edit.at, owner));
369
+ if (edit.end > reach) {
370
+ reach = edit.end;
371
+ owner = edit.at;
372
+ }
373
+ }
374
+ }
375
+ function firstHeadings(content, bodyStart) {
376
+ const first = new Map();
377
+ for (const span of headingSpans(content, bodyStart)) {
378
+ if (!first.has(span.name))
379
+ first.set(span.name, span.headingStart);
380
+ }
381
+ return first;
382
+ }
383
+ function hidden(before, bodyStart, after, shifts) {
384
+ const placed = firstHeadings(after, bodyStartOf(after));
385
+ let shift = 0;
386
+ let index = 0;
387
+ for (const [name, at] of firstHeadings(before, bodyStart)) {
388
+ while (index < shifts.length && shifts[index][0] <= at) {
389
+ shift += shifts[index][1];
390
+ index += 1;
391
+ }
392
+ if (placed.get(name) !== at + shift)
393
+ return name;
394
+ }
395
+ return undefined;
396
+ }
397
+ function spliced(source, edits) {
398
+ const shifts = [];
399
+ const pieces = [];
400
+ let cursor = 0;
401
+ let entered = false;
402
+ let ended = false;
403
+ for (const edit of edits) {
404
+ const kept = source.slice(cursor, edit.start);
405
+ pieces.push(kept);
406
+ if (kept !== '')
407
+ ended = kept.endsWith('\n');
408
+ let text = edit.text;
409
+ if (edit.entry === true && !entered) {
410
+ entered = true;
411
+ if (!ended)
412
+ text = `${lineEnding(source)}${text}`;
413
+ }
414
+ pieces.push(text);
415
+ if (text !== '')
416
+ ended = text.endsWith('\n');
417
+ shifts.push([edit.start, text.length - (edit.end - edit.start)]);
418
+ cursor = edit.end;
419
+ }
420
+ pieces.push(source.slice(cursor));
421
+ return { content: pieces.join(''), shifts };
422
+ }
423
+ export function apply(document, changes) {
424
+ const internal = internalOf(document);
425
+ if (!('bodyStart' in internal))
426
+ return { outcome: 'refuse', refusal: internal };
427
+ const plan = planner(document, internal);
428
+ const edits = [];
429
+ const stale = new Set();
430
+ const named = new Set();
431
+ for (const [at, change] of changes.entries()) {
432
+ const planned = plan(change, at);
433
+ if (planned === 'stale') {
434
+ stale.add(at);
435
+ continue;
436
+ }
437
+ if (!Array.isArray(planned))
438
+ return { outcome: 'refuse', refusal: planned };
439
+ const part = partOf(change);
440
+ if (named.has(part)) {
441
+ stale.add(at);
442
+ continue;
443
+ }
444
+ named.add(part);
445
+ for (const edit of planned)
446
+ edits.push(edit);
447
+ }
448
+ edits.sort((left, right) => left.start - right.start || left.end - right.end || left.at - right.at);
449
+ overlapping(edits, stale);
450
+ if (stale.size > 0)
451
+ return { outcome: 'stale', document, stale: [...stale].sort((left, right) => left - right) };
452
+ const source = document.content;
453
+ const { content } = spliced(source, edits);
454
+ if (content === source)
455
+ return { outcome: 'applied', document };
456
+ const replaced = edits.filter((edit) => {
457
+ const kind = changes[edit.at].kind;
458
+ return kind === 'section' || kind === 'preamble';
459
+ });
460
+ if (replaced.length > 0) {
461
+ const checked = spliced(source, replaced);
462
+ const name = hidden(source, internal.bodyStart, checked.content, checked.shifts);
463
+ if (name !== undefined)
464
+ return { outcome: 'refuse', refusal: refuse('', 'overlapping-section', name) };
465
+ }
466
+ const parsed = parse(content);
467
+ if (!parsed.ok)
468
+ return { outcome: 'refuse', refusal: parsed.refusal };
469
+ return { outcome: 'applied', document: parsed.document };
344
470
  }
345
471
  export function serialize(document) {
346
472
  return document.content;
347
473
  }
348
- export function fieldStart(document, pointer) {
349
- const root = internals.get(document)?.root;
350
- return root === null || root === undefined ? undefined : nodeAt(root, pointer)?.start;
351
- }
352
474
  export function frontmatter(document) {
353
- return internals.get(document)?.root ?? null;
475
+ return heldOf(document)?.internal.root ?? null;
354
476
  }
package/dist/index.d.ts CHANGED
@@ -1,18 +1,20 @@
1
- export { dated, datePointers, factKind, facts, fires, holds, instant, itemFires, pointed, prosePaths, WRITE_DOORS, } from './condition.js';
2
- export type { Actor, Condition, ConditionScope, ConditionTest, ConditionValue, CronCondition, DateCondition, DocumentCondition, DocumentEvent, DocumentFacts, Fact, FactKind, ItemCondition, ItemFacts, Resolved, WriteDoor, } from './condition.js';
1
+ export { ACTOR_PATHS, actorOperators, changeFields, CONDITION_OPERATORS, dated, datePointers, DOCUMENT_PATHS, factKind, facts, fires, holds, inScope, instant, ITEM_PATHS, itemFires, itemKind, itemOperators, memberPointers, members, MESSAGE_PATHS, messageFires, messageOperators, notificationFires, pointed, prosePaths, testOperators, TEXT_FIELD, valuedChange, WRITE_DOORS, } from './condition.js';
2
+ export type { Actor, Condition, ConditionOperator, ConditionScope, ConditionTest, ConditionValue, CronCondition, DateCondition, DocumentCondition, DocumentEvent, DocumentFacts, Fact, FactKind, ItemCondition, ItemFacts, MessageCondition, MessageFacts, NotificationCondition, Resolved, WriteDoor, } from './condition.js';
3
3
  export { headingLines } from './blocks.js';
4
4
  export type { HeadingLine } from './blocks.js';
5
5
  export { divide } from './divide.js';
6
- export { apply, bodyLinks, fieldStart, linkOccurrences, links, parse, parseFrontmatter, relativize, relativizeBody, relinkBody, serialize, } from './document.js';
6
+ export { apply, bodyLinks, links, outline, parse, parseFrontmatter, relativize, relativizeBody, relinkBody, serialize, } from './document.js';
7
7
  export { HOUSE_FIELDS, MEMBER_FIELD, RECEIVED_FIELD, SITE_FIELD, TYPE_FIELD } from './fields.js';
8
- export { relative, retarget, target } from './paths.js';
8
+ export { relative, retarget, roomPath, roomTarget, target } from './paths.js';
9
+ export type { RoomTarget } from './paths.js';
9
10
  export { MARKDOWN_SYNTAX_PROFILE } from './profile.js';
10
11
  export { project } from './project.js';
11
12
  export { references } from './references.js';
12
- export { ARRAY_INDEX, ATTACHMENT, crossedFacts, escapePointer, itemFacts, memberFacts, pointerFacts, pointerSegments, } from './definition.js';
13
+ export { ARRAY_INDEX, ATTACHMENT, crossedFacts, escapePointer, itemFacts, JSON_POINTER, memberFacts, pointerFacts, pointerSegments, } from './definition.js';
13
14
  export type { Definition, FieldRules, JsonType, PatternFacts, PointerFacts, RuleValue, SectionSpec, Shape, Transition, ValueFacts, } from './definition.js';
14
- export type { ApplyResult, BodyResult, Change, Division, DivisionContext, Document, DocumentCheck, Frontmatter, FrontmatterResult, Library, ModelVerification, Link, LinkOccurrence, ParseResult, Projection, ProjectionContext, Reached, Reference, Refusal, Span, Validation, ValidationContext, VerifiedModel, } from './types.js';
15
- export { checkDocument, resolves, sectionReferences, validate, validating, verify, } from './validate.js';
15
+ export type { ApplyResult, BodyResult, Change, Division, DivisionContext, Document, DocumentCheck, Frontmatter, FrontmatterResult, Library, ModelVerification, Link, Outline, ParseResult, Projection, ProjectionContext, Reached, Reference, Refusal, RoomReference, Span, Validation, ValidationContext, ValidationStep, VerifiedModel, } from './types.js';
16
+ export { assess, bodyRoomLinks, checkDocument, resolves, roomReferences, sectionReferences, validate, validating, verdicts, verify, } from './validate.js';
17
+ export type { Assessment, Verdicts } from './validate.js';
16
18
  export { VERSION } from './version.js';
17
19
  export { bodyAfterFrontmatter, equalValue, frontmatterValue } from './yaml.js';
18
20
  export type { FrontmatterValue } from './yaml.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,KAAK,EACL,KAAK,EACL,OAAO,EACP,SAAS,EACT,OAAO,EACP,UAAU,EACV,WAAW,GACZ,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,KAAK,EACL,SAAS,EACT,cAAc,EACd,aAAa,EACb,cAAc,EACd,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,IAAI,EACJ,QAAQ,EACR,aAAa,EACb,SAAS,EACT,QAAQ,EACR,SAAS,GACV,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EACL,KAAK,EACL,SAAS,EACT,UAAU,EACV,eAAe,EACf,KAAK,EACL,KAAK,EACL,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,UAAU,EACV,SAAS,GACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACjG,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EACL,WAAW,EACX,UAAU,EACV,YAAY,EACZ,aAAa,EACb,SAAS,EACT,WAAW,EACX,YAAY,EACZ,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,UAAU,EACV,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,WAAW,EACX,KAAK,EACL,UAAU,EACV,UAAU,GACX,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,WAAW,EACX,UAAU,EACV,MAAM,EACN,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,OAAO,EACP,iBAAiB,EACjB,IAAI,EACJ,cAAc,EACd,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,OAAO,EACP,SAAS,EACT,OAAO,EACP,IAAI,EACJ,UAAU,EACV,iBAAiB,EACjB,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,aAAa,EACb,QAAQ,EACR,iBAAiB,EACjB,QAAQ,EACR,UAAU,EACV,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC/E,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
- export { dated, datePointers, factKind, facts, fires, holds, instant, itemFires, pointed, prosePaths, WRITE_DOORS, } from './condition.js';
1
+ export { ACTOR_PATHS, actorOperators, changeFields, CONDITION_OPERATORS, dated, datePointers, DOCUMENT_PATHS, factKind, facts, fires, holds, inScope, instant, ITEM_PATHS, itemFires, itemKind, itemOperators, memberPointers, members, MESSAGE_PATHS, messageFires, messageOperators, notificationFires, pointed, prosePaths, testOperators, TEXT_FIELD, valuedChange, WRITE_DOORS, } from './condition.js';
2
2
  export { headingLines } from './blocks.js';
3
3
  export { divide } from './divide.js';
4
- export { apply, bodyLinks, fieldStart, linkOccurrences, links, parse, parseFrontmatter, relativize, relativizeBody, relinkBody, serialize, } from './document.js';
4
+ export { apply, bodyLinks, links, outline, parse, parseFrontmatter, relativize, relativizeBody, relinkBody, serialize, } from './document.js';
5
5
  export { HOUSE_FIELDS, MEMBER_FIELD, RECEIVED_FIELD, SITE_FIELD, TYPE_FIELD } from './fields.js';
6
- export { relative, retarget, target } from './paths.js';
6
+ export { relative, retarget, roomPath, roomTarget, target } from './paths.js';
7
7
  export { MARKDOWN_SYNTAX_PROFILE } from './profile.js';
8
8
  export { project } from './project.js';
9
9
  export { references } from './references.js';
10
- export { ARRAY_INDEX, ATTACHMENT, crossedFacts, escapePointer, itemFacts, memberFacts, pointerFacts, pointerSegments, } from './definition.js';
11
- export { checkDocument, resolves, sectionReferences, validate, validating, verify, } from './validate.js';
10
+ export { ARRAY_INDEX, ATTACHMENT, crossedFacts, escapePointer, itemFacts, JSON_POINTER, memberFacts, pointerFacts, pointerSegments, } from './definition.js';
11
+ export { assess, bodyRoomLinks, checkDocument, resolves, roomReferences, sectionReferences, validate, validating, verdicts, verify, } from './validate.js';
12
12
  export { VERSION } from './version.js';
13
13
  export { bodyAfterFrontmatter, equalValue, frontmatterValue } from './yaml.js';
package/dist/links.d.ts CHANGED
@@ -24,5 +24,6 @@ export type LinkInventory = {
24
24
  };
25
25
  export declare function fenceSpans(text: string): readonly (readonly [number, number])[];
26
26
  export declare function linkInventory(body: string): LinkInventory;
27
+ export declare function destinations(inventory: LinkInventory): readonly HrefOccurrence[];
27
28
  export declare function hrefOccurrences(body: string): readonly HrefOccurrence[];
28
29
  //# sourceMappingURL=links.d.ts.map