@graview/core 0.1.12 → 0.1.15
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/README.md +31 -2
- package/dist/address.d.ts +31 -0
- package/dist/address.d.ts.map +1 -0
- package/dist/address.js +53 -0
- package/dist/address.js.map +1 -0
- package/dist/app.d.ts +1 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/arrange.d.ts +13 -183
- package/dist/arrange.d.ts.map +1 -1
- package/dist/arrange.js +12 -470
- package/dist/arrange.js.map +1 -1
- package/dist/arrangement.d.ts +169 -0
- package/dist/arrangement.d.ts.map +1 -0
- package/dist/arrangement.js +220 -0
- package/dist/arrangement.js.map +1 -0
- package/dist/arranging.d.ts +28 -0
- package/dist/arranging.d.ts.map +1 -0
- package/dist/arranging.js +264 -0
- package/dist/arranging.js.map +1 -0
- package/dist/capabilities.js +1 -1
- package/dist/capabilities.js.map +1 -1
- package/dist/cli/check/hidden-reads.d.ts.map +1 -1
- package/dist/cli/check/hidden-reads.js +32 -1
- package/dist/cli/check/hidden-reads.js.map +1 -1
- package/dist/cli/check/lenses.d.ts.map +1 -1
- package/dist/cli/check/lenses.js +29 -1
- package/dist/cli/check/lenses.js.map +1 -1
- package/dist/cli/describe.d.ts.map +1 -1
- package/dist/cli/describe.js +16 -6
- package/dist/cli/describe.js.map +1 -1
- package/dist/cli/docs.js +1 -1
- package/dist/cli/docs.js.map +1 -1
- package/dist/columns.d.ts +48 -16
- package/dist/columns.d.ts.map +1 -1
- package/dist/columns.js +65 -22
- package/dist/columns.js.map +1 -1
- package/dist/compiled.d.ts +19 -0
- package/dist/compiled.d.ts.map +1 -0
- package/dist/compiled.js +60 -0
- package/dist/compiled.js.map +1 -0
- package/dist/conformance/fixtures.d.ts.map +1 -1
- package/dist/conformance/fixtures.js +41 -1
- package/dist/conformance/fixtures.js.map +1 -1
- package/dist/describe.d.ts +2 -2
- package/dist/describe.d.ts.map +1 -1
- package/dist/describe.js +1 -1
- package/dist/describe.js.map +1 -1
- package/dist/document/act-findings.d.ts +5 -0
- package/dist/document/act-findings.d.ts.map +1 -0
- package/dist/document/act-findings.js +84 -0
- package/dist/document/act-findings.js.map +1 -0
- package/dist/document/blocks.d.ts.map +1 -1
- package/dist/document/blocks.js +2 -1
- package/dist/document/blocks.js.map +1 -1
- package/dist/document/compile-checked.d.ts.map +1 -1
- package/dist/document/compile-checked.js +11 -4
- package/dist/document/compile-checked.js.map +1 -1
- package/dist/document/compile.d.ts +7 -21
- package/dist/document/compile.d.ts.map +1 -1
- package/dist/document/compile.js +145 -371
- package/dist/document/compile.js.map +1 -1
- package/dist/document/compiled.d.ts +130 -0
- package/dist/document/compiled.d.ts.map +1 -0
- package/dist/document/compiled.js +466 -0
- package/dist/document/compiled.js.map +1 -0
- package/dist/document/describe-coverage.d.ts +37 -0
- package/dist/document/describe-coverage.d.ts.map +1 -0
- package/dist/document/describe-coverage.js +162 -0
- package/dist/document/describe-coverage.js.map +1 -0
- package/dist/document/describe-place.d.ts.map +1 -1
- package/dist/document/describe-place.js +14 -1
- package/dist/document/describe-place.js.map +1 -1
- package/dist/document/diff.d.ts +5 -0
- package/dist/document/diff.d.ts.map +1 -1
- package/dist/document/diff.js +12 -3
- package/dist/document/diff.js.map +1 -1
- package/dist/document/edit.d.ts +1 -1
- package/dist/document/edit.d.ts.map +1 -1
- package/dist/document/edit.js +64 -7
- package/dist/document/edit.js.map +1 -1
- package/dist/document/far-end.d.ts +15 -0
- package/dist/document/far-end.d.ts.map +1 -0
- package/dist/document/far-end.js +26 -0
- package/dist/document/far-end.js.map +1 -0
- package/dist/document/index.d.ts +5 -2
- package/dist/document/index.d.ts.map +1 -1
- package/dist/document/index.js +3 -1
- package/dist/document/index.js.map +1 -1
- package/dist/document/migrate.d.ts.map +1 -1
- package/dist/document/migrate.js +7 -5
- package/dist/document/migrate.js.map +1 -1
- package/dist/document/range.d.ts +17 -0
- package/dist/document/range.d.ts.map +1 -0
- package/dist/document/range.js +46 -0
- package/dist/document/range.js.map +1 -0
- package/dist/document/remembered.d.ts +6 -0
- package/dist/document/remembered.d.ts.map +1 -0
- package/dist/document/remembered.js +16 -0
- package/dist/document/remembered.js.map +1 -0
- package/dist/document/rules.d.ts.map +1 -1
- package/dist/document/rules.js +2 -1
- package/dist/document/rules.js.map +1 -1
- package/dist/document/schema.d.ts +23 -0
- package/dist/document/schema.d.ts.map +1 -1
- package/dist/document/schema.js +26 -2
- package/dist/document/schema.js.map +1 -1
- package/dist/document/template-parse.d.ts +18 -0
- package/dist/document/template-parse.d.ts.map +1 -0
- package/dist/document/template-parse.js +124 -0
- package/dist/document/template-parse.js.map +1 -0
- package/dist/document/template.d.ts +1 -17
- package/dist/document/template.d.ts.map +1 -1
- package/dist/document/template.js +6 -115
- package/dist/document/template.js.map +1 -1
- package/dist/document/thumbnail.d.ts +25 -1
- package/dist/document/thumbnail.d.ts.map +1 -1
- package/dist/document/thumbnail.js +167 -32
- package/dist/document/thumbnail.js.map +1 -1
- package/dist/document/to-document.d.ts +1 -4
- package/dist/document/to-document.d.ts.map +1 -1
- package/dist/document/to-document.js +18 -11
- package/dist/document/to-document.js.map +1 -1
- package/dist/document/views.d.ts.map +1 -1
- package/dist/document/views.js +1 -1
- package/dist/document/views.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/mutations/define-mutation.d.ts +12 -0
- package/dist/mutations/define-mutation.d.ts.map +1 -1
- package/dist/mutations/define-mutation.js +38 -2
- package/dist/mutations/define-mutation.js.map +1 -1
- package/dist/mutations/derive-edits.d.ts +8 -0
- package/dist/mutations/derive-edits.d.ts.map +1 -1
- package/dist/mutations/derive-edits.js +30 -7
- package/dist/mutations/derive-edits.js.map +1 -1
- package/dist/mutations/form.d.ts +2 -0
- package/dist/mutations/form.d.ts.map +1 -1
- package/dist/mutations/form.js +1 -0
- package/dist/mutations/form.js.map +1 -1
- package/dist/mutations/node-ref.d.ts +4 -1
- package/dist/mutations/node-ref.d.ts.map +1 -1
- package/dist/mutations/node-ref.js +6 -0
- package/dist/mutations/node-ref.js.map +1 -1
- package/dist/mutations/types.d.ts +16 -0
- package/dist/mutations/types.d.ts.map +1 -1
- package/dist/places.js +1 -1
- package/dist/places.js.map +1 -1
- package/dist/refusal.d.ts +11 -6
- package/dist/refusal.d.ts.map +1 -1
- package/dist/refusal.js +15 -6
- package/dist/refusal.js.map +1 -1
- package/dist/refused.d.ts +27 -0
- package/dist/refused.d.ts.map +1 -0
- package/dist/refused.js +31 -0
- package/dist/refused.js.map +1 -0
- package/dist/scaffold/project.d.ts.map +1 -1
- package/dist/scaffold/project.js +4 -0
- package/dist/scaffold/project.js.map +1 -1
- package/dist/schema/json-schema.d.ts.map +1 -1
- package/dist/schema/json-schema.js +4 -1
- package/dist/schema/json-schema.js.map +1 -1
- package/dist/search.d.ts +1 -1
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +1 -1
- package/dist/search.js.map +1 -1
- package/dist/theme/derive.d.ts.map +1 -1
- package/dist/theme/derive.js +5 -4
- package/dist/theme/derive.js.map +1 -1
- package/dist/theme/types.d.ts.map +1 -1
- package/dist/theme/types.js +5 -0
- package/dist/theme/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +9 -1
package/dist/document/compile.js
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { refTo } from "../mutations/node-ref.js";
|
|
1
|
+
import { brandFromAccent, SCHEMES } from "../index.js";
|
|
2
|
+
import { ActRefusal } from "../refused.js";
|
|
4
3
|
import { analyzeExpr } from "./expr/analyze.js";
|
|
5
|
-
import { rememberDocument } from "./to-document.js";
|
|
6
|
-
import { evaluateExpr, ExprBudgetError, ExprEvalError } from "./expr/evaluate.js";
|
|
7
4
|
import { ExprSyntaxError, parseExpr } from "./expr/parse.js";
|
|
8
5
|
import { error, hasErrors, warning } from "./findings.js";
|
|
9
6
|
import { DocumentSpec, MAX_DOCUMENT_BYTES, } from "./schema.js";
|
|
10
|
-
import { parseTemplate,
|
|
7
|
+
import { parseTemplate, TemplateError } from "./template-parse.js";
|
|
11
8
|
import { upgradeDocument } from "./upgrade.js";
|
|
12
9
|
import { homeOf, validateViews, viewsOf } from "./views.js";
|
|
10
|
+
import { farEnd } from "./far-end.js";
|
|
13
11
|
import { computedOf, parsedComputed, validateComputed, workedOutAlone } from "./computed.js";
|
|
12
|
+
import { build, COMPILED_FORMAT, defaultLabelField, refName, SUBJECT_ARG, } from "./compiled.js";
|
|
14
13
|
const BUILTIN_REFS = new Set(["subject", "now", "today"]);
|
|
15
|
-
const SUBJECT_ARG = "id";
|
|
16
14
|
/** Parse and validate without compiling — the document's own sentences, before the framework's. */
|
|
17
15
|
export function readDocument(raw) {
|
|
18
16
|
if (typeof raw === "string") {
|
|
@@ -88,6 +86,13 @@ export function kindShapes(document) {
|
|
|
88
86
|
export function effectsOf(act, document) {
|
|
89
87
|
// Shorthands first, then any listed effects: an act may say "writes quote" and also set a status.
|
|
90
88
|
const effects = [];
|
|
89
|
+
const subject = asArray(act.on);
|
|
90
|
+
// A relation joins its subject from whichever end the declaration puts it (FR-115): `owns` on a component links from the person given.
|
|
91
|
+
const link = (relation, verb) => {
|
|
92
|
+
const reversed = farEnd(document.kinds, relation, subject)?.reversed === true;
|
|
93
|
+
const [from, to] = reversed ? ["$to", "$subject"] : ["$subject", "$to"];
|
|
94
|
+
return verb === "connect" ? { connect: relation, from, to } : { sever: relation, from, to };
|
|
95
|
+
};
|
|
91
96
|
if (act.creates) {
|
|
92
97
|
const kind = document.kinds[act.creates];
|
|
93
98
|
const set = {};
|
|
@@ -104,17 +109,22 @@ export function effectsOf(act, document) {
|
|
|
104
109
|
// An act that makes a record already asks for each of its fields: `writes` beside `creates` (and no `on`) names some of them again, not a subject.
|
|
105
110
|
if (act.writes && !(act.creates && act.on === undefined))
|
|
106
111
|
effects.push({ set: Object.fromEntries(act.writes.map((f) => [f, `$${f}`])) });
|
|
107
|
-
if (act.connects)
|
|
108
|
-
|
|
112
|
+
if (act.connects) {
|
|
113
|
+
if (act.replaces)
|
|
114
|
+
effects.push({ replace: act.replaces === true ? [act.connects] : [...act.replaces], keep: act.connects });
|
|
115
|
+
effects.push(link(act.connects, "connect"));
|
|
116
|
+
}
|
|
109
117
|
if (act.severs)
|
|
110
|
-
effects.push(
|
|
118
|
+
effects.push(link(act.severs, "sever"));
|
|
119
|
+
// What it sets on the record at the other end (FR-115): that record is its `$to`.
|
|
120
|
+
if (act.setsOther && (act.connects || act.severs))
|
|
121
|
+
effects.push({ set: act.setsOther, target: "$to" });
|
|
111
122
|
if (act.removes)
|
|
112
123
|
effects.push({ remove: "$subject" });
|
|
113
124
|
if (act.effects)
|
|
114
125
|
effects.push(...act.effects.filter((e) => !(act.creates && "set" in e && !("create" in e) && refName(e.target) === "new")));
|
|
115
126
|
return effects;
|
|
116
127
|
}
|
|
117
|
-
const refName = (v) => (typeof v === "string" && /^\$[A-Za-z][A-Za-z0-9]*$/.test(v) ? v.slice(1) : undefined);
|
|
118
128
|
function tryExpr(source, path, findings) {
|
|
119
129
|
try {
|
|
120
130
|
const expr = parseExpr(source);
|
|
@@ -336,75 +346,30 @@ function validate(document) {
|
|
|
336
346
|
findings.push(...validateComputed(new Map(Object.entries(document.kinds).map(([kind, spec]) => [kind, { fields: new Set(Object.keys(spec.fields)), edges: new Set(Object.keys(spec.edges ?? {})), computed: computedOf(spec) }])), (kind, name) => `kinds.${kind}.computed.${name}`));
|
|
337
347
|
return findings;
|
|
338
348
|
}
|
|
339
|
-
/*
|
|
340
|
-
* A FIELD'S SCHEMA, in zod/mini (schema/zod.ts): a page that compiles a
|
|
341
|
-
* document in the browser pays for the checks it calls, not for classic
|
|
342
|
-
* zod whole (FR-57). The checks, their messages and their definitions are
|
|
343
|
-
* the ones classic's methods make.
|
|
344
|
-
*/
|
|
345
|
-
function fieldSchema(spec, optional) {
|
|
346
|
-
let schema;
|
|
347
|
-
switch (spec.type) {
|
|
348
|
-
case "string":
|
|
349
|
-
schema = z.string().check(z.maxLength(500));
|
|
350
|
-
break;
|
|
351
|
-
case "text":
|
|
352
|
-
schema = z.string().check(z.maxLength(20_000));
|
|
353
|
-
break;
|
|
354
|
-
case "number":
|
|
355
|
-
schema = z.number();
|
|
356
|
-
break;
|
|
357
|
-
case "integer":
|
|
358
|
-
schema = z.number().check(z.int());
|
|
359
|
-
break;
|
|
360
|
-
case "boolean":
|
|
361
|
-
schema = z.boolean();
|
|
362
|
-
break;
|
|
363
|
-
case "date":
|
|
364
|
-
schema = z.string().check(z.regex(/^\d{4}-\d{2}-\d{2}$/, "a date like 2026-10-02"));
|
|
365
|
-
break;
|
|
366
|
-
case "datetime":
|
|
367
|
-
schema = z.string().check(z.refine((s) => !Number.isNaN(Date.parse(s)), "a date and time like 2026-10-02T14:30:00Z"));
|
|
368
|
-
break;
|
|
369
|
-
case "enum":
|
|
370
|
-
schema = z.enum(spec.options);
|
|
371
|
-
break;
|
|
372
|
-
case "list":
|
|
373
|
-
schema = z.array(spec.of === "number" ? z.number() : spec.of === "date" ? z.string().check(z.regex(/^\d{4}-\d{2}-\d{2}$/)) : z.string().check(z.maxLength(500))).check(z.maxLength(200));
|
|
374
|
-
break;
|
|
375
|
-
case "url":
|
|
376
|
-
schema = z.url();
|
|
377
|
-
break;
|
|
378
|
-
case "email":
|
|
379
|
-
schema = z.email();
|
|
380
|
-
break;
|
|
381
|
-
}
|
|
382
|
-
if (spec.description)
|
|
383
|
-
schema = schema.check(z.describe(spec.description));
|
|
384
|
-
return optional ? z.optional(schema) : schema;
|
|
385
|
-
}
|
|
386
|
-
/** What one of these is called, when the kind says nothing: the first required word-ish field. */
|
|
387
|
-
function defaultLabelField(spec) {
|
|
388
|
-
const entries = Object.entries(spec.fields);
|
|
389
|
-
return (entries.find(([, f]) => f.required && (f.type === "string" || f.type === "text"))?.[0] ??
|
|
390
|
-
entries.find(([, f]) => f.type === "string")?.[0]);
|
|
391
|
-
}
|
|
392
349
|
/** The arguments an act asks for: declared ones, then whatever its effects reference. */
|
|
393
|
-
function argsOf(
|
|
350
|
+
function argsOf(act, effects, document, edges) {
|
|
394
351
|
const args = new Map();
|
|
395
352
|
const subjectKinds = asArray(act.on);
|
|
396
353
|
const created = new Map();
|
|
354
|
+
/** The kinds a record argument names, by argument: what a `set` on it (a `setsOther`) is checked against. */
|
|
355
|
+
const named = new Map();
|
|
397
356
|
const usesSubject = subjectKinds.length > 0 ||
|
|
398
357
|
effects.some((e) => ("remove" in e && refName(e.remove) === "subject") || ("from" in e && (refName(e.from) === "subject" || refName(e.to) === "subject")) || ("set" in e && !("create" in e) && refName(e.target ?? "$subject") === "subject"));
|
|
399
358
|
if (usesSubject)
|
|
400
|
-
args.set(SUBJECT_ARG, {
|
|
359
|
+
args.set(SUBJECT_ARG, { ref: subjectKinds.length > 0 ? subjectKinds : "*", required: true });
|
|
401
360
|
const declared = act.args ?? {};
|
|
402
361
|
const writesOnlyOne = act.writes?.length === 1;
|
|
403
|
-
|
|
362
|
+
/*
|
|
363
|
+
* A declared argument that fills a number field and says no range of its
|
|
364
|
+
* own is asked for within the field's (FR-114): the form will not take
|
|
365
|
+
* what the record would refuse.
|
|
366
|
+
*/
|
|
367
|
+
const withRangeOf = (d, fed) => fed && d.type === fed.type && d.min === undefined && d.max === undefined && d.step === undefined ? { ...d, ...(fed.min === undefined ? {} : { min: fed.min }), ...(fed.max === undefined ? {} : { max: fed.max }), ...(fed.step === undefined ? {} : { step: fed.step }) } : d;
|
|
368
|
+
const note = (arg, schema, fed) => {
|
|
404
369
|
if (arg === SUBJECT_ARG || BUILTIN_REFS.has(arg) || created.has(arg) || args.has(arg))
|
|
405
370
|
return;
|
|
406
|
-
const d = declared[arg];
|
|
407
|
-
args.set(arg, d ? {
|
|
371
|
+
const d = declared[arg] ? withRangeOf(declared[arg], fed) : undefined;
|
|
372
|
+
args.set(arg, d ? { field: d, optional: !d.required, required: Boolean(d.required) } : schema());
|
|
408
373
|
};
|
|
409
374
|
for (const effect of effects) {
|
|
410
375
|
if ("create" in effect) {
|
|
@@ -414,7 +379,7 @@ function argsOf(name, act, effects, document, edges) {
|
|
|
414
379
|
const spec = kind?.fields[field];
|
|
415
380
|
if (arg && spec) {
|
|
416
381
|
const required = Boolean(spec.required) && spec.default === undefined;
|
|
417
|
-
note(arg, () => ({
|
|
382
|
+
note(arg, () => ({ field: spec, optional: !required, required }), spec);
|
|
418
383
|
}
|
|
419
384
|
}
|
|
420
385
|
if (effect.as)
|
|
@@ -427,36 +392,35 @@ function argsOf(name, act, effects, document, edges) {
|
|
|
427
392
|
if (!arg)
|
|
428
393
|
continue;
|
|
429
394
|
const kinds = end === "to" ? edge?.to ?? "*" : edge?.from ?? "*";
|
|
430
|
-
|
|
395
|
+
if (kinds !== "*" && !named.has(arg))
|
|
396
|
+
named.set(arg, kinds);
|
|
397
|
+
note(arg, () => ({ ref: kinds, required: true }));
|
|
431
398
|
}
|
|
432
399
|
}
|
|
433
400
|
else if ("set" in effect) {
|
|
434
401
|
const target = refName(effect.target ?? "$subject");
|
|
435
|
-
const targetKind = target === "subject" ? subjectKinds[0] : created.get(target ?? "");
|
|
402
|
+
const targetKind = target === "subject" ? subjectKinds[0] : (created.get(target ?? "") ?? named.get(target ?? "")?.[0]);
|
|
436
403
|
if (target && target !== "subject" && !created.has(target))
|
|
437
|
-
note(target, () => ({
|
|
404
|
+
note(target, () => ({ ref: "*", required: true }));
|
|
438
405
|
for (const [field, value] of Object.entries(effect.set)) {
|
|
439
406
|
const arg = refName(value);
|
|
440
407
|
if (!arg)
|
|
441
408
|
continue;
|
|
442
409
|
const spec = targetKind ? document.kinds[targetKind]?.fields[field] : undefined;
|
|
443
|
-
note(arg, () => (spec ? {
|
|
410
|
+
note(arg, () => (spec ? { field: spec, optional: !writesOnlyOne, required: writesOnlyOne } : { any: true, required: false }), spec);
|
|
444
411
|
}
|
|
445
412
|
}
|
|
446
413
|
else if ("remove" in effect) {
|
|
447
414
|
const arg = refName(effect.remove);
|
|
448
415
|
if (arg)
|
|
449
|
-
note(arg, () => ({
|
|
416
|
+
note(arg, () => ({ ref: "*", required: true }));
|
|
450
417
|
}
|
|
451
418
|
}
|
|
452
419
|
for (const [arg, d] of Object.entries(declared))
|
|
453
420
|
if (!args.has(arg))
|
|
454
|
-
args.set(arg, {
|
|
455
|
-
void name;
|
|
421
|
+
args.set(arg, { field: d, optional: !d.required, required: Boolean(d.required) });
|
|
456
422
|
return args;
|
|
457
423
|
}
|
|
458
|
-
class Refused extends Error {
|
|
459
|
-
}
|
|
460
424
|
/**
|
|
461
425
|
* A DOCUMENT COMPILED WITHOUT THE FRAMEWORK'S CHECKER: its own sentences
|
|
462
426
|
* still judged, `checkApp` not asked. For a page drawing a document its host
|
|
@@ -473,71 +437,80 @@ export function compileDocumentWithoutCheck(raw, options = {}) {
|
|
|
473
437
|
const findings = [...read.findings];
|
|
474
438
|
if (options.previous)
|
|
475
439
|
findings.push(...renamesFromNothing(document, options.previous));
|
|
476
|
-
const
|
|
477
|
-
|
|
440
|
+
const plan = planOf(document, findings);
|
|
441
|
+
if (hasErrors(plan.findings))
|
|
442
|
+
return { ok: false, findings: plan.findings };
|
|
443
|
+
return build(plan, options);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* The first half of compiling (FR-123): what a document that has been read
|
|
447
|
+
* and judged compiles to, as data — each template and expression parsed,
|
|
448
|
+
* each act's arguments and effects said, the brand derived. `findings` is
|
|
449
|
+
* added to (a brand that cannot be read is a warning) and carried.
|
|
450
|
+
*/
|
|
451
|
+
function planOf(document, findings) {
|
|
478
452
|
const edges = edgeIndex(document);
|
|
479
|
-
// What `{x | money}` says in a label, a sentence or a refusal: the app's currency and locale (FR-100).
|
|
480
|
-
const { accent, name: wordmark, ...money } = document.brand ?? {};
|
|
481
453
|
// ── kinds ────────────────────────────────────────────────────────────────
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
...[...computed].filter(([, c]) => c.label).map(([n, c]) => [n, c.label]),
|
|
494
|
-
]);
|
|
495
|
-
// Defaults stay out of the zod schema (hydration must not invent values) and ride beside it, for a repair to read.
|
|
496
|
-
const defaults = Object.fromEntries(Object.entries(spec.fields).filter(([, f]) => f.default !== undefined).map(([n, f]) => [n, f.default]));
|
|
497
|
-
const edgeDecls = Object.fromEntries(Object.entries(spec.edges ?? {}).map(([n, e]) => [
|
|
498
|
-
n,
|
|
499
|
-
{
|
|
500
|
-
to: e.to,
|
|
501
|
-
...(e.cardinality ? { cardinality: e.cardinality } : {}),
|
|
502
|
-
...(e.description ? { description: e.description } : {}),
|
|
503
|
-
...(e.inverse ? { inverse: e.inverse } : {}),
|
|
504
|
-
...(e.appendOnly ? { appendOnly: e.appendOnly } : {}),
|
|
505
|
-
},
|
|
506
|
-
]));
|
|
507
|
-
return defineNode(kind, {
|
|
508
|
-
fields: z.object(shape),
|
|
509
|
-
edges: edgeDecls,
|
|
510
|
-
...(spec.plural ? { plural: spec.plural } : {}),
|
|
511
|
-
...(spec.noun ? { noun: spec.noun } : {}),
|
|
512
|
-
...(spec.description ? { description: spec.description } : {}),
|
|
513
|
-
...(labelParts ? { label: (node) => renderTemplate(labelParts, { node: node, kinds: shapes, today: today(), money }) || node.id } : {}),
|
|
514
|
-
...(describeParts ? { describe: (node) => renderTemplate(describeParts, { node: node, kinds: shapes, today: today(), money }) } : {}),
|
|
515
|
-
...(spec.lifecycle ? { lifecycle: { field: spec.lifecycle.field, retired: spec.lifecycle.retired } } : {}),
|
|
516
|
-
...(spec.figure ? { figure: spec.figure } : {}),
|
|
517
|
-
...(spec.computed && Object.keys(spec.computed).length > 0 ? { computed: spec.computed } : {}),
|
|
518
|
-
...(Object.keys(labels).length > 0 || spec.glance ? { display: { ...(Object.keys(labels).length > 0 ? { labels } : {}), ...(spec.glance ? { glance: [...spec.glance] } : {}) } } : {}),
|
|
519
|
-
...(Object.keys(defaults).length > 0 ? { defaults } : {}),
|
|
520
|
-
});
|
|
521
|
-
});
|
|
522
|
-
const schema = createSchema(definitions);
|
|
454
|
+
const kinds = {};
|
|
455
|
+
for (const [kind, spec] of Object.entries(document.kinds)) {
|
|
456
|
+
const labelField = defaultLabelField(spec);
|
|
457
|
+
const labelSource = spec.label ?? (labelField ? `{${labelField}}` : undefined);
|
|
458
|
+
const computed = parsedComputed(spec);
|
|
459
|
+
kinds[kind] = {
|
|
460
|
+
...(labelSource ? { label: parseTemplate(labelSource) } : {}),
|
|
461
|
+
...(spec.describe ? { describe: parseTemplate(spec.describe) } : {}),
|
|
462
|
+
...(computed ? { computed: [...computed] } : {}),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
523
465
|
// ── acts ─────────────────────────────────────────────────────────────────
|
|
524
|
-
const
|
|
466
|
+
const acts = {};
|
|
525
467
|
for (const [name, act] of Object.entries(document.acts ?? {})) {
|
|
526
468
|
const effects = effectsOf(act, document);
|
|
527
|
-
const args = argsOf(
|
|
528
|
-
const
|
|
529
|
-
for (const [arg, a] of args)
|
|
530
|
-
shape[arg] = a.schema;
|
|
531
|
-
const subjectKinds = asArray(act.on);
|
|
532
|
-
const title = act.title ?? name.replace(/-/g, " ");
|
|
533
|
-
const guard = act.allowedWhen ? parseExpr(act.allowedWhen) : undefined;
|
|
534
|
-
const refusal = act.refusal ? parseTemplate(act.refusal) : undefined;
|
|
535
|
-
const destructive = act.destructive ?? effects.some((e) => "remove" in e || "sever" in e);
|
|
469
|
+
const args = argsOf(act, effects, document, edges);
|
|
470
|
+
const destructive = act.destructive ?? effects.some((e) => "remove" in e || "sever" in e || "replace" in e);
|
|
536
471
|
const creates = [...new Set(effects.flatMap((e) => ("create" in e ? [e.create] : [])))];
|
|
537
472
|
const connects = [...new Set(effects.flatMap((e) => ("connect" in e ? [e.connect] : [])))];
|
|
538
|
-
|
|
473
|
+
// What it may sever: what it says it severs, and what it replaces (FR-115).
|
|
474
|
+
const severs = [...new Set(effects.flatMap((e) => ("sever" in e ? [e.sever] : "replace" in e ? e.replace : [])))];
|
|
539
475
|
// An act WRITES only what it sets on its subject; setting fields on a record it just made is part of making it.
|
|
540
476
|
const writes = [...new Set(effects.flatMap((e) => ("set" in e && !("create" in e) && (refName(e.target ?? "$subject") === "subject") ? Object.keys(e.set) : [])))];
|
|
477
|
+
/*
|
|
478
|
+
* And what it sets on the record at an end of a link it makes or breaks
|
|
479
|
+
* (FR-115's `setsOther`), by that end's kinds: not the subject's, but a
|
|
480
|
+
* field another act sets, so no derived edit offers it freely.
|
|
481
|
+
*/
|
|
482
|
+
const ends = new Map();
|
|
483
|
+
for (const e of effects) {
|
|
484
|
+
if (!("connect" in e) && !("sever" in e))
|
|
485
|
+
continue;
|
|
486
|
+
const edge = edges.get("connect" in e ? e.connect : e.sever);
|
|
487
|
+
for (const [end, value] of [["from", e.from], ["to", e.to]]) {
|
|
488
|
+
const ref = refName(value);
|
|
489
|
+
const kinds = end === "to" ? edge?.to : edge?.from;
|
|
490
|
+
if (ref && ref !== "subject" && kinds && kinds !== "*")
|
|
491
|
+
ends.set(ref, [...new Set([...(ends.get(ref) ?? []), ...kinds])]);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const writesOther = {};
|
|
495
|
+
for (const e of effects) {
|
|
496
|
+
if (!("set" in e) || "create" in e)
|
|
497
|
+
continue;
|
|
498
|
+
const target = refName(e.target ?? "$subject");
|
|
499
|
+
for (const kind of (target && target !== "subject" && ends.get(target)) || [])
|
|
500
|
+
writesOther[kind] = [...new Set([...(writesOther[kind] ?? []), ...Object.keys(e.set)])];
|
|
501
|
+
}
|
|
502
|
+
// What it sets on its subject to a fixed value, whatever it is told — a named step (FR-108), resolved as `apply` resolves a literal.
|
|
503
|
+
const sets = {};
|
|
504
|
+
for (const e of effects) {
|
|
505
|
+
if (!("set" in e) || "create" in e || refName(e.target ?? "$subject") !== "subject")
|
|
506
|
+
continue;
|
|
507
|
+
for (const [field, v] of Object.entries(e.set ?? {})) {
|
|
508
|
+
if ((typeof v === "string" && refName(v) === undefined) || typeof v === "number" || typeof v === "boolean")
|
|
509
|
+
sets[field] = typeof v === "string" && v.startsWith("$$") ? v.slice(1) : v;
|
|
510
|
+
else
|
|
511
|
+
delete sets[field];
|
|
512
|
+
}
|
|
513
|
+
}
|
|
541
514
|
/*
|
|
542
515
|
* Twice is once when nothing is made and every value set is given
|
|
543
516
|
* (a literal or an argument), never computed from what is there.
|
|
@@ -552,227 +525,37 @@ export function compileDocumentWithoutCheck(raw, options = {}) {
|
|
|
552
525
|
if (v && typeof v === "object" && !Array.isArray(v) && "expr" in v)
|
|
553
526
|
exprs.set(v.expr, parseExpr(v.expr));
|
|
554
527
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
...(act.
|
|
560
|
-
...(
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
},
|
|
572
|
-
apply(context, input) {
|
|
573
|
-
const graph = context.graph;
|
|
574
|
-
const subjectId = typeof input[SUBJECT_ARG] === "string" ? input[SUBJECT_ARG] : undefined;
|
|
575
|
-
const subject = subjectId ? graph.getNode(subjectId) : undefined;
|
|
576
|
-
if (subjectId && !subject)
|
|
577
|
-
throw new Refused(`there is no record "${subjectId}"`);
|
|
578
|
-
if (subject && subjectKinds.length > 0 && !subjectKinds.includes(subject.kind))
|
|
579
|
-
throw new Refused(`"${title}" acts on ${subjectKinds.join(" or ")}, not on ${withArticle(subject.kind)}`);
|
|
580
|
-
const bindings = {};
|
|
581
|
-
for (const [k, v] of Object.entries(input))
|
|
582
|
-
if (v !== undefined)
|
|
583
|
-
bindings[k] = v;
|
|
584
|
-
if (guard && subject) {
|
|
585
|
-
let allowed;
|
|
586
|
-
try {
|
|
587
|
-
allowed = evaluateExpr(guard, { graph, subject, kinds: shapes, bindings, today: today() });
|
|
588
|
-
}
|
|
589
|
-
catch (e) {
|
|
590
|
-
if (e instanceof ExprEvalError)
|
|
591
|
-
throw new Refused(`"${title}" could not be judged: ${e.sentence}`);
|
|
592
|
-
throw e;
|
|
593
|
-
}
|
|
594
|
-
if (allowed !== true) {
|
|
595
|
-
throw new Refused(refusal ? renderTemplate(refusal, { node: subject, kinds: shapes, graph, bindings, today: today(), money }) : `"${title}" is not allowed for ${labelFor(subject)} right now`);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
const made = new Map();
|
|
599
|
-
const resolve = (value, current) => {
|
|
600
|
-
if (value && typeof value === "object" && !Array.isArray(value) && "expr" in value) {
|
|
601
|
-
try {
|
|
602
|
-
return evaluateExpr(exprs.get(value.expr), { graph, subject: current ?? subject ?? null, kinds: shapes, bindings, today: today() });
|
|
603
|
-
}
|
|
604
|
-
catch (e) {
|
|
605
|
-
if (e instanceof ExprEvalError)
|
|
606
|
-
throw new Refused(`"${title}" could not work out a value: ${e.sentence}`);
|
|
607
|
-
throw e;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
const ref = refName(value);
|
|
611
|
-
if (ref === undefined)
|
|
612
|
-
return typeof value === "string" && value.startsWith("$$") ? value.slice(1) : value;
|
|
613
|
-
if (ref === "subject")
|
|
614
|
-
return subjectId;
|
|
615
|
-
if (ref === "now")
|
|
616
|
-
return new Date().toISOString();
|
|
617
|
-
if (ref === "today")
|
|
618
|
-
return today();
|
|
619
|
-
if (made.has(ref))
|
|
620
|
-
return made.get(ref);
|
|
621
|
-
return input[ref];
|
|
622
|
-
};
|
|
623
|
-
for (const effect of effects) {
|
|
624
|
-
if ("create" in effect) {
|
|
625
|
-
const kind = document.kinds[effect.create];
|
|
626
|
-
const node = { kind: effect.create };
|
|
627
|
-
for (const [field, f] of Object.entries(kind.fields))
|
|
628
|
-
if (f.default !== undefined)
|
|
629
|
-
node[field] = f.default;
|
|
630
|
-
for (const [field, value] of Object.entries(effect.set ?? {})) {
|
|
631
|
-
const v = resolve(value, undefined);
|
|
632
|
-
if (v !== undefined)
|
|
633
|
-
node[field] = v;
|
|
634
|
-
}
|
|
635
|
-
const labelField = defaultLabelField(kind);
|
|
636
|
-
const id = context.freshId(String((labelField && node[labelField]) ?? effect.create), effect.create);
|
|
637
|
-
context.addNode({ id, ...node });
|
|
638
|
-
if (effect.as)
|
|
639
|
-
made.set(effect.as, id);
|
|
640
|
-
}
|
|
641
|
-
else if ("connect" in effect || "sever" in effect) {
|
|
642
|
-
const kind = "connect" in effect ? effect.connect : effect.sever;
|
|
643
|
-
const from = String(resolve(effect.from, undefined) ?? "");
|
|
644
|
-
const to = String(resolve(effect.to, undefined) ?? "");
|
|
645
|
-
if (!graph.has(from) && ![...made.values()].includes(from))
|
|
646
|
-
throw new Refused(`there is no record "${from}"`);
|
|
647
|
-
if (!graph.has(to) && ![...made.values()].includes(to))
|
|
648
|
-
throw new Refused(`there is no record "${to}"`);
|
|
649
|
-
if ("connect" in effect) {
|
|
650
|
-
if (edges.get(kind)?.cardinality === "one")
|
|
651
|
-
for (const existing of graph.out(from, kind))
|
|
652
|
-
if (existing.id !== to)
|
|
653
|
-
context.removeEdge({ kind, from, to: existing.id });
|
|
654
|
-
if (!graph.out(from, kind).some((n) => n.id === to))
|
|
655
|
-
context.addEdge({ kind, from, to });
|
|
656
|
-
}
|
|
657
|
-
else {
|
|
658
|
-
context.removeEdge({ kind, from, to });
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
else if ("set" in effect) {
|
|
662
|
-
const targetId = String(resolve(effect.target ?? "$subject", undefined) ?? "");
|
|
663
|
-
const target = graph.getNode(targetId);
|
|
664
|
-
if (!target && ![...made.values()].includes(targetId))
|
|
665
|
-
throw new Refused(`there is no record "${targetId}"`);
|
|
666
|
-
const patch = {};
|
|
667
|
-
for (const [field, value] of Object.entries(effect.set)) {
|
|
668
|
-
const v = resolve(value, target);
|
|
669
|
-
if (v !== undefined)
|
|
670
|
-
patch[field] = v;
|
|
671
|
-
}
|
|
672
|
-
if (Object.keys(patch).length > 0)
|
|
673
|
-
context.patchNode(targetId, patch);
|
|
674
|
-
}
|
|
675
|
-
else if ("remove" in effect) {
|
|
676
|
-
const id = String(resolve(effect.remove, undefined) ?? "");
|
|
677
|
-
if (!graph.has(id))
|
|
678
|
-
throw new Refused(`there is no record "${id}"`);
|
|
679
|
-
context.removeNode(id);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
},
|
|
683
|
-
}));
|
|
684
|
-
}
|
|
685
|
-
function labelFor(node) {
|
|
686
|
-
const definition = schema.tryDefinition(node.kind);
|
|
687
|
-
return definition?.label?.(node) ?? node.id;
|
|
528
|
+
acts[name] = {
|
|
529
|
+
title: act.title ?? name.replace(/-/g, " "),
|
|
530
|
+
effects,
|
|
531
|
+
args: [...args],
|
|
532
|
+
...(act.allowedWhen ? { guard: parseExpr(act.allowedWhen) } : {}),
|
|
533
|
+
...(act.refusal ? { refusal: parseTemplate(act.refusal) } : {}),
|
|
534
|
+
exprs: [...exprs],
|
|
535
|
+
destructive,
|
|
536
|
+
idempotent,
|
|
537
|
+
creates,
|
|
538
|
+
connects,
|
|
539
|
+
severs,
|
|
540
|
+
writes,
|
|
541
|
+
writesOther,
|
|
542
|
+
sets,
|
|
543
|
+
};
|
|
688
544
|
}
|
|
689
545
|
// ── rules ────────────────────────────────────────────────────────────────
|
|
690
|
-
const
|
|
546
|
+
const rules = {};
|
|
691
547
|
for (const [name, rule] of Object.entries(document.rules ?? {})) {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
const judge = (graph, subject) => {
|
|
698
|
-
const ctx = { graph, subject, kinds: shapes, today: today() };
|
|
699
|
-
let holds;
|
|
700
|
-
/*
|
|
701
|
-
* A RULE THAT CANNOT ANSWER IS THE ENGINE'S TO SAY (FR-29). Out of
|
|
702
|
-
* budget it throws RuleBudgetError, and the violation's status is
|
|
703
|
-
* `over-budget`; any other mistake (a word added to a number) is
|
|
704
|
-
* `could-not-judge`. Both name the rule and its subject, and neither
|
|
705
|
-
* is a hang or a crash that takes the other rules down.
|
|
706
|
-
*/
|
|
707
|
-
try {
|
|
708
|
-
if (when && evaluateExpr(when, ctx) !== true)
|
|
709
|
-
return [];
|
|
710
|
-
holds = evaluateExpr(require, ctx) === true;
|
|
711
|
-
}
|
|
712
|
-
catch (e) {
|
|
713
|
-
if (e instanceof ExprBudgetError)
|
|
714
|
-
throw new RuleBudgetError(e.sentence);
|
|
715
|
-
if (e instanceof ExprEvalError)
|
|
716
|
-
throw new Error(e.sentence);
|
|
717
|
-
throw e;
|
|
718
|
-
}
|
|
719
|
-
if (holds)
|
|
720
|
-
return [];
|
|
721
|
-
const repairList = repairs.flatMap((r) => {
|
|
722
|
-
const act = document.acts?.[r.act];
|
|
723
|
-
const args = {};
|
|
724
|
-
if (subject && (act?.on || /^(edit|remove)-/.test(r.act)))
|
|
725
|
-
args[SUBJECT_ARG] = subject.id;
|
|
726
|
-
for (const [k, v] of Object.entries(r.args ?? {}))
|
|
727
|
-
args[k] = refName(v) === "subject" ? subject?.id : v;
|
|
728
|
-
const declared = act ? argsOf(r.act, act, effectsOf(act, document), document, edges) : new Map();
|
|
729
|
-
const missing = [...declared].filter(([k, a]) => a.required && args[k] === undefined).map(([k]) => k);
|
|
730
|
-
return [{ mutation: r.act, label: r.label ?? act?.title ?? r.act.replace(/-/g, " "), args, ...(missing.length > 0 ? { missing } : {}) }];
|
|
731
|
-
});
|
|
732
|
-
return [
|
|
733
|
-
{
|
|
734
|
-
invariant: name,
|
|
735
|
-
...(subject ? { subjectId: subject.id } : {}),
|
|
736
|
-
label: title,
|
|
737
|
-
message: (says ? renderTemplate(says, { node: subject, kinds: shapes, graph, today: today(), money }) : subject ? `${labelFor(subject)}: ${title}` : title),
|
|
738
|
-
nodeIds: subject ? [subject.id] : [],
|
|
739
|
-
repairs: repairList,
|
|
740
|
-
},
|
|
741
|
-
];
|
|
548
|
+
rules[name] = {
|
|
549
|
+
title: rule.title ?? name.replace(/-/g, " "),
|
|
550
|
+
require: parseExpr(rule.require),
|
|
551
|
+
...(rule.when ? { when: parseExpr(rule.when) } : {}),
|
|
552
|
+
...(rule.says ? { says: parseTemplate(rule.says) } : {}),
|
|
742
553
|
};
|
|
743
|
-
invariants.push((rule.over === "graph"
|
|
744
|
-
? defineInvariant(name, {
|
|
745
|
-
scope: "graph",
|
|
746
|
-
label: title,
|
|
747
|
-
judgement: { require: rule.require, ...(rule.when ? { when: rule.when } : {}), ...(rule.says ? { says: rule.says } : {}) },
|
|
748
|
-
...(rule.description ? { description: rule.description } : {}),
|
|
749
|
-
...(repairs.length > 0 ? { repairs: repairs.map((r) => r.act) } : {}),
|
|
750
|
-
evaluate: ({ graph }) => judge(graph, null),
|
|
751
|
-
})
|
|
752
|
-
: defineInvariant(name, {
|
|
753
|
-
scope: { kind: rule.over },
|
|
754
|
-
label: title,
|
|
755
|
-
judgement: { require: rule.require, ...(rule.when ? { when: rule.when } : {}), ...(rule.says ? { says: rule.says } : {}) },
|
|
756
|
-
...(rule.description ? { description: rule.description } : {}),
|
|
757
|
-
...(repairs.length > 0 ? { repairs: repairs.map((r) => r.act) } : {}),
|
|
758
|
-
evaluate: ({ graph, subject }) => judge(graph, subject),
|
|
759
|
-
})));
|
|
760
554
|
}
|
|
761
|
-
// ──
|
|
762
|
-
/*
|
|
763
|
-
* WHO SEES WHAT is the app's, in the framework's one meaning (FR-02):
|
|
764
|
-
* absent, everybody sees everything; present, a kind no sight names is
|
|
765
|
-
* seen by nobody but the system, and `own` is the principal's own records.
|
|
766
|
-
*/
|
|
767
|
-
const policy = document.policy
|
|
768
|
-
? {
|
|
769
|
-
grants: document.policy.grants,
|
|
770
|
-
...(document.policy.sees ? { sees: document.policy.sees } : {}),
|
|
771
|
-
...(document.roles ? { roles: document.roles } : {}),
|
|
772
|
-
}
|
|
773
|
-
: undefined;
|
|
555
|
+
// ── brand, views ─────────────────────────────────────────────────────────
|
|
774
556
|
let brand;
|
|
775
557
|
if (document.brand) {
|
|
558
|
+
const { accent, name: wordmark, ...money } = document.brand;
|
|
776
559
|
const derived = accent ? brandFromAccent({ accent, base: SCHEMES }) : undefined;
|
|
777
560
|
// A colour that cannot be read is not a reason to refuse an app: it wears the default colours and says why.
|
|
778
561
|
if (derived && !derived.ok)
|
|
@@ -781,29 +564,20 @@ export function compileDocumentWithoutCheck(raw, options = {}) {
|
|
|
781
564
|
if (!derived || derived.ok || money.currency || money.locale)
|
|
782
565
|
brand = { name: wordmark ?? document.name, schemes: derived?.ok ? derived.schemes : SCHEMES, ...money };
|
|
783
566
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
...(
|
|
567
|
+
const viewSpecs = viewsOf(document);
|
|
568
|
+
const home = homeOf(document);
|
|
569
|
+
return {
|
|
570
|
+
format: COMPILED_FORMAT,
|
|
571
|
+
document,
|
|
572
|
+
findings,
|
|
573
|
+
kinds,
|
|
574
|
+
edges: [...edges].map(([name, { from, to, cardinality }]) => [name, { from, to, cardinality }]),
|
|
575
|
+
acts,
|
|
576
|
+
rules,
|
|
792
577
|
...(brand ? { brand } : {}),
|
|
793
|
-
...(
|
|
794
|
-
...(
|
|
795
|
-
// The arrangement is the app's (FR-80): every face reads it beside the places.
|
|
796
|
-
...(document.pages ? { pages: document.pages } : {}),
|
|
797
|
-
...(document.settings ? { settings: document.settings } : {}),
|
|
798
|
-
// The document's views are the declaration's view specs (FR-03): data the framework draws.
|
|
799
|
-
...(Object.keys(viewsOf(document)).length > 0 ? { viewSpecs: viewsOf(document) } : {}),
|
|
800
|
-
// And its home view, about no one record, is the app's home (FR-81).
|
|
801
|
-
...(homeOf(document) ? { home: homeOf(document) } : {}),
|
|
802
|
-
version: document.version ?? 1,
|
|
578
|
+
...(Object.keys(viewSpecs).length > 0 ? { viewSpecs } : {}),
|
|
579
|
+
...(home ? { home } : {}),
|
|
803
580
|
};
|
|
804
|
-
// `toDocument(app)` gives this document back, byte for byte.
|
|
805
|
-
rememberDocument(app, document);
|
|
806
|
-
return { ok: true, app, document, findings, sights: document.policy?.sees, kinds: shapes };
|
|
807
581
|
}
|
|
808
582
|
/** Map a framework check's "where" (a kind, an act, a rule by name) back to a document path when we can. */
|
|
809
583
|
export function frameworkPath(where, document) {
|
|
@@ -829,7 +603,7 @@ export function inDocumentWords(finding, where, document) {
|
|
|
829
603
|
const first = Object.keys(spec.fields).filter((field) => field !== "label").slice(0, 3);
|
|
830
604
|
return { ...finding, path: `kinds.${kind}.glance`, fix: `Say which: "glance": [${first.map((field) => `"${field}"`).join(", ")}] on kinds.${kind} — the facts a person compares one by, at a glance.` };
|
|
831
605
|
}
|
|
832
|
-
export {
|
|
606
|
+
export { ActRefusal };
|
|
833
607
|
/** Every `renamedFrom` that names nothing in the version before: the values it was meant to carry would be lost. */
|
|
834
608
|
function renamesFromNothing(document, previous) {
|
|
835
609
|
const out = [];
|