@agentshouse/mdmodel 0.1.0-c5a71fe539a49 → 0.1.0-c60b1ddb74fcc
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.
- package/API.md +220 -164
- package/dist/blocks.d.ts.map +1 -1
- package/dist/blocks.js +31 -17
- package/dist/condition.d.ts +19 -7
- package/dist/condition.d.ts.map +1 -1
- package/dist/condition.js +223 -117
- package/dist/definition.d.ts +23 -0
- package/dist/definition.d.ts.map +1 -1
- package/dist/definition.js +266 -25
- package/dist/document.d.ts +7 -8
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +105 -49
- package/dist/fields.d.ts +2 -0
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +7 -0
- package/dist/index.d.ts +11 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/links.d.ts +1 -0
- package/dist/links.d.ts.map +1 -1
- package/dist/links.js +1 -1
- package/dist/paths.d.ts +1 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +92 -12
- package/dist/references.d.ts +2 -2
- package/dist/references.d.ts.map +1 -1
- package/dist/schema.js +2 -2
- package/dist/types.d.ts +32 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/validate.d.ts +6 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +163 -109
- package/dist/yaml.d.ts +7 -3
- package/dist/yaml.d.ts.map +1 -1
- package/dist/yaml.js +72 -43
- package/fixtures/README.md +1 -1
- package/fixtures/cases/absent-link-dangles/context.json +1 -0
- package/fixtures/cases/absent-link-dangles/expected.json +1 -0
- package/fixtures/cases/absent-link-dangles/library/item.md +8 -0
- package/fixtures/cases/absent-link-dangles/schema.yaml +6 -0
- package/fixtures/cases/absolute-link/context.json +1 -0
- package/fixtures/cases/{missing-binary-image → absolute-link}/expected.json +1 -1
- package/fixtures/cases/absolute-link/library/item.md +8 -0
- package/fixtures/cases/absolute-link/schema.yaml +6 -0
- package/fixtures/cases/binary-image-dangles/expected.json +1 -0
- package/fixtures/cases/binary-link-dangles/expected.json +1 -0
- package/fixtures/cases/quoted-link-definition/library/notes/draft.md +1 -1
- package/fixtures/cases/shortcut-reference-link/library/item.md +1 -1
- package/fixtures/cases/truncated-link-definition/library/notes/draft.md +1 -1
- package/fixtures/cases/unresolved-link/library/item.md +1 -1
- package/package.json +4 -2
- package/fixtures/cases/missing-binary-link/expected.json +0 -5
- /package/fixtures/cases/{missing-binary-image → binary-image-dangles}/context.json +0 -0
- /package/fixtures/cases/{missing-binary-image → binary-image-dangles}/library/notes/draft.md +0 -0
- /package/fixtures/cases/{missing-binary-image → binary-image-dangles}/schema.yaml +0 -0
- /package/fixtures/cases/{missing-binary-link → binary-link-dangles}/context.json +0 -0
- /package/fixtures/cases/{missing-binary-link → binary-link-dangles}/library/notes/draft.md +0 -0
- /package/fixtures/cases/{missing-binary-link → binary-link-dangles}/schema.yaml +0 -0
package/dist/definition.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MEMBER_FIELD, RECEIVED_FIELD, SITE_FIELD } from './fields.js';
|
|
1
|
+
import { MEMBER_FIELD, RECEIVED_FIELD, SITE_FIELD, TYPE_FIELD } from './fields.js';
|
|
2
2
|
import { equalValue, isRecord, jsValue, parseYamlMapping, YamlError } from './yaml.js';
|
|
3
3
|
export const JSON_TYPES = [
|
|
4
4
|
'string',
|
|
@@ -12,11 +12,23 @@ export const JSON_TYPES = [
|
|
|
12
12
|
export const BUILT_IN_TYPE = 'any';
|
|
13
13
|
export const ATTACHMENT = 'attachment';
|
|
14
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'];
|
|
15
27
|
const DEFS_REFERENCE = '#/$defs/';
|
|
16
28
|
const UNRESOLVED = /[\u0000-\u001f\u007f%[\]\\^{|}]|\s$/u;
|
|
17
29
|
export const ARRAY_INDEX = /^(?:0|[1-9][0-9]*)$/u;
|
|
18
30
|
const HOUSE_KEYS = {
|
|
19
|
-
|
|
31
|
+
[TYPE_FIELD]: { prose: false },
|
|
20
32
|
[SITE_FIELD]: { prose: false },
|
|
21
33
|
[RECEIVED_FIELD]: { prose: false },
|
|
22
34
|
};
|
|
@@ -270,24 +282,43 @@ function united(left, right) {
|
|
|
270
282
|
return undefined;
|
|
271
283
|
return [...left, ...right.filter((value) => !left.some((held) => equalValue(held, value)))];
|
|
272
284
|
}
|
|
285
|
+
function both(left, right) {
|
|
286
|
+
return left !== undefined && right !== undefined ? joined(left, right) : undefined;
|
|
287
|
+
}
|
|
273
288
|
function joined(left, right) {
|
|
274
289
|
const type = left.type === right.type ? left.type : undefined;
|
|
275
290
|
const format = left.format === right.format ? left.format : undefined;
|
|
276
291
|
const values = united(left.values, right.values);
|
|
277
292
|
const reference = united(left.reference, right.reference);
|
|
278
|
-
const items = left.items
|
|
279
|
-
|
|
280
|
-
|
|
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);
|
|
281
308
|
return {
|
|
282
309
|
...(type === undefined ? {} : { type }),
|
|
283
310
|
...(format === undefined ? {} : { format }),
|
|
284
311
|
...(values === undefined ? {} : { values }),
|
|
285
312
|
...(reference === undefined ? {} : { reference }),
|
|
286
313
|
prose: left.prose && right.prose,
|
|
314
|
+
...(prefix === undefined ? {} : { prefix }),
|
|
287
315
|
...(items === undefined ? {} : { items }),
|
|
316
|
+
...(Object.keys(properties).length === 0 ? {} : { properties }),
|
|
317
|
+
...(patterns.length === 0 ? {} : { patterns }),
|
|
318
|
+
...(additional === undefined ? {} : { additional }),
|
|
288
319
|
};
|
|
289
320
|
}
|
|
290
|
-
function propertiesOf(held, defs, ancestry) {
|
|
321
|
+
function propertiesOf(held, variants, defs, ancestry) {
|
|
291
322
|
const declared = new Map();
|
|
292
323
|
for (const node of held) {
|
|
293
324
|
if (!isRecord(node.properties))
|
|
@@ -298,45 +329,87 @@ function propertiesOf(held, defs, ancestry) {
|
|
|
298
329
|
const properties = Object.create(null);
|
|
299
330
|
for (const [name, schemas] of declared)
|
|
300
331
|
properties[name] = factsOf(schemas, defs, ancestry);
|
|
301
|
-
const
|
|
302
|
-
for (const branch of
|
|
303
|
-
for (const [name, facts] of Object.entries(
|
|
332
|
+
const conditional = Object.create(null);
|
|
333
|
+
for (const branch of variants.values()) {
|
|
334
|
+
for (const [name, facts] of Object.entries(branch.properties ?? {})) {
|
|
304
335
|
if (Object.hasOwn(properties, name))
|
|
305
336
|
continue;
|
|
306
|
-
|
|
337
|
+
conditional[name] = Object.hasOwn(conditional, name)
|
|
338
|
+
? joined(conditional[name], facts)
|
|
339
|
+
: facts;
|
|
307
340
|
}
|
|
308
341
|
}
|
|
309
|
-
Object.
|
|
342
|
+
for (const [name, facts] of Object.entries(conditional))
|
|
343
|
+
properties[name] = { ...facts, conditional: true };
|
|
310
344
|
return Object.keys(properties).length === 0 ? undefined : properties;
|
|
311
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
|
+
}
|
|
312
368
|
function factsOf(nodes, defs, ancestry) {
|
|
313
369
|
const { held, ancestry: reached } = conjoined(nodes, defs, ancestry);
|
|
314
|
-
const
|
|
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
|
+
];
|
|
315
380
|
const values = listed.length === 0
|
|
316
381
|
? undefined
|
|
317
382
|
: listed.reduce((kept, list) => kept.filter((value) => list.some((other) => equalValue(other, value))));
|
|
318
383
|
const type = firstOf(held, (node) => singleType(node.type)) ??
|
|
319
|
-
(values === undefined ? undefined : sharedType(values))
|
|
384
|
+
(values === undefined ? undefined : sharedType(values)) ??
|
|
385
|
+
alternatives.find((facts) => facts.type !== undefined)?.type;
|
|
320
386
|
const reference = firstOf(held, (node) => typeof node.reference === 'string'
|
|
321
387
|
? [node.reference]
|
|
322
388
|
: Array.isArray(node.reference)
|
|
323
389
|
? node.reference
|
|
324
|
-
: undefined);
|
|
325
|
-
const format = firstOf(held, (node) => typeof node.format === 'string' ? node.format : undefined)
|
|
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;
|
|
326
393
|
const prose = type === 'string' &&
|
|
327
394
|
values === undefined &&
|
|
328
395
|
format === undefined &&
|
|
329
396
|
reference === undefined &&
|
|
330
|
-
held.every((node) => node.pattern === undefined)
|
|
397
|
+
held.every((node) => node.pattern === undefined) &&
|
|
398
|
+
unions.every((kept) => kept.every((facts) => facts.type !== 'string' || facts.prose));
|
|
331
399
|
const listedItems = held.flatMap((node) => (node.items === undefined ? [] : [node.items]));
|
|
332
|
-
const items = type
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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))));
|
|
338
411
|
const mapped = type === 'object' || type === undefined;
|
|
339
|
-
const properties = mapped ? propertiesOf(held, defs, reached) : undefined;
|
|
412
|
+
const properties = mapped ? propertiesOf(held, variants, defs, reached) : undefined;
|
|
340
413
|
const patterns = mapped
|
|
341
414
|
? held.flatMap((node) => Object.entries(isRecord(node.patternProperties) ? node.patternProperties : {}).map(([pattern, schema]) => ({ pattern, facts: factsOf([schema], defs, reached) })))
|
|
342
415
|
: [];
|
|
@@ -369,6 +442,54 @@ export function memberFacts(facts, key) {
|
|
|
369
442
|
const pattern = facts.patterns?.find((one) => new RegExp(one.pattern, 'u').test(key));
|
|
370
443
|
return pattern === undefined ? facts.additional : pattern.facts;
|
|
371
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
|
+
}
|
|
372
493
|
function reachedDefs(value, defs, reached) {
|
|
373
494
|
if (Array.isArray(value)) {
|
|
374
495
|
for (const item of value)
|
|
@@ -429,21 +550,134 @@ function deepFreeze(value) {
|
|
|
429
550
|
}
|
|
430
551
|
return value;
|
|
431
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
|
+
}
|
|
432
652
|
function readType(name, value, context) {
|
|
433
653
|
const at = `/${escapePointer(name)}`;
|
|
434
654
|
if (!isRecord(value))
|
|
435
655
|
return refuse('schema', at);
|
|
436
|
-
const { sections: declared, member_document: mark, ...
|
|
656
|
+
const { sections: declared, member_document: mark, create_roles: createRoles, delete_roles: deleteRoles, ...ruled } = value;
|
|
437
657
|
if (mark !== undefined && typeof mark !== 'boolean')
|
|
438
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`);
|
|
439
663
|
const read = declared === undefined
|
|
440
664
|
? { sections: Object.create(null) }
|
|
441
665
|
: readSections(declared, `${at}/sections`, context);
|
|
442
666
|
if ('failure' in read)
|
|
443
667
|
return read.failure;
|
|
668
|
+
const { schema, written } = unruled(ruled);
|
|
444
669
|
const failure = schemaFailure(schema, at, context);
|
|
445
670
|
if (failure !== undefined)
|
|
446
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
|
+
}
|
|
447
681
|
const memberDocument = mark === true;
|
|
448
682
|
const root = valueFacts(schema, context.defs);
|
|
449
683
|
const fields = Object.assign(Object.create(null), root.properties);
|
|
@@ -451,13 +685,20 @@ function readType(name, value, context) {
|
|
|
451
685
|
fields[MEMBER_FIELD] = { type: 'string', prose: false };
|
|
452
686
|
const defs = Object.create(null);
|
|
453
687
|
reachedDefs(value, context.defs, defs);
|
|
688
|
+
const { create_roles: _create, delete_roles: _delete, ...shaped } = value;
|
|
454
689
|
return {
|
|
455
690
|
name,
|
|
456
691
|
memberDocument,
|
|
457
692
|
frontmatter: { ...root, properties: { ...HOUSE_KEYS, ...fields } },
|
|
458
693
|
fields,
|
|
459
694
|
sections: read.sections,
|
|
460
|
-
|
|
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
|
+
},
|
|
461
702
|
};
|
|
462
703
|
}
|
|
463
704
|
function lineAt(text, offset) {
|
package/dist/document.d.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { type Definition } from './definition.js';
|
|
2
|
-
import type { ApplyResult, Change, Document, Link,
|
|
2
|
+
import type { ApplyResult, BodyResult, FrontmatterResult, Change, Document, Link, LinkOccurrence, ParseResult } from './types.js';
|
|
3
3
|
import { type YamlMap } from './yaml.js';
|
|
4
|
+
export declare function parseFrontmatter(content: string, file?: string): FrontmatterResult;
|
|
4
5
|
export declare function parse(content: string, file?: string): ParseResult;
|
|
5
6
|
export declare function relativize(document: Document, file: string, handle: string, definition?: Definition): ApplyResult;
|
|
6
|
-
export declare function relativizeBody(body: string, file: string, handle: string):
|
|
7
|
-
|
|
8
|
-
readonly body: string;
|
|
9
|
-
} | {
|
|
10
|
-
readonly outcome: 'refuse';
|
|
11
|
-
readonly refusal: Refusal;
|
|
12
|
-
};
|
|
7
|
+
export declare function relativizeBody(body: string, file: string, handle: string): BodyResult;
|
|
8
|
+
export declare function relinkBody(body: string, file: string, moves: ReadonlyMap<string, string>): BodyResult;
|
|
13
9
|
export declare function links(document: Document, file: string): readonly Link[];
|
|
10
|
+
export declare function linkOccurrences(document: Document, file: string): readonly LinkOccurrence[];
|
|
11
|
+
export declare function bodyLinks(body: string, file: string): readonly Link[];
|
|
14
12
|
export declare function apply(document: Document, change: Change): ApplyResult;
|
|
15
13
|
export declare function serialize(document: Document): string;
|
|
14
|
+
export declare function fieldStart(document: Document, pointer: string): number | undefined;
|
|
16
15
|
export declare function frontmatter(document: Document): YamlMap | null;
|
|
17
16
|
//# sourceMappingURL=document.d.ts.map
|
package/dist/document.d.ts.map
CHANGED
|
@@ -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,
|
|
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,iBAAiB,EACjB,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,cAAc,EACd,WAAW,EAEZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAaL,KAAK,OAAO,EAEb,MAAM,WAAW,CAAC;AAmEnB,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;AAqMD,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,CASrF;AAED,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,UAAU,CAeZ;AAuBD,wBAAgB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,IAAI,EAAE,CAEvE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,cAAc,EAAE,CAG3F;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,UAAU,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGlF;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAE9D"}
|
package/dist/document.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ARRAY_INDEX, pointerSegments } from './definition.js';
|
|
2
2
|
import { hrefOccurrences } from './links.js';
|
|
3
|
-
import { relative, retarget, SHELL_ROOMS, shellTarget, target } from './paths.js';
|
|
3
|
+
import { destinationPath, relative, retarget, SHELL_ROOMS, shellTarget, target } from './paths.js';
|
|
4
4
|
import { references } from './references.js';
|
|
5
5
|
import { headingSpans, sectionsRecord } from './sections.js';
|
|
6
6
|
import { refusal } from './refuse.js';
|
|
7
|
-
import { bodyStartAfterYaml, bodyStartOf, equalValue, extractFrontmatter, jsValue, lineEnding, mapEntry, parseYamlMapping, serializeJs, serializeKey, YamlError, } from './yaml.js';
|
|
7
|
+
import { bodyStartAfterYaml, bodyStartOf, equalValue, extractFrontmatter, frontmatterValue, jsValue, lineEnding, mapEntry, parseYamlMapping, serializeJs, serializeKey, YamlError, } from './yaml.js';
|
|
8
8
|
const internals = new WeakMap();
|
|
9
9
|
function refuse(file, rule, location) {
|
|
10
10
|
return location === undefined ? refusal(file, rule) : refusal(file, rule, { location });
|
|
@@ -28,19 +28,10 @@ function declaredType(fields) {
|
|
|
28
28
|
return undefined;
|
|
29
29
|
return value;
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
function opened(content, file) {
|
|
32
32
|
const fence = extractFrontmatter(content);
|
|
33
|
-
if (fence.kind === 'none')
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
ok: true,
|
|
37
|
-
document: freezeDocument(content, undefined, {}, content.slice(0, spans[0]?.headingStart), sectionsRecord(spans, content), {
|
|
38
|
-
root: null,
|
|
39
|
-
yamlEnd: 0,
|
|
40
|
-
bodyStart: 0,
|
|
41
|
-
}),
|
|
42
|
-
};
|
|
43
|
-
}
|
|
33
|
+
if (fence.kind === 'none')
|
|
34
|
+
return { ok: true, root: null, fields: {}, yamlEnd: 0 };
|
|
44
35
|
if (fence.kind === 'unclosed')
|
|
45
36
|
return { ok: false, refusal: refuse(file, 'frontmatter-fence') };
|
|
46
37
|
try {
|
|
@@ -49,17 +40,7 @@ export function parse(content, file = '') {
|
|
|
49
40
|
if (fields === null || typeof fields !== 'object' || Array.isArray(fields)) {
|
|
50
41
|
return { ok: false, refusal: refuse(file, 'frontmatter-fence') };
|
|
51
42
|
}
|
|
52
|
-
|
|
53
|
-
const spans = headingSpans(content, bodyStart);
|
|
54
|
-
const record = fields;
|
|
55
|
-
return {
|
|
56
|
-
ok: true,
|
|
57
|
-
document: freezeDocument(content, declaredType(record), record, content.slice(bodyStart, spans[0]?.headingStart), sectionsRecord(spans, content), {
|
|
58
|
-
root,
|
|
59
|
-
yamlEnd: fence.yamlEnd,
|
|
60
|
-
bodyStart,
|
|
61
|
-
}),
|
|
62
|
-
};
|
|
43
|
+
return { ok: true, root, fields: fields, yamlEnd: fence.yamlEnd };
|
|
63
44
|
}
|
|
64
45
|
catch (error) {
|
|
65
46
|
if (error instanceof YamlError)
|
|
@@ -67,6 +48,30 @@ export function parse(content, file = '') {
|
|
|
67
48
|
throw error;
|
|
68
49
|
}
|
|
69
50
|
}
|
|
51
|
+
export function parseFrontmatter(content, file = '') {
|
|
52
|
+
const read = opened(content, file);
|
|
53
|
+
if (!read.ok)
|
|
54
|
+
return read;
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
frontmatter: Object.freeze({
|
|
58
|
+
content,
|
|
59
|
+
type: declaredType(read.fields),
|
|
60
|
+
fields: Object.freeze(read.fields),
|
|
61
|
+
}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function parse(content, file = '') {
|
|
65
|
+
const read = opened(content, file);
|
|
66
|
+
if (!read.ok)
|
|
67
|
+
return read;
|
|
68
|
+
const bodyStart = read.root === null ? 0 : bodyStartAfterYaml(content, read.yamlEnd);
|
|
69
|
+
const spans = headingSpans(content, bodyStart);
|
|
70
|
+
return {
|
|
71
|
+
ok: true,
|
|
72
|
+
document: freezeDocument(content, declaredType(read.fields), read.fields, content.slice(bodyStart, spans[0]?.headingStart), sectionsRecord(spans, content), { root: read.root, yamlEnd: read.yamlEnd, bodyStart }),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
70
75
|
function splice(content, start, end, insert) {
|
|
71
76
|
return content.slice(0, start) + insert + content.slice(end);
|
|
72
77
|
}
|
|
@@ -78,10 +83,18 @@ function applyField(document, name, value, prior) {
|
|
|
78
83
|
if (!equalValue(document.fields[name], prior)) {
|
|
79
84
|
return { outcome: 'stale', document };
|
|
80
85
|
}
|
|
86
|
+
if (!frontmatterValue(value)) {
|
|
87
|
+
return { outcome: 'refuse', refusal: refuse('', 'frontmatter-fence', name) };
|
|
88
|
+
}
|
|
81
89
|
let nextContent;
|
|
82
90
|
const existing = mapEntry(internal.root, name);
|
|
83
91
|
if (existing !== undefined) {
|
|
84
|
-
|
|
92
|
+
const held = existing.value;
|
|
93
|
+
const inline = held.start > existing.after &&
|
|
94
|
+
!/[\r\n]/.test(document.content.slice(existing.after, held.start));
|
|
95
|
+
nextContent = inline
|
|
96
|
+
? splice(document.content, held.start, held.end, serializeJs(value))
|
|
97
|
+
: splice(document.content, existing.after, held.end, ` ${serializeJs(value)}`);
|
|
85
98
|
}
|
|
86
99
|
else if (document.content[internal.root.start] === '{') {
|
|
87
100
|
nextContent = splice(document.content, internal.root.end - 1, internal.root.end - 1, `${internal.root.entries.length === 0 ? '' : ', '}${serializeKey(name)}: ${serializeJs(value)}`);
|
|
@@ -111,16 +124,30 @@ function applySection(document, name, value, prior) {
|
|
|
111
124
|
const current = document.content.slice(span.bodyStart, span.bodyEnd);
|
|
112
125
|
if (current !== prior)
|
|
113
126
|
return { outcome: 'stale', document };
|
|
114
|
-
|
|
115
|
-
if (!parsed.ok)
|
|
116
|
-
return { outcome: 'refuse', refusal: parsed.refusal };
|
|
117
|
-
return { outcome: 'applied', document: parsed.document };
|
|
127
|
+
return replaced(document, internal.bodyStart, span.bodyStart, span.bodyEnd, value);
|
|
118
128
|
}
|
|
119
129
|
function applyPreamble(document, value, prior) {
|
|
120
130
|
if (document.preamble !== prior)
|
|
121
131
|
return { outcome: 'stale', document };
|
|
122
132
|
const start = bodyStartOf(document.content);
|
|
123
|
-
|
|
133
|
+
return replaced(document, start, start, start + document.preamble.length, value);
|
|
134
|
+
}
|
|
135
|
+
function firstHeadings(content, bodyStart) {
|
|
136
|
+
const first = new Map();
|
|
137
|
+
for (const span of headingSpans(content, bodyStart)) {
|
|
138
|
+
if (!first.has(span.name))
|
|
139
|
+
first.set(span.name, span.headingStart);
|
|
140
|
+
}
|
|
141
|
+
return first;
|
|
142
|
+
}
|
|
143
|
+
function replaced(document, bodyStart, start, end, value) {
|
|
144
|
+
const content = splice(document.content, start, end, value);
|
|
145
|
+
const after = firstHeadings(content, bodyStartOf(content));
|
|
146
|
+
for (const [name, at] of firstHeadings(document.content, bodyStart)) {
|
|
147
|
+
if (after.get(name) !== (at < start ? at : at + value.length - (end - start)))
|
|
148
|
+
return { outcome: 'refuse', refusal: refuse('', 'overlapping-section', name) };
|
|
149
|
+
}
|
|
150
|
+
const parsed = parse(content);
|
|
124
151
|
if (!parsed.ok)
|
|
125
152
|
return { outcome: 'refuse', refusal: parsed.refusal };
|
|
126
153
|
return { outcome: 'applied', document: parsed.document };
|
|
@@ -248,25 +275,58 @@ export function relativizeBody(body, file, handle) {
|
|
|
248
275
|
const relinkedBody = shellRelinked(body, 0, file, handle);
|
|
249
276
|
if (typeof relinkedBody !== 'string')
|
|
250
277
|
return { outcome: 'refuse', refusal: relinkedBody };
|
|
251
|
-
const elsewhere = hrefOccurrences(relinkedBody).find(({ href }) => href
|
|
278
|
+
const elsewhere = hrefOccurrences(relinkedBody).find(({ href }) => destinationPath(href)?.startsWith(SHELL_ROOMS) === true);
|
|
252
279
|
if (elsewhere !== undefined)
|
|
253
280
|
return { outcome: 'refuse', refusal: refuse(file, 'unresolved-link', elsewhere.href) };
|
|
254
281
|
return { outcome: 'applied', body: relinkedBody };
|
|
255
282
|
}
|
|
256
|
-
export function
|
|
283
|
+
export function relinkBody(body, file, moves) {
|
|
284
|
+
let rewritten = '';
|
|
285
|
+
let cursor = 0;
|
|
286
|
+
for (const match of hrefOccurrences(body)) {
|
|
287
|
+
const from = target(file, match.href);
|
|
288
|
+
const to = from === null ? undefined : moves.get(from);
|
|
289
|
+
if (to === undefined)
|
|
290
|
+
continue;
|
|
291
|
+
const written = retarget(match.href, relative(file, to));
|
|
292
|
+
const text = destinationText(written, match.bracketed);
|
|
293
|
+
if (text === null)
|
|
294
|
+
return { outcome: 'refuse', refusal: refuse(file, 'unwritable-link', written) };
|
|
295
|
+
rewritten += body.slice(cursor, match.start) + text;
|
|
296
|
+
cursor = match.end;
|
|
297
|
+
}
|
|
298
|
+
return { outcome: 'applied', body: rewritten + body.slice(cursor) };
|
|
299
|
+
}
|
|
300
|
+
function occurrencesIn(text, file, offset) {
|
|
301
|
+
const found = [];
|
|
302
|
+
for (const occurrence of hrefOccurrences(text)) {
|
|
303
|
+
const resolved = target(file, occurrence.href);
|
|
304
|
+
if (resolved !== null)
|
|
305
|
+
found.push({ href: occurrence.href, target: resolved, start: offset + occurrence.start });
|
|
306
|
+
}
|
|
307
|
+
return found;
|
|
308
|
+
}
|
|
309
|
+
function linksIn(text, file) {
|
|
257
310
|
const found = [];
|
|
258
311
|
const seen = new Set();
|
|
259
|
-
for (const occurrence of
|
|
312
|
+
for (const occurrence of occurrencesIn(text, file, 0)) {
|
|
260
313
|
if (seen.has(occurrence.href))
|
|
261
314
|
continue;
|
|
262
|
-
const resolved = target(file, occurrence.href);
|
|
263
|
-
if (resolved === null)
|
|
264
|
-
continue;
|
|
265
315
|
seen.add(occurrence.href);
|
|
266
|
-
found.push({ href: occurrence.href, target:
|
|
316
|
+
found.push({ href: occurrence.href, target: occurrence.target });
|
|
267
317
|
}
|
|
268
318
|
return found;
|
|
269
319
|
}
|
|
320
|
+
export function links(document, file) {
|
|
321
|
+
return linksIn(document.content.slice(bodyStartOf(document.content)), file);
|
|
322
|
+
}
|
|
323
|
+
export function linkOccurrences(document, file) {
|
|
324
|
+
const bodyStart = bodyStartOf(document.content);
|
|
325
|
+
return occurrencesIn(document.content.slice(bodyStart), file, bodyStart);
|
|
326
|
+
}
|
|
327
|
+
export function bodyLinks(body, file) {
|
|
328
|
+
return linksIn(body, file);
|
|
329
|
+
}
|
|
270
330
|
export function apply(document, change) {
|
|
271
331
|
if (change.kind === 'link')
|
|
272
332
|
return applyLink(document, change.href, change.to);
|
|
@@ -274,21 +334,17 @@ export function apply(document, change) {
|
|
|
274
334
|
return applyReference(document, change.pointer, change.value, change.prior);
|
|
275
335
|
if (change.kind === 'preamble')
|
|
276
336
|
return applyPreamble(document, change.value, change.prior);
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
return applySection(document, change.name, change.value, change.prior);
|
|
281
|
-
}
|
|
282
|
-
catch (error) {
|
|
283
|
-
if (error instanceof YamlError) {
|
|
284
|
-
return { outcome: 'refuse', refusal: refuse('', 'frontmatter-fence', change.name) };
|
|
285
|
-
}
|
|
286
|
-
throw error;
|
|
287
|
-
}
|
|
337
|
+
if (change.kind === 'field')
|
|
338
|
+
return applyField(document, change.name, change.value, change.prior);
|
|
339
|
+
return applySection(document, change.name, change.value, change.prior);
|
|
288
340
|
}
|
|
289
341
|
export function serialize(document) {
|
|
290
342
|
return document.content;
|
|
291
343
|
}
|
|
344
|
+
export function fieldStart(document, pointer) {
|
|
345
|
+
const root = internals.get(document)?.root;
|
|
346
|
+
return root === null || root === undefined ? undefined : nodeAt(root, pointer)?.start;
|
|
347
|
+
}
|
|
292
348
|
export function frontmatter(document) {
|
|
293
349
|
return internals.get(document)?.root ?? null;
|
|
294
350
|
}
|