@agentshouse/mdmodel 0.1.0-c83456e1cebfb → 0.1.0-cabd61587647f

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 (295) hide show
  1. package/API.md +242 -181
  2. package/README.md +17 -18
  3. package/dist/blocks.d.ts.map +1 -1
  4. package/dist/blocks.js +33 -26
  5. package/dist/condition.d.ts +149 -0
  6. package/dist/condition.d.ts.map +1 -0
  7. package/dist/condition.js +500 -0
  8. package/dist/definition.d.ts +62 -24
  9. package/dist/definition.d.ts.map +1 -1
  10. package/dist/definition.js +703 -138
  11. package/dist/divide.d.ts.map +1 -1
  12. package/dist/divide.js +18 -8
  13. package/dist/document.d.ts +10 -2
  14. package/dist/document.d.ts.map +1 -1
  15. package/dist/document.js +370 -114
  16. package/dist/fields.d.ts +2 -0
  17. package/dist/fields.d.ts.map +1 -1
  18. package/dist/fields.js +7 -0
  19. package/dist/index.d.ts +14 -6
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +9 -4
  22. package/dist/links.d.ts +2 -0
  23. package/dist/links.d.ts.map +1 -1
  24. package/dist/links.js +23 -12
  25. package/dist/paths.d.ts +4 -0
  26. package/dist/paths.d.ts.map +1 -1
  27. package/dist/paths.js +110 -10
  28. package/dist/project.d.ts +1 -0
  29. package/dist/project.d.ts.map +1 -1
  30. package/dist/project.js +52 -30
  31. package/dist/references.d.ts +4 -0
  32. package/dist/references.d.ts.map +1 -0
  33. package/dist/references.js +25 -0
  34. package/dist/schema.d.ts +7 -6
  35. package/dist/schema.d.ts.map +1 -1
  36. package/dist/schema.js +157 -31
  37. package/dist/types.d.ts +43 -1
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/validate.d.ts +6 -1
  40. package/dist/validate.d.ts.map +1 -1
  41. package/dist/validate.js +167 -144
  42. package/dist/yaml.d.ts +8 -3
  43. package/dist/yaml.d.ts.map +1 -1
  44. package/dist/yaml.js +133 -43
  45. package/fixtures/README.md +49 -38
  46. package/fixtures/cases/absent-link-dangles/context.json +1 -0
  47. package/fixtures/cases/absent-link-dangles/expected.json +1 -0
  48. package/fixtures/cases/absent-link-dangles/library/item.md +8 -0
  49. package/fixtures/cases/absent-link-dangles/schema.yaml +6 -0
  50. package/fixtures/cases/absent-optional-field/context.json +1 -0
  51. package/fixtures/cases/absent-optional-field/expected.json +1 -0
  52. package/fixtures/cases/absent-optional-field/library/companies/acme.md +4 -0
  53. package/fixtures/cases/absent-optional-field/library/deals/lead.md +6 -0
  54. package/fixtures/cases/absent-optional-field/schema.yaml +56 -0
  55. package/fixtures/cases/absolute-link/context.json +1 -0
  56. package/fixtures/cases/{missing-binary-image → absolute-link}/expected.json +1 -1
  57. package/fixtures/cases/absolute-link/library/item.md +8 -0
  58. package/fixtures/cases/absolute-link/schema.yaml +6 -0
  59. package/fixtures/cases/attachment-reference-any-file/context.json +1 -0
  60. package/fixtures/cases/attachment-reference-any-file/expected.json +1 -0
  61. package/fixtures/cases/attachment-reference-any-file/library/companies/acme.md +4 -0
  62. package/fixtures/cases/attachment-reference-any-file/library/deals/renewal.md +15 -0
  63. package/fixtures/cases/attachment-reference-any-file/library/places/depot.md +4 -0
  64. package/fixtures/cases/attachment-reference-any-file/library/products/seats.md +4 -0
  65. package/fixtures/cases/attachment-reference-any-file/library/products/support.md +4 -0
  66. package/fixtures/cases/attachment-reference-any-file/schema.yaml +32 -0
  67. package/fixtures/cases/attachment-reference-dangles/context.json +1 -0
  68. package/fixtures/cases/attachment-reference-dangles/expected.json +1 -0
  69. package/fixtures/cases/attachment-reference-dangles/library/companies/acme.md +4 -0
  70. package/fixtures/cases/attachment-reference-dangles/library/deals/renewal.md +15 -0
  71. package/fixtures/cases/attachment-reference-dangles/library/places/depot.md +4 -0
  72. package/fixtures/cases/attachment-reference-dangles/library/products/seats.md +4 -0
  73. package/fixtures/cases/attachment-reference-dangles/library/products/support.md +4 -0
  74. package/fixtures/cases/attachment-reference-dangles/schema.yaml +32 -0
  75. package/fixtures/cases/attachment-reference-outside/context.json +1 -0
  76. package/fixtures/cases/attachment-reference-outside/expected.json +6 -0
  77. package/fixtures/cases/attachment-reference-outside/library/companies/acme.md +4 -0
  78. package/fixtures/cases/attachment-reference-outside/library/deals/renewal.md +15 -0
  79. package/fixtures/cases/attachment-reference-outside/library/places/depot.md +4 -0
  80. package/fixtures/cases/attachment-reference-outside/library/products/seats.md +4 -0
  81. package/fixtures/cases/attachment-reference-outside/library/products/support.md +4 -0
  82. package/fixtures/cases/attachment-reference-outside/schema.yaml +32 -0
  83. package/fixtures/cases/binary-any-reference/expected.json +1 -1
  84. package/fixtures/cases/binary-any-reference/schema.yaml +4 -4
  85. package/fixtures/cases/binary-document-collision/schema.yaml +1 -2
  86. package/fixtures/cases/binary-image-dangles/expected.json +1 -0
  87. package/fixtures/cases/binary-image-dangles/schema.yaml +1 -0
  88. package/fixtures/cases/binary-link-dangles/expected.json +1 -0
  89. package/fixtures/cases/binary-link-dangles/schema.yaml +1 -0
  90. package/fixtures/cases/binary-link-outside-collection/schema.yaml +1 -2
  91. package/fixtures/cases/binary-prototype-reference/expected.json +1 -1
  92. package/fixtures/cases/binary-prototype-reference/schema.yaml +4 -4
  93. package/fixtures/cases/binary-typed-reference/expected.json +1 -1
  94. package/fixtures/cases/binary-typed-reference/schema.yaml +5 -5
  95. package/fixtures/cases/boolean-field/context.json +1 -0
  96. package/fixtures/cases/boolean-field/expected.json +6 -0
  97. package/fixtures/cases/boolean-field/library/companies/acme.md +4 -0
  98. package/fixtures/cases/boolean-field/library/deals/renewal.md +31 -0
  99. package/fixtures/cases/boolean-field/schema.yaml +56 -0
  100. package/fixtures/cases/bracketed-link-definition/schema.yaml +6 -6
  101. package/fixtures/cases/closed-nested-object/context.json +1 -0
  102. package/fixtures/cases/closed-nested-object/expected.json +6 -0
  103. package/fixtures/cases/closed-nested-object/library/companies/acme.md +4 -0
  104. package/fixtures/cases/closed-nested-object/library/deals/renewal.md +32 -0
  105. package/fixtures/cases/closed-nested-object/schema.yaml +56 -0
  106. package/fixtures/cases/composed-root-reference/context.json +1 -0
  107. package/fixtures/cases/composed-root-reference/expected.json +6 -0
  108. package/fixtures/cases/composed-root-reference/library/a.md +5 -0
  109. package/fixtures/cases/composed-root-reference/schema.yaml +13 -0
  110. package/fixtures/cases/date-time-format/context.json +1 -0
  111. package/fixtures/cases/date-time-format/expected.json +6 -0
  112. package/fixtures/cases/date-time-format/library/companies/acme.md +4 -0
  113. package/fixtures/cases/date-time-format/library/deals/renewal.md +31 -0
  114. package/fixtures/cases/date-time-format/schema.yaml +56 -0
  115. package/fixtures/cases/declared-binary-links/schema.yaml +1 -2
  116. package/fixtures/cases/default-unfilled/context.json +1 -0
  117. package/fixtures/cases/default-unfilled/expected.json +6 -0
  118. package/fixtures/cases/default-unfilled/library/companies/acme.md +4 -0
  119. package/fixtures/cases/default-unfilled/library/deals/renewal.md +29 -0
  120. package/fixtures/cases/default-unfilled/schema.yaml +56 -0
  121. package/fixtures/cases/defs-pattern/context.json +1 -0
  122. package/fixtures/cases/defs-pattern/expected.json +6 -0
  123. package/fixtures/cases/defs-pattern/library/companies/acme.md +4 -0
  124. package/fixtures/cases/defs-pattern/library/deals/renewal.md +31 -0
  125. package/fixtures/cases/defs-pattern/schema.yaml +56 -0
  126. package/fixtures/cases/dependent-rule/context.json +1 -0
  127. package/fixtures/cases/dependent-rule/expected.json +6 -0
  128. package/fixtures/cases/dependent-rule/library/companies/acme.md +4 -0
  129. package/fixtures/cases/dependent-rule/library/deals/renewal.md +30 -0
  130. package/fixtures/cases/dependent-rule/schema.yaml +56 -0
  131. package/fixtures/cases/duplicate-link-definition/schema.yaml +6 -6
  132. package/fixtures/cases/email-format/context.json +1 -0
  133. package/fixtures/cases/email-format/expected.json +6 -0
  134. package/fixtures/cases/email-format/library/companies/acme.md +4 -0
  135. package/fixtures/cases/email-format/library/deals/renewal.md +31 -0
  136. package/fixtures/cases/email-format/schema.yaml +56 -0
  137. package/fixtures/cases/fenced-link-definition/schema.yaml +6 -6
  138. package/fixtures/cases/field-enum/expected.json +2 -2
  139. package/fixtures/cases/field-enum/schema.yaml +7 -7
  140. package/fixtures/cases/frontmatter-schema/schema.yaml +3 -4
  141. package/fixtures/cases/integer-field/context.json +1 -0
  142. package/fixtures/cases/integer-field/expected.json +6 -0
  143. package/fixtures/cases/integer-field/library/companies/acme.md +4 -0
  144. package/fixtures/cases/integer-field/library/deals/renewal.md +31 -0
  145. package/fixtures/cases/integer-field/schema.yaml +56 -0
  146. package/fixtures/cases/interrupted-link-definition/schema.yaml +1 -2
  147. package/fixtures/cases/json-schema-constructs/context.json +1 -0
  148. package/fixtures/cases/json-schema-constructs/expected.json +1 -0
  149. package/fixtures/cases/json-schema-constructs/library/companies/acme.md +4 -0
  150. package/fixtures/cases/json-schema-constructs/library/deals/renewal.md +31 -0
  151. package/fixtures/cases/json-schema-constructs/schema.yaml +56 -0
  152. package/fixtures/cases/list-of-objects-closed/context.json +1 -0
  153. package/fixtures/cases/list-of-objects-closed/expected.json +6 -0
  154. package/fixtures/cases/list-of-objects-closed/library/companies/acme.md +4 -0
  155. package/fixtures/cases/list-of-objects-closed/library/deals/renewal.md +32 -0
  156. package/fixtures/cases/list-of-objects-closed/schema.yaml +56 -0
  157. package/fixtures/cases/list-of-objects-item/context.json +1 -0
  158. package/fixtures/cases/list-of-objects-item/expected.json +6 -0
  159. package/fixtures/cases/list-of-objects-item/library/companies/acme.md +4 -0
  160. package/fixtures/cases/list-of-objects-item/library/deals/renewal.md +31 -0
  161. package/fixtures/cases/list-of-objects-item/schema.yaml +56 -0
  162. package/fixtures/cases/list-of-objects-reference/context.json +1 -0
  163. package/fixtures/cases/list-of-objects-reference/expected.json +6 -0
  164. package/fixtures/cases/list-of-objects-reference/library/companies/acme.md +4 -0
  165. package/fixtures/cases/list-of-objects-reference/library/deals/renewal.md +14 -0
  166. package/fixtures/cases/list-of-objects-reference/library/places/depot.md +4 -0
  167. package/fixtures/cases/list-of-objects-reference/library/products/seats.md +4 -0
  168. package/fixtures/cases/list-of-objects-reference/library/products/support.md +4 -0
  169. package/fixtures/cases/list-of-objects-reference/schema.yaml +32 -0
  170. package/fixtures/cases/list-size/context.json +1 -0
  171. package/fixtures/cases/list-size/expected.json +6 -0
  172. package/fixtures/cases/list-size/library/companies/acme.md +4 -0
  173. package/fixtures/cases/list-size/library/deals/renewal.md +31 -0
  174. package/fixtures/cases/list-size/schema.yaml +56 -0
  175. package/fixtures/cases/long-fence-link-definition/schema.yaml +1 -2
  176. package/fixtures/cases/malformed-definition/expected.json +2 -2
  177. package/fixtures/cases/malformed-definition/schema.yaml +3 -4
  178. package/fixtures/cases/member-document/schema.yaml +5 -5
  179. package/fixtures/cases/missing-required-field/expected.json +2 -2
  180. package/fixtures/cases/missing-required-field/schema.yaml +7 -7
  181. package/fixtures/cases/missing-section-item-kind/expected.json +2 -2
  182. package/fixtures/cases/missing-section-item-kind/schema.yaml +6 -6
  183. package/fixtures/cases/misspelled-key/context.json +1 -0
  184. package/fixtures/cases/misspelled-key/expected.json +6 -0
  185. package/fixtures/cases/misspelled-key/library/companies/acme.md +4 -0
  186. package/fixtures/cases/misspelled-key/library/deals/renewal.md +32 -0
  187. package/fixtures/cases/misspelled-key/schema.yaml +56 -0
  188. package/fixtures/cases/nested-defs/context.json +1 -0
  189. package/fixtures/cases/nested-defs/expected.json +6 -0
  190. package/fixtures/cases/nested-defs/library/deal.md +6 -0
  191. package/fixtures/cases/nested-defs/schema.yaml +11 -0
  192. package/fixtures/cases/nested-object-reference/context.json +1 -0
  193. package/fixtures/cases/nested-object-reference/expected.json +6 -0
  194. package/fixtures/cases/nested-object-reference/library/companies/acme.md +4 -0
  195. package/fixtures/cases/nested-object-reference/library/deals/renewal.md +14 -0
  196. package/fixtures/cases/nested-object-reference/library/places/depot.md +4 -0
  197. package/fixtures/cases/nested-object-reference/library/products/seats.md +4 -0
  198. package/fixtures/cases/nested-object-reference/library/products/support.md +4 -0
  199. package/fixtures/cases/nested-object-reference/schema.yaml +32 -0
  200. package/fixtures/cases/nested-reference/context.json +1 -0
  201. package/fixtures/cases/nested-reference/expected.json +1 -0
  202. package/fixtures/cases/nested-reference/library/companies/acme.md +4 -0
  203. package/fixtures/cases/nested-reference/library/deals/renewal.md +15 -0
  204. package/fixtures/cases/nested-reference/library/places/depot.md +4 -0
  205. package/fixtures/cases/nested-reference/library/products/seats.md +4 -0
  206. package/fixtures/cases/nested-reference/library/products/support.md +4 -0
  207. package/fixtures/cases/nested-reference/schema.yaml +32 -0
  208. package/fixtures/cases/nested-required-field/context.json +1 -0
  209. package/fixtures/cases/nested-required-field/expected.json +6 -0
  210. package/fixtures/cases/nested-required-field/library/companies/acme.md +4 -0
  211. package/fixtures/cases/nested-required-field/library/deals/renewal.md +30 -0
  212. package/fixtures/cases/nested-required-field/schema.yaml +56 -0
  213. package/fixtures/cases/null-refused/context.json +1 -0
  214. package/fixtures/cases/null-refused/expected.json +6 -0
  215. package/fixtures/cases/null-refused/library/companies/acme.md +4 -0
  216. package/fixtures/cases/null-refused/library/deals/renewal.md +31 -0
  217. package/fixtures/cases/null-refused/schema.yaml +56 -0
  218. package/fixtures/cases/numeric-range/context.json +1 -0
  219. package/fixtures/cases/numeric-range/expected.json +6 -0
  220. package/fixtures/cases/numeric-range/library/companies/acme.md +4 -0
  221. package/fixtures/cases/numeric-range/library/deals/renewal.md +31 -0
  222. package/fixtures/cases/numeric-range/schema.yaml +56 -0
  223. package/fixtures/cases/one-of-variant/context.json +1 -0
  224. package/fixtures/cases/one-of-variant/expected.json +6 -0
  225. package/fixtures/cases/one-of-variant/library/companies/acme.md +4 -0
  226. package/fixtures/cases/one-of-variant/library/deals/renewal.md +31 -0
  227. package/fixtures/cases/one-of-variant/schema.yaml +56 -0
  228. package/fixtures/cases/open-map-value/context.json +1 -0
  229. package/fixtures/cases/open-map-value/expected.json +6 -0
  230. package/fixtures/cases/open-map-value/library/companies/acme.md +4 -0
  231. package/fixtures/cases/open-map-value/library/deals/renewal.md +31 -0
  232. package/fixtures/cases/open-map-value/schema.yaml +56 -0
  233. package/fixtures/cases/other-room-reference/context.json +1 -0
  234. package/fixtures/cases/other-room-reference/expected.json +6 -0
  235. package/fixtures/cases/other-room-reference/library/companies/acme.md +4 -0
  236. package/fixtures/cases/other-room-reference/library/deals/renewal.md +14 -0
  237. package/fixtures/cases/other-room-reference/library/places/depot.md +4 -0
  238. package/fixtures/cases/other-room-reference/library/products/seats.md +4 -0
  239. package/fixtures/cases/other-room-reference/library/products/support.md +4 -0
  240. package/fixtures/cases/other-room-reference/schema.yaml +32 -0
  241. package/fixtures/cases/paragraph-link-definition/schema.yaml +1 -2
  242. package/fixtures/cases/prototype-field/expected.json +1 -1
  243. package/fixtures/cases/prototype-field/schema.yaml +6 -6
  244. package/fixtures/cases/quoted-link-definition/library/notes/draft.md +1 -1
  245. package/fixtures/cases/quoted-link-definition/schema.yaml +1 -2
  246. package/fixtures/cases/referenced-missing-file/schema.yaml +11 -10
  247. package/fixtures/cases/remote-ref/context.json +1 -0
  248. package/fixtures/cases/remote-ref/expected.json +5 -0
  249. package/fixtures/cases/remote-ref/schema.yaml +3 -0
  250. package/fixtures/cases/second-member-document/expected.json +2 -2
  251. package/fixtures/cases/second-member-document/schema.yaml +4 -5
  252. package/fixtures/cases/section-item-enum/schema.yaml +3 -4
  253. package/fixtures/cases/section-item-kind/schema.yaml +3 -4
  254. package/fixtures/cases/section-items/library/page.md +14 -0
  255. package/fixtures/cases/section-items/schema.yaml +10 -8
  256. package/fixtures/cases/section-reference-path/schema.yaml +3 -4
  257. package/fixtures/cases/section-reference-type/schema.yaml +3 -4
  258. package/fixtures/cases/shortcut-reference-link/library/item.md +1 -1
  259. package/fixtures/cases/shortcut-reference-link/schema.yaml +6 -6
  260. package/fixtures/cases/string-length/context.json +1 -0
  261. package/fixtures/cases/string-length/expected.json +6 -0
  262. package/fixtures/cases/string-length/library/companies/acme.md +4 -0
  263. package/fixtures/cases/string-length/library/deals/renewal.md +31 -0
  264. package/fixtures/cases/string-length/schema.yaml +56 -0
  265. package/fixtures/cases/structured-section/schema.yaml +7 -7
  266. package/fixtures/cases/submission-keys/schema.yaml +4 -4
  267. package/fixtures/cases/text-field-kind/expected.json +2 -2
  268. package/fixtures/cases/text-field-kind/schema.yaml +4 -4
  269. package/fixtures/cases/truncated-link-definition/library/notes/draft.md +1 -1
  270. package/fixtures/cases/truncated-link-definition/schema.yaml +1 -2
  271. package/fixtures/cases/types-folder-content/schema.yaml +4 -4
  272. package/fixtures/cases/undeclared-field/expected.json +1 -1
  273. package/fixtures/cases/undeclared-field/schema.yaml +6 -6
  274. package/fixtures/cases/unevaluated-items-closed/context.json +1 -0
  275. package/fixtures/cases/unevaluated-items-closed/expected.json +6 -0
  276. package/fixtures/cases/unevaluated-items-closed/library/trip.md +6 -0
  277. package/fixtures/cases/unevaluated-items-closed/schema.yaml +10 -0
  278. package/fixtures/cases/unknown-type/expected.json +1 -1
  279. package/fixtures/cases/unknown-type/schema.yaml +3 -4
  280. package/fixtures/cases/unlisted-reference-type/expected.json +2 -2
  281. package/fixtures/cases/unlisted-reference-type/schema.yaml +3 -4
  282. package/fixtures/cases/unresolved-link/library/item.md +1 -1
  283. package/fixtures/cases/unresolved-link/schema.yaml +6 -6
  284. package/fixtures/cases/valid-typed-document/schema.yaml +16 -15
  285. package/fixtures/cases/wide-item-fence-link/schema.yaml +1 -2
  286. package/fixtures/division.json +1 -1
  287. package/fixtures/projection.json +33 -1
  288. package/package.json +4 -2
  289. package/fixtures/cases/missing-binary-image/schema.yaml +0 -2
  290. package/fixtures/cases/missing-binary-link/expected.json +0 -5
  291. package/fixtures/cases/missing-binary-link/schema.yaml +0 -2
  292. /package/fixtures/cases/{missing-binary-image → binary-image-dangles}/context.json +0 -0
  293. /package/fixtures/cases/{missing-binary-image → binary-image-dangles}/library/notes/draft.md +0 -0
  294. /package/fixtures/cases/{missing-binary-link → binary-link-dangles}/context.json +0 -0
  295. /package/fixtures/cases/{missing-binary-link → binary-link-dangles}/library/notes/draft.md +0 -0
@@ -1,138 +1,705 @@
1
- import { jsValue, parseYamlMapping, YamlError } from './yaml.js';
1
+ import { MEMBER_FIELD, RECEIVED_FIELD, SITE_FIELD, TYPE_FIELD } from './fields.js';
2
+ import { equalValue, isRecord, jsValue, parseYamlMapping, YamlError } from './yaml.js';
3
+ export const JSON_TYPES = [
4
+ 'string',
5
+ 'number',
6
+ 'integer',
7
+ 'boolean',
8
+ 'object',
9
+ 'array',
10
+ 'null',
11
+ ];
12
+ export const BUILT_IN_TYPE = 'any';
13
+ export const ATTACHMENT = 'attachment';
14
+ const SCALAR_TYPES = new Set(['string', 'number', 'integer', 'boolean']);
15
+ const COMPOSITES = ['array', 'object'];
16
+ const ANNOTATIONS = new Set([
17
+ '$comment',
18
+ 'title',
19
+ 'description',
20
+ 'default',
21
+ 'examples',
22
+ 'deprecated',
23
+ 'readOnly',
24
+ 'writeOnly',
25
+ ]);
26
+ const FIELD_RULES = ['change_roles', 'initial_roles', 'transitions'];
27
+ const DEFS_REFERENCE = '#/$defs/';
28
+ const UNRESOLVED = /[\u0000-\u001f\u007f%[\]\\^{|}]|\s$/u;
29
+ export const ARRAY_INDEX = /^(?:0|[1-9][0-9]*)$/u;
30
+ const HOUSE_KEYS = {
31
+ [TYPE_FIELD]: { prose: false },
32
+ [SITE_FIELD]: { prose: false },
33
+ [RECEIVED_FIELD]: { prose: false },
34
+ };
2
35
  function refuse(rule, location) {
3
36
  return location === undefined ? { rule } : { rule, location };
4
37
  }
5
- function isRecord(value) {
6
- return value !== null && typeof value === 'object' && !Array.isArray(value);
38
+ export function escapePointer(key) {
39
+ return key.replaceAll('~', '~0').replaceAll('/', '~1');
7
40
  }
8
- function requiredFlag(value) {
9
- if (value === undefined)
41
+ function unescapePointer(segment) {
42
+ return segment.replaceAll('~1', '/').replaceAll('~0', '~');
43
+ }
44
+ export function pointerSegments(pointer) {
45
+ return pointer.split('/').slice(1).map(unescapePointer);
46
+ }
47
+ export function refTarget(reference, defs) {
48
+ if (!reference.startsWith(DEFS_REFERENCE) || UNRESOLVED.test(reference))
49
+ return undefined;
50
+ const segments = reference.slice(DEFS_REFERENCE.length).split('/');
51
+ const [name = '', ...rest] = segments.map(unescapePointer);
52
+ if (segments.some((segment) => escapePointer(unescapePointer(segment)) !== segment))
53
+ return undefined;
54
+ if (!Object.hasOwn(defs, name))
55
+ return undefined;
56
+ let node = defs[name];
57
+ for (let index = 0; index < rest.length; index += 1) {
58
+ const keyword = rest[index];
59
+ if (!isRecord(node) || !Object.hasOwn(node, keyword) || !Object.hasOwn(KEYWORDS, keyword))
60
+ return undefined;
61
+ const check = KEYWORDS[keyword];
62
+ const held = node[keyword];
63
+ if (check === SCHEMA) {
64
+ node = held;
65
+ continue;
66
+ }
67
+ const key = rest[index + 1];
68
+ index += 1;
69
+ if (check === SCHEMA_LIST && Array.isArray(held) && key !== undefined && ARRAY_INDEX.test(key))
70
+ node = held[Number(key)];
71
+ else if (check === SCHEMA_MAP &&
72
+ isRecord(held) &&
73
+ key !== undefined &&
74
+ Object.hasOwn(held, key))
75
+ node = held[key];
76
+ else
77
+ return undefined;
78
+ if (node === undefined)
79
+ return undefined;
80
+ }
81
+ return { name, node };
82
+ }
83
+ function compiles(pattern) {
84
+ if (typeof pattern !== 'string')
85
+ return false;
86
+ try {
87
+ new RegExp(pattern, 'u');
88
+ return true;
89
+ }
90
+ catch {
10
91
  return false;
92
+ }
93
+ }
94
+ function finite(value) {
95
+ return typeof value === 'number' && Number.isFinite(value);
96
+ }
97
+ function count(value) {
98
+ return Number.isSafeInteger(value) && value >= 0;
99
+ }
100
+ function strings(value) {
101
+ return (Array.isArray(value) &&
102
+ value.every((item) => typeof item === 'string') &&
103
+ new Set(value).size === value.length);
104
+ }
105
+ function schemaFailure(value, at, context) {
11
106
  if (typeof value === 'boolean')
12
- return value;
107
+ return undefined;
108
+ if (!isRecord(value))
109
+ return refuse('schema', at);
110
+ for (const [key, held] of Object.entries(value)) {
111
+ const where = `${at}/${escapePointer(key)}`;
112
+ if (!Object.hasOwn(KEYWORDS, key))
113
+ return refuse('schema-keyword', where);
114
+ const check = KEYWORDS[key];
115
+ if (check === SCHEMA) {
116
+ const failure = schemaFailure(held, where, context);
117
+ if (failure !== undefined)
118
+ return failure;
119
+ continue;
120
+ }
121
+ if (check === SCHEMA_LIST) {
122
+ if (!Array.isArray(held) || held.length === 0)
123
+ return refuse(key, where);
124
+ for (const [index, item] of held.entries()) {
125
+ const failure = schemaFailure(item, `${where}/${index}`, context);
126
+ if (failure !== undefined)
127
+ return failure;
128
+ }
129
+ continue;
130
+ }
131
+ if (check === SCHEMA_MAP) {
132
+ if (!isRecord(held))
133
+ return refuse(key, where);
134
+ for (const [name, item] of Object.entries(held)) {
135
+ if (key === 'patternProperties' && !compiles(name))
136
+ return refuse(key, `${where}/${escapePointer(name)}`);
137
+ const failure = schemaFailure(item, `${where}/${escapePointer(name)}`, context);
138
+ if (failure !== undefined)
139
+ return failure;
140
+ }
141
+ continue;
142
+ }
143
+ if (!check(held, where, value, context))
144
+ return refuse(key, where);
145
+ }
13
146
  return undefined;
14
147
  }
15
- function stringList(value) {
16
- if (!Array.isArray(value) || value.length === 0)
148
+ const SCHEMA = () => true;
149
+ const SCHEMA_LIST = () => true;
150
+ const SCHEMA_MAP = () => true;
151
+ const ANY = () => true;
152
+ const TEXT = (value) => typeof value === 'string';
153
+ const FLAG = (value) => typeof value === 'boolean';
154
+ const NUMBER = finite;
155
+ const COUNT = count;
156
+ const KEYWORDS = {
157
+ $comment: TEXT,
158
+ $ref: (value, _at, _node, context) => typeof value === 'string' && refTarget(value, context.defs) !== undefined,
159
+ $defs: SCHEMA_MAP,
160
+ allOf: SCHEMA_LIST,
161
+ anyOf: SCHEMA_LIST,
162
+ oneOf: SCHEMA_LIST,
163
+ prefixItems: SCHEMA_LIST,
164
+ not: SCHEMA,
165
+ if: SCHEMA,
166
+ then: SCHEMA,
167
+ else: SCHEMA,
168
+ items: SCHEMA,
169
+ contains: SCHEMA,
170
+ additionalProperties: SCHEMA,
171
+ unevaluatedProperties: SCHEMA,
172
+ unevaluatedItems: SCHEMA,
173
+ propertyNames: SCHEMA,
174
+ contentSchema: SCHEMA,
175
+ properties: SCHEMA_MAP,
176
+ patternProperties: SCHEMA_MAP,
177
+ dependentSchemas: SCHEMA_MAP,
178
+ type: (value) => (typeof value === 'string' && JSON_TYPES.includes(value)) ||
179
+ (Array.isArray(value) &&
180
+ value.length > 0 &&
181
+ strings(value) &&
182
+ value.every((item) => JSON_TYPES.includes(item))),
183
+ enum: (value) => Array.isArray(value) && value.length > 0,
184
+ const: ANY,
185
+ multipleOf: (value) => finite(value) && value > 0,
186
+ maximum: NUMBER,
187
+ exclusiveMaximum: NUMBER,
188
+ minimum: NUMBER,
189
+ exclusiveMinimum: NUMBER,
190
+ maxLength: COUNT,
191
+ minLength: COUNT,
192
+ maxItems: COUNT,
193
+ minItems: COUNT,
194
+ maxContains: COUNT,
195
+ minContains: COUNT,
196
+ maxProperties: COUNT,
197
+ minProperties: COUNT,
198
+ pattern: compiles,
199
+ uniqueItems: FLAG,
200
+ required: strings,
201
+ dependentRequired: (value) => isRecord(value) && Object.values(value).every(strings),
202
+ format: TEXT,
203
+ contentEncoding: TEXT,
204
+ contentMediaType: TEXT,
205
+ title: TEXT,
206
+ description: TEXT,
207
+ default: ANY,
208
+ examples: (value) => Array.isArray(value),
209
+ deprecated: FLAG,
210
+ readOnly: FLAG,
211
+ writeOnly: FLAG,
212
+ reference: (value, _at, node, context) => {
213
+ if (node.type !== 'string')
214
+ return false;
215
+ const named = typeof value === 'string' ? [value] : value;
216
+ return (Array.isArray(named) &&
217
+ named.length > 0 &&
218
+ strings(named) &&
219
+ named.every((name) => name === BUILT_IN_TYPE || name === ATTACHMENT || context.types.has(name)));
220
+ },
221
+ };
222
+ function sharedType(values) {
223
+ if (values.length === 0)
17
224
  return undefined;
18
- if (!value.every((item) => typeof item === 'string' && item !== ''))
225
+ if (values.every((value) => typeof value === 'string'))
226
+ return 'string';
227
+ if (values.every((value) => typeof value === 'number'))
228
+ return 'number';
229
+ if (values.every((value) => typeof value === 'boolean'))
230
+ return 'boolean';
231
+ return undefined;
232
+ }
233
+ function singleType(type) {
234
+ if (typeof type === 'string')
235
+ return type;
236
+ if (!Array.isArray(type))
19
237
  return undefined;
20
- return [...value];
21
- }
22
- const SCALAR_KINDS = new Set(['text', 'string', 'number', 'date', 'enum', 'reference']);
23
- function parseScalar(names, location, spec, required) {
24
- const kind = spec.kind;
25
- if (kind === 'text')
26
- return { kind: 'text', required };
27
- if (kind === 'string')
28
- return { kind: 'string', required };
29
- if (kind === 'number')
30
- return { kind: 'number', required };
31
- if (kind === 'date')
32
- return { kind: 'date', required };
33
- if (kind === 'enum') {
34
- const values = stringList(spec.values);
35
- if (values === undefined)
36
- return refuse('definition-enum-values', location);
37
- return { kind: 'enum', required, values };
38
- }
39
- if (kind === 'reference') {
40
- if (typeof spec.to !== 'string' || (spec.to !== 'any' && !names.has(spec.to)))
41
- return refuse('definition-reference-to', location);
42
- return { kind: 'reference', required, to: spec.to };
43
- }
44
- return refuse('definition-field-kind', location);
45
- }
46
- function extraKeys(spec, allowed) {
47
- return Object.keys(spec).some((key) => !allowed.includes(key));
48
- }
49
- function parseList(names, location, spec, required, malformed) {
50
- if (typeof spec.of !== 'string' || !SCALAR_KINDS.has(spec.of)) {
51
- return refuse('definition-list-of', location);
52
- }
53
- const allowed = spec.of === 'enum'
54
- ? ['kind', 'required', 'of', 'values']
55
- : spec.of === 'reference'
56
- ? ['kind', 'required', 'of', 'to']
57
- : ['kind', 'required', 'of'];
58
- if (extraKeys(spec, allowed))
59
- return refuse(malformed, location);
60
- const inner = parseScalar(names, location, { ...spec, kind: spec.of }, false);
61
- if ('rule' in inner)
62
- return inner;
63
- return { kind: 'list', required, of: inner };
64
- }
65
- function parseField(names, location, value) {
66
- if (!isRecord(value) || typeof value.kind !== 'string')
67
- return refuse('definition-field', location);
68
- const required = requiredFlag(value.required);
69
- if (required === undefined)
70
- return refuse('definition-field', location);
71
- if (value.kind === 'list')
72
- return parseList(names, location, value, required, 'definition-field');
73
- const allowed = value.kind === 'enum'
74
- ? ['kind', 'required', 'values']
75
- : value.kind === 'reference'
76
- ? ['kind', 'required', 'to']
77
- : ['kind', 'required'];
78
- if (extraKeys(value, allowed))
79
- return refuse('definition-field', location);
80
- return parseScalar(names, location, value, required);
81
- }
82
- function parseSection(names, location, value) {
83
- if (!isRecord(value) || (value.kind !== 'text' && value.kind !== 'list')) {
84
- return refuse('definition-section-kind', location);
85
- }
86
- const required = requiredFlag(value.required);
87
- if (required === undefined)
88
- return refuse('definition-section', location);
89
- if (value.kind === 'list')
90
- return parseList(names, location, value, required, 'definition-section');
91
- if (extraKeys(value, ['kind', 'required']))
92
- return refuse('definition-section', location);
93
- return { kind: 'text', required };
94
- }
95
- function readType(names, name, value) {
96
- const location = `types.${name}`;
97
- if (!isRecord(value))
98
- return refuse('definition-shape', location);
99
- for (const key of Object.keys(value)) {
100
- if (key !== 'fields' && key !== 'sections' && key !== 'member_document') {
101
- return refuse('definition-shape', `${location}.${key}`);
238
+ const kept = type.filter((one) => one !== 'null');
239
+ return kept.length === 1 ? kept[0] : undefined;
240
+ }
241
+ function firstOf(held, read) {
242
+ for (const node of held) {
243
+ const value = read(node);
244
+ if (value !== undefined)
245
+ return value;
246
+ }
247
+ return undefined;
248
+ }
249
+ function conjoined(nodes, defs, ancestry) {
250
+ const held = [];
251
+ const reached = new Set(ancestry);
252
+ const visit = (node) => {
253
+ if (!isRecord(node) || held.includes(node))
254
+ return;
255
+ held.push(node);
256
+ if (typeof node.$ref === 'string') {
257
+ const target = refTarget(node.$ref, defs)?.node;
258
+ if (target !== undefined && !reached.has(target)) {
259
+ reached.add(target);
260
+ visit(target);
261
+ }
102
262
  }
263
+ if (Array.isArray(node.allOf))
264
+ for (const one of node.allOf)
265
+ visit(one);
266
+ };
267
+ for (const node of nodes)
268
+ visit(node);
269
+ return { held, ancestry: reached };
270
+ }
271
+ function variantsOf(node) {
272
+ return [
273
+ ...(Array.isArray(node.anyOf) ? node.anyOf : []),
274
+ ...(Array.isArray(node.oneOf) ? node.oneOf : []),
275
+ node.then,
276
+ node.else,
277
+ ...(isRecord(node.dependentSchemas) ? Object.values(node.dependentSchemas) : []),
278
+ ].filter(isRecord);
279
+ }
280
+ function united(left, right) {
281
+ if (left === undefined || right === undefined)
282
+ return undefined;
283
+ return [...left, ...right.filter((value) => !left.some((held) => equalValue(held, value)))];
284
+ }
285
+ function both(left, right) {
286
+ return left !== undefined && right !== undefined ? joined(left, right) : undefined;
287
+ }
288
+ function joined(left, right) {
289
+ const type = left.type === right.type ? left.type : undefined;
290
+ const format = left.format === right.format ? left.format : undefined;
291
+ const values = united(left.values, right.values);
292
+ const reference = united(left.reference, right.reference);
293
+ const items = both(left.items, right.items);
294
+ const prefix = left.prefix === undefined && right.prefix === undefined
295
+ ? undefined
296
+ : Array.from({ length: Math.max(left.prefix?.length ?? 0, right.prefix?.length ?? 0) }, (_, index) => both(itemFacts(left, index), itemFacts(right, index)) ?? { prose: false });
297
+ const properties = Object.create(null);
298
+ for (const [name, facts] of Object.entries(left.properties ?? {})) {
299
+ const shared = both(facts, right.properties?.[name]);
300
+ if (shared !== undefined)
301
+ properties[name] = shared;
302
+ }
303
+ const patterns = (left.patterns ?? []).flatMap(({ pattern, facts }) => {
304
+ const shared = both(facts, right.patterns?.find((one) => one.pattern === pattern)?.facts);
305
+ return shared === undefined ? [] : [{ pattern, facts: shared }];
306
+ });
307
+ const additional = both(left.additional, right.additional);
308
+ return {
309
+ ...(type === undefined ? {} : { type }),
310
+ ...(format === undefined ? {} : { format }),
311
+ ...(values === undefined ? {} : { values }),
312
+ ...(reference === undefined ? {} : { reference }),
313
+ prose: left.prose && right.prose,
314
+ ...(prefix === undefined ? {} : { prefix }),
315
+ ...(items === undefined ? {} : { items }),
316
+ ...(Object.keys(properties).length === 0 ? {} : { properties }),
317
+ ...(patterns.length === 0 ? {} : { patterns }),
318
+ ...(additional === undefined ? {} : { additional }),
319
+ };
320
+ }
321
+ function propertiesOf(held, variants, defs, ancestry) {
322
+ const declared = new Map();
323
+ for (const node of held) {
324
+ if (!isRecord(node.properties))
325
+ continue;
326
+ for (const [name, schema] of Object.entries(node.properties))
327
+ declared.set(name, [...(declared.get(name) ?? []), schema]);
103
328
  }
104
- const mark = value.member_document === undefined ? false : value.member_document;
105
- if (typeof mark !== 'boolean')
106
- return refuse('definition-shape', `${location}.member_document`);
107
- const fieldsValue = value.fields;
108
- const sectionsValue = value.sections;
109
- if (fieldsValue !== undefined && !isRecord(fieldsValue))
110
- return refuse('definition-fields', `${location}.fields`);
111
- if (sectionsValue !== undefined && !isRecord(sectionsValue)) {
112
- return refuse('definition-sections', `${location}.sections`);
113
- }
114
- const fields = Object.create(null);
115
- for (const [fieldName, spec] of Object.entries(fieldsValue ?? {})) {
116
- const parsed = parseField(names, `${location}.fields.${fieldName}`, spec);
117
- if ('rule' in parsed)
118
- return parsed;
119
- fields[fieldName] = parsed;
329
+ const properties = Object.create(null);
330
+ for (const [name, schemas] of declared)
331
+ properties[name] = factsOf(schemas, defs, ancestry);
332
+ const conditional = Object.create(null);
333
+ for (const branch of variants.values()) {
334
+ for (const [name, facts] of Object.entries(branch.properties ?? {})) {
335
+ if (Object.hasOwn(properties, name))
336
+ continue;
337
+ conditional[name] = Object.hasOwn(conditional, name)
338
+ ? joined(conditional[name], facts)
339
+ : facts;
340
+ }
120
341
  }
342
+ for (const [name, facts] of Object.entries(conditional))
343
+ properties[name] = { ...facts, conditional: true };
344
+ return Object.keys(properties).length === 0 ? undefined : properties;
345
+ }
346
+ function nullOnly(facts) {
347
+ return facts.type === 'null' || (facts.values?.every((value) => value === null) ?? false);
348
+ }
349
+ function alternativesOf(held, variants) {
350
+ return held.flatMap((node) => [node.anyOf, node.oneOf].flatMap((branches) => {
351
+ if (!Array.isArray(branches))
352
+ return [];
353
+ const kept = branches
354
+ .flatMap((branch) => {
355
+ const facts = isRecord(branch) ? variants.get(branch) : undefined;
356
+ return facts === undefined ? [] : [facts];
357
+ })
358
+ .filter((facts) => !nullOnly(facts));
359
+ return kept.length === 0 ? [] : [kept];
360
+ }));
361
+ }
362
+ function inheritedOf(held) {
363
+ return held.flatMap((node) => {
364
+ const kept = Object.fromEntries(['type', 'items', 'prefixItems'].flatMap((key) => node[key] === undefined ? [] : [[key, node[key]]]));
365
+ return Object.keys(kept).length === 0 ? [] : [kept];
366
+ });
367
+ }
368
+ function factsOf(nodes, defs, ancestry) {
369
+ const { held, ancestry: reached } = conjoined(nodes, defs, ancestry);
370
+ const inherited = inheritedOf(held);
371
+ const variants = new Map(held
372
+ .flatMap(variantsOf)
373
+ .map((branch) => [branch, factsOf([branch, ...inherited], defs, reached)]));
374
+ const unions = alternativesOf(held, variants);
375
+ const alternatives = unions.map((kept) => kept.reduce(joined));
376
+ const listed = [
377
+ ...held.flatMap((node) => Array.isArray(node.enum) ? [node.enum] : node.const !== undefined ? [[node.const]] : []),
378
+ ...alternatives.flatMap((facts) => (facts.values === undefined ? [] : [facts.values])),
379
+ ];
380
+ const values = listed.length === 0
381
+ ? undefined
382
+ : listed.reduce((kept, list) => kept.filter((value) => list.some((other) => equalValue(other, value))));
383
+ const type = firstOf(held, (node) => singleType(node.type)) ??
384
+ (values === undefined ? undefined : sharedType(values)) ??
385
+ alternatives.find((facts) => facts.type !== undefined)?.type;
386
+ const reference = firstOf(held, (node) => typeof node.reference === 'string'
387
+ ? [node.reference]
388
+ : Array.isArray(node.reference)
389
+ ? node.reference
390
+ : undefined) ?? alternatives.find((facts) => facts.reference !== undefined)?.reference;
391
+ const format = firstOf(held, (node) => (typeof node.format === 'string' ? node.format : undefined)) ??
392
+ alternatives.find((facts) => facts.format !== undefined)?.format;
393
+ const prose = type === 'string' &&
394
+ values === undefined &&
395
+ format === undefined &&
396
+ reference === undefined &&
397
+ held.every((node) => node.pattern === undefined) &&
398
+ unions.every((kept) => kept.every((facts) => facts.type !== 'string' || facts.prose));
399
+ const listedItems = held.flatMap((node) => (node.items === undefined ? [] : [node.items]));
400
+ const items = type !== 'array'
401
+ ? undefined
402
+ : (alternatives.find((facts) => facts.items !== undefined)?.items ??
403
+ (listedItems.length > 0 ? factsOf(listedItems, defs, reached) : undefined));
404
+ const tuples = held.flatMap((node) => Array.isArray(node.prefixItems) ? [node.prefixItems] : []);
405
+ const prefix = type !== 'array'
406
+ ? undefined
407
+ : (alternatives.find((facts) => facts.prefix !== undefined)?.prefix ??
408
+ (tuples.length === 0
409
+ ? undefined
410
+ : Array.from({ length: Math.max(...tuples.map((tuple) => tuple.length)) }, (_, index) => factsOf(tuples.flatMap((tuple) => (index < tuple.length ? [tuple[index]] : [])), defs, reached))));
411
+ const mapped = type === 'object' || type === undefined;
412
+ const properties = mapped ? propertiesOf(held, variants, defs, reached) : undefined;
413
+ const patterns = mapped
414
+ ? held.flatMap((node) => Object.entries(isRecord(node.patternProperties) ? node.patternProperties : {}).map(([pattern, schema]) => ({ pattern, facts: factsOf([schema], defs, reached) })))
415
+ : [];
416
+ const opened = mapped
417
+ ? held.flatMap((node) => [node.additionalProperties, node.unevaluatedProperties].filter(isRecord))
418
+ : [];
419
+ const additional = opened.length === 0 ? undefined : factsOf(opened, defs, reached);
420
+ return {
421
+ ...(type === undefined ? {} : { type }),
422
+ ...(format === undefined ? {} : { format }),
423
+ ...(values === undefined ? {} : { values }),
424
+ ...(reference === undefined ? {} : { reference }),
425
+ prose,
426
+ ...(prefix === undefined ? {} : { prefix }),
427
+ ...(items === undefined ? {} : { items }),
428
+ ...(properties === undefined ? {} : { properties }),
429
+ ...(patterns.length === 0 ? {} : { patterns }),
430
+ ...(additional === undefined ? {} : { additional }),
431
+ };
432
+ }
433
+ export function valueFacts(node, defs) {
434
+ return factsOf([node], defs, new Set());
435
+ }
436
+ export function itemFacts(facts, index) {
437
+ return facts.prefix?.[index] ?? facts.items;
438
+ }
439
+ export function memberFacts(facts, key) {
440
+ if (facts.properties !== undefined && Object.hasOwn(facts.properties, key))
441
+ return facts.properties[key];
442
+ const pattern = facts.patterns?.find((one) => new RegExp(one.pattern, 'u').test(key));
443
+ return pattern === undefined ? facts.additional : pattern.facts;
444
+ }
445
+ export function crossedFacts(facts) {
446
+ if (facts.type !== 'array')
447
+ return [facts];
448
+ return [...(facts.prefix ?? []), ...(facts.items === undefined ? [] : [facts.items])].flatMap(crossedFacts);
449
+ }
450
+ export function fieldFacts(definition, key) {
451
+ if (Object.hasOwn(definition.fields, key))
452
+ return definition.fields[key];
453
+ if (Object.hasOwn(HOUSE_KEYS, key))
454
+ return undefined;
455
+ return memberFacts(definition.frontmatter, key);
456
+ }
457
+ function stepped({ facts, through, conditional }, token) {
458
+ if (facts.type === 'array' && ARRAY_INDEX.test(token)) {
459
+ const item = itemFacts(facts, Number(token));
460
+ return item === undefined ? [] : [{ facts: item, through, conditional }];
461
+ }
462
+ return crossedFacts(facts).flatMap((crossed) => {
463
+ const member = memberFacts(crossed, token);
464
+ return member === undefined
465
+ ? []
466
+ : [
467
+ {
468
+ facts: member,
469
+ through: through || facts.type === 'array',
470
+ conditional: conditional || member.conditional === true,
471
+ },
472
+ ];
473
+ });
474
+ }
475
+ export function pointerFacts(definition, pointer) {
476
+ const [field, ...rest] = pointerSegments(pointer);
477
+ const declared = field === undefined ? undefined : fieldFacts(definition, field);
478
+ if (declared === undefined)
479
+ return undefined;
480
+ let reached = [
481
+ { facts: declared, through: false, conditional: declared.conditional === true },
482
+ ];
483
+ for (const token of rest)
484
+ reached = reached.flatMap((held) => stepped(held, token));
485
+ if (reached.length === 0)
486
+ return undefined;
487
+ return reached.reduce((kept, held) => ({
488
+ facts: joined(kept.facts, held.facts),
489
+ through: kept.through || held.through,
490
+ conditional: kept.conditional || held.conditional,
491
+ }));
492
+ }
493
+ function reachedDefs(value, defs, reached) {
494
+ if (Array.isArray(value)) {
495
+ for (const item of value)
496
+ reachedDefs(item, defs, reached);
497
+ return;
498
+ }
499
+ if (!isRecord(value))
500
+ return;
501
+ for (const [key, held] of Object.entries(value)) {
502
+ if (key === '$ref' && typeof held === 'string') {
503
+ const name = refTarget(held, defs)?.name;
504
+ if (name === undefined || Object.hasOwn(reached, name))
505
+ continue;
506
+ reached[name] = defs[name];
507
+ reachedDefs(defs[name], defs, reached);
508
+ continue;
509
+ }
510
+ reachedDefs(held, defs, reached);
511
+ }
512
+ }
513
+ function readSections(value, at, context) {
514
+ const failed = (rule, location) => ({ failure: refuse(rule, location) });
515
+ if (!isRecord(value))
516
+ return failed('sections', at);
121
517
  const sections = Object.create(null);
122
- for (const [sectionName, spec] of Object.entries(sectionsValue ?? {})) {
123
- const parsed = parseSection(names, `${location}.sections.${sectionName}`, spec);
124
- if ('rule' in parsed)
125
- return parsed;
126
- sections[sectionName] = parsed;
518
+ for (const [name, spec] of Object.entries(value)) {
519
+ const where = `${at}/${escapePointer(name)}`;
520
+ if (!isRecord(spec) || (spec.kind !== 'text' && spec.kind !== 'list'))
521
+ return failed('sections', where);
522
+ const allowed = spec.kind === 'list' ? ['kind', 'required', 'items'] : ['kind', 'required'];
523
+ const extra = Object.keys(spec).find((key) => !allowed.includes(key));
524
+ if (extra !== undefined)
525
+ return failed('sections', `${where}/${escapePointer(extra)}`);
526
+ if (spec.required !== undefined && typeof spec.required !== 'boolean')
527
+ return failed('sections', `${where}/required`);
528
+ const required = spec.required === true;
529
+ if (spec.kind === 'text') {
530
+ sections[name] = { kind: 'text', required };
531
+ continue;
532
+ }
533
+ if (spec.items === undefined)
534
+ return failed('sections', `${where}/items`);
535
+ const failure = schemaFailure(spec.items, `${where}/items`, context);
536
+ if (failure !== undefined)
537
+ return { failure };
538
+ const items = valueFacts(spec.items, context.defs);
539
+ if (items.type === undefined || !SCALAR_TYPES.has(items.type))
540
+ return failed('sections', `${where}/items`);
541
+ sections[name] = { kind: 'list', required, items };
127
542
  }
128
- return { name, memberDocument: mark, fields, sections };
543
+ return { sections };
129
544
  }
130
- function freezeSpec(spec) {
131
- if (spec.kind === 'list')
132
- freezeSpec(spec.of);
133
- if (spec.kind === 'enum')
134
- Object.freeze(spec.values);
135
- Object.freeze(spec);
545
+ function deepFreeze(value) {
546
+ if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
547
+ Object.freeze(value);
548
+ for (const held of Object.values(value))
549
+ deepFreeze(held);
550
+ }
551
+ return value;
552
+ }
553
+ function ruleValue(value) {
554
+ return value === null || typeof value === 'string' || typeof value === 'boolean' || finite(value);
555
+ }
556
+ function roleNames(value) {
557
+ return strings(value) && value.every((name) => name !== '');
558
+ }
559
+ function compositesOf(value) {
560
+ return Array.isArray(value) ? ['array'] : isRecord(value) ? ['object'] : [];
561
+ }
562
+ function typed(type) {
563
+ const types = typeof type === 'string' ? [type] : type;
564
+ return Array.isArray(types) ? COMPOSITES.filter((kind) => types.includes(kind)) : undefined;
565
+ }
566
+ function admittedWhole(node) {
567
+ if (node === true)
568
+ return COMPOSITES;
569
+ if (!isRecord(node) || Object.keys(node).some((key) => key !== 'type' && !ANNOTATIONS.has(key)))
570
+ return [];
571
+ return typed(node.type) ?? COMPOSITES;
572
+ }
573
+ function composites(node, defs, seen) {
574
+ if (node === false)
575
+ return [];
576
+ if (!isRecord(node))
577
+ return COMPOSITES;
578
+ const each = (nodes) => Array.isArray(nodes) ? nodes.map((one) => composites(one, defs, seen)) : [];
579
+ const target = typeof node.$ref === 'string' ? refTarget(node.$ref, defs)?.node : undefined;
580
+ const conditioned = () => {
581
+ const met = composites(node.then ?? true, defs, seen);
582
+ return COMPOSITES.filter((kind) => (composites(node.if, defs, seen).includes(kind) && met.includes(kind)) ||
583
+ composites(node.else ?? true, defs, seen).includes(kind));
584
+ };
585
+ const bounds = [
586
+ typed(node.type),
587
+ Array.isArray(node.enum) ? node.enum.flatMap(compositesOf) : undefined,
588
+ Object.hasOwn(node, 'const') ? compositesOf(node.const) : undefined,
589
+ target === undefined || seen.has(target)
590
+ ? undefined
591
+ : composites(target, defs, new Set([...seen, target])),
592
+ ...each(node.allOf),
593
+ ...[node.anyOf, node.oneOf].filter(Array.isArray).map((branches) => each(branches).flat()),
594
+ Object.hasOwn(node, 'if') ? conditioned() : undefined,
595
+ Object.hasOwn(node, 'not')
596
+ ? COMPOSITES.filter((kind) => !admittedWhole(node.not).includes(kind))
597
+ : undefined,
598
+ ];
599
+ return COMPOSITES.filter((kind) => bounds.every((bound) => bound?.includes(kind) ?? true));
600
+ }
601
+ function transitionOf(value) {
602
+ if (!isRecord(value) || !Object.hasOwn(value, 'from') || !Object.hasOwn(value, 'to'))
603
+ return undefined;
604
+ const { from, to, roles, ...rest } = value;
605
+ if (Object.keys(rest).length > 0 || !ruleValue(from) || !ruleValue(to))
606
+ return undefined;
607
+ if (roles === undefined)
608
+ return { from, to };
609
+ return roleNames(roles) ? { from, to, roles } : undefined;
610
+ }
611
+ function fieldRules(written, field, at, defs) {
612
+ const { change_roles: changeRoles, initial_roles: initialRoles, transitions } = written;
613
+ if (changeRoles !== undefined && !roleNames(changeRoles))
614
+ return refuse('change_roles', `${at}/change_roles`);
615
+ if (initialRoles !== undefined && !roleNames(initialRoles))
616
+ return refuse('initial_roles', `${at}/initial_roles`);
617
+ if (transitions === undefined)
618
+ return {
619
+ ...(changeRoles === undefined ? {} : { changeRoles }),
620
+ ...(initialRoles === undefined ? {} : { initialRoles }),
621
+ };
622
+ if (!Array.isArray(transitions) || composites(field, defs, new Set()).length > 0)
623
+ return refuse('transitions', `${at}/transitions`);
624
+ const listed = [];
625
+ for (const [index, held] of transitions.entries()) {
626
+ const transition = transitionOf(held);
627
+ if (transition === undefined)
628
+ return refuse('transitions', `${at}/transitions/${index}`);
629
+ listed.push(transition);
630
+ }
631
+ return {
632
+ ...(changeRoles === undefined ? {} : { changeRoles }),
633
+ ...(initialRoles === undefined ? {} : { initialRoles }),
634
+ transitions: listed,
635
+ };
636
+ }
637
+ function unruled(schema) {
638
+ const written = new Map();
639
+ if (!isRecord(schema.properties))
640
+ return { schema, written };
641
+ const properties = Object.create(null);
642
+ for (const [field, node] of Object.entries(schema.properties)) {
643
+ if (!isRecord(node) || !FIELD_RULES.some((keyword) => Object.hasOwn(node, keyword))) {
644
+ properties[field] = node;
645
+ continue;
646
+ }
647
+ written.set(field, node);
648
+ properties[field] = Object.fromEntries(Object.entries(node).filter(([keyword]) => !FIELD_RULES.includes(keyword)));
649
+ }
650
+ return { schema: written.size === 0 ? schema : { ...schema, properties }, written };
651
+ }
652
+ function readType(name, value, context) {
653
+ const at = `/${escapePointer(name)}`;
654
+ if (!isRecord(value))
655
+ return refuse('schema', at);
656
+ const { sections: declared, member_document: mark, create_roles: createRoles, delete_roles: deleteRoles, ...ruled } = value;
657
+ if (mark !== undefined && typeof mark !== 'boolean')
658
+ return refuse('member_document', `${at}/member_document`);
659
+ if (createRoles !== undefined && !roleNames(createRoles))
660
+ return refuse('create_roles', `${at}/create_roles`);
661
+ if (deleteRoles !== undefined && !roleNames(deleteRoles))
662
+ return refuse('delete_roles', `${at}/delete_roles`);
663
+ const read = declared === undefined
664
+ ? { sections: Object.create(null) }
665
+ : readSections(declared, `${at}/sections`, context);
666
+ if ('failure' in read)
667
+ return read.failure;
668
+ const { schema, written } = unruled(ruled);
669
+ const failure = schemaFailure(schema, at, context);
670
+ if (failure !== undefined)
671
+ return failure;
672
+ const rules = Object.create(null);
673
+ for (const [field, keywords] of written) {
674
+ const properties = schema.properties;
675
+ const where = `${at}/properties/${escapePointer(field)}`;
676
+ const ruling = fieldRules(keywords, properties[field], where, context.defs);
677
+ if ('rule' in ruling)
678
+ return ruling;
679
+ rules[field] = ruling;
680
+ }
681
+ const memberDocument = mark === true;
682
+ const root = valueFacts(schema, context.defs);
683
+ const fields = Object.assign(Object.create(null), root.properties);
684
+ if (memberDocument && !Object.hasOwn(fields, MEMBER_FIELD))
685
+ fields[MEMBER_FIELD] = { type: 'string', prose: false };
686
+ const defs = Object.create(null);
687
+ reachedDefs(value, context.defs, defs);
688
+ const { create_roles: _create, delete_roles: _delete, ...shaped } = value;
689
+ return {
690
+ name,
691
+ memberDocument,
692
+ frontmatter: { ...root, properties: { ...HOUSE_KEYS, ...fields } },
693
+ fields,
694
+ sections: read.sections,
695
+ rules,
696
+ ...(createRoles === undefined ? {} : { createRoles }),
697
+ ...(deleteRoles === undefined ? {} : { deleteRoles }),
698
+ shape: {
699
+ schema: written.size === 0 ? shaped : { ...shaped, properties: schema.properties },
700
+ defs,
701
+ },
702
+ };
136
703
  }
137
704
  function lineAt(text, offset) {
138
705
  return text.slice(0, offset).split(/\r\n|\r|\n/).length;
@@ -147,34 +714,32 @@ export function readSchema(text) {
147
714
  return refuse('schema-yaml', `line ${lineAt(text, error.offset)}`);
148
715
  throw error;
149
716
  }
150
- const schema = root;
151
- for (const key of Object.keys(schema)) {
152
- if (key !== 'types')
153
- return refuse('schema-shape', key);
717
+ const defs = root.$defs ?? Object.create(null);
718
+ if (!isRecord(defs))
719
+ return refuse('$defs', '/$defs');
720
+ const names = Object.keys(root).filter((key) => key !== '$defs');
721
+ for (const name of names) {
722
+ if (name === '' || name.startsWith('$') || name === BUILT_IN_TYPE || name === ATTACHMENT)
723
+ return refuse('schema-type-name', `/${escapePointer(name)}`);
724
+ }
725
+ const context = { defs, types: new Set(names) };
726
+ for (const [name, def] of Object.entries(defs)) {
727
+ const failure = schemaFailure(def, `/$defs/${escapePointer(name)}`, context);
728
+ if (failure !== undefined)
729
+ return failure;
154
730
  }
155
- if (!isRecord(schema.types))
156
- return refuse('schema-shape', 'types');
157
- const names = new Set(Object.keys(schema.types));
158
- if (names.has(''))
159
- return refuse('schema-shape', 'types');
160
731
  const types = Object.create(null);
161
732
  let marked = false;
162
- for (const [name, value] of Object.entries(schema.types)) {
163
- const definition = readType(names, name, value);
733
+ for (const name of names) {
734
+ const definition = readType(name, root[name], context);
164
735
  if ('rule' in definition)
165
736
  return definition;
166
737
  if (definition.memberDocument) {
167
738
  if (marked)
168
- return refuse('schema-member-document', `types.${name}.member_document`);
739
+ return refuse('member_document', `/${escapePointer(name)}/member_document`);
169
740
  marked = true;
170
741
  }
171
- for (const spec of Object.values(definition.fields))
172
- freezeSpec(spec);
173
- for (const spec of Object.values(definition.sections))
174
- freezeSpec(spec);
175
- Object.freeze(definition.fields);
176
- Object.freeze(definition.sections);
177
- types[name] = Object.freeze(definition);
742
+ types[name] = deepFreeze(definition);
178
743
  }
179
744
  return Object.freeze({ types: Object.freeze(types) });
180
745
  }