@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/validate.js CHANGED
@@ -1,15 +1,62 @@
1
1
  import { ATTACHMENT, BUILT_IN_TYPE, readSchema } from './definition.js';
2
- import { parse } from './document.js';
2
+ import { links, parse } from './document.js';
3
+ import { MEMBER_FIELD } from './fields.js';
3
4
  import { hrefOccurrences, linkInventory } from './links.js';
4
- import { classifyHref } from './paths.js';
5
+ import { classifyHref, roomTarget } from './paths.js';
5
6
  import { references } from './references.js';
7
+ import { isDocumentPath, ROOM_DESCRIPTION } from './reserved.js';
6
8
  import { refusal } from './refuse.js';
7
- import { fitsItem, frontmatterRefusal } from './schema.js';
9
+ import { conditionedSections, fitsItem, frontmatterRefusal } from './schema.js';
10
+ import { openPatternBudget, PatternTooCostly } from './pattern.js';
8
11
  import { headingSpans, listItems } from './sections.js';
9
12
  import { bodyAfterFrontmatter, bodyStartOf } from './yaml.js';
10
13
  function refuse(file, rule, extra) {
11
14
  return { verdict: 'refuse', ...refusal(file, rule, extra) };
12
15
  }
16
+ function unmarkedMember(file, document, definition) {
17
+ return Object.hasOwn(document.fields, MEMBER_FIELD) && definition?.memberDocument !== true
18
+ ? refuse(file, 'undeclared-field', { location: `/${MEMBER_FIELD}` })
19
+ : null;
20
+ }
21
+ function reservedLink(file, document) {
22
+ return (document.type !== undefined &&
23
+ isDocumentPath(file) &&
24
+ links(document, file).some((link) => !isDocumentPath(link.target)));
25
+ }
26
+ function describedRoom(file, document) {
27
+ return file === ROOM_DESCRIPTION && document.type !== undefined
28
+ ? refuse(file, 'room-md-type', { location: '/type' })
29
+ : null;
30
+ }
31
+ function nameFits(pattern, name) {
32
+ const at = pattern.indexOf('*');
33
+ if (at === -1)
34
+ return pattern === name;
35
+ const head = pattern.slice(0, at);
36
+ const tail = pattern.slice(at + 1);
37
+ return name.length > head.length + tail.length && name.startsWith(head) && name.endsWith(tail);
38
+ }
39
+ function typedPathOf(file, typedPaths = []) {
40
+ const names = file.split('/');
41
+ return typedPaths.find(({ path }) => {
42
+ const patterns = path.split('/');
43
+ return (patterns.length === names.length &&
44
+ patterns.every((pattern, at) => nameFits(pattern, names[at])));
45
+ });
46
+ }
47
+ const HELD_TEXT = /\S/u;
48
+ function typedPathRefusal(file, content, document, typedPath) {
49
+ if (typedPath === undefined)
50
+ return null;
51
+ if (document.type !== (typedPath.named ? typedPath.type : undefined))
52
+ return refuse(file, 'unknown-type', { location: '/type' });
53
+ return typedPath.body && !HELD_TEXT.test(bodyAfterFrontmatter(content))
54
+ ? refuse(file, 'required-body')
55
+ : null;
56
+ }
57
+ function kindOf(document, typedPath) {
58
+ return typedPath !== undefined && !typedPath.named ? typedPath.type : instanceType(document);
59
+ }
13
60
  function instanceType(document) {
14
61
  const raw = document.fields.type;
15
62
  if (raw === undefined || raw === null)
@@ -60,8 +107,8 @@ function itemHrefs(content, items, uses) {
60
107
  }
61
108
  return hrefs;
62
109
  }
63
- export function verify(schema) {
64
- const read = readSchema(schema);
110
+ export function verify(schema, provided) {
111
+ const read = readSchema(schema, provided);
65
112
  if ('types' in read)
66
113
  return { verdict: 'accept', model: read };
67
114
  return {
@@ -70,6 +117,12 @@ export function verify(schema) {
70
117
  ...(read.location === undefined ? {} : { location: read.location }),
71
118
  };
72
119
  }
120
+ export function readTypes(schema, provided) {
121
+ const read = readSchema(schema, provided, false);
122
+ if ('types' in read)
123
+ return read;
124
+ throw new Error(`the schema reads as no types: ${read.rule} ${read.location ?? ''}`.trim());
125
+ }
73
126
  function structure(file, content, document, definition) {
74
127
  if (definition === undefined)
75
128
  return { refusal: refuse(file, 'unknown-type', { location: '/type' }) };
@@ -80,10 +133,14 @@ function structure(file, content, document, definition) {
80
133
  const spans = headingSpans(content, bodyStartOf(content));
81
134
  const uses = usesIn(content);
82
135
  const sections = [];
136
+ const conditioned = conditionedSections(definition, document.fields);
83
137
  for (const [name, spec] of Object.entries(definition.sections)) {
84
138
  const occurrences = spans.filter((one) => one.name === name);
85
139
  if (occurrences.length === 0 && spec.required)
86
140
  return { refusal: refuse(file, 'required-section', { location: name }) };
141
+ if (conditioned.includes(name) &&
142
+ occurrences.every((span) => content.slice(span.bodyStart, span.bodyEnd).trim() === ''))
143
+ return { refusal: refuse(file, 'conditional-section', { location: name }) };
87
144
  if (spec.kind !== 'list')
88
145
  continue;
89
146
  for (const span of occurrences) {
@@ -96,10 +153,16 @@ function structure(file, content, document, definition) {
96
153
  const hrefs = itemHrefs(content, items, uses);
97
154
  if (hrefs === null)
98
155
  return { refusal: refuse(file, 'structured-section', { location: name }) };
99
- sections.push({ section: name, to: spec.items.reference, hrefs });
156
+ sections.push({
157
+ section: name,
158
+ to: spec.items.reference,
159
+ crossRoom: spec.items.crossRoom === true,
160
+ hrefs,
161
+ });
100
162
  }
101
163
  }
102
- return { sections };
164
+ const member = unmarkedMember(file, document, definition);
165
+ return member === null ? { sections } : { refusal: member };
103
166
  }
104
167
  function linked(file, content) {
105
168
  return hrefOccurrences(bodyAfterFrontmatter(content)).every((occurrence) => classifyHref(file, occurrence.href).kind !== 'invalid');
@@ -108,19 +171,39 @@ function reached(file, href, to) {
108
171
  const resolved = classifyHref(file, href);
109
172
  return { target: resolved.kind === 'path' ? resolved.path : null, to };
110
173
  }
111
- function definitionOf(document, model) {
112
- const kind = instanceType(document);
174
+ function referred(file, href, to) {
175
+ const reference = reached(file, href, to);
176
+ return reference.target !== null &&
177
+ isDocumentPath(file) &&
178
+ !isDocumentPath(reference.target) &&
179
+ !to.includes(ATTACHMENT)
180
+ ? { target: null, to }
181
+ : reference;
182
+ }
183
+ function crossing(file, href, crossRoom) {
184
+ return crossRoom === true && classifyHref(file, href).kind === 'room';
185
+ }
186
+ function definitionOf(kind, model) {
113
187
  return kind === null || !Object.hasOwn(model.types, kind) ? undefined : model.types[kind];
114
188
  }
115
- export function checkDocument(file, content, model) {
189
+ export function checkDocument(file, content, model, context = {}) {
190
+ const anyRoom = context.anyRoom === true;
116
191
  const parsed = parse(content, file);
117
192
  if (!parsed.ok)
118
193
  return { ok: false };
119
194
  const document = parsed.document;
120
195
  const type = document.type ?? BUILT_IN_TYPE;
121
- if (instanceType(document) === BUILT_IN_TYPE)
122
- return { ok: true, type, typed: false, references: [] };
123
- const definition = definitionOf(document, model);
196
+ const typedPath = typedPathOf(file, context.typedPaths);
197
+ if (describedRoom(file, document) !== null ||
198
+ typedPathRefusal(file, content, document, typedPath) !== null ||
199
+ reservedLink(file, document))
200
+ return { ok: false };
201
+ const kind = kindOf(document, typedPath);
202
+ if (kind === BUILT_IN_TYPE)
203
+ return unmarkedMember(file, document, undefined) === null
204
+ ? { ok: true, type, typed: false, references: [] }
205
+ : { ok: false };
206
+ const definition = definitionOf(kind, model);
124
207
  const read = structure(file, content, document, definition);
125
208
  if ('refusal' in read || definition === undefined || !linked(file, content))
126
209
  return { ok: false };
@@ -129,92 +212,225 @@ export function checkDocument(file, content, model) {
129
212
  type,
130
213
  typed: true,
131
214
  references: [
132
- ...references(document, definition).map(({ value, to }) => reached(file, value, to)),
133
- ...read.sections.flatMap(({ to, hrefs }) => hrefs.map((href) => reached(file, href, to))),
215
+ ...references(document, definition).flatMap(({ value, to, crossRoom }) => crossing(file, value, anyRoom || crossRoom) ? [] : [referred(file, value, to)]),
216
+ ...read.sections.flatMap(({ to, crossRoom, hrefs }) => hrefs.flatMap((href) => crossing(file, href, anyRoom || crossRoom) ? [] : [referred(file, href, to)])),
134
217
  ],
135
218
  };
136
219
  }
137
- export function sectionReferences(file, content, definition) {
138
- const referring = (name) => {
139
- const spec = Object.hasOwn(definition.sections, name) ? definition.sections[name] : undefined;
140
- return spec?.kind === 'list' ? spec.items.reference : undefined;
141
- };
142
- if (!Object.keys(definition.sections).some((name) => referring(name) !== undefined))
143
- return [];
144
- const uses = usesIn(content);
220
+ export function bodyRoomLinks(body) {
145
221
  const found = [];
222
+ for (const href of new Set(hrefOccurrences(body).map((occurrence) => occurrence.href))) {
223
+ const room = roomTarget(href);
224
+ if (room !== null)
225
+ found.push({ kind: 'link', href, ...room });
226
+ }
227
+ return found;
228
+ }
229
+ export function roomReferences(file, content, definition) {
230
+ const found = bodyRoomLinks(bodyAfterFrontmatter(content));
231
+ if (definition === undefined)
232
+ return found;
233
+ const parsed = parse(content, file);
234
+ if (!parsed.ok)
235
+ return found;
236
+ for (const { pointer, value, to, crossRoom } of references(parsed.document, definition)) {
237
+ const room = roomTarget(value);
238
+ if (room !== null)
239
+ found.push({
240
+ kind: 'field',
241
+ pointer,
242
+ value,
243
+ to,
244
+ ...room,
245
+ ...(crossRoom ? { crossRoom } : {}),
246
+ });
247
+ }
248
+ const uses = usesIn(content);
146
249
  for (const span of headingSpans(content, bodyStartOf(content))) {
147
- const to = referring(span.name);
148
- if (to === undefined)
250
+ const spec = Object.hasOwn(definition.sections, span.name)
251
+ ? definition.sections[span.name]
252
+ : undefined;
253
+ if (spec?.kind !== 'list' || spec.items.reference === undefined)
149
254
  continue;
150
- const items = listItems(content, span.bodyStart, span.bodyEnd);
151
- const hrefs = items === null ? null : itemHrefs(content, items, uses);
152
- for (const href of hrefs ?? [])
153
- found.push(reached(file, href, to));
255
+ const listed = listItems(content, span.bodyStart, span.bodyEnd);
256
+ for (const href of (listed === null ? null : itemHrefs(content, listed, uses)) ?? []) {
257
+ const room = roomTarget(href);
258
+ if (room !== null)
259
+ found.push({
260
+ kind: 'item',
261
+ section: span.name,
262
+ href,
263
+ to: spec.items.reference,
264
+ ...room,
265
+ ...(spec.items.crossRoom === true ? { crossRoom: true } : {}),
266
+ });
267
+ }
154
268
  }
155
269
  return found;
156
270
  }
157
- export function validate(library, context = { binaryTargets: [] }, model = { types: {} }) {
271
+ function least(refused, file, verdict) {
272
+ return refused === undefined || file < refused.file ? { file, verdict } : refused;
273
+ }
274
+ export function assess(file, content, binary, model = { types: {} }, context = {}) {
275
+ const unread = { file, type: undefined, targets: [], structured: null, fields: [], sections: [] };
276
+ if (binary)
277
+ return { ...unread, read: refuse(file, 'binary-target-document'), linked: true };
278
+ const parsed = parse(content, file);
279
+ if (!parsed.ok)
280
+ return { ...unread, read: { verdict: 'refuse', ...parsed.refusal }, linked: true };
281
+ const document = parsed.document;
282
+ const type = document.type ?? BUILT_IN_TYPE;
283
+ const typed = { file, type, read: null, structured: null };
284
+ const typedPath = typedPathOf(file, context.typedPaths);
285
+ const described = describedRoom(file, document) ?? typedPathRefusal(file, content, document, typedPath);
286
+ if (described !== null)
287
+ return { ...typed, targets: [], structured: described, fields: [], linked: true, sections: [] };
288
+ const kind = kindOf(document, typedPath);
289
+ if (kind === BUILT_IN_TYPE) {
290
+ const structured = unmarkedMember(file, document, undefined);
291
+ return {
292
+ ...typed,
293
+ targets: [],
294
+ structured,
295
+ fields: [],
296
+ linked: !reservedLink(file, document),
297
+ sections: [],
298
+ };
299
+ }
300
+ const definition = definitionOf(kind, model);
301
+ try {
302
+ return structured(file, content, typed, document, definition, context);
303
+ }
304
+ catch (error) {
305
+ if (!(error instanceof PatternTooCostly))
306
+ throw error;
307
+ return {
308
+ ...typed,
309
+ targets: [],
310
+ structured: refuse(file, 'pattern', { location: '' }),
311
+ fields: [],
312
+ linked: true,
313
+ sections: [],
314
+ };
315
+ }
316
+ }
317
+ function structured(file, content, typed, document, definition, context) {
318
+ const read = structure(file, content, document, definition);
319
+ if ('refusal' in read || definition === undefined) {
320
+ return {
321
+ ...typed,
322
+ targets: [],
323
+ structured: 'refusal' in read ? read.refusal : null,
324
+ fields: [],
325
+ linked: true,
326
+ sections: [],
327
+ };
328
+ }
329
+ const anyRoom = context.anyRoom === true;
330
+ const fields = references(document, definition).flatMap(({ pointer, value, to, crossRoom }) => crossing(file, value, anyRoom || crossRoom)
331
+ ? []
332
+ : [{ reference: referred(file, value, to), location: pointer }]);
333
+ const sections = read.sections.flatMap(({ section, to, crossRoom, hrefs }) => hrefs.flatMap((href) => crossing(file, href, anyRoom || crossRoom)
334
+ ? []
335
+ : [{ reference: referred(file, href, to), location: section }]));
336
+ const targets = new Set();
337
+ for (const { reference } of [...fields, ...sections]) {
338
+ if (reference.target !== null)
339
+ targets.add(reference.target);
340
+ }
341
+ return {
342
+ ...typed,
343
+ targets: [...targets],
344
+ fields,
345
+ linked: linked(file, content) && !reservedLink(file, document),
346
+ sections,
347
+ };
348
+ }
349
+ export function verdicts() {
350
+ let read;
351
+ let structured;
352
+ let referred;
353
+ let missed;
354
+ const unreached = (placed, typeOf, binary) => placed.find(({ reference }) => !reaches(reference, typeOf, binary));
355
+ return {
356
+ add(assessment, typeOf, binary) {
357
+ const { file } = assessment;
358
+ if (assessment.read !== null) {
359
+ read = least(read, file, assessment.read);
360
+ return 0;
361
+ }
362
+ if (assessment.structured !== null) {
363
+ structured = least(structured, file, assessment.structured);
364
+ return 0;
365
+ }
366
+ const field = unreached(assessment.fields, typeOf, binary);
367
+ if (field !== undefined) {
368
+ referred = least(referred, file, refuse(file, 'unresolved-reference', { location: field.location }));
369
+ return 0;
370
+ }
371
+ if (!assessment.linked) {
372
+ referred = least(referred, file, refuse(file, 'unresolved-link'));
373
+ return 0;
374
+ }
375
+ const section = unreached(assessment.sections, typeOf, binary);
376
+ if (section !== undefined) {
377
+ referred = least(referred, file, refuse(file, 'unresolved-reference', { location: section.location }));
378
+ return 0;
379
+ }
380
+ const all = [...assessment.fields, ...assessment.sections];
381
+ for (const { reference } of all) {
382
+ if (!missing(reference, typeOf, binary))
383
+ continue;
384
+ const path = reference.target;
385
+ if (missed === undefined || path < missed.path)
386
+ missed = { path, referrers: [] };
387
+ if (path === missed.path && missed.referrers.at(-1) !== file)
388
+ missed.referrers.push(file);
389
+ }
390
+ return all.length;
391
+ },
392
+ verdict() {
393
+ const refused = read ?? structured ?? referred;
394
+ if (refused !== undefined)
395
+ return refused.verdict;
396
+ if (missed !== undefined) {
397
+ return refuse(missed.path, 'referenced-missing-file', {
398
+ referrers: [...missed.referrers].sort(),
399
+ });
400
+ }
401
+ return { verdict: 'accept' };
402
+ },
403
+ };
404
+ }
405
+ export function* validating(library, context = { binaryTargets: [] }, model = { types: {} }) {
158
406
  const binaryTargets = new Set(context.binaryTargets);
159
407
  const binary = (path) => binaryTargets.has(path);
160
- const files = Object.keys(library).sort();
161
408
  const types = new Map();
162
- const typeOf = (path) => types.get(path);
163
- const documents = new Map();
164
- for (const file of files) {
165
- if (binaryTargets.has(file))
166
- return refuse(file, 'binary-target-document');
167
- const parsed = parse(library[file], file);
168
- if (!parsed.ok)
169
- return { verdict: 'refuse', ...parsed.refusal };
170
- types.set(file, parsed.document.type ?? BUILT_IN_TYPE);
171
- if (instanceType(parsed.document) !== BUILT_IN_TYPE)
172
- documents.set(file, parsed.document);
173
- }
174
- const sectionReferences = new Map();
175
- for (const [file, document] of documents) {
176
- const read = structure(file, library[file], document, definitionOf(document, model));
177
- if ('refusal' in read)
178
- return read.refusal;
179
- sectionReferences.set(file, read.sections);
180
- }
181
- const named = new Map();
182
- for (const [file, document] of documents) {
183
- const definition = definitionOf(document, model);
184
- const fields = references(document, definition).map(({ pointer, value, to }) => ({
185
- reference: reached(file, value, to),
186
- location: pointer,
187
- }));
188
- for (const { reference, location } of fields) {
189
- if (!reaches(reference, typeOf, binary))
190
- return refuse(file, 'unresolved-reference', { location });
191
- }
192
- if (!linked(file, library[file]))
193
- return refuse(file, 'unresolved-link');
194
- const sections = sectionReferences
195
- .get(file)
196
- .flatMap(({ section, to, hrefs }) => hrefs.map((href) => ({ reference: reached(file, href, to), location: section })));
197
- for (const { reference, location } of sections) {
198
- if (!reaches(reference, typeOf, binary))
199
- return refuse(file, 'unresolved-reference', { location });
409
+ const assessments = [];
410
+ const closeBudget = openPatternBudget();
411
+ try {
412
+ for (const [file, content] of library) {
413
+ const assessment = assess(file, content, binary(file), model, context);
414
+ if (assessment.type !== undefined)
415
+ types.set(file, assessment.type);
416
+ assessments.push(assessment);
417
+ yield { path: file, resolved: 0 };
200
418
  }
201
- named.set(file, [...fields, ...sections]);
202
- }
203
- const absent = new Map();
204
- for (const [file, references] of named) {
205
- for (const { reference } of references) {
206
- if (!missing(reference, typeOf, binary))
207
- continue;
208
- const referrers = absent.get(reference.target) ?? [];
209
- referrers.push(file);
210
- absent.set(reference.target, referrers);
419
+ const judged = verdicts();
420
+ for (const assessment of assessments) {
421
+ const resolved = judged.add(assessment, (path) => types.get(path), binary);
422
+ yield { path: assessment.file, resolved };
211
423
  }
424
+ return judged.verdict();
212
425
  }
213
- const missingPaths = [...absent.keys()].sort();
214
- if (missingPaths.length > 0) {
215
- const path = missingPaths[0];
216
- const referrers = [...new Set(absent.get(path) ?? [])].sort();
217
- return refuse(path, 'referenced-missing-file', { referrers });
426
+ finally {
427
+ closeBudget();
218
428
  }
219
- return { verdict: 'accept' };
429
+ }
430
+ export function validate(library, context = { binaryTargets: [] }, model = { types: {} }) {
431
+ const steps = validating(new Map(Object.entries(library)), context, model);
432
+ let step = steps.next();
433
+ while (step.done !== true)
434
+ step = steps.next();
435
+ return step.value;
220
436
  }
@@ -1 +1 @@
1
- {"version":3,"file":"yaml.d.ts","sourceRoot":"","sources":["../src/yaml.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"yaml.d.ts","sourceRoot":"","sources":["../src/yaml.ts"],"names":[],"mappings":"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"}
package/dist/yaml.js CHANGED
@@ -6,6 +6,18 @@ export class YamlError extends Error {
6
6
  this.name = 'YamlError';
7
7
  }
8
8
  }
9
+ const YAML_DEPTH_MAX = 64;
10
+ function nested(c, parse) {
11
+ if (c.depth >= YAML_DEPTH_MAX)
12
+ throw new YamlError(c.i);
13
+ c.depth += 1;
14
+ try {
15
+ return parse();
16
+ }
17
+ finally {
18
+ c.depth -= 1;
19
+ }
20
+ }
9
21
  function peek(c) {
10
22
  return c.i < c.end ? c.text[c.i] : '';
11
23
  }
@@ -70,7 +82,10 @@ function indentAt(c) {
70
82
  }
71
83
  return n;
72
84
  }
85
+ const UNHELD_TEXT = /[\0\ud800-\udfff]/u;
73
86
  function scalar(value, start, end) {
87
+ if (typeof value === 'string' && UNHELD_TEXT.test(value))
88
+ throw new YamlError(start);
74
89
  return { kind: 'scalar', value, start, end };
75
90
  }
76
91
  export function interpretPlain(text) {
@@ -202,6 +217,9 @@ function parseFlowValue(c) {
202
217
  return parseScalar(c, true);
203
218
  }
204
219
  function parseFlowMap(c) {
220
+ return nested(c, () => flowMap(c));
221
+ }
222
+ function flowMap(c) {
205
223
  const start = c.i;
206
224
  if (peek(c) !== '{')
207
225
  throw new YamlError(c.i);
@@ -240,6 +258,9 @@ function parseFlowMap(c) {
240
258
  throw new YamlError(c.i);
241
259
  }
242
260
  function parseFlowSeq(c) {
261
+ return nested(c, () => flowSeq(c));
262
+ }
263
+ function flowSeq(c) {
243
264
  const start = c.i;
244
265
  if (peek(c) !== '[')
245
266
  throw new YamlError(c.i);
@@ -336,6 +357,9 @@ function parseSeqItem(c, indent) {
336
357
  return parseValue(c, indent);
337
358
  }
338
359
  function parseBlockSeq(c, indent) {
360
+ return nested(c, () => blockSeq(c, indent));
361
+ }
362
+ function blockSeq(c, indent) {
339
363
  const start = c.i;
340
364
  const items = [];
341
365
  let end = start;
@@ -355,6 +379,9 @@ function parseBlockSeq(c, indent) {
355
379
  return { kind: 'seq', items, start, end };
356
380
  }
357
381
  function parseBlockMap(c, indent, inline = false) {
382
+ return nested(c, () => blockMap(c, indent, inline));
383
+ }
384
+ function blockMap(c, indent, inline) {
358
385
  const start = c.i;
359
386
  const entries = [];
360
387
  const seen = new Set();
@@ -387,7 +414,7 @@ function parseBlockMap(c, indent, inline = false) {
387
414
  return { kind: 'map', entries, start, end };
388
415
  }
389
416
  export function parseYamlMapping(text, start, end) {
390
- const c = { text, i: start, end };
417
+ const c = { text, i: start, end, depth: 0 };
391
418
  skipBlankAndComments(c);
392
419
  if (c.i >= end)
393
420
  return { kind: 'map', entries: [], start, end };
@@ -439,17 +466,23 @@ function listed(value) {
439
466
  return keys.length === value.length && keys.every((key, index) => key === String(index));
440
467
  }
441
468
  function heldWithin(value, within) {
442
- if (value === null || typeof value === 'string' || typeof value === 'boolean')
469
+ if (typeof value === 'string')
470
+ return !UNHELD_TEXT.test(value);
471
+ if (value === null || typeof value === 'boolean')
443
472
  return true;
444
473
  if (typeof value === 'number')
445
474
  return Number.isFinite(value);
446
475
  if (typeof value !== 'object' || within.includes(value))
447
476
  return false;
477
+ if (within.length + 1 >= YAML_DEPTH_MAX)
478
+ return false;
448
479
  const path = [...within, value];
449
480
  const held = (item) => heldWithin(item, path);
450
481
  if (Array.isArray(value))
451
482
  return listed(value) && value.every(held);
452
- return plain(value) && Object.values(value).every(held);
483
+ return (plain(value) &&
484
+ Object.keys(value).every((key) => !UNHELD_TEXT.test(key)) &&
485
+ Object.values(value).every(held));
453
486
  }
454
487
  export function frontmatterValue(value) {
455
488
  return heldWithin(value, []);
@@ -51,8 +51,8 @@ change to a value that is not prose leaves the projection alone.
51
51
 
52
52
  `division.json` exercises the public `divide` seam separately from the
53
53
  validation Libraries. It carries `schema` and `cases` like `projection.json`,
54
- and each case adds the `budget` the division runs under. Parse the source, call
55
- `divide(document, file, context)` with `{ documentTargets, definition, budget }`,
54
+ and each case adds the `bound` the division runs under. Parse the source, call
55
+ `divide(document, file, context)` with `{ documentTargets, definition, bound }`,
56
56
  and compare against `expected`: either `{ "ok": true, "spans": [...] }`, where
57
57
  each span carries the exact `text` to embed and the exact `source` the returned
58
58
  `start` and `end` address in that case's source, or `{ "ok": false, "refusal": ... }`. Then
@@ -65,6 +65,19 @@ value one span carries part of. `start` and `end` are UTF-8 byte offsets into th
65
65
  source; every source in this corpus is ASCII, so a consumer compares the
66
66
  addressed `source` with an ordinary string slice.
67
67
 
68
+ ## Queries
69
+
70
+ `queries.json` is the one table of cases both query evaluators answer. It carries `schema`,
71
+ one schema text; `documents`, each named and carrying its `path`, the `type` a collection
72
+ stores for it (`any` for an untyped Markdown Document, `null` for one that is not Markdown),
73
+ its `fileId`, the `text` the device holds or `null`, and its `fields`; and `cases`, each with a
74
+ `name`, a `query` of filter parts, the `document` it names and whether it `matches`. Verify
75
+ the schema and plan `{ ...query, select: {} }` with `planOf` against its types. Then
76
+ `matches(plan, candidate)` over the named Document, given its type's definition, and the
77
+ condition `sql.tests` writes over a collection holding every Document of the table with the
78
+ values `indexed` derives from it must both answer `matches`. A new operator adds its cases
79
+ here first.
80
+
68
81
  ## Verdict format
69
82
 
70
83
  Acceptance:
@@ -107,6 +120,7 @@ schema text. The model's own rules:
107
120
  | `frontmatter-fence` | A present frontmatter fence does not parse as the accepted YAML subset |
108
121
  | `schema-yaml` | A schema text is not a mapping in the accepted YAML subset |
109
122
  | `schema-type-name` | A type name is empty, starts with `$`, or is `any` or `attachment` |
123
+ | `provided-type` | A schema names a type its caller provides with a key but its two role keywords |
110
124
  | `schema` | A type or subschema is neither a mapping nor a boolean |
111
125
  | `schema-keyword` | A schema carries a key that is no JSON Schema 2020-12 keyword the model admits |
112
126
  | `$defs` | `$defs` is not a mapping |
@@ -114,17 +128,23 @@ schema text. The model's own rules:
114
128
  | `reference` | `reference` is not on a string or names something but a type, `any`, `attachment` |
115
129
  | `sections` | A section is not `{ kind: text }` or `{ kind: list, items }` over one scalar |
116
130
  | `member_document` | The mark is not a boolean, or a second type carries it |
117
- | `unknown-type` | A file declares a type absent from the governing schema |
131
+ | `unknown-type` | A file declares a type absent from the governing schema, or other than its typed path requires it to name |
118
132
  | `undeclared-field` | A closed object carries a key it does not declare |
119
133
  | `required-section` | A required section heading is missing |
134
+ | `conditional-section` | A section its type's `if` branch requires is absent or holds no text |
120
135
  | `unresolved-reference` | A reference at any depth or a section item reaches no member it admits |
121
- | `unresolved-link` | A body link is an absolute path or leaves the collection |
136
+ | `unresolved-link` | A body link is an absolute path, leaves the collection or, from a typed Document at a document path, reaches a reserved name |
137
+ | `room-md-type` | `ROOM.md` declares a type |
138
+ | `required-body` | A file at a typed path that requires a body has a body holding no text |
122
139
  | `structured-section` | A declared list section is not a list of values of its item schema |
123
140
  | `referenced-missing-file` | Remaining files still reference an absent path |
124
141
  | `unwritable-link` | No Markdown destination form can carry a new link destination |
125
- | `division-budget` | A division budget is not a whole number of at least 2 |
142
+ | `division-bound` | A division bound is not a whole number of at least 2 |
143
+
144
+ A `then` or `else` of an `if` on a type, in its `allOf` or in such a branch may hold
145
+ `sections`, naming sections the type declares as the type declares them, else `sections`.
126
146
 
127
- `unwritable-link` is refused by `apply` and `division-budget` by `divide`, not
147
+ `unwritable-link` is refused by `apply` and `division-bound` by `divide`, not
128
148
  by `validate`, so no case under `cases/` carries either.
129
149
 
130
150
  A reference that admits `attachment` reaches any path in the same Library, so
@@ -0,0 +1 @@
1
+ { "binaryTargets": [] }
@@ -0,0 +1,6 @@
1
+ {
2
+ "verdict": "refuse",
3
+ "file": "questions/payments.md",
4
+ "location": "Answer",
5
+ "rule": "conditional-section"
6
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ type: question
3
+ title: Where does it run
4
+ status: open
5
+ ---
6
+
7
+ ## Question
8
+
9
+ Which host runs the shop?