@graview/core 0.1.0
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/LICENSE +96 -0
- package/README.md +35 -0
- package/dist/app.d.ts +294 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +49 -0
- package/dist/app.js.map +1 -0
- package/dist/arrange.d.ts +185 -0
- package/dist/arrange.d.ts.map +1 -0
- package/dist/arrange.js +456 -0
- package/dist/arrange.js.map +1 -0
- package/dist/beginning.d.ts +65 -0
- package/dist/beginning.d.ts.map +1 -0
- package/dist/beginning.js +94 -0
- package/dist/beginning.js.map +1 -0
- package/dist/bind.d.ts +42 -0
- package/dist/bind.d.ts.map +1 -0
- package/dist/bind.js +26 -0
- package/dist/bind.js.map +1 -0
- package/dist/city.d.ts +93 -0
- package/dist/city.d.ts.map +1 -0
- package/dist/city.js +160 -0
- package/dist/city.js.map +1 -0
- package/dist/cli/check/arrangement.d.ts +13 -0
- package/dist/cli/check/arrangement.d.ts.map +1 -0
- package/dist/cli/check/arrangement.js +63 -0
- package/dist/cli/check/arrangement.js.map +1 -0
- package/dist/cli/check/beginnings.d.ts +16 -0
- package/dist/cli/check/beginnings.d.ts.map +1 -0
- package/dist/cli/check/beginnings.js +53 -0
- package/dist/cli/check/beginnings.js.map +1 -0
- package/dist/cli/check/brand.d.ts +17 -0
- package/dist/cli/check/brand.d.ts.map +1 -0
- package/dist/cli/check/brand.js +153 -0
- package/dist/cli/check/brand.js.map +1 -0
- package/dist/cli/check/city.d.ts +17 -0
- package/dist/cli/check/city.d.ts.map +1 -0
- package/dist/cli/check/city.js +58 -0
- package/dist/cli/check/city.js.map +1 -0
- package/dist/cli/check/context.d.ts +50 -0
- package/dist/cli/check/context.d.ts.map +1 -0
- package/dist/cli/check/context.js +37 -0
- package/dist/cli/check/context.js.map +1 -0
- package/dist/cli/check/fields.d.ts +16 -0
- package/dist/cli/check/fields.d.ts.map +1 -0
- package/dist/cli/check/fields.js +237 -0
- package/dist/cli/check/fields.js.map +1 -0
- package/dist/cli/check/index.d.ts +14 -0
- package/dist/cli/check/index.d.ts.map +1 -0
- package/dist/cli/check/index.js +14 -0
- package/dist/cli/check/index.js.map +1 -0
- package/dist/cli/check/intelligence.d.ts +17 -0
- package/dist/cli/check/intelligence.d.ts.map +1 -0
- package/dist/cli/check/intelligence.js +136 -0
- package/dist/cli/check/intelligence.js.map +1 -0
- package/dist/cli/check/lenses.d.ts +17 -0
- package/dist/cli/check/lenses.d.ts.map +1 -0
- package/dist/cli/check/lenses.js +239 -0
- package/dist/cli/check/lenses.js.map +1 -0
- package/dist/cli/check/migrations.d.ts +16 -0
- package/dist/cli/check/migrations.d.ts.map +1 -0
- package/dist/cli/check/migrations.js +43 -0
- package/dist/cli/check/migrations.js.map +1 -0
- package/dist/cli/check/modules.d.ts +16 -0
- package/dist/cli/check/modules.d.ts.map +1 -0
- package/dist/cli/check/modules.js +123 -0
- package/dist/cli/check/modules.js.map +1 -0
- package/dist/cli/check/policy.d.ts +16 -0
- package/dist/cli/check/policy.d.ts.map +1 -0
- package/dist/cli/check/policy.js +90 -0
- package/dist/cli/check/policy.js.map +1 -0
- package/dist/cli/check/relations.d.ts +29 -0
- package/dist/cli/check/relations.d.ts.map +1 -0
- package/dist/cli/check/relations.js +279 -0
- package/dist/cli/check/relations.js.map +1 -0
- package/dist/cli/check/routes.d.ts +16 -0
- package/dist/cli/check/routes.d.ts.map +1 -0
- package/dist/cli/check/routes.js +31 -0
- package/dist/cli/check/routes.js.map +1 -0
- package/dist/cli/check/settings.d.ts +16 -0
- package/dist/cli/check/settings.d.ts.map +1 -0
- package/dist/cli/check/settings.js +81 -0
- package/dist/cli/check/settings.js.map +1 -0
- package/dist/cli/check.d.ts +36 -0
- package/dist/cli/check.d.ts.map +1 -0
- package/dist/cli/check.js +76 -0
- package/dist/cli/check.js.map +1 -0
- package/dist/cli/create.d.ts +20 -0
- package/dist/cli/create.d.ts.map +1 -0
- package/dist/cli/create.js +251 -0
- package/dist/cli/create.js.map +1 -0
- package/dist/cli/describe.d.ts +35 -0
- package/dist/cli/describe.d.ts.map +1 -0
- package/dist/cli/describe.js +230 -0
- package/dist/cli/describe.js.map +1 -0
- package/dist/cli/docs.d.ts +11 -0
- package/dist/cli/docs.d.ts.map +1 -0
- package/dist/cli/docs.js +152 -0
- package/dist/cli/docs.js.map +1 -0
- package/dist/cli/index.d.ts +18 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +281 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/extend.d.ts +23 -0
- package/dist/extend.d.ts.map +1 -0
- package/dist/extend.js +53 -0
- package/dist/extend.js.map +1 -0
- package/dist/graph/diff.d.ts +31 -0
- package/dist/graph/diff.d.ts.map +1 -0
- package/dist/graph/diff.js +71 -0
- package/dist/graph/diff.js.map +1 -0
- package/dist/graph/graph.d.ts +69 -0
- package/dist/graph/graph.d.ts.map +1 -0
- package/dist/graph/graph.js +284 -0
- package/dist/graph/graph.js.map +1 -0
- package/dist/graph/primitives.d.ts +52 -0
- package/dist/graph/primitives.d.ts.map +1 -0
- package/dist/graph/primitives.js +62 -0
- package/dist/graph/primitives.js.map +1 -0
- package/dist/graph/tracked.d.ts +31 -0
- package/dist/graph/tracked.d.ts.map +1 -0
- package/dist/graph/tracked.js +69 -0
- package/dist/graph/tracked.js.map +1 -0
- package/dist/graph/types.d.ts +34 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +0 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/index.d.ts +100 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +90 -0
- package/dist/index.js.map +1 -0
- package/dist/installation.d.ts +112 -0
- package/dist/installation.d.ts.map +1 -0
- package/dist/installation.js +193 -0
- package/dist/installation.js.map +1 -0
- package/dist/intelligence-bridge.d.ts +73 -0
- package/dist/intelligence-bridge.d.ts.map +1 -0
- package/dist/intelligence-bridge.js +31 -0
- package/dist/intelligence-bridge.js.map +1 -0
- package/dist/invariants/engine.d.ts +36 -0
- package/dist/invariants/engine.d.ts.map +1 -0
- package/dist/invariants/engine.js +88 -0
- package/dist/invariants/engine.js.map +1 -0
- package/dist/invariants/types.d.ts +75 -0
- package/dist/invariants/types.d.ts.map +1 -0
- package/dist/invariants/types.js +2 -0
- package/dist/invariants/types.js.map +1 -0
- package/dist/modules.d.ts +75 -0
- package/dist/modules.d.ts.map +1 -0
- package/dist/modules.js +87 -0
- package/dist/modules.js.map +1 -0
- package/dist/mutations/decidable.d.ts +26 -0
- package/dist/mutations/decidable.d.ts.map +1 -0
- package/dist/mutations/decidable.js +39 -0
- package/dist/mutations/decidable.js.map +1 -0
- package/dist/mutations/define-mutation.d.ts +32 -0
- package/dist/mutations/define-mutation.d.ts.map +1 -0
- package/dist/mutations/define-mutation.js +181 -0
- package/dist/mutations/define-mutation.js.map +1 -0
- package/dist/mutations/derive-edits.d.ts +75 -0
- package/dist/mutations/derive-edits.d.ts.map +1 -0
- package/dist/mutations/derive-edits.js +240 -0
- package/dist/mutations/derive-edits.js.map +1 -0
- package/dist/mutations/form.d.ts +63 -0
- package/dist/mutations/form.d.ts.map +1 -0
- package/dist/mutations/form.js +113 -0
- package/dist/mutations/form.js.map +1 -0
- package/dist/mutations/node-ref.d.ts +83 -0
- package/dist/mutations/node-ref.d.ts.map +1 -0
- package/dist/mutations/node-ref.js +113 -0
- package/dist/mutations/node-ref.js.map +1 -0
- package/dist/mutations/types.d.ts +136 -0
- package/dist/mutations/types.d.ts.map +1 -0
- package/dist/mutations/types.js +2 -0
- package/dist/mutations/types.js.map +1 -0
- package/dist/ops/log.d.ts +35 -0
- package/dist/ops/log.d.ts.map +1 -0
- package/dist/ops/log.js +103 -0
- package/dist/ops/log.js.map +1 -0
- package/dist/ops/types.d.ts +52 -0
- package/dist/ops/types.d.ts.map +1 -0
- package/dist/ops/types.js +2 -0
- package/dist/ops/types.js.map +1 -0
- package/dist/ops/undo.d.ts +33 -0
- package/dist/ops/undo.d.ts.map +1 -0
- package/dist/ops/undo.js +65 -0
- package/dist/ops/undo.js.map +1 -0
- package/dist/permissions/policy.d.ts +68 -0
- package/dist/permissions/policy.d.ts.map +1 -0
- package/dist/permissions/policy.js +191 -0
- package/dist/permissions/policy.js.map +1 -0
- package/dist/permissions/types.d.ts +78 -0
- package/dist/permissions/types.d.ts.map +1 -0
- package/dist/permissions/types.js +17 -0
- package/dist/permissions/types.js.map +1 -0
- package/dist/persistence/memory.d.ts +6 -0
- package/dist/persistence/memory.d.ts.map +1 -0
- package/dist/persistence/memory.js +31 -0
- package/dist/persistence/memory.js.map +1 -0
- package/dist/persistence/sqlite.d.ts +59 -0
- package/dist/persistence/sqlite.d.ts.map +1 -0
- package/dist/persistence/sqlite.js +131 -0
- package/dist/persistence/sqlite.js.map +1 -0
- package/dist/persistence/types.d.ts +22 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/persistence/types.js +2 -0
- package/dist/persistence/types.js.map +1 -0
- package/dist/presence.d.ts +38 -0
- package/dist/presence.d.ts.map +1 -0
- package/dist/presence.js +50 -0
- package/dist/presence.js.map +1 -0
- package/dist/scaffold/domain.d.ts +8 -0
- package/dist/scaffold/domain.d.ts.map +1 -0
- package/dist/scaffold/domain.js +331 -0
- package/dist/scaffold/domain.js.map +1 -0
- package/dist/scaffold/index.d.ts +66 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +142 -0
- package/dist/scaffold/index.js.map +1 -0
- package/dist/scaffold/lens.d.ts +31 -0
- package/dist/scaffold/lens.d.ts.map +1 -0
- package/dist/scaffold/lens.js +176 -0
- package/dist/scaffold/lens.js.map +1 -0
- package/dist/scaffold/names.d.ts +44 -0
- package/dist/scaffold/names.d.ts.map +1 -0
- package/dist/scaffold/names.js +69 -0
- package/dist/scaffold/names.js.map +1 -0
- package/dist/scaffold/project.d.ts +41 -0
- package/dist/scaffold/project.d.ts.map +1 -0
- package/dist/scaffold/project.js +604 -0
- package/dist/scaffold/project.js.map +1 -0
- package/dist/scaffold/ui.d.ts +15 -0
- package/dist/scaffold/ui.d.ts.map +1 -0
- package/dist/scaffold/ui.js +536 -0
- package/dist/scaffold/ui.js.map +1 -0
- package/dist/schema/define-node.d.ts +132 -0
- package/dist/schema/define-node.d.ts.map +1 -0
- package/dist/schema/define-node.js +254 -0
- package/dist/schema/define-node.js.map +1 -0
- package/dist/schema/figures.d.ts +42 -0
- package/dist/schema/figures.d.ts.map +1 -0
- package/dist/schema/figures.js +220 -0
- package/dist/schema/figures.js.map +1 -0
- package/dist/schema/json-schema.d.ts +39 -0
- package/dist/schema/json-schema.d.ts.map +1 -0
- package/dist/schema/json-schema.js +74 -0
- package/dist/schema/json-schema.js.map +1 -0
- package/dist/schema/schema.d.ts +43 -0
- package/dist/schema/schema.d.ts.map +1 -0
- package/dist/schema/schema.js +102 -0
- package/dist/schema/schema.js.map +1 -0
- package/dist/schema/types.d.ts +201 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +2 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/search.d.ts +215 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +449 -0
- package/dist/search.js.map +1 -0
- package/dist/settings.d.ts +30 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +74 -0
- package/dist/settings.js.map +1 -0
- package/dist/store.d.ts +284 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +660 -0
- package/dist/store.js.map +1 -0
- package/dist/studio-door.d.ts +187 -0
- package/dist/studio-door.d.ts.map +1 -0
- package/dist/studio-door.js +20 -0
- package/dist/studio-door.js.map +1 -0
- package/dist/sync/conflict.d.ts +37 -0
- package/dist/sync/conflict.d.ts.map +1 -0
- package/dist/sync/conflict.js +56 -0
- package/dist/sync/conflict.js.map +1 -0
- package/dist/sync/engine.d.ts +118 -0
- package/dist/sync/engine.d.ts.map +1 -0
- package/dist/sync/engine.js +341 -0
- package/dist/sync/engine.js.map +1 -0
- package/dist/sync/google-calendar.d.ts +54 -0
- package/dist/sync/google-calendar.d.ts.map +1 -0
- package/dist/sync/google-calendar.js +167 -0
- package/dist/sync/google-calendar.js.map +1 -0
- package/dist/sync/types.d.ts +125 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/sync/types.js +6 -0
- package/dist/sync/types.js.map +1 -0
- package/dist/temporal/effectivity.d.ts +75 -0
- package/dist/temporal/effectivity.d.ts.map +1 -0
- package/dist/temporal/effectivity.js +89 -0
- package/dist/temporal/effectivity.js.map +1 -0
- package/dist/testing.d.ts +63 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +194 -0
- package/dist/testing.js.map +1 -0
- package/dist/theme/contrast.d.ts +47 -0
- package/dist/theme/contrast.d.ts.map +1 -0
- package/dist/theme/contrast.js +172 -0
- package/dist/theme/contrast.js.map +1 -0
- package/dist/theme/derive.d.ts +81 -0
- package/dist/theme/derive.d.ts.map +1 -0
- package/dist/theme/derive.js +171 -0
- package/dist/theme/derive.js.map +1 -0
- package/dist/theme/kit.d.ts +106 -0
- package/dist/theme/kit.d.ts.map +1 -0
- package/dist/theme/kit.js +84 -0
- package/dist/theme/kit.js.map +1 -0
- package/dist/theme/palettes.d.ts +32 -0
- package/dist/theme/palettes.d.ts.map +1 -0
- package/dist/theme/palettes.js +96 -0
- package/dist/theme/palettes.js.map +1 -0
- package/dist/theme/types.d.ts +131 -0
- package/dist/theme/types.d.ts.map +1 -0
- package/dist/theme/types.js +23 -0
- package/dist/theme/types.js.map +1 -0
- package/dist/views/types.d.ts +89 -0
- package/dist/views/types.d.ts.map +1 -0
- package/dist/views/types.js +88 -0
- package/dist/views/types.js.map +1 -0
- package/dist/watched.d.ts +33 -0
- package/dist/watched.d.ts.map +1 -0
- package/dist/watched.js +50 -0
- package/dist/watched.js.map +1 -0
- package/dist/who.d.ts +26 -0
- package/dist/who.d.ts.map +1 -0
- package/dist/who.js +20 -0
- package/dist/who.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
export function checkModules(ctx) {
|
|
2
|
+
const { app, kinds, mutations, invariants, add } = ctx;
|
|
3
|
+
/*
|
|
4
|
+
* MODULE BOUNDARIES hold statically, before any workspace flips a toggle.
|
|
5
|
+
*
|
|
6
|
+
* The runtime is forgiving — enabling a module drags its requirements in —
|
|
7
|
+
* so the strict reading lives here: a module naming things nobody
|
|
8
|
+
* declared, a requirement naming a module nobody wrote, and the leak that
|
|
9
|
+
* only shows the day someone turns a module off: an always-on kind whose
|
|
10
|
+
* edge reaches into a module. Ownership by kind, so the reasoning is
|
|
11
|
+
* checkable per edge.
|
|
12
|
+
*/
|
|
13
|
+
const moduleEntries = Object.entries(app.modules ?? {});
|
|
14
|
+
const owners = new Map();
|
|
15
|
+
for (const [name, module] of moduleEntries) {
|
|
16
|
+
for (const kind of module.kinds ?? []) {
|
|
17
|
+
const set = owners.get(kind) ?? new Set();
|
|
18
|
+
set.add(name);
|
|
19
|
+
owners.set(kind, set);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const reach = (name) => {
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
const walk = (current) => {
|
|
25
|
+
if (seen.has(current))
|
|
26
|
+
return;
|
|
27
|
+
seen.add(current);
|
|
28
|
+
for (const required of (app.modules ?? {})[current]?.requires ?? [])
|
|
29
|
+
walk(required);
|
|
30
|
+
};
|
|
31
|
+
walk(name);
|
|
32
|
+
return seen;
|
|
33
|
+
};
|
|
34
|
+
for (const [name, module] of moduleEntries) {
|
|
35
|
+
for (const kind of module.kinds ?? []) {
|
|
36
|
+
if (!kinds.has(kind)) {
|
|
37
|
+
add({
|
|
38
|
+
severity: "error",
|
|
39
|
+
code: "module-unknown-kind",
|
|
40
|
+
where: `modules["${name}"].kinds`,
|
|
41
|
+
message: `Module "${name}" claims kind "${kind}", which no defineNode declares.`,
|
|
42
|
+
fix: `Declare the kind, or remove it from the module.`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const mutation of module.mutations ?? []) {
|
|
47
|
+
if (!mutations.has(mutation)) {
|
|
48
|
+
add({
|
|
49
|
+
severity: "error",
|
|
50
|
+
code: "module-unknown-mutation",
|
|
51
|
+
where: `modules["${name}"].mutations`,
|
|
52
|
+
message: `Module "${name}" claims mutation "${mutation}", which is not registered.`,
|
|
53
|
+
fix: `Register the mutation, or remove it from the module.`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const invariant of module.invariants ?? []) {
|
|
58
|
+
if (!invariants.has(invariant)) {
|
|
59
|
+
add({
|
|
60
|
+
severity: "error",
|
|
61
|
+
code: "module-unknown-invariant",
|
|
62
|
+
where: `modules["${name}"].invariants`,
|
|
63
|
+
message: `Module "${name}" claims invariant "${invariant}", which is not registered.`,
|
|
64
|
+
fix: `Register the invariant, or remove it from the module.`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (const required of module.requires ?? []) {
|
|
69
|
+
if (!(required in (app.modules ?? {}))) {
|
|
70
|
+
add({
|
|
71
|
+
severity: "error",
|
|
72
|
+
code: "module-unknown-requirement",
|
|
73
|
+
where: `modules["${name}"].requires`,
|
|
74
|
+
message: `Module "${name}" requires "${required}", which no module declares.`,
|
|
75
|
+
fix: `Declare the module, or drop the requirement.`,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (moduleEntries.length > 0) {
|
|
81
|
+
for (const definition of app.schema.definitions) {
|
|
82
|
+
const from = owners.get(definition.kind);
|
|
83
|
+
const fromReach = from === undefined
|
|
84
|
+
? null
|
|
85
|
+
: new Set([...from].flatMap((name) => [...reach(name)]));
|
|
86
|
+
for (const [edgeKind, edge] of Object.entries(definition.edges)) {
|
|
87
|
+
if (edge.to === "*")
|
|
88
|
+
continue;
|
|
89
|
+
for (const target of edge.to) {
|
|
90
|
+
const targetOwners = owners.get(target);
|
|
91
|
+
if (!targetOwners)
|
|
92
|
+
continue; // core is always on
|
|
93
|
+
/*
|
|
94
|
+
* A module that declares itself required is never off, so no line
|
|
95
|
+
* into it can dangle — the same reason core is skipped one line
|
|
96
|
+
* up. Without this the only answers an app has are to drop the
|
|
97
|
+
* module, losing the thing it is for, or to carry a permanent
|
|
98
|
+
* warning per edge; and a warning that can only ever be
|
|
99
|
+
* acknowledged is one people learn to scroll past, which costs
|
|
100
|
+
* the checker its authority on the warnings that matter.
|
|
101
|
+
*/
|
|
102
|
+
if ([...targetOwners].every((owner) => app.modules?.[owner]?.required === true))
|
|
103
|
+
continue;
|
|
104
|
+
const safe = fromReach === null
|
|
105
|
+
? false // an always-on kind reaching into a module
|
|
106
|
+
: [...targetOwners].some((owner) => fromReach.has(owner));
|
|
107
|
+
if (!safe) {
|
|
108
|
+
add({
|
|
109
|
+
severity: "warning",
|
|
110
|
+
code: "module-edge-leak",
|
|
111
|
+
where: `defineNode("${definition.kind}").edges["${edgeKind}"]`,
|
|
112
|
+
message: `Edge "${edgeKind}" reaches "${target}", owned by module ${[...targetOwners].map((o) => `"${o}"`).join("/")} — the line dangles wherever that module is off.`,
|
|
113
|
+
fix: fromReach === null
|
|
114
|
+
? `Move "${definition.kind}" into the module, or make the module require nothing this kind depends on.`
|
|
115
|
+
: `Add the target's module to modules["${[...(from ?? [])].join('"/"')}"].requires, or move the edge into the module.`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=modules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modules.js","sourceRoot":"","sources":["../../../src/cli/check/modules.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AnySchema } from "../../schema/schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* `note` is a QUESTION ASKED OUT LOUD, not a problem.
|
|
4
|
+
*
|
|
5
|
+
* Some things a checker can see are legitimate designs that the author
|
|
6
|
+
* should nonetheless have looked at once: a lens written for this app and
|
|
7
|
+
* never proved against another domain, a role name two vocabularies both
|
|
8
|
+
* use, a kind unreachable on an empty graph. Filed as warnings they would
|
|
9
|
+
* be warnings that can only ever be acknowledged, and those are the ones
|
|
10
|
+
* people learn to scroll past — which costs the checker its authority on
|
|
11
|
+
* the warnings that matter. So they have their own voice: counted, printed,
|
|
12
|
+
* and never a failure.
|
|
13
|
+
*/
|
|
14
|
+
import type { CheckContext } from "./context.js";
|
|
15
|
+
export declare function checkPolicy<S extends AnySchema>(ctx: CheckContext<S>): void;
|
|
16
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../../src/cli/check/policy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,wBAAgB,WAAW,CAAC,CAAC,SAAS,SAAS,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CAiG3E"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { permits, rolesOf } from "../../permissions/policy.js";
|
|
2
|
+
import { derivedVia } from "../../mutations/derive-edits.js";
|
|
3
|
+
export function checkPolicy(ctx) {
|
|
4
|
+
const { app, kinds, declaredMutations, mutations, add } = ctx;
|
|
5
|
+
/*
|
|
6
|
+
* A policy that locks somebody out of everything, or locks everybody out of
|
|
7
|
+
* something.
|
|
8
|
+
*
|
|
9
|
+
* Both are mistakes in the declaration rather than at runtime, and both are
|
|
10
|
+
* silent: a mutation no role can run looks exactly like a mutation nobody
|
|
11
|
+
* happens to have needed yet, and a role with nothing to do looks exactly
|
|
12
|
+
* like a role whose grants are elsewhere. The person who finds either is
|
|
13
|
+
* otherwise the person standing in front of a button they cannot press.
|
|
14
|
+
*/
|
|
15
|
+
if (app.policy) {
|
|
16
|
+
const roles = rolesOf(app.policy);
|
|
17
|
+
for (const mutation of app.mutations ?? []) {
|
|
18
|
+
const subjectKinds = mutation.subject && mutation.subject.kinds !== "*"
|
|
19
|
+
? mutation.subject.kinds
|
|
20
|
+
: [undefined];
|
|
21
|
+
const reachable = roles.some((role) => subjectKinds.some(
|
|
22
|
+
// A self grant counts: a role that may edit its own record may do
|
|
23
|
+
// something, so the role is judged as somebody acting on themselves.
|
|
24
|
+
(kind) => permits(app.policy, { kind: "human", id: "themselves", roles: [role] }, mutation.name, kind, undefined, "themselves").ok));
|
|
25
|
+
const openToAll = subjectKinds.some((kind) => permits(app.policy, { kind: "human", roles: [] }, mutation.name, kind).ok);
|
|
26
|
+
if (!reachable && !openToAll) {
|
|
27
|
+
add({
|
|
28
|
+
severity: "error",
|
|
29
|
+
code: "mutation-unreachable-by-any-role",
|
|
30
|
+
where: `policy.grants (mutation "${mutation.name}")`,
|
|
31
|
+
message: `No role may ever run "${mutation.name}", so it is declared and unreachable.`,
|
|
32
|
+
fix: `Grant it to a role, or remove the mutation.${roles.length > 0 ? ` Roles in this policy: ${roles.join(", ")}.` : ""}`,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const role of roles) {
|
|
37
|
+
/*
|
|
38
|
+
* Declared AND derived acts count, and a self grant counts: a role
|
|
39
|
+
* that may edit its own record may do something. The role is judged
|
|
40
|
+
* as somebody acting on themselves, which is the most a self grant
|
|
41
|
+
* ever allows.
|
|
42
|
+
*/
|
|
43
|
+
const canDo = [...mutations.values()].some((mutation) => {
|
|
44
|
+
const subjectKinds = mutation.subject && mutation.subject.kinds !== "*"
|
|
45
|
+
? mutation.subject.kinds
|
|
46
|
+
: [undefined];
|
|
47
|
+
const via = derivedVia(app.schema, declaredMutations, mutation);
|
|
48
|
+
return subjectKinds.some((kind) => permits(app.policy, { kind: "human", id: "themselves", roles: [role] }, mutation.name, kind, via, "themselves").ok);
|
|
49
|
+
});
|
|
50
|
+
if (!canDo) {
|
|
51
|
+
add({
|
|
52
|
+
severity: "warning",
|
|
53
|
+
code: "role-may-do-nothing",
|
|
54
|
+
where: `policy.grants (role "${role}")`,
|
|
55
|
+
message: `"${role}" may run no mutation, so anyone holding it can only read.`,
|
|
56
|
+
fix: "Grant it something, or drop the role if read-only was the intent.",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const grant of app.policy.grants) {
|
|
61
|
+
if (grant.mutations === "*")
|
|
62
|
+
continue;
|
|
63
|
+
for (const name of grant.mutations) {
|
|
64
|
+
if (mutations.has(name))
|
|
65
|
+
continue;
|
|
66
|
+
add({
|
|
67
|
+
severity: "error",
|
|
68
|
+
code: "grant-unknown-mutation",
|
|
69
|
+
where: "policy.grants",
|
|
70
|
+
message: `Grants "${name}", which no mutation declares.`,
|
|
71
|
+
fix: `Register a mutation called "${name}", or drop it from the grant.`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (grant.kinds === undefined || grant.kinds === "*")
|
|
75
|
+
continue;
|
|
76
|
+
for (const kind of grant.kinds) {
|
|
77
|
+
if (kinds.has(kind))
|
|
78
|
+
continue;
|
|
79
|
+
add({
|
|
80
|
+
severity: "error",
|
|
81
|
+
code: "grant-unknown-kind",
|
|
82
|
+
where: "policy.grants",
|
|
83
|
+
message: `Restricted to "${kind}", which no defineNode declares.`,
|
|
84
|
+
fix: `Use one of: ${[...kinds].join(", ")}.`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../src/cli/check/policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AnySchema } from "../../schema/schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* `note` is a QUESTION ASKED OUT LOUD, not a problem.
|
|
4
|
+
*
|
|
5
|
+
* Some things a checker can see are legitimate designs that the author
|
|
6
|
+
* should nonetheless have looked at once: a lens written for this app and
|
|
7
|
+
* never proved against another domain, a role name two vocabularies both
|
|
8
|
+
* use, a kind unreachable on an empty graph. Filed as warnings they would
|
|
9
|
+
* be warnings that can only ever be acknowledged, and those are the ones
|
|
10
|
+
* people learn to scroll past — which costs the checker its authority on
|
|
11
|
+
* the warnings that matter. So they have their own voice: counted, printed,
|
|
12
|
+
* and never a failure.
|
|
13
|
+
*/
|
|
14
|
+
import type { CheckContext } from "./context.js";
|
|
15
|
+
export declare function checkReadings<S extends AnySchema>(ctx: CheckContext<S>): void;
|
|
16
|
+
/**
|
|
17
|
+
* ONE EDGE NAME, ONE RELATION. Every surface treats an edge kind as a single
|
|
18
|
+
* relation: the connections on a record, the pages' groups, the captions
|
|
19
|
+
* over a band, the arrangement's `by:` all key on its name. Declared on two
|
|
20
|
+
* kinds with two sets of words — `by` on a song ("the artist whose song it
|
|
21
|
+
* is" / "their songs") and on an album ("the artist whose release it is" /
|
|
22
|
+
* "their releases") — the artist's page listed thirty-five songs and six
|
|
23
|
+
* albums together under "Their releases". Declaring the same relation from
|
|
24
|
+
* several kinds in the SAME words is fine and is left alone.
|
|
25
|
+
*/
|
|
26
|
+
export declare function checkEdgeNamesAgree<S extends AnySchema>(ctx: CheckContext<S>): void;
|
|
27
|
+
export declare function checkActsFromEnds<S extends AnySchema>(ctx: CheckContext<S>, writtenByAModel: ReadonlySet<string>): void;
|
|
28
|
+
export declare function checkUnmakeable<S extends AnySchema>(ctx: CheckContext<S>): void;
|
|
29
|
+
//# sourceMappingURL=relations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relations.d.ts","sourceRoot":"","sources":["../../../src/cli/check/relations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,wBAAgB,aAAa,CAAC,CAAC,SAAS,SAAS,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CA8B7E;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,SAAS,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CA0BnF;AAED,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,SAAS,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CA+JvH;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,SAAS,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CAgD/E"}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { nodeRefArgs } from "../../mutations/node-ref.js";
|
|
2
|
+
import { withArticle } from "../../schema/define-node.js";
|
|
3
|
+
import { bounded } from "./context.js";
|
|
4
|
+
export function checkReadings(ctx) {
|
|
5
|
+
const { app, add } = ctx;
|
|
6
|
+
/*
|
|
7
|
+
* AN EDGE HAS ONE DIRECTION AND TWO READINGS, and every surface reads it
|
|
8
|
+
* from the end it is standing on: a plot's page says "who looks after
|
|
9
|
+
* it", the gardener's page says what she looks after. With only the
|
|
10
|
+
* declaring side's words, the other end is captioned with the edge kind
|
|
11
|
+
* in plain words — "tended by", on the gardener — which reads the wrong
|
|
12
|
+
* way round. The words are part of the declaration, so their absence is
|
|
13
|
+
* a finding here rather than a surprise on a page.
|
|
14
|
+
*/
|
|
15
|
+
for (const definition of app.schema.definitions) {
|
|
16
|
+
for (const [edgeKind, edge] of Object.entries(definition.edges)) {
|
|
17
|
+
if (edge.inverse)
|
|
18
|
+
continue;
|
|
19
|
+
const farEnd = edge.to === "*" ? "the other end" : edge.to.map((kind) => withArticle(kind)).join(" or ");
|
|
20
|
+
add({
|
|
21
|
+
severity: "warning",
|
|
22
|
+
code: "edge-without-inverse",
|
|
23
|
+
where: `defineNode("${definition.kind}").edges["${edgeKind}"]`,
|
|
24
|
+
message: edge.description
|
|
25
|
+
? `"${edgeKind}" reads from ${withArticle(definition.kind)} only ("${edge.description}"); from ${farEnd} it is captioned "${edgeKind.replace(/-/g, " ")}", which is the wrong way round.`
|
|
26
|
+
: `"${edgeKind}" has no words in either direction; both ends are captioned "${edgeKind.replace(/-/g, " ")}".`,
|
|
27
|
+
fix: `Add inverse: "…" — how the relation reads from ${farEnd}` +
|
|
28
|
+
(edge.description ? "" : `, and description: "…" for how it reads from ${withArticle(definition.kind)}`) +
|
|
29
|
+
`.`,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* ONE EDGE NAME, ONE RELATION. Every surface treats an edge kind as a single
|
|
36
|
+
* relation: the connections on a record, the pages' groups, the captions
|
|
37
|
+
* over a band, the arrangement's `by:` all key on its name. Declared on two
|
|
38
|
+
* kinds with two sets of words — `by` on a song ("the artist whose song it
|
|
39
|
+
* is" / "their songs") and on an album ("the artist whose release it is" /
|
|
40
|
+
* "their releases") — the artist's page listed thirty-five songs and six
|
|
41
|
+
* albums together under "Their releases". Declaring the same relation from
|
|
42
|
+
* several kinds in the SAME words is fine and is left alone.
|
|
43
|
+
*/
|
|
44
|
+
export function checkEdgeNamesAgree(ctx) {
|
|
45
|
+
const { app, add } = ctx;
|
|
46
|
+
const declared = new Map();
|
|
47
|
+
for (const definition of app.schema.definitions) {
|
|
48
|
+
for (const [edgeKind, edge] of Object.entries(definition.edges)) {
|
|
49
|
+
const held = declared.get(edgeKind) ?? [];
|
|
50
|
+
held.push({ kind: definition.kind, ...(edge.description ? { description: edge.description } : {}), ...(edge.inverse ? { inverse: edge.inverse } : {}) });
|
|
51
|
+
declared.set(edgeKind, held);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const [edgeKind, ends] of declared) {
|
|
55
|
+
if (ends.length < 2)
|
|
56
|
+
continue;
|
|
57
|
+
const words = new Set(ends.map((end) => `${end.description ?? ""}|${end.inverse ?? ""}`));
|
|
58
|
+
if (words.size < 2)
|
|
59
|
+
continue;
|
|
60
|
+
const said = ends
|
|
61
|
+
.map((end) => `on ${withArticle(end.kind)} ("${end.description ?? edgeKind}" / "${end.inverse ?? edgeKind}")`)
|
|
62
|
+
.join(" and ");
|
|
63
|
+
const [first, second] = ends;
|
|
64
|
+
add({
|
|
65
|
+
severity: "error",
|
|
66
|
+
code: "edge-name-shared",
|
|
67
|
+
where: ends.map((end) => `defineNode("${end.kind}").edges["${edgeKind}"]`).join(", "),
|
|
68
|
+
message: `"${edgeKind}" is declared ${said}, but to every surface an edge name is ONE relation: from the far end both are listed together under ${JSON.stringify(first.inverse ?? edgeKind)}, including the ${second.kind === first.kind ? "second" : `${second.kind}s`}.`,
|
|
69
|
+
fix: `Give each relation its own name (say "${edgeKind}" on ${withArticle(first.kind)} and "${second.kind}-${edgeKind}" on ${withArticle(second.kind)}), or give every declaration the same description and inverse.`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function checkActsFromEnds(ctx, writtenByAModel) {
|
|
74
|
+
const { app, kinds, mutations, add } = ctx;
|
|
75
|
+
/*
|
|
76
|
+
* AN ACT OFFERED FROM AN END IT HAS NO WORDS FOR.
|
|
77
|
+
*
|
|
78
|
+
* A mutation that declares what it connects or severs is offered from
|
|
79
|
+
* EITHER endpoint — standing on a person, "take this one off the run" is
|
|
80
|
+
* the natural thing to say. The button there is labelled with `title`,
|
|
81
|
+
* which is written from the subject's side: "Hand it to someone", offered
|
|
82
|
+
* on the owner, reads as handing the owner to someone. The same shape as
|
|
83
|
+
* `edge-without-inverse`, one layer up: the relation has two readings and
|
|
84
|
+
* the act only has one.
|
|
85
|
+
*/
|
|
86
|
+
for (const mutation of app.mutations ?? []) {
|
|
87
|
+
if (mutation.fromTheOtherEnd)
|
|
88
|
+
continue;
|
|
89
|
+
const subject = mutation.subject;
|
|
90
|
+
const ties = [...(mutation.connects ?? []), ...(mutation.severs ?? [])];
|
|
91
|
+
if (!subject || ties.length === 0 || subject.kinds === "*")
|
|
92
|
+
continue;
|
|
93
|
+
const subjectKinds = subject.kinds;
|
|
94
|
+
// The far ends: node-reference arguments naming a kind the subject is not.
|
|
95
|
+
const farEnds = new Set();
|
|
96
|
+
for (const ref of nodeRefArgs(mutation.input)) {
|
|
97
|
+
if (ref.name === subject.arg)
|
|
98
|
+
continue;
|
|
99
|
+
for (const kind of ref.kinds) {
|
|
100
|
+
if (kind !== "*" && !subjectKinds.includes(kind))
|
|
101
|
+
farEnds.add(kind);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (farEnds.size === 0)
|
|
105
|
+
continue;
|
|
106
|
+
const spoken = [...farEnds].map((kind) => withArticle(kind)).join(" or ");
|
|
107
|
+
add({
|
|
108
|
+
severity: "warning",
|
|
109
|
+
code: "act-without-far-end-reading",
|
|
110
|
+
where: `defineMutation("${mutation.name}")`,
|
|
111
|
+
message: `"${mutation.title ?? mutation.name}" is written from ${subjectKinds.map((kind) => withArticle(kind)).join(" or ")}, ` +
|
|
112
|
+
`and is also offered on ${spoken}, where it is labelled with those same words — which is the wrong way round.`,
|
|
113
|
+
fix: `Add fromTheOtherEnd: "…" — how this act reads standing on ${spoken}.`,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
for (const definition of app.schema.definitions) {
|
|
117
|
+
for (const [edgeKind, edge] of Object.entries(definition.edges)) {
|
|
118
|
+
if (edge.to === "*")
|
|
119
|
+
continue;
|
|
120
|
+
for (const target of edge.to) {
|
|
121
|
+
if (!kinds.has(target)) {
|
|
122
|
+
add({
|
|
123
|
+
severity: "error",
|
|
124
|
+
code: "edge-target-undeclared",
|
|
125
|
+
where: `defineNode("${definition.kind}").edges["${edgeKind}"]`,
|
|
126
|
+
message: `Edge "${edgeKind}" points at "${target}", which no defineNode declares.`,
|
|
127
|
+
fix: `Declare a node kind "${target}", or change the target to one of: ${[...kinds].join(", ")}.`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/*
|
|
133
|
+
* A LABEL A MODEL WRITES IS A NAME, AND NOTHING SAYS SO.
|
|
134
|
+
*
|
|
135
|
+
* Every derived surface in this framework draws `label`: on a card, in
|
|
136
|
+
* a list, on a plan, in a chip. And the declaration almost always says
|
|
137
|
+
* `z.string().min(1)`, which permits a paragraph. When a person types
|
|
138
|
+
* it that is fine — people write names. When a MODEL writes it, it
|
|
139
|
+
* writes whatever the prompt implied, and the prompt was generated from
|
|
140
|
+
* this schema, which said nothing.
|
|
141
|
+
*
|
|
142
|
+
* A product asked a model to survey a garden and got back seven areas
|
|
143
|
+
* called things like "Pea-gravel corner with river-rock border, log
|
|
144
|
+
* seats and a fire bowl". Every one is a true, useful sentence and a
|
|
145
|
+
* terrible name, and the model was not wrong — it was never told. The
|
|
146
|
+
* plan drew them across each other, the cards wrapped to four lines,
|
|
147
|
+
* and nothing failed anywhere.
|
|
148
|
+
*
|
|
149
|
+
* So this asks only about kinds a declared provider may actually
|
|
150
|
+
* create. A note, not a warning: a long label is legal and sometimes
|
|
151
|
+
* right. What is not defensible is not having made the call.
|
|
152
|
+
*/
|
|
153
|
+
if (writtenByAModel.has(definition.kind)) {
|
|
154
|
+
const field = definition.fields.shape["label"];
|
|
155
|
+
if (field !== undefined && !bounded(field)) {
|
|
156
|
+
add({
|
|
157
|
+
severity: "note",
|
|
158
|
+
code: "label-unbounded",
|
|
159
|
+
where: `defineNode("${definition.kind}").fields.label`,
|
|
160
|
+
message: `A declared provider may create ${withArticle(definition.kind)}, and "label" has no maximum length — so a model may write a paragraph where a name goes, and every surface that draws it will try.`,
|
|
161
|
+
fix: `Bound it to something an interface can draw: z.string().min(1).max(60). Whatever else there is to say about ${withArticle(definition.kind)} belongs in a field of its own, where a person will actually read it.`,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/*
|
|
166
|
+
* A lifecycle must name a real field. A currency read off a field that
|
|
167
|
+
* does not exist would silently make every node current for ever —
|
|
168
|
+
* which is exactly the state a kind was in before declaring anything,
|
|
169
|
+
* minus the honesty.
|
|
170
|
+
*/
|
|
171
|
+
if (definition.lifecycle) {
|
|
172
|
+
const shape = definition.fields.shape;
|
|
173
|
+
if (!(definition.lifecycle.field in shape)) {
|
|
174
|
+
add({
|
|
175
|
+
severity: "error",
|
|
176
|
+
code: "lifecycle-missing-field",
|
|
177
|
+
where: `defineNode("${definition.kind}").lifecycle`,
|
|
178
|
+
message: `Lifecycle reads field "${definition.lifecycle.field}", which is not in this kind's fields.`,
|
|
179
|
+
fix: `Point it at one of: ${Object.keys(shape).join(", ")}.`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (definition.lifecycle.retired !== "date" &&
|
|
183
|
+
definition.lifecycle.retired.length === 0) {
|
|
184
|
+
add({
|
|
185
|
+
severity: "error",
|
|
186
|
+
code: "lifecycle-never-retires",
|
|
187
|
+
where: `defineNode("${definition.kind}").lifecycle`,
|
|
188
|
+
message: `Lifecycle lists no retired values, so nothing can ever leave the horizon.`,
|
|
189
|
+
fix: `List the values that mean "past", or use "date" for an expiry field.`,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (const [role, field] of Object.entries(definition.fieldRoles ?? {})) {
|
|
194
|
+
const shape = definition.fields.shape;
|
|
195
|
+
if (!(field in shape)) {
|
|
196
|
+
add({
|
|
197
|
+
severity: "error",
|
|
198
|
+
code: "field-role-missing-field",
|
|
199
|
+
where: `defineNode("${definition.kind}").fieldRoles.${role}`,
|
|
200
|
+
message: `Field role "${role}" maps to field "${field}", which is not in this kind's fields.`,
|
|
201
|
+
fix: `Point the role at one of: ${Object.keys(shape).join(", ")}.`,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
for (const invariant of app.invariants ?? []) {
|
|
207
|
+
if (invariant.scope !== "graph" && !kinds.has(invariant.scope.kind)) {
|
|
208
|
+
add({
|
|
209
|
+
severity: "error",
|
|
210
|
+
code: "invariant-scope-undeclared",
|
|
211
|
+
where: `defineInvariant("${invariant.name}").scope`,
|
|
212
|
+
message: `Scoped to node kind "${invariant.scope.kind}", which no defineNode declares.`,
|
|
213
|
+
fix: `Use one of: ${[...kinds].join(", ")}.`,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
for (const repair of invariant.repairs ?? []) {
|
|
217
|
+
if (!mutations.has(repair)) {
|
|
218
|
+
add({
|
|
219
|
+
severity: "error",
|
|
220
|
+
code: "repair-unknown-mutation",
|
|
221
|
+
where: `defineInvariant("${invariant.name}").repairs`,
|
|
222
|
+
message: `Names repair mutation "${repair}", which is not registered on this app.`,
|
|
223
|
+
fix: `Register a mutation called "${repair}", or drop it from repairs. Registered: ${[...mutations.keys()].join(", ") || "(none)"}.`,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
export function checkUnmakeable(ctx) {
|
|
230
|
+
const { app, add } = ctx;
|
|
231
|
+
/*
|
|
232
|
+
* A RELATION YOU CAN MAKE BUT NEVER UNMAKE.
|
|
233
|
+
*
|
|
234
|
+
* An edge kind with a connecting act and no severing one is an asymmetry
|
|
235
|
+
* the declaration exposes: whoever draws the line can never erase it, and
|
|
236
|
+
* nobody notices until the first person needs to. Some edges are like that
|
|
237
|
+
* on purpose — history, rationale, a record — and the way to say so is
|
|
238
|
+
* `appendOnly: true` on the edge declaration, which suppresses this and
|
|
239
|
+
* documents the intent in the same stroke.
|
|
240
|
+
*/
|
|
241
|
+
{
|
|
242
|
+
const connectors = new Map();
|
|
243
|
+
const severers = new Set();
|
|
244
|
+
for (const mutation of app.mutations ?? []) {
|
|
245
|
+
for (const edgeKind of mutation.connects ?? []) {
|
|
246
|
+
connectors.set(edgeKind, [...(connectors.get(edgeKind) ?? []), mutation.name]);
|
|
247
|
+
}
|
|
248
|
+
for (const edgeKind of mutation.severs ?? [])
|
|
249
|
+
severers.add(edgeKind);
|
|
250
|
+
}
|
|
251
|
+
for (const [edgeKind, makers] of connectors) {
|
|
252
|
+
if (severers.has(edgeKind))
|
|
253
|
+
continue;
|
|
254
|
+
const declaredOn = app.schema.definitions.filter((definition) => edgeKind in definition.edges);
|
|
255
|
+
// An unknown edge kind is already `edge-claim-unknown-kind` above.
|
|
256
|
+
if (declaredOn.length === 0)
|
|
257
|
+
continue;
|
|
258
|
+
/*
|
|
259
|
+
* EVERY declaration of the edge kind must say appendOnly, not just
|
|
260
|
+
* one. Two kinds can share an edge-kind name, and a suppression on
|
|
261
|
+
* one must not hide the other's makeable-but-never-unmakeable
|
|
262
|
+
* relation — that is the exact asymmetry this check exists to catch.
|
|
263
|
+
*/
|
|
264
|
+
if (declaredOn.every((definition) => definition.edges[edgeKind]?.appendOnly))
|
|
265
|
+
continue;
|
|
266
|
+
add({
|
|
267
|
+
severity: "warning",
|
|
268
|
+
code: "edge-without-severer",
|
|
269
|
+
where: `defineMutation("${makers[0]}").connects`,
|
|
270
|
+
message: `"${edgeKind}" is a relation you can make (${makers.join(", ")}) but never unmake — no mutation declares it in severs.`,
|
|
271
|
+
fix: `Declare severs: ["${edgeKind}"] on the act that removes it, or mark the edge ` +
|
|
272
|
+
`appendOnly: true on ${declaredOn
|
|
273
|
+
.map((definition) => `defineNode("${definition.kind}").edges["${edgeKind}"]`)
|
|
274
|
+
.join(" / ")} if it is genuinely never unmade.`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=relations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relations.js","sourceRoot":"","sources":["../../../src/cli/check/relations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AnySchema } from "../../schema/schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* `note` is a QUESTION ASKED OUT LOUD, not a problem.
|
|
4
|
+
*
|
|
5
|
+
* Some things a checker can see are legitimate designs that the author
|
|
6
|
+
* should nonetheless have looked at once: a lens written for this app and
|
|
7
|
+
* never proved against another domain, a role name two vocabularies both
|
|
8
|
+
* use, a kind unreachable on an empty graph. Filed as warnings they would
|
|
9
|
+
* be warnings that can only ever be acknowledged, and those are the ones
|
|
10
|
+
* people learn to scroll past — which costs the checker its authority on
|
|
11
|
+
* the warnings that matter. So they have their own voice: counted, printed,
|
|
12
|
+
* and never a failure.
|
|
13
|
+
*/
|
|
14
|
+
import type { CheckContext } from "./context.js";
|
|
15
|
+
export declare function checkRoutes<S extends AnySchema>(ctx: CheckContext<S>): void;
|
|
16
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/cli/check/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,wBAAgB,WAAW,CAAC,CAAC,SAAS,SAAS,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CA6B3E"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function checkRoutes(ctx) {
|
|
2
|
+
const { app, add } = ctx;
|
|
3
|
+
/*
|
|
4
|
+
* The routed face derives `/:plural` from each kind's plural. Two kinds
|
|
5
|
+
* whose plurals slug identically would leave one of them unreachable by
|
|
6
|
+
* registration order — a route nobody can link to.
|
|
7
|
+
*/
|
|
8
|
+
const slugs = new Map();
|
|
9
|
+
for (const definition of app.schema.definitions) {
|
|
10
|
+
const plural = definition.plural ?? `${definition.kind}s`;
|
|
11
|
+
const slug = plural.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
12
|
+
const taken = slugs.get(slug);
|
|
13
|
+
if (taken) {
|
|
14
|
+
add({
|
|
15
|
+
severity: "error",
|
|
16
|
+
code: "plural-slug-collision",
|
|
17
|
+
where: `defineNode("${definition.kind}").plural`,
|
|
18
|
+
message: `"${plural}" slugs to "/${slug}", already taken by kind "${taken}".`,
|
|
19
|
+
fix: `Give one of them a distinct plural.`,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
slugs.set(slug, definition.kind);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/*
|
|
27
|
+
* A declared provider's allowlist must name real mutations — a metering
|
|
28
|
+
* or narrowing rule pointing at nothing enforces nothing.
|
|
29
|
+
*/
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/cli/check/routes.ts"],"names":[],"mappings":"AAgBA,MAAM,UAAU,WAAW,CAAsB,GAAoB;IACnE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;IACzB;;;;OAIG;IACH,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,KAAK,MAAM,UAAU,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,GAAG,CAAC;QAC1D,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACpF,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,KAAK,EAAE,CAAC;YACV,GAAG,CAAC;gBACF,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,uBAAuB;gBAC7B,KAAK,EAAE,eAAe,UAAU,CAAC,IAAI,WAAW;gBAChD,OAAO,EAAE,IAAI,MAAM,gBAAgB,IAAI,6BAA6B,KAAK,IAAI;gBAC7E,GAAG,EAAE,qCAAqC;aAC3C,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED;;;OAGG;AACL,CAAC"}
|