@agentshouse/mdmodel 0.1.0-ccf1367190878 → 0.1.0-cdbd463e01dc2

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 (135) hide show
  1. package/API.md +197 -178
  2. package/README.md +21 -22
  3. package/dist/blocks.d.ts.map +1 -1
  4. package/dist/blocks.js +35 -20
  5. package/dist/definition.d.ts +3 -1
  6. package/dist/definition.d.ts.map +1 -1
  7. package/dist/definition.js +112 -24
  8. package/dist/divide.d.ts.map +1 -1
  9. package/dist/divide.js +27 -17
  10. package/dist/document.d.ts +5 -5
  11. package/dist/document.d.ts.map +1 -1
  12. package/dist/document.js +304 -187
  13. package/dist/event.d.ts +78 -0
  14. package/dist/event.d.ts.map +1 -0
  15. package/dist/event.js +230 -0
  16. package/dist/facts.d.ts +15 -0
  17. package/dist/facts.d.ts.map +1 -0
  18. package/dist/facts.js +39 -0
  19. package/dist/index.d.ts +22 -8
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +13 -6
  22. package/dist/kinds.d.ts +19 -0
  23. package/dist/kinds.d.ts.map +1 -0
  24. package/dist/kinds.js +161 -0
  25. package/dist/links.d.ts +2 -0
  26. package/dist/links.d.ts.map +1 -1
  27. package/dist/links.js +107 -50
  28. package/dist/paths.d.ts +9 -1
  29. package/dist/paths.d.ts.map +1 -1
  30. package/dist/paths.js +28 -2
  31. package/dist/pattern.d.ts +10 -0
  32. package/dist/pattern.d.ts.map +1 -0
  33. package/dist/pattern.js +355 -0
  34. package/dist/project.d.ts +1 -0
  35. package/dist/project.d.ts.map +1 -1
  36. package/dist/project.js +38 -15
  37. package/dist/query/language.d.ts +223 -0
  38. package/dist/query/language.d.ts.map +1 -0
  39. package/dist/query/language.js +565 -0
  40. package/dist/query/match.d.ts +14 -0
  41. package/dist/query/match.d.ts.map +1 -0
  42. package/dist/query/match.js +121 -0
  43. package/dist/query/rows.d.ts +32 -0
  44. package/dist/query/rows.d.ts.map +1 -0
  45. package/dist/query/rows.js +161 -0
  46. package/dist/query/sql.d.ts +39 -0
  47. package/dist/query/sql.d.ts.map +1 -0
  48. package/dist/query/sql.js +279 -0
  49. package/dist/references.d.ts.map +1 -1
  50. package/dist/references.js +6 -1
  51. package/dist/reserved.d.ts +5 -0
  52. package/dist/reserved.d.ts.map +1 -0
  53. package/dist/reserved.js +20 -0
  54. package/dist/schema.d.ts +1 -0
  55. package/dist/schema.d.ts.map +1 -1
  56. package/dist/schema.js +55 -4
  57. package/dist/types.d.ts +35 -4
  58. package/dist/types.d.ts.map +1 -1
  59. package/dist/validate.d.ts +29 -4
  60. package/dist/validate.d.ts.map +1 -1
  61. package/dist/validate.js +301 -85
  62. package/dist/yaml.d.ts.map +1 -1
  63. package/dist/yaml.js +36 -3
  64. package/fixtures/README.md +26 -6
  65. package/fixtures/cases/conditional-section/context.json +1 -0
  66. package/fixtures/cases/conditional-section/expected.json +6 -0
  67. package/fixtures/cases/conditional-section/library/questions/hosting.md +9 -0
  68. package/fixtures/cases/conditional-section/library/questions/payments.md +12 -0
  69. package/fixtures/cases/conditional-section/schema.yaml +14 -0
  70. package/fixtures/cases/cross-room-link/context.json +1 -0
  71. package/fixtures/cases/cross-room-link/expected.json +1 -0
  72. package/fixtures/cases/cross-room-link/library/clients/acme.md +6 -0
  73. package/fixtures/cases/cross-room-link/schema.yaml +3 -0
  74. package/fixtures/cases/cross-room-reference/context.json +1 -0
  75. package/fixtures/cases/cross-room-reference/expected.json +1 -0
  76. package/fixtures/cases/cross-room-reference/library/clients/acme.md +9 -0
  77. package/fixtures/cases/cross-room-reference/schema.yaml +9 -0
  78. package/fixtures/cases/link-into-a-reserved-name/context.json +1 -0
  79. package/fixtures/cases/link-into-a-reserved-name/expected.json +5 -0
  80. package/fixtures/cases/link-into-a-reserved-name/library/notes/a.md +6 -0
  81. package/fixtures/cases/link-into-a-reserved-name/schema.yaml +3 -0
  82. package/fixtures/cases/link-into-a-reserved-name-from-an-untyped-document/context.json +1 -0
  83. package/fixtures/cases/link-into-a-reserved-name-from-an-untyped-document/expected.json +5 -0
  84. package/fixtures/cases/link-into-a-reserved-name-from-an-untyped-document/library/notes/b.md +5 -0
  85. package/fixtures/cases/member-on-an-untyped-document/context.json +1 -0
  86. package/fixtures/cases/member-on-an-untyped-document/expected.json +6 -0
  87. package/fixtures/cases/member-on-an-untyped-document/library/note.md +4 -0
  88. package/fixtures/cases/member-on-another-type/context.json +1 -0
  89. package/fixtures/cases/member-on-another-type/expected.json +6 -0
  90. package/fixtures/cases/member-on-another-type/library/note.md +4 -0
  91. package/fixtures/cases/member-on-another-type/schema.yaml +7 -0
  92. package/fixtures/cases/reference-into-a-reserved-name/context.json +1 -0
  93. package/fixtures/cases/reference-into-a-reserved-name/expected.json +6 -0
  94. package/fixtures/cases/reference-into-a-reserved-name/library/notes/a.md +4 -0
  95. package/fixtures/cases/reference-into-a-reserved-name/library/settings/brief.md +1 -0
  96. package/fixtures/cases/reference-into-a-reserved-name/schema.yaml +3 -0
  97. package/fixtures/cases/room-md-declares-a-type/context.json +1 -0
  98. package/fixtures/cases/room-md-declares-a-type/expected.json +6 -0
  99. package/fixtures/cases/room-md-declares-a-type/library/ROOM.md +5 -0
  100. package/fixtures/cases/typed-path-names-another-type/context.json +11 -0
  101. package/fixtures/cases/typed-path-names-another-type/expected.json +6 -0
  102. package/fixtures/cases/typed-path-names-another-type/library/settings/prefs/one.md +3 -0
  103. package/fixtures/cases/typed-path-names-another-type/schema.yaml +6 -0
  104. package/fixtures/cases/typed-path-names-its-type/context.json +11 -0
  105. package/fixtures/cases/typed-path-names-its-type/expected.json +3 -0
  106. package/fixtures/cases/typed-path-names-its-type/library/settings/prefs/deeper/two.md +1 -0
  107. package/fixtures/cases/typed-path-names-its-type/library/settings/prefs/one.md +4 -0
  108. package/fixtures/cases/typed-path-names-its-type/schema.yaml +6 -0
  109. package/fixtures/cases/typed-path-names-no-type/context.json +11 -0
  110. package/fixtures/cases/typed-path-names-no-type/expected.json +6 -0
  111. package/fixtures/cases/typed-path-names-no-type/library/settings/prefs/one.md +1 -0
  112. package/fixtures/cases/typed-path-names-no-type/schema.yaml +6 -0
  113. package/fixtures/cases/unnamed-typed-path-checks-its-fields/context.json +11 -0
  114. package/fixtures/cases/unnamed-typed-path-checks-its-fields/expected.json +6 -0
  115. package/fixtures/cases/unnamed-typed-path-checks-its-fields/library/rooms/team/tasks/plan.md +4 -0
  116. package/fixtures/cases/unnamed-typed-path-checks-its-fields/schema.yaml +5 -0
  117. package/fixtures/cases/unnamed-typed-path-names-a-type/context.json +11 -0
  118. package/fixtures/cases/unnamed-typed-path-names-a-type/expected.json +6 -0
  119. package/fixtures/cases/unnamed-typed-path-names-a-type/library/rooms/team/tasks/plan.md +6 -0
  120. package/fixtures/cases/unnamed-typed-path-names-a-type/schema.yaml +5 -0
  121. package/fixtures/cases/unnamed-typed-path-reads-its-type/context.json +11 -0
  122. package/fixtures/cases/unnamed-typed-path-reads-its-type/expected.json +3 -0
  123. package/fixtures/cases/unnamed-typed-path-reads-its-type/library/rooms/team/plan.md +1 -0
  124. package/fixtures/cases/unnamed-typed-path-reads-its-type/library/rooms/team/tasks/plan.md +5 -0
  125. package/fixtures/cases/unnamed-typed-path-reads-its-type/schema.yaml +5 -0
  126. package/fixtures/cases/unnamed-typed-path-without-a-body/context.json +11 -0
  127. package/fixtures/cases/unnamed-typed-path-without-a-body/expected.json +5 -0
  128. package/fixtures/cases/unnamed-typed-path-without-a-body/library/rooms/team/tasks/plan.md +5 -0
  129. package/fixtures/cases/unnamed-typed-path-without-a-body/schema.yaml +5 -0
  130. package/fixtures/division.json +26 -26
  131. package/fixtures/queries.json +913 -0
  132. package/package.json +7 -2
  133. package/dist/condition.d.ts +0 -149
  134. package/dist/condition.d.ts.map +0 -1
  135. package/dist/condition.js +0 -500
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
7
  import { bodyStartAfterYaml, bodyStartOf, equalValue, extractFrontmatter, frontmatterValue, jsValue, lineEnding, mapEntry, parseYamlMapping, serializeJs, serializeKey, YamlError, } from './yaml.js';
8
- const internals = new WeakMap();
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;
@@ -72,86 +83,6 @@ export function parse(content, file = '') {
72
83
  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
84
  };
74
85
  }
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
86
  function fitsBare(to) {
156
87
  if (to === '' || to.startsWith('<'))
157
88
  return false;
@@ -179,58 +110,34 @@ function destinationText(to, bracketed) {
179
110
  return `<${to}>`;
180
111
  return null;
181
112
  }
182
- function relinked(content, bodyStart, href, to) {
183
- const written = retarget(href, to);
184
- let rewritten = '';
185
- let cursor = 0;
186
- for (const match of hrefOccurrences(content.slice(bodyStart))) {
187
- if (match.href !== href)
188
- continue;
189
- const text = destinationText(written, match.bracketed);
190
- if (text === null)
191
- return refuse('', 'unwritable-link', written);
192
- rewritten += content.slice(cursor, bodyStart + match.start);
193
- rewritten += text;
194
- cursor = bodyStart + match.end;
195
- }
196
- return rewritten + content.slice(cursor);
197
- }
198
- function shellRelinked(content, bodyStart, file, handle) {
199
- const hrefs = new Set(hrefOccurrences(content.slice(bodyStart)).map(({ href }) => href));
200
- let held = content;
201
- for (const href of hrefs) {
113
+ function shellRelinked(body, file, handle) {
114
+ const occurrences = hrefOccurrences(body);
115
+ const written = new Map();
116
+ for (const href of new Set(occurrences.map((occurrence) => occurrence.href))) {
202
117
  const path = shellTarget(href, handle);
203
118
  if (path === null)
204
119
  continue;
205
- const text = relinked(held, bodyStart, href, relative(file, path));
206
- if (typeof text !== 'string')
207
- return text;
208
- held = text;
120
+ const to = retarget(href, relative(file, path));
121
+ if (destinationText(to, false) === null)
122
+ return refuse('', 'unwritable-link', to);
123
+ written.set(href, to);
209
124
  }
210
- return held;
211
- }
212
- function reparsed(document, content) {
213
- if (typeof content !== 'string')
214
- return { outcome: 'refuse', refusal: content };
215
- if (content === document.content)
216
- return { outcome: 'applied', document };
217
- const parsed = parse(content);
218
- if (!parsed.ok)
219
- return { outcome: 'refuse', refusal: parsed.refusal };
220
- return { outcome: 'applied', document: parsed.document };
221
- }
222
- function applyLink(document, href, to) {
223
- const content = document.content;
224
- const bodyStart = bodyStartOf(content);
225
- if (!hrefOccurrences(content.slice(bodyStart)).some((occurrence) => occurrence.href === href))
226
- return { outcome: 'stale', document };
227
- return reparsed(document, relinked(content, bodyStart, href, to));
125
+ let rewritten = '';
126
+ let cursor = 0;
127
+ for (const match of occurrences) {
128
+ const to = written.get(match.href);
129
+ if (to === undefined)
130
+ continue;
131
+ rewritten += body.slice(cursor, match.start) + destinationText(to, match.bracketed);
132
+ cursor = match.end;
133
+ }
134
+ return rewritten + body.slice(cursor);
228
135
  }
229
- function nodeAt(root, pointer) {
136
+ function nodeAt(root, pointer, entryOf = mapEntry) {
230
137
  let node = root;
231
138
  for (const key of pointerSegments(pointer)) {
232
139
  if (node?.kind === 'map')
233
- node = mapEntry(node, key)?.value;
140
+ node = entryOf(node, key)?.value;
234
141
  else if (node?.kind === 'seq' && ARRAY_INDEX.test(key))
235
142
  node = node.items[Number(key)];
236
143
  else
@@ -245,37 +152,36 @@ function scalarText(source, value) {
245
152
  return JSON.stringify(value);
246
153
  return serializeJs(value);
247
154
  }
248
- function applyReference(document, pointer, value, prior) {
249
- const root = internals.get(document)?.root;
250
- const node = root === null || root === undefined ? undefined : nodeAt(root, pointer);
251
- if (node?.kind !== 'scalar' || node.value !== prior)
252
- return { outcome: 'stale', document };
253
- const text = scalarText(document.content[node.start], value);
254
- const parsed = parse(splice(document.content, node.start, node.end, text));
255
- if (!parsed.ok)
256
- return { outcome: 'refuse', refusal: parsed.refusal };
257
- return { outcome: 'applied', document: parsed.document };
258
- }
259
- export function relativize(document, file, handle, definition) {
260
- let held = document;
155
+ export function relativize(document, file, handle, definition, kept = new Set()) {
156
+ const relativized = (href) => {
157
+ const path = shellTarget(href, handle);
158
+ return path === null || kept.has(`${SHELL_ROOMS}${handle}/${path}`) ? null : path;
159
+ };
160
+ const changes = [];
261
161
  for (const { pointer, value } of definition === undefined
262
162
  ? []
263
163
  : references(document, definition)) {
264
- const path = shellTarget(value, handle);
265
- if (path === null)
266
- continue;
267
- const result = applyReference(held, pointer, retarget(value, relative(file, path)), value);
268
- if (result.outcome !== 'applied')
269
- return result;
270
- held = result.document;
164
+ const path = relativized(value);
165
+ if (path !== null)
166
+ changes.push({
167
+ kind: 'reference',
168
+ pointer,
169
+ value: retarget(value, relative(file, path)),
170
+ prior: value,
171
+ });
271
172
  }
272
- return reparsed(held, shellRelinked(held.content, bodyStartOf(held.content), file, handle));
173
+ for (const href of new Set(destinations(inventoryOf(document)).map(({ href }) => href))) {
174
+ const path = relativized(href);
175
+ if (path !== null)
176
+ changes.push({ kind: 'link', href, to: relative(file, path) });
177
+ }
178
+ return apply(document, changes);
273
179
  }
274
180
  export function relativizeBody(body, file, handle) {
275
- const relinkedBody = shellRelinked(body, 0, file, handle);
181
+ const relinkedBody = shellRelinked(body, file, handle);
276
182
  if (typeof relinkedBody !== 'string')
277
183
  return { outcome: 'refuse', refusal: relinkedBody };
278
- const elsewhere = hrefOccurrences(relinkedBody).find(({ href }) => destinationPath(href)?.startsWith(SHELL_ROOMS) === true);
184
+ const elsewhere = hrefOccurrences(relinkedBody).find(({ href }) => destinationPath(href)?.startsWith(SHELL_ROOMS) === true && roomTarget(href) === null);
279
185
  if (elsewhere !== undefined)
280
186
  return { outcome: 'refuse', refusal: refuse(file, 'unresolved-link', elsewhere.href) };
281
187
  return { outcome: 'applied', body: relinkedBody };
@@ -284,11 +190,11 @@ export function relinkBody(body, file, moves) {
284
190
  let rewritten = '';
285
191
  let cursor = 0;
286
192
  for (const match of hrefOccurrences(body)) {
287
- const from = target(file, match.href);
193
+ const from = target(file, match.href) ?? roomPath(match.href);
288
194
  const to = from === null ? undefined : moves.get(from);
289
195
  if (to === undefined)
290
196
  continue;
291
- const written = retarget(match.href, relative(file, to));
197
+ const written = retarget(match.href, from.startsWith(SHELL_ROOMS) || to.startsWith(SHELL_ROOMS) ? to : relative(file, to));
292
198
  const text = destinationText(written, match.bracketed);
293
199
  if (text === null)
294
200
  return { outcome: 'refuse', refusal: refuse(file, 'unwritable-link', written) };
@@ -297,54 +203,265 @@ export function relinkBody(body, file, moves) {
297
203
  }
298
204
  return { outcome: 'applied', body: rewritten + body.slice(cursor) };
299
205
  }
300
- function occurrencesIn(text, file, offset) {
301
- const found = [];
302
- for (const occurrence of hrefOccurrences(text)) {
303
- const resolved = target(file, occurrence.href);
304
- if (resolved !== null)
305
- found.push({ href: occurrence.href, target: resolved, start: offset + occurrence.start });
306
- }
307
- return found;
308
- }
309
- function linksIn(text, file) {
206
+ function linksIn(occurrences, file) {
310
207
  const found = [];
311
208
  const seen = new Set();
312
- for (const occurrence of occurrencesIn(text, file, 0)) {
209
+ for (const occurrence of occurrences) {
313
210
  if (seen.has(occurrence.href))
314
211
  continue;
212
+ const resolved = target(file, occurrence.href);
213
+ if (resolved === null)
214
+ continue;
315
215
  seen.add(occurrence.href);
316
- found.push({ href: occurrence.href, target: occurrence.target });
216
+ found.push({ href: occurrence.href, target: resolved });
317
217
  }
318
218
  return found;
319
219
  }
320
220
  export function links(document, file) {
321
- return linksIn(document.content.slice(bodyStartOf(document.content)), file);
322
- }
323
- export function linkOccurrences(document, file) {
324
- const bodyStart = bodyStartOf(document.content);
325
- return occurrencesIn(document.content.slice(bodyStart), file, bodyStart);
221
+ return linksIn(destinations(inventoryOf(document)), file);
326
222
  }
327
223
  export function bodyLinks(body, file) {
328
- return linksIn(body, file);
224
+ return linksIn(hrefOccurrences(body), file);
329
225
  }
330
- export function apply(document, change) {
226
+ function internalOf(document) {
227
+ const held = heldOf(document);
228
+ if (held !== undefined)
229
+ return held.internal;
230
+ const read = opened(document.content, '');
231
+ if (!read.ok)
232
+ return read.refusal;
233
+ return {
234
+ root: read.root,
235
+ yamlEnd: read.yamlEnd,
236
+ bodyStart: read.root === null ? 0 : bodyStartAfterYaml(document.content, read.yamlEnd),
237
+ };
238
+ }
239
+ function partOf(change) {
240
+ if (change.kind === 'field' || change.kind === 'section')
241
+ return `${change.kind} ${change.name}`;
331
242
  if (change.kind === 'link')
332
- return applyLink(document, change.href, change.to);
243
+ return `link ${change.href}`;
333
244
  if (change.kind === 'reference')
334
- return applyReference(document, change.pointer, change.value, change.prior);
335
- if (change.kind === 'preamble')
336
- return applyPreamble(document, change.value, change.prior);
337
- if (change.kind === 'field')
338
- return applyField(document, change.name, change.value, change.prior);
339
- return applySection(document, change.name, change.value, change.prior);
245
+ return `reference ${change.pointer}`;
246
+ return 'preamble';
247
+ }
248
+ function planner(document, internal) {
249
+ const content = document.content;
250
+ const keyed = new Map();
251
+ let spans;
252
+ let occurrences;
253
+ let ending;
254
+ let added = 0;
255
+ const entryOf = (map, key) => {
256
+ let index = keyed.get(map);
257
+ if (index === undefined) {
258
+ index = new Map();
259
+ for (const entry of map.entries)
260
+ if (!index.has(entry.key))
261
+ index.set(entry.key, entry);
262
+ keyed.set(map, index);
263
+ }
264
+ return index.get(key);
265
+ };
266
+ const field = (name, value, prior, at) => {
267
+ const root = internal.root;
268
+ if (root === null)
269
+ return refuse('', 'frontmatter-fence', name);
270
+ if (!equalValue(document.fields[name], prior))
271
+ return 'stale';
272
+ if (!frontmatterValue(value))
273
+ return refuse('', 'frontmatter-fence', name);
274
+ const existing = entryOf(root, name);
275
+ if (existing !== undefined) {
276
+ const held = existing.value;
277
+ const inline = held.start > existing.after && !/[\r\n]/.test(content.slice(existing.after, held.start));
278
+ return inline
279
+ ? [{ start: held.start, end: held.end, text: serializeJs(value), at }]
280
+ : [{ start: existing.after, end: held.end, text: ` ${serializeJs(value)}`, at }];
281
+ }
282
+ const entry = `${serializeKey(name)}: ${serializeJs(value)}`;
283
+ added += 1;
284
+ if (content[root.start] === '{') {
285
+ const separator = root.entries.length === 0 && added === 1 ? '' : ', ';
286
+ return [{ start: root.end - 1, end: root.end - 1, text: `${separator}${entry}`, at }];
287
+ }
288
+ const lineStart = content.lastIndexOf('\n', root.start - 1) + 1;
289
+ ending ??= lineEnding(content);
290
+ const line = `${content.slice(lineStart, root.start)}${entry}${ending}`;
291
+ return [{ start: internal.yamlEnd, end: internal.yamlEnd, text: line, at, entry: true }];
292
+ };
293
+ const section = (name, value, prior, at) => {
294
+ if (spans === undefined) {
295
+ spans = new Map();
296
+ for (const span of headingSpans(content, internal.bodyStart))
297
+ if (!spans.has(span.name))
298
+ spans.set(span.name, span);
299
+ }
300
+ const span = spans.get(name);
301
+ if (span === undefined)
302
+ return refuse('', 'required-section', name);
303
+ if (content.slice(span.bodyStart, span.bodyEnd) !== prior)
304
+ return 'stale';
305
+ return [{ start: span.bodyStart, end: span.bodyEnd, text: value, at }];
306
+ };
307
+ const link = (href, to, at) => {
308
+ if (occurrences === undefined) {
309
+ occurrences = new Map();
310
+ for (const occurrence of destinations(inventoryOf(document))) {
311
+ const same = occurrences.get(occurrence.href);
312
+ if (same === undefined)
313
+ occurrences.set(occurrence.href, [occurrence]);
314
+ else
315
+ same.push(occurrence);
316
+ }
317
+ }
318
+ const found = occurrences.get(href);
319
+ if (found === undefined)
320
+ return 'stale';
321
+ const written = retarget(href, to);
322
+ if (destinationText(written, false) === null)
323
+ return refuse('', 'unwritable-link', written);
324
+ return found.map((occurrence) => ({
325
+ start: internal.bodyStart + occurrence.start,
326
+ end: internal.bodyStart + occurrence.end,
327
+ text: destinationText(written, occurrence.bracketed),
328
+ at,
329
+ }));
330
+ };
331
+ const reference = (pointer, value, prior, at) => {
332
+ const node = internal.root === null ? undefined : nodeAt(internal.root, pointer, entryOf);
333
+ if (node?.kind !== 'scalar' || node.value !== prior)
334
+ return 'stale';
335
+ return [
336
+ { start: node.start, end: node.end, text: scalarText(content[node.start], value), at },
337
+ ];
338
+ };
339
+ return (change, at) => {
340
+ if (change.kind === 'field')
341
+ return field(change.name, change.value, change.prior, at);
342
+ if (change.kind === 'section')
343
+ return section(change.name, change.value, change.prior, at);
344
+ if (change.kind === 'link')
345
+ return link(change.href, change.to, at);
346
+ if (change.kind === 'reference')
347
+ return reference(change.pointer, change.value, change.prior, at);
348
+ if (document.preamble !== change.prior)
349
+ return 'stale';
350
+ const start = internal.bodyStart;
351
+ return [{ start, end: start + document.preamble.length, text: change.value, at }];
352
+ };
353
+ }
354
+ function overlapping(edits, stale) {
355
+ let reach = -1;
356
+ let owner = -1;
357
+ for (const edit of edits) {
358
+ if (edit.start < reach)
359
+ stale.add(Math.max(edit.at, owner));
360
+ if (edit.end > reach) {
361
+ reach = edit.end;
362
+ owner = edit.at;
363
+ }
364
+ }
365
+ }
366
+ function firstHeadings(content, bodyStart) {
367
+ const first = new Map();
368
+ for (const span of headingSpans(content, bodyStart)) {
369
+ if (!first.has(span.name))
370
+ first.set(span.name, span.headingStart);
371
+ }
372
+ return first;
373
+ }
374
+ function hidden(before, bodyStart, after, shifts) {
375
+ const placed = firstHeadings(after, bodyStartOf(after));
376
+ let shift = 0;
377
+ let index = 0;
378
+ for (const [name, at] of firstHeadings(before, bodyStart)) {
379
+ while (index < shifts.length && shifts[index][0] <= at) {
380
+ shift += shifts[index][1];
381
+ index += 1;
382
+ }
383
+ if (placed.get(name) !== at + shift)
384
+ return name;
385
+ }
386
+ return undefined;
387
+ }
388
+ function spliced(source, edits) {
389
+ const shifts = [];
390
+ const pieces = [];
391
+ let cursor = 0;
392
+ let entered = false;
393
+ let ended = false;
394
+ for (const edit of edits) {
395
+ const kept = source.slice(cursor, edit.start);
396
+ pieces.push(kept);
397
+ if (kept !== '')
398
+ ended = kept.endsWith('\n');
399
+ let text = edit.text;
400
+ if (edit.entry === true && !entered) {
401
+ entered = true;
402
+ if (!ended)
403
+ text = `${lineEnding(source)}${text}`;
404
+ }
405
+ pieces.push(text);
406
+ if (text !== '')
407
+ ended = text.endsWith('\n');
408
+ shifts.push([edit.start, text.length - (edit.end - edit.start)]);
409
+ cursor = edit.end;
410
+ }
411
+ pieces.push(source.slice(cursor));
412
+ return { content: pieces.join(''), shifts };
413
+ }
414
+ export function apply(document, changes) {
415
+ const internal = internalOf(document);
416
+ if (!('bodyStart' in internal))
417
+ return { outcome: 'refuse', refusal: internal };
418
+ const plan = planner(document, internal);
419
+ const edits = [];
420
+ const stale = new Set();
421
+ const named = new Set();
422
+ for (const [at, change] of changes.entries()) {
423
+ const planned = plan(change, at);
424
+ if (planned === 'stale') {
425
+ stale.add(at);
426
+ continue;
427
+ }
428
+ if (!Array.isArray(planned))
429
+ return { outcome: 'refuse', refusal: planned };
430
+ const part = partOf(change);
431
+ if (named.has(part)) {
432
+ stale.add(at);
433
+ continue;
434
+ }
435
+ named.add(part);
436
+ for (const edit of planned)
437
+ edits.push(edit);
438
+ }
439
+ edits.sort((left, right) => left.start - right.start || left.end - right.end || left.at - right.at);
440
+ overlapping(edits, stale);
441
+ if (stale.size > 0)
442
+ return { outcome: 'stale', document, stale: [...stale].sort((left, right) => left - right) };
443
+ const source = document.content;
444
+ const { content } = spliced(source, edits);
445
+ if (content === source)
446
+ return { outcome: 'applied', document };
447
+ const replaced = edits.filter((edit) => {
448
+ const kind = changes[edit.at].kind;
449
+ return kind === 'section' || kind === 'preamble';
450
+ });
451
+ if (replaced.length > 0) {
452
+ const checked = spliced(source, replaced);
453
+ const name = hidden(source, internal.bodyStart, checked.content, checked.shifts);
454
+ if (name !== undefined)
455
+ return { outcome: 'refuse', refusal: refuse('', 'overlapping-section', name) };
456
+ }
457
+ const parsed = parse(content);
458
+ if (!parsed.ok)
459
+ return { outcome: 'refuse', refusal: parsed.refusal };
460
+ return { outcome: 'applied', document: parsed.document };
340
461
  }
341
462
  export function serialize(document) {
342
463
  return document.content;
343
464
  }
344
- export function fieldStart(document, pointer) {
345
- const root = internals.get(document)?.root;
346
- return root === null || root === undefined ? undefined : nodeAt(root, pointer)?.start;
347
- }
348
465
  export function frontmatter(document) {
349
- return internals.get(document)?.root ?? null;
466
+ return heldOf(document)?.internal.root ?? null;
350
467
  }
@@ -0,0 +1,78 @@
1
+ import { type Definition } from './definition.js';
2
+ import type { Candidate } from './query/match.js';
3
+ import type { Document, VerifiedModel } from './types.js';
4
+ export type FactKind = 'string' | 'number' | 'boolean' | 'date' | 'time' | 'enum' | 'reference';
5
+ type Scalar = string | number | boolean;
6
+ export type Fact = {
7
+ readonly kind: FactKind;
8
+ readonly value: Scalar;
9
+ readonly member?: string;
10
+ } | {
11
+ readonly kind: 'prose';
12
+ } | {
13
+ readonly kind: 'null';
14
+ } | {
15
+ readonly kind: 'unreadable';
16
+ } | {
17
+ readonly kind: 'list';
18
+ readonly items: readonly Fact[];
19
+ } | {
20
+ readonly kind: 'object';
21
+ readonly fields: Readonly<Record<string, Fact>>;
22
+ };
23
+ export type DocumentFacts = {
24
+ readonly id: string;
25
+ readonly path: string;
26
+ readonly type: string | null;
27
+ readonly fields: Readonly<Record<string, Fact>>;
28
+ readonly sections: readonly string[];
29
+ };
30
+ export declare const TEXT_FIELD = "/text";
31
+ export declare const WRITE_DOORS: readonly ['browser', 'agent', 'client', 'function'];
32
+ export type WriteDoor = (typeof WRITE_DOORS)[number];
33
+ export type Actor = {
34
+ readonly user?: string;
35
+ readonly site?: string;
36
+ readonly app?: string;
37
+ readonly door?: WriteDoor;
38
+ };
39
+ export type DocumentEvent = {
40
+ readonly before: DocumentFacts | null;
41
+ readonly after: DocumentFacts | null;
42
+ readonly changed: readonly string[];
43
+ readonly by: Actor;
44
+ readonly moved_between_rooms: boolean;
45
+ };
46
+ export type ItemFacts = {
47
+ readonly delivery: string;
48
+ readonly source: string | null;
49
+ readonly address: string | null;
50
+ readonly container: string | null;
51
+ readonly author: string | null;
52
+ readonly headers: Readonly<Record<string, string>>;
53
+ readonly json: {
54
+ readonly value: unknown;
55
+ } | null;
56
+ };
57
+ export type MessageFacts = {
58
+ readonly sender: string;
59
+ readonly agent_function: string | null;
60
+ readonly path: string | null;
61
+ };
62
+ export type Resolved = {
63
+ readonly id: string;
64
+ readonly member?: string;
65
+ };
66
+ export declare function facts(document: Pick<Document, 'type' | 'fields' | 'sections'>, at: {
67
+ readonly id: string;
68
+ readonly path: string;
69
+ }, model: VerifiedModel, resolve: (target: string) => Resolved | undefined): DocumentFacts;
70
+ export declare function prosePaths(facts: DocumentFacts): string[];
71
+ export declare function members(document: DocumentFacts, path: string): readonly string[];
72
+ export declare function pointed(value: unknown, pointer: string): unknown;
73
+ export declare function valuedChange(definition: Definition, pointer: string): boolean;
74
+ export declare function datePointers(definition: Definition): string[];
75
+ export declare function memberPointers(definition: Definition, memberType: string): string[];
76
+ export declare function candidateOf(document: DocumentFacts): Candidate;
77
+ export {};
78
+ //# sourceMappingURL=event.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../src/event.ts"],"names":[],"mappings":"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"}