@agentshouse/mdmodel 0.1.0-cc9fa0498a7f7 → 0.1.0-cd90851746292

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 (59) hide show
  1. package/API.md +231 -225
  2. package/README.md +16 -19
  3. package/dist/blocks.d.ts.map +1 -1
  4. package/dist/blocks.js +35 -20
  5. package/dist/condition.d.ts +41 -11
  6. package/dist/condition.d.ts.map +1 -1
  7. package/dist/condition.js +223 -87
  8. package/dist/definition.d.ts +17 -1
  9. package/dist/definition.d.ts.map +1 -1
  10. package/dist/definition.js +319 -43
  11. package/dist/divide.d.ts.map +1 -1
  12. package/dist/divide.js +27 -17
  13. package/dist/document.d.ts +6 -3
  14. package/dist/document.d.ts.map +1 -1
  15. package/dist/document.js +326 -176
  16. package/dist/index.d.ts +13 -9
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +7 -6
  19. package/dist/links.d.ts +3 -0
  20. package/dist/links.d.ts.map +1 -1
  21. package/dist/links.js +108 -51
  22. package/dist/paths.d.ts +10 -1
  23. package/dist/paths.d.ts.map +1 -1
  24. package/dist/paths.js +118 -12
  25. package/dist/pattern.d.ts +10 -0
  26. package/dist/pattern.d.ts.map +1 -0
  27. package/dist/pattern.js +355 -0
  28. package/dist/project.d.ts +1 -0
  29. package/dist/project.d.ts.map +1 -1
  30. package/dist/project.js +38 -15
  31. package/dist/references.d.ts +2 -2
  32. package/dist/references.d.ts.map +1 -1
  33. package/dist/references.js +6 -1
  34. package/dist/schema.d.ts +1 -0
  35. package/dist/schema.d.ts.map +1 -1
  36. package/dist/schema.js +55 -4
  37. package/dist/types.d.ts +50 -2
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/validate.d.ts +30 -2
  40. package/dist/validate.d.ts.map +1 -1
  41. package/dist/validate.js +317 -114
  42. package/dist/yaml.d.ts.map +1 -1
  43. package/dist/yaml.js +66 -21
  44. package/fixtures/README.md +10 -5
  45. package/fixtures/cases/conditional-section/context.json +1 -0
  46. package/fixtures/cases/conditional-section/expected.json +6 -0
  47. package/fixtures/cases/conditional-section/library/questions/hosting.md +9 -0
  48. package/fixtures/cases/conditional-section/library/questions/payments.md +12 -0
  49. package/fixtures/cases/conditional-section/schema.yaml +14 -0
  50. package/fixtures/cases/cross-room-link/context.json +1 -0
  51. package/fixtures/cases/cross-room-link/expected.json +1 -0
  52. package/fixtures/cases/cross-room-link/library/clients/acme.md +6 -0
  53. package/fixtures/cases/cross-room-link/schema.yaml +3 -0
  54. package/fixtures/cases/cross-room-reference/context.json +1 -0
  55. package/fixtures/cases/cross-room-reference/expected.json +1 -0
  56. package/fixtures/cases/cross-room-reference/library/clients/acme.md +9 -0
  57. package/fixtures/cases/cross-room-reference/schema.yaml +9 -0
  58. package/fixtures/division.json +26 -26
  59. package/package.json +10 -3
@@ -1,4 +1,5 @@
1
1
  import { MEMBER_FIELD, RECEIVED_FIELD, SITE_FIELD, TYPE_FIELD } from './fields.js';
2
+ import { linearPattern } from './pattern.js';
2
3
  import { equalValue, isRecord, jsValue, parseYamlMapping, YamlError } from './yaml.js';
3
4
  export const JSON_TYPES = [
4
5
  'string',
@@ -12,9 +13,25 @@ export const JSON_TYPES = [
12
13
  export const BUILT_IN_TYPE = 'any';
13
14
  export const ATTACHMENT = 'attachment';
14
15
  const SCALAR_TYPES = new Set(['string', 'number', 'integer', 'boolean']);
16
+ const COMPOSITES = ['array', 'object'];
17
+ const ANNOTATIONS = new Set([
18
+ '$comment',
19
+ 'title',
20
+ 'description',
21
+ 'default',
22
+ 'examples',
23
+ 'deprecated',
24
+ 'readOnly',
25
+ 'writeOnly',
26
+ ]);
27
+ const FIELD_RULES = ['change_roles', 'initial_roles', 'transitions'];
28
+ const PROVIDED_KEYWORDS = ['create_roles', 'delete_roles'];
29
+ const NO_TYPES = Object.freeze({ types: Object.freeze({}) });
15
30
  const DEFS_REFERENCE = '#/$defs/';
31
+ const UNBOUNDED_FORMAT = 'url';
16
32
  const UNRESOLVED = /[\u0000-\u001f\u007f%[\]\\^{|}]|\s$/u;
17
33
  export const ARRAY_INDEX = /^(?:0|[1-9][0-9]*)$/u;
34
+ export const JSON_POINTER = /^(?:\/(?:[^~/]|~[01])*)*$/u;
18
35
  const HOUSE_KEYS = {
19
36
  [TYPE_FIELD]: { prose: false },
20
37
  [SITE_FIELD]: { prose: false },
@@ -33,12 +50,12 @@ export function pointerSegments(pointer) {
33
50
  return pointer.split('/').slice(1).map(unescapePointer);
34
51
  }
35
52
  export function refTarget(reference, defs) {
36
- if (!reference.startsWith(DEFS_REFERENCE) || UNRESOLVED.test(reference))
37
- return undefined;
38
- const segments = reference.slice(DEFS_REFERENCE.length).split('/');
39
- const [name = '', ...rest] = segments.map(unescapePointer);
40
- if (segments.some((segment) => escapePointer(unescapePointer(segment)) !== segment))
53
+ const pointer = reference.slice('#'.length);
54
+ if (!reference.startsWith(DEFS_REFERENCE) ||
55
+ UNRESOLVED.test(reference) ||
56
+ !JSON_POINTER.test(pointer))
41
57
  return undefined;
58
+ const [, name = '', ...rest] = pointerSegments(pointer);
42
59
  if (!Object.hasOwn(defs, name))
43
60
  return undefined;
44
61
  let node = defs[name];
@@ -69,15 +86,7 @@ export function refTarget(reference, defs) {
69
86
  return { name, node };
70
87
  }
71
88
  function compiles(pattern) {
72
- if (typeof pattern !== 'string')
73
- return false;
74
- try {
75
- new RegExp(pattern, 'u');
76
- return true;
77
- }
78
- catch {
79
- return false;
80
- }
89
+ return typeof pattern === 'string' && linearPattern(pattern) !== null;
81
90
  }
82
91
  function finite(value) {
83
92
  return typeof value === 'number' && Number.isFinite(value);
@@ -187,7 +196,7 @@ const KEYWORDS = {
187
196
  uniqueItems: FLAG,
188
197
  required: strings,
189
198
  dependentRequired: (value) => isRecord(value) && Object.values(value).every(strings),
190
- format: TEXT,
199
+ format: (value) => typeof value === 'string' && value !== UNBOUNDED_FORMAT,
191
200
  contentEncoding: TEXT,
192
201
  contentMediaType: TEXT,
193
202
  title: TEXT,
@@ -204,8 +213,12 @@ const KEYWORDS = {
204
213
  return (Array.isArray(named) &&
205
214
  named.length > 0 &&
206
215
  strings(named) &&
207
- named.every((name) => name === BUILT_IN_TYPE || name === ATTACHMENT || context.types.has(name)));
216
+ named.every((name) => name === BUILT_IN_TYPE ||
217
+ name === ATTACHMENT ||
218
+ context.types.has(name) ||
219
+ node.cross_room === true));
208
220
  },
221
+ cross_room: (value, _at, node) => typeof value === 'boolean' && node.reference !== undefined,
209
222
  };
210
223
  function sharedType(values) {
211
224
  if (values.length === 0)
@@ -270,24 +283,45 @@ function united(left, right) {
270
283
  return undefined;
271
284
  return [...left, ...right.filter((value) => !left.some((held) => equalValue(held, value)))];
272
285
  }
286
+ function both(left, right) {
287
+ return left !== undefined && right !== undefined ? joined(left, right) : undefined;
288
+ }
273
289
  function joined(left, right) {
274
290
  const type = left.type === right.type ? left.type : undefined;
275
291
  const format = left.format === right.format ? left.format : undefined;
276
292
  const values = united(left.values, right.values);
277
293
  const reference = united(left.reference, right.reference);
278
- const items = left.items !== undefined && right.items !== undefined
279
- ? joined(left.items, right.items)
280
- : undefined;
294
+ const crossRoom = left.crossRoom === true || right.crossRoom === true;
295
+ const items = both(left.items, right.items);
296
+ const prefix = left.prefix === undefined && right.prefix === undefined
297
+ ? undefined
298
+ : Array.from({ length: Math.max(left.prefix?.length ?? 0, right.prefix?.length ?? 0) }, (_, index) => both(itemFacts(left, index), itemFacts(right, index)) ?? { prose: false });
299
+ const properties = Object.create(null);
300
+ for (const [name, facts] of Object.entries(left.properties ?? {})) {
301
+ const shared = both(facts, right.properties?.[name]);
302
+ if (shared !== undefined)
303
+ properties[name] = shared;
304
+ }
305
+ const patterns = (left.patterns ?? []).flatMap(({ pattern, facts }) => {
306
+ const shared = both(facts, right.patterns?.find((one) => one.pattern === pattern)?.facts);
307
+ return shared === undefined ? [] : [{ pattern, facts: shared }];
308
+ });
309
+ const additional = both(left.additional, right.additional);
281
310
  return {
282
311
  ...(type === undefined ? {} : { type }),
283
312
  ...(format === undefined ? {} : { format }),
284
313
  ...(values === undefined ? {} : { values }),
285
314
  ...(reference === undefined ? {} : { reference }),
315
+ ...(reference !== undefined && crossRoom ? { crossRoom: true } : {}),
286
316
  prose: left.prose && right.prose,
317
+ ...(prefix === undefined ? {} : { prefix }),
287
318
  ...(items === undefined ? {} : { items }),
319
+ ...(Object.keys(properties).length === 0 ? {} : { properties }),
320
+ ...(patterns.length === 0 ? {} : { patterns }),
321
+ ...(additional === undefined ? {} : { additional }),
288
322
  };
289
323
  }
290
- function propertiesOf(held, defs, ancestry) {
324
+ function propertiesOf(held, variants, defs, ancestry) {
291
325
  const declared = new Map();
292
326
  for (const node of held) {
293
327
  if (!isRecord(node.properties))
@@ -298,46 +332,88 @@ function propertiesOf(held, defs, ancestry) {
298
332
  const properties = Object.create(null);
299
333
  for (const [name, schemas] of declared)
300
334
  properties[name] = factsOf(schemas, defs, ancestry);
301
- const variants = Object.create(null);
302
- for (const branch of held.flatMap(variantsOf)) {
303
- for (const [name, facts] of Object.entries(factsOf([branch], defs, ancestry).properties ?? {})) {
335
+ const conditional = Object.create(null);
336
+ for (const branch of variants.values()) {
337
+ for (const [name, facts] of Object.entries(branch.properties ?? {})) {
304
338
  if (Object.hasOwn(properties, name))
305
339
  continue;
306
- variants[name] = Object.hasOwn(variants, name) ? joined(variants[name], facts) : facts;
340
+ conditional[name] = Object.hasOwn(conditional, name)
341
+ ? joined(conditional[name], facts)
342
+ : facts;
307
343
  }
308
344
  }
309
- for (const [name, facts] of Object.entries(variants))
345
+ for (const [name, facts] of Object.entries(conditional))
310
346
  properties[name] = { ...facts, conditional: true };
311
347
  return Object.keys(properties).length === 0 ? undefined : properties;
312
348
  }
349
+ function nullOnly(facts) {
350
+ return facts.type === 'null' || (facts.values?.every((value) => value === null) ?? false);
351
+ }
352
+ function alternativesOf(held, variants) {
353
+ return held.flatMap((node) => [node.anyOf, node.oneOf].flatMap((branches) => {
354
+ if (!Array.isArray(branches))
355
+ return [];
356
+ const kept = branches
357
+ .flatMap((branch) => {
358
+ const facts = isRecord(branch) ? variants.get(branch) : undefined;
359
+ return facts === undefined ? [] : [facts];
360
+ })
361
+ .filter((facts) => !nullOnly(facts));
362
+ return kept.length === 0 ? [] : [kept];
363
+ }));
364
+ }
365
+ function inheritedOf(held) {
366
+ return held.flatMap((node) => {
367
+ const kept = Object.fromEntries(['type', 'items', 'prefixItems'].flatMap((key) => node[key] === undefined ? [] : [[key, node[key]]]));
368
+ return Object.keys(kept).length === 0 ? [] : [kept];
369
+ });
370
+ }
313
371
  function factsOf(nodes, defs, ancestry) {
314
372
  const { held, ancestry: reached } = conjoined(nodes, defs, ancestry);
315
- const listed = held.flatMap((node) => Array.isArray(node.enum) ? [node.enum] : node.const !== undefined ? [[node.const]] : []);
373
+ const inherited = inheritedOf(held);
374
+ const variants = new Map(held
375
+ .flatMap(variantsOf)
376
+ .map((branch) => [branch, factsOf([branch, ...inherited], defs, reached)]));
377
+ const unions = alternativesOf(held, variants);
378
+ const alternatives = unions.map((kept) => kept.reduce(joined));
379
+ const listed = [
380
+ ...held.flatMap((node) => Array.isArray(node.enum) ? [node.enum] : node.const !== undefined ? [[node.const]] : []),
381
+ ...alternatives.flatMap((facts) => (facts.values === undefined ? [] : [facts.values])),
382
+ ];
316
383
  const values = listed.length === 0
317
384
  ? undefined
318
385
  : listed.reduce((kept, list) => kept.filter((value) => list.some((other) => equalValue(other, value))));
319
386
  const type = firstOf(held, (node) => singleType(node.type)) ??
320
- (values === undefined ? undefined : sharedType(values));
387
+ (values === undefined ? undefined : sharedType(values)) ??
388
+ alternatives.find((facts) => facts.type !== undefined)?.type;
321
389
  const reference = firstOf(held, (node) => typeof node.reference === 'string'
322
390
  ? [node.reference]
323
391
  : Array.isArray(node.reference)
324
392
  ? node.reference
325
- : undefined);
326
- const format = firstOf(held, (node) => typeof node.format === 'string' ? node.format : undefined);
393
+ : undefined) ?? alternatives.find((facts) => facts.reference !== undefined)?.reference;
394
+ const crossRoom = firstOf(held, (node) => node.reference === undefined ? undefined : node.cross_room === true) ?? alternatives.find((facts) => facts.reference !== undefined)?.crossRoom === true;
395
+ const format = firstOf(held, (node) => (typeof node.format === 'string' ? node.format : undefined)) ??
396
+ alternatives.find((facts) => facts.format !== undefined)?.format;
327
397
  const prose = type === 'string' &&
328
398
  values === undefined &&
329
399
  format === undefined &&
330
400
  reference === undefined &&
331
- held.every((node) => node.pattern === undefined);
401
+ held.every((node) => node.pattern === undefined) &&
402
+ unions.every((kept) => kept.every((facts) => facts.type !== 'string' || facts.prose));
332
403
  const listedItems = held.flatMap((node) => (node.items === undefined ? [] : [node.items]));
333
- const items = type === 'array' && listedItems.length > 0 ? factsOf(listedItems, defs, reached) : undefined;
334
- const prefix = type === 'array'
335
- ? firstOf(held, (node) => Array.isArray(node.prefixItems)
336
- ? node.prefixItems.map((schema) => factsOf([schema], defs, reached))
337
- : undefined)
338
- : undefined;
404
+ const items = type !== 'array'
405
+ ? undefined
406
+ : (alternatives.find((facts) => facts.items !== undefined)?.items ??
407
+ (listedItems.length > 0 ? factsOf(listedItems, defs, reached) : undefined));
408
+ const tuples = held.flatMap((node) => Array.isArray(node.prefixItems) ? [node.prefixItems] : []);
409
+ const prefix = type !== 'array'
410
+ ? undefined
411
+ : (alternatives.find((facts) => facts.prefix !== undefined)?.prefix ??
412
+ (tuples.length === 0
413
+ ? undefined
414
+ : Array.from({ length: Math.max(...tuples.map((tuple) => tuple.length)) }, (_, index) => factsOf(tuples.flatMap((tuple) => (index < tuple.length ? [tuple[index]] : [])), defs, reached))));
339
415
  const mapped = type === 'object' || type === undefined;
340
- const properties = mapped ? propertiesOf(held, defs, reached) : undefined;
416
+ const properties = mapped ? propertiesOf(held, variants, defs, reached) : undefined;
341
417
  const patterns = mapped
342
418
  ? held.flatMap((node) => Object.entries(isRecord(node.patternProperties) ? node.patternProperties : {}).map(([pattern, schema]) => ({ pattern, facts: factsOf([schema], defs, reached) })))
343
419
  : [];
@@ -350,6 +426,7 @@ function factsOf(nodes, defs, ancestry) {
350
426
  ...(format === undefined ? {} : { format }),
351
427
  ...(values === undefined ? {} : { values }),
352
428
  ...(reference === undefined ? {} : { reference }),
429
+ ...(reference !== undefined && crossRoom ? { crossRoom: true } : {}),
353
430
  prose,
354
431
  ...(prefix === undefined ? {} : { prefix }),
355
432
  ...(items === undefined ? {} : { items }),
@@ -367,7 +444,7 @@ export function itemFacts(facts, index) {
367
444
  export function memberFacts(facts, key) {
368
445
  if (facts.properties !== undefined && Object.hasOwn(facts.properties, key))
369
446
  return facts.properties[key];
370
- const pattern = facts.patterns?.find((one) => new RegExp(one.pattern, 'u').test(key));
447
+ const pattern = facts.patterns?.find((one) => linearPattern(one.pattern).test(key));
371
448
  return pattern === undefined ? facts.additional : pattern.facts;
372
449
  }
373
450
  export function crossedFacts(facts) {
@@ -470,6 +547,56 @@ function readSections(value, at, context) {
470
547
  }
471
548
  return { sections };
472
549
  }
550
+ function branchSectionsFailure(sections, declared, where) {
551
+ if (!isRecord(sections))
552
+ return refuse('sections', where);
553
+ for (const [name, spec] of Object.entries(sections)) {
554
+ const place = `${where}/${escapePointer(name)}`;
555
+ const own = isRecord(declared) && Object.hasOwn(declared, name) ? declared[name] : undefined;
556
+ if (!isRecord(spec) || !isRecord(own))
557
+ return refuse('sections', place);
558
+ const { required, ...kind } = spec;
559
+ const { required: _required, ...ownKind } = own;
560
+ if (required !== undefined && typeof required !== 'boolean')
561
+ return refuse('sections', `${place}/required`);
562
+ if (!equalValue(kind, ownKind))
563
+ return refuse('sections', place);
564
+ }
565
+ return undefined;
566
+ }
567
+ function withoutBranchSections(schema, declared, at) {
568
+ const kept = { ...schema };
569
+ if (Array.isArray(schema.allOf)) {
570
+ const entries = [];
571
+ for (const [index, entry] of schema.allOf.entries()) {
572
+ const read = isRecord(entry)
573
+ ? withoutBranchSections(entry, declared, `${at}/allOf/${index}`)
574
+ : { schema: entry };
575
+ if ('failure' in read)
576
+ return read;
577
+ entries.push(read.schema);
578
+ }
579
+ kept.allOf = entries;
580
+ }
581
+ if (schema.if === undefined)
582
+ return { schema: kept };
583
+ for (const branch of ['then', 'else']) {
584
+ const held = schema[branch];
585
+ if (!isRecord(held))
586
+ continue;
587
+ const { sections, ...rest } = held;
588
+ const failure = sections === undefined
589
+ ? undefined
590
+ : branchSectionsFailure(sections, declared, `${at}/${branch}/sections`);
591
+ if (failure !== undefined)
592
+ return { failure };
593
+ const read = withoutBranchSections(rest, declared, `${at}/${branch}`);
594
+ if ('failure' in read)
595
+ return read;
596
+ kept[branch] = read.schema;
597
+ }
598
+ return { schema: kept };
599
+ }
473
600
  function deepFreeze(value) {
474
601
  if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
475
602
  Object.freeze(value);
@@ -478,21 +605,157 @@ function deepFreeze(value) {
478
605
  }
479
606
  return value;
480
607
  }
608
+ function ruleValue(value) {
609
+ return value === null || typeof value === 'string' || typeof value === 'boolean' || finite(value);
610
+ }
611
+ function roleNames(value) {
612
+ return strings(value) && value.every((name) => name !== '');
613
+ }
614
+ function compositesOf(value) {
615
+ return Array.isArray(value) ? ['array'] : isRecord(value) ? ['object'] : [];
616
+ }
617
+ function typed(type) {
618
+ const types = typeof type === 'string' ? [type] : type;
619
+ return Array.isArray(types) ? COMPOSITES.filter((kind) => types.includes(kind)) : undefined;
620
+ }
621
+ function admittedWhole(node) {
622
+ if (node === true)
623
+ return COMPOSITES;
624
+ if (!isRecord(node) || Object.keys(node).some((key) => key !== 'type' && !ANNOTATIONS.has(key)))
625
+ return [];
626
+ return typed(node.type) ?? COMPOSITES;
627
+ }
628
+ function composites(node, defs, seen) {
629
+ if (node === false)
630
+ return [];
631
+ if (!isRecord(node))
632
+ return COMPOSITES;
633
+ const each = (nodes) => Array.isArray(nodes) ? nodes.map((one) => composites(one, defs, seen)) : [];
634
+ const target = typeof node.$ref === 'string' ? refTarget(node.$ref, defs)?.node : undefined;
635
+ const conditioned = () => {
636
+ const met = composites(node.then ?? true, defs, seen);
637
+ return COMPOSITES.filter((kind) => (composites(node.if, defs, seen).includes(kind) && met.includes(kind)) ||
638
+ composites(node.else ?? true, defs, seen).includes(kind));
639
+ };
640
+ const bounds = [
641
+ typed(node.type),
642
+ Array.isArray(node.enum) ? node.enum.flatMap(compositesOf) : undefined,
643
+ Object.hasOwn(node, 'const') ? compositesOf(node.const) : undefined,
644
+ target === undefined || seen.has(target)
645
+ ? undefined
646
+ : composites(target, defs, new Set([...seen, target])),
647
+ ...each(node.allOf),
648
+ ...[node.anyOf, node.oneOf].filter(Array.isArray).map((branches) => each(branches).flat()),
649
+ Object.hasOwn(node, 'if') ? conditioned() : undefined,
650
+ Object.hasOwn(node, 'not')
651
+ ? COMPOSITES.filter((kind) => !admittedWhole(node.not).includes(kind))
652
+ : undefined,
653
+ ];
654
+ return COMPOSITES.filter((kind) => bounds.every((bound) => bound?.includes(kind) ?? true));
655
+ }
656
+ function transitionOf(value) {
657
+ if (!isRecord(value) || !Object.hasOwn(value, 'from') || !Object.hasOwn(value, 'to'))
658
+ return undefined;
659
+ const { from, to, roles, ...rest } = value;
660
+ if (Object.keys(rest).length > 0 || !ruleValue(from) || !ruleValue(to))
661
+ return undefined;
662
+ if (roles === undefined)
663
+ return { from, to };
664
+ return roleNames(roles) ? { from, to, roles } : undefined;
665
+ }
666
+ function fieldRules(written, field, at, defs) {
667
+ const { change_roles: changeRoles, initial_roles: initialRoles, transitions } = written;
668
+ if (changeRoles !== undefined && !roleNames(changeRoles))
669
+ return refuse('change_roles', `${at}/change_roles`);
670
+ if (initialRoles !== undefined && !roleNames(initialRoles))
671
+ return refuse('initial_roles', `${at}/initial_roles`);
672
+ if (transitions === undefined)
673
+ return {
674
+ ...(changeRoles === undefined ? {} : { changeRoles }),
675
+ ...(initialRoles === undefined ? {} : { initialRoles }),
676
+ };
677
+ if (!Array.isArray(transitions) || composites(field, defs, new Set()).length > 0)
678
+ return refuse('transitions', `${at}/transitions`);
679
+ const listed = [];
680
+ for (const [index, held] of transitions.entries()) {
681
+ const transition = transitionOf(held);
682
+ if (transition === undefined)
683
+ return refuse('transitions', `${at}/transitions/${index}`);
684
+ listed.push(transition);
685
+ }
686
+ return {
687
+ ...(changeRoles === undefined ? {} : { changeRoles }),
688
+ ...(initialRoles === undefined ? {} : { initialRoles }),
689
+ transitions: listed,
690
+ };
691
+ }
692
+ function unruled(schema) {
693
+ const written = new Map();
694
+ if (!isRecord(schema.properties))
695
+ return { schema, written };
696
+ const properties = Object.create(null);
697
+ for (const [field, node] of Object.entries(schema.properties)) {
698
+ if (!isRecord(node) || !FIELD_RULES.some((keyword) => Object.hasOwn(node, keyword))) {
699
+ properties[field] = node;
700
+ continue;
701
+ }
702
+ written.set(field, node);
703
+ properties[field] = Object.fromEntries(Object.entries(node).filter(([keyword]) => !FIELD_RULES.includes(keyword)));
704
+ }
705
+ return { schema: written.size === 0 ? schema : { ...schema, properties }, written };
706
+ }
707
+ function providedType(name, value, provided) {
708
+ const at = `/${escapePointer(name)}`;
709
+ if (provided.nameable?.includes(name) !== true)
710
+ return refuse('provided-type', at);
711
+ if (!isRecord(value))
712
+ return refuse('schema', at);
713
+ const declared = Object.keys(value).find((key) => !PROVIDED_KEYWORDS.includes(key));
714
+ if (declared !== undefined)
715
+ return refuse('provided-type', `${at}/${escapePointer(declared)}`);
716
+ const { create_roles: createRoles, delete_roles: deleteRoles } = value;
717
+ if (createRoles !== undefined && !roleNames(createRoles))
718
+ return refuse('create_roles', `${at}/create_roles`);
719
+ if (deleteRoles !== undefined && !roleNames(deleteRoles))
720
+ return refuse('delete_roles', `${at}/delete_roles`);
721
+ return {
722
+ ...provided.types[name],
723
+ ...(createRoles === undefined ? {} : { createRoles }),
724
+ ...(deleteRoles === undefined ? {} : { deleteRoles }),
725
+ };
726
+ }
481
727
  function readType(name, value, context) {
482
728
  const at = `/${escapePointer(name)}`;
483
729
  if (!isRecord(value))
484
730
  return refuse('schema', at);
485
- const { sections: declared, member_document: mark, ...schema } = value;
731
+ const { sections: declared, member_document: mark, create_roles: createRoles, delete_roles: deleteRoles, ...ruled } = value;
486
732
  if (mark !== undefined && typeof mark !== 'boolean')
487
733
  return refuse('member_document', `${at}/member_document`);
734
+ if (createRoles !== undefined && !roleNames(createRoles))
735
+ return refuse('create_roles', `${at}/create_roles`);
736
+ if (deleteRoles !== undefined && !roleNames(deleteRoles))
737
+ return refuse('delete_roles', `${at}/delete_roles`);
488
738
  const read = declared === undefined
489
739
  ? { sections: Object.create(null) }
490
740
  : readSections(declared, `${at}/sections`, context);
491
741
  if ('failure' in read)
492
742
  return read.failure;
743
+ const unbranched = withoutBranchSections(ruled, declared, at);
744
+ if ('failure' in unbranched)
745
+ return unbranched.failure;
746
+ const { schema, written } = unruled(unbranched.schema);
493
747
  const failure = schemaFailure(schema, at, context);
494
748
  if (failure !== undefined)
495
749
  return failure;
750
+ const rules = Object.create(null);
751
+ for (const [field, keywords] of written) {
752
+ const properties = schema.properties;
753
+ const where = `${at}/properties/${escapePointer(field)}`;
754
+ const ruling = fieldRules(keywords, properties[field], where, context.defs);
755
+ if ('rule' in ruling)
756
+ return ruling;
757
+ rules[field] = ruling;
758
+ }
496
759
  const memberDocument = mark === true;
497
760
  const root = valueFacts(schema, context.defs);
498
761
  const fields = Object.assign(Object.create(null), root.properties);
@@ -500,19 +763,26 @@ function readType(name, value, context) {
500
763
  fields[MEMBER_FIELD] = { type: 'string', prose: false };
501
764
  const defs = Object.create(null);
502
765
  reachedDefs(value, context.defs, defs);
766
+ const { create_roles: _create, delete_roles: _delete, ...shaped } = value;
503
767
  return {
504
768
  name,
505
769
  memberDocument,
506
770
  frontmatter: { ...root, properties: { ...HOUSE_KEYS, ...fields } },
507
771
  fields,
508
772
  sections: read.sections,
509
- shape: { schema: value, defs },
773
+ rules,
774
+ ...(createRoles === undefined ? {} : { createRoles }),
775
+ ...(deleteRoles === undefined ? {} : { deleteRoles }),
776
+ shape: {
777
+ schema: written.size === 0 ? shaped : { ...shaped, properties: schema.properties },
778
+ defs,
779
+ },
510
780
  };
511
781
  }
512
782
  function lineAt(text, offset) {
513
783
  return text.slice(0, offset).split(/\r\n|\r|\n/).length;
514
784
  }
515
- export function readSchema(text) {
785
+ export function readSchema(text, provided = NO_TYPES) {
516
786
  let root;
517
787
  try {
518
788
  root = jsValue(parseYamlMapping(text, 0, text.length));
@@ -530,7 +800,7 @@ export function readSchema(text) {
530
800
  if (name === '' || name.startsWith('$') || name === BUILT_IN_TYPE || name === ATTACHMENT)
531
801
  return refuse('schema-type-name', `/${escapePointer(name)}`);
532
802
  }
533
- const context = { defs, types: new Set(names) };
803
+ const context = { defs, types: new Set([...names, ...Object.keys(provided.types)]) };
534
804
  for (const [name, def] of Object.entries(defs)) {
535
805
  const failure = schemaFailure(def, `/$defs/${escapePointer(name)}`, context);
536
806
  if (failure !== undefined)
@@ -539,7 +809,9 @@ export function readSchema(text) {
539
809
  const types = Object.create(null);
540
810
  let marked = false;
541
811
  for (const name of names) {
542
- const definition = readType(name, root[name], context);
812
+ const definition = Object.hasOwn(provided.types, name)
813
+ ? providedType(name, root[name], provided)
814
+ : readType(name, root[name], context);
543
815
  if ('rule' in definition)
544
816
  return definition;
545
817
  if (definition.memberDocument) {
@@ -549,5 +821,9 @@ export function readSchema(text) {
549
821
  }
550
822
  types[name] = deepFreeze(definition);
551
823
  }
824
+ for (const [name, definition] of Object.entries(provided.types)) {
825
+ if (!Object.hasOwn(types, name))
826
+ types[name] = definition;
827
+ }
552
828
  return Object.freeze({ types: Object.freeze(types) });
553
829
  }
@@ -1 +1 @@
1
- {"version":3,"file":"divide.d.ts","sourceRoot":"","sources":["../src/divide.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAgB,MAAM,YAAY,CAAC;AA4WpF,wBAAgB,MAAM,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,QAAQ,CAc3F"}
1
+ {"version":3,"file":"divide.d.ts","sourceRoot":"","sources":["../src/divide.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAgB,MAAM,YAAY,CAAC;AAwXpF,wBAAgB,MAAM,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,QAAQ,CAc3F"}
package/dist/divide.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { blockRanges, headingLines } from './blocks.js';
2
- import { projectionInput, rangeText } from './project.js';
2
+ import { firstEditFrom, projectionInput, rangeText, } from './project.js';
3
3
  import { refusal } from './refuse.js';
4
- const SMALLEST_BUDGET = 2;
4
+ const SMALLEST_BOUND = 2;
5
5
  function node(draft) {
6
6
  return {
7
7
  start: draft.start,
@@ -86,8 +86,8 @@ function isTrailingSurrogate(text, index) {
86
86
  function characterLength(text, index) {
87
87
  return isTrailingSurrogate(text, index + 1) ? 2 : 1;
88
88
  }
89
- function boundOf(cutting, from, to, budget) {
90
- const limit = from + budget;
89
+ function boundOf(cutting, from, to, bound) {
90
+ const limit = from + bound;
91
91
  if (limit >= to)
92
92
  return to;
93
93
  for (let index = limit; index > from + 1; index -= 1) {
@@ -131,16 +131,26 @@ function textOf(state, extent) {
131
131
  return rangeText(state.content, extent, state.edits);
132
132
  }
133
133
  function sourceCutting(state, extent) {
134
- let text = state.content.slice(extent.start, extent.end);
134
+ const source = state.content.slice(extent.start, extent.end);
135
135
  const masks = [];
136
- for (const edit of state.edits) {
137
- if (edit.start < extent.start || edit.end > extent.end)
136
+ for (let index = firstEditFrom(state.edits, extent.start); index < state.edits.length; index += 1) {
137
+ const edit = state.edits[index];
138
+ if (edit.start > extent.end)
139
+ break;
140
+ if (edit.end > extent.end)
138
141
  continue;
139
- const from = edit.start - extent.start;
140
- const to = edit.end - extent.start;
141
- text = `${text.slice(0, from)}${'x'.repeat(to - from)}${text.slice(to)}`;
142
- masks.push({ start: from, end: to });
142
+ masks.push({ start: edit.start - extent.start, end: edit.end - extent.start });
143
143
  }
144
+ let text = '';
145
+ let cursor = 0;
146
+ for (const mask of masks) {
147
+ if (mask.end <= cursor)
148
+ continue;
149
+ const from = Math.max(mask.start, cursor);
150
+ text += `${source.slice(cursor, from)}${'x'.repeat(mask.end - from)}`;
151
+ cursor = mask.end;
152
+ }
153
+ text += source.slice(cursor);
144
154
  return {
145
155
  text,
146
156
  masks,
@@ -158,12 +168,12 @@ function valueCutting(value) {
158
168
  };
159
169
  }
160
170
  function prefixOf(state, body) {
161
- if (body.emitted === 0 || body.heading.length >= state.budget)
171
+ if (body.emitted === 0 || body.heading.length >= state.bound)
162
172
  return '';
163
173
  return body.heading;
164
174
  }
165
175
  function available(state, body) {
166
- return state.budget - prefixOf(state, body).length;
176
+ return state.bound - prefixOf(state, body).length;
167
177
  }
168
178
  function emit(state, body, piece, bare = false) {
169
179
  if (piece.text.trim() === '')
@@ -206,7 +216,7 @@ function divideBound(state, body, cutting, range) {
206
216
  let start = range.start;
207
217
  while (start < range.end) {
208
218
  const bare = available(state, body) < characterLength(cutting.text, start);
209
- const room = bare ? state.budget : available(state, body);
219
+ const room = bare ? state.bound : available(state, body);
210
220
  const stop = boundOf(cutting, start, range.end, room);
211
221
  const piece = cutting.piece(start, stop);
212
222
  if (cutting.masks.length === 0 || piece.text.length <= room) {
@@ -293,8 +303,8 @@ function divideInput(state, input) {
293
303
  ]);
294
304
  }
295
305
  export function divide(document, file, context) {
296
- if (!Number.isInteger(context.budget) || context.budget < SMALLEST_BUDGET) {
297
- return { ok: false, refusal: refusal(file, 'division-budget') };
306
+ if (!Number.isInteger(context.bound) || context.bound < SMALLEST_BOUND) {
307
+ return { ok: false, refusal: refusal(file, 'division-bound') };
298
308
  }
299
309
  const result = projectionInput(document, file, context);
300
310
  if (!result.ok)
@@ -302,7 +312,7 @@ export function divide(document, file, context) {
302
312
  const state = {
303
313
  content: document.content,
304
314
  edits: result.input.edits,
305
- budget: context.budget,
315
+ bound: context.bound,
306
316
  spans: [],
307
317
  };
308
318
  divideInput(state, result.input);
@@ -1,13 +1,16 @@
1
1
  import { type Definition } from './definition.js';
2
- import type { ApplyResult, BodyResult, Change, Document, Link, ParseResult } from './types.js';
2
+ import { type LinkInventory } from './links.js';
3
+ import type { ApplyResult, BodyResult, FrontmatterResult, Change, Document, Link, ParseResult } from './types.js';
3
4
  import { type YamlMap } from './yaml.js';
5
+ export declare function inventoryOf(document: Document): LinkInventory;
6
+ export declare function parseFrontmatter(content: string, file?: string): FrontmatterResult;
4
7
  export declare function parse(content: string, file?: string): ParseResult;
5
- export declare function relativize(document: Document, file: string, handle: string, definition?: Definition): ApplyResult;
8
+ export declare function relativize(document: Document, file: string, handle: string, definition?: Definition, kept?: ReadonlySet<string>): ApplyResult;
6
9
  export declare function relativizeBody(body: string, file: string, handle: string): BodyResult;
7
10
  export declare function relinkBody(body: string, file: string, moves: ReadonlyMap<string, string>): BodyResult;
8
11
  export declare function links(document: Document, file: string): readonly Link[];
9
12
  export declare function bodyLinks(body: string, file: string): readonly Link[];
10
- export declare function apply(document: Document, change: Change): ApplyResult;
13
+ export declare function apply(document: Document, changes: readonly Change[]): ApplyResult;
11
14
  export declare function serialize(document: Document): string;
12
15
  export declare function frontmatter(document: Document): YamlMap | null;
13
16
  //# sourceMappingURL=document.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMhF,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,WAAW,EAEZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAaL,KAAK,OAAO,EAEb,MAAM,WAAW,CAAC;AAwCnB,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,WAAW,CAkD7D;AA+KD,wBAAgB,UAAU,CACxB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,UAAU,GACtB,WAAW,CAYb;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAOrF;AAED,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,UAAU,CAeZ;AAeD,wBAAgB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,IAAI,EAAE,CAEvE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,IAAI,EAAE,CAErE;AAED,wBAAgB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CAOrE;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAEpD;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAE9D"}
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAcpB,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,WAAW,EAEZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAcL,KAAK,OAAO,EAEb,MAAM,WAAW,CAAC;AAgBnB,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,aAAa,CAM7D;AAgED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,iBAAiB,CAW9E;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,WAAW,CAgB7D;AAmED,wBAAgB,UAAU,CACxB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,UAAU,EACvB,IAAI,GAAE,WAAW,CAAC,MAAM,CAAa,GACpC,WAAW,CAuBb;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAUrF;AAED,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,UAAU,CAkBZ;AAeD,wBAAgB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,IAAI,EAAE,CAEvE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,IAAI,EAAE,CAErE;AA4LD,wBAAgB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,CA4CjF;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAEpD;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAE9D"}