@cynodia/axiom-compiler 0.6.3-alpha.1 → 0.7.0-alpha.2
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/dist/normalize.d.ts +15 -0
- package/dist/normalize.js +23 -4
- package/dist/server.js +30 -3
- package/dist/stylesheet.js +29 -0
- package/package.json +3 -3
package/dist/normalize.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ApplicationGraph, ApplicationIR, ValidationIssue, ValidationResult } from '@cynodia/axiom-core';
|
|
2
|
+
import type { RendererCapabilities } from '@cynodia/axiom-core';
|
|
2
3
|
export declare class GraphValidationError extends Error {
|
|
3
4
|
readonly problems: ValidationIssue[];
|
|
4
5
|
constructor(result: ValidationResult);
|
|
@@ -6,6 +7,20 @@ export declare class GraphValidationError extends Error {
|
|
|
6
7
|
export interface CompileOptions {
|
|
7
8
|
/** Compilation refuses invalid graphs by default; disable only for diagnostics. */
|
|
8
9
|
validate?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Keep authoring metadata in the compiled artifact.
|
|
12
|
+
*
|
|
13
|
+
* Off by default, and that default is the point: metadata describing how a node was
|
|
14
|
+
* *authored* has no business in a browser payload or across a trust boundary. A development
|
|
15
|
+
* tool, a pattern inspector or an agent debugging an expansion asks for it explicitly.
|
|
16
|
+
*/
|
|
17
|
+
includeAuthoringMetadata?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* The renderer the IR is being compiled for. Defaults to the browser renderer, because
|
|
20
|
+
* that is what `compileToIR` produces a page for — so a UI node kind no browser can draw is
|
|
21
|
+
* rejected at compile time rather than discovered on screen.
|
|
22
|
+
*/
|
|
23
|
+
renderer?: RendererCapabilities;
|
|
9
24
|
}
|
|
10
25
|
/**
|
|
11
26
|
* Validates a graph and normalizes it into the runtime-ready IR: references resolved,
|
package/dist/normalize.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { actionAuthority, actionGuards, authorityContext, inferExpressionType, inferLocationType, itemTypeOf, locationFieldIds, locationRootStateId, referencedIds, resolvePresentationMap, semanticContextFromGraph, stateAuthority, validateGraph, } from '@cynodia/axiom-core';
|
|
2
|
-
import { isUINode } from '@cynodia/axiom-core';
|
|
2
|
+
import { isUINode, stripAuthoringMetadata } from '@cynodia/axiom-core';
|
|
3
|
+
import { BROWSER_RENDERER_CAPABILITIES } from '@cynodia/axiom-runtime';
|
|
3
4
|
export class GraphValidationError extends Error {
|
|
4
5
|
problems;
|
|
5
6
|
constructor(result) {
|
|
@@ -38,7 +39,9 @@ function compileRoute(route) {
|
|
|
38
39
|
*/
|
|
39
40
|
export function compileToIR(graph, options = {}) {
|
|
40
41
|
if (options.validate !== false) {
|
|
41
|
-
const result = validateGraph(graph
|
|
42
|
+
const result = validateGraph(graph, {
|
|
43
|
+
renderer: options.renderer ?? BROWSER_RENDERER_CAPABILITIES,
|
|
44
|
+
});
|
|
42
45
|
if (!result.valid) {
|
|
43
46
|
throw new GraphValidationError(result);
|
|
44
47
|
}
|
|
@@ -54,7 +57,11 @@ export function compileToIR(graph, options = {}) {
|
|
|
54
57
|
const remoteActionIds = [];
|
|
55
58
|
const authority = {};
|
|
56
59
|
/** Nothing that reads state the client may not observe may reach the client. */
|
|
57
|
-
const readsHiddenState = (expressions) => expressions.some((expression) =>
|
|
60
|
+
const readsHiddenState = (expressions) => expressions.some((expression) =>
|
|
61
|
+
// Following named expressions is not optional here: a calculation that reads
|
|
62
|
+
// server-only state must not reach the client because a consumer named it instead of
|
|
63
|
+
// inlining it.
|
|
64
|
+
referencedIds(expression, (id) => authorityOf.expressions.get(id)).some((id) => hiddenStateIds.has(id)));
|
|
58
65
|
const nodes = {};
|
|
59
66
|
const actions = {};
|
|
60
67
|
const uiNodes = {};
|
|
@@ -62,8 +69,12 @@ export function compileToIR(graph, options = {}) {
|
|
|
62
69
|
const states = [];
|
|
63
70
|
const constraints = [];
|
|
64
71
|
const transitionConstraints = [];
|
|
72
|
+
const expressionDefs = {};
|
|
65
73
|
const routes = [];
|
|
66
|
-
|
|
74
|
+
// Authoring metadata is stripped on the way in, so no later stage has to remember to.
|
|
75
|
+
const forIR = (node) => options.includeAuthoringMetadata ? node : stripAuthoringMetadata(node);
|
|
76
|
+
for (const raw of graph.listNodes()) {
|
|
77
|
+
const node = forIR(raw);
|
|
67
78
|
nodes[node.id] = node;
|
|
68
79
|
if (isUINode(node)) {
|
|
69
80
|
uiNodes[node.id] = node;
|
|
@@ -137,6 +148,13 @@ export function compileToIR(graph, options = {}) {
|
|
|
137
148
|
case 'route':
|
|
138
149
|
routes.push(compileRoute(node));
|
|
139
150
|
break;
|
|
151
|
+
case 'expression':
|
|
152
|
+
if (readsHiddenState([node.expression])) {
|
|
153
|
+
delete nodes[node.id];
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
expressionDefs[node.id] = node;
|
|
157
|
+
break;
|
|
140
158
|
default:
|
|
141
159
|
}
|
|
142
160
|
}
|
|
@@ -201,6 +219,7 @@ export function compileToIR(graph, options = {}) {
|
|
|
201
219
|
uiNodes,
|
|
202
220
|
constraints,
|
|
203
221
|
transitionConstraints,
|
|
222
|
+
expressionDefs,
|
|
204
223
|
routes,
|
|
205
224
|
// An edge naming a node the client does not receive would tell it that node exists.
|
|
206
225
|
edges: graph.semanticEdges().filter((edge) => nodes[edge.from] && nodes[edge.to]),
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { actionAuthority, actionGuards, authorityContext, expressionDefsIn, isObservable, requiredServerContract, serverIRExpressions, serverStateClosure, stateAuthority, validateGraph, } from '@cynodia/axiom-core';
|
|
2
2
|
import { GraphValidationError } from './normalize.js';
|
|
3
3
|
/**
|
|
4
4
|
* Compiles the authoritative half of a graph.
|
|
@@ -24,6 +24,7 @@ export function compileToServerIR(graph, options = {}) {
|
|
|
24
24
|
const entities = [];
|
|
25
25
|
const constraints = [];
|
|
26
26
|
const transitionConstraints = [];
|
|
27
|
+
const expressionDefs = {};
|
|
27
28
|
for (const node of nodes) {
|
|
28
29
|
if (node.kind === 'entity') {
|
|
29
30
|
entities.push(node);
|
|
@@ -34,6 +35,9 @@ export function compileToServerIR(graph, options = {}) {
|
|
|
34
35
|
else if (node.kind === 'transition-constraint') {
|
|
35
36
|
transitionConstraints.push(node);
|
|
36
37
|
}
|
|
38
|
+
else if (node.kind === 'expression') {
|
|
39
|
+
expressionDefs[node.id] = node;
|
|
40
|
+
}
|
|
37
41
|
}
|
|
38
42
|
const states = [];
|
|
39
43
|
const observableStateIds = [];
|
|
@@ -65,8 +69,7 @@ export function compileToServerIR(graph, options = {}) {
|
|
|
65
69
|
for (const entry of graph.listFields()) {
|
|
66
70
|
fields[entry.field.id] = entry;
|
|
67
71
|
}
|
|
68
|
-
|
|
69
|
-
contract: SERVER_IR_CONTRACT,
|
|
72
|
+
const rules = {
|
|
70
73
|
id: graph.id,
|
|
71
74
|
name: graph.name,
|
|
72
75
|
version: graph.version,
|
|
@@ -79,6 +82,30 @@ export function compileToServerIR(graph, options = {}) {
|
|
|
79
82
|
...(graph.principalEntityId ? { principalEntityId: graph.principalEntityId } : {}),
|
|
80
83
|
observableStateIds,
|
|
81
84
|
};
|
|
85
|
+
// Only the definitions the authority's own rules reach. A calculation used by the client
|
|
86
|
+
// alone decides nothing here, and the Server IR carries nothing that decides nothing.
|
|
87
|
+
const used = {};
|
|
88
|
+
const pending = serverIRExpressions(rules).flatMap((expression) => expressionDefsIn(expression));
|
|
89
|
+
while (pending.length > 0) {
|
|
90
|
+
const id = pending.pop();
|
|
91
|
+
const definition = expressionDefs[id];
|
|
92
|
+
if (!definition || used[id]) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
used[id] = definition;
|
|
96
|
+
pending.push(...expressionDefsIn(definition.expression));
|
|
97
|
+
}
|
|
98
|
+
const carriesDefinitions = Object.keys(used).length > 0;
|
|
99
|
+
const document = { ...rules, ...(carriesDefinitions ? { expressionDefs: used } : {}) };
|
|
100
|
+
/**
|
|
101
|
+
* The label follows the vocabulary the document actually uses. An application that uses
|
|
102
|
+
* nothing from 0.7 compiles to the same `axiom.server.v1` document it always did; one
|
|
103
|
+
* that groups or names an expression says so, because a v1 runtime could not execute it.
|
|
104
|
+
*/
|
|
105
|
+
const contract = carriesDefinitions
|
|
106
|
+
? 'axiom.server.v2'
|
|
107
|
+
: requiredServerContract(serverIRExpressions(document));
|
|
108
|
+
return { contract, ...document };
|
|
82
109
|
}
|
|
83
110
|
export function serializeServerIR(ir) {
|
|
84
111
|
return JSON.stringify(ir);
|
package/dist/stylesheet.js
CHANGED
|
@@ -361,6 +361,35 @@ button.axiom-button.axiom-layout-horizontal { flex-direction: row; }
|
|
|
361
361
|
.axiom-ux-empty-state { text-align: center; }
|
|
362
362
|
.axiom-ux-form-section + .axiom-ux-form-section { padding-top: ${space('small')}; border-top: 1px solid var(--axiom-color-border); }
|
|
363
363
|
|
|
364
|
+
/*
|
|
365
|
+
* A dialog. The renderer emits the semantics — role, aria-modal, the title relationship — and
|
|
366
|
+
* these are the affordances that make it read as a modal: a backdrop, elevation, centring and
|
|
367
|
+
* a scroll bound. They belong here for the same reason button metrics do: every application
|
|
368
|
+
* would otherwise have to correct the same thing, which is what makes a default a framework's
|
|
369
|
+
* job rather than an author's.
|
|
370
|
+
*/
|
|
371
|
+
.axiom-dialog {
|
|
372
|
+
position: fixed;
|
|
373
|
+
inset: 0;
|
|
374
|
+
z-index: 100;
|
|
375
|
+
margin: auto;
|
|
376
|
+
width: min(32rem, calc(100vw - ${space('large')} * 2));
|
|
377
|
+
max-height: calc(100vh - ${space('large')} * 2);
|
|
378
|
+
overflow: auto;
|
|
379
|
+
display: flex;
|
|
380
|
+
flex-direction: column;
|
|
381
|
+
gap: ${space('medium')};
|
|
382
|
+
padding: ${space('large')};
|
|
383
|
+
background: var(--axiom-color-surface-raised);
|
|
384
|
+
color: var(--axiom-color-text);
|
|
385
|
+
border: 1px solid var(--axiom-color-border);
|
|
386
|
+
border-radius: var(--axiom-radius-large);
|
|
387
|
+
box-shadow: var(--axiom-elevation-raised);
|
|
388
|
+
}
|
|
389
|
+
.axiom-dialog::backdrop { background: rgb(0 0 0 / 0.45); }
|
|
390
|
+
.axiom-dialog > .axiom-dialog-title { margin: 0; }
|
|
391
|
+
.axiom-dialog > .axiom-dialog-description { margin: 0; color: var(--axiom-color-text-subtle); }
|
|
392
|
+
|
|
364
393
|
.axiom-diagnostic[data-empty="true"] { display: none; }
|
|
365
394
|
.axiom-diagnostic-entry { color: inherit; }
|
|
366
395
|
.axiom-no-route { padding: ${space('large')}; background: var(--axiom-color-surface); border-radius: var(--axiom-radius-medium); }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cynodia/axiom-compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0-alpha.2",
|
|
4
4
|
"description": "Normalizes an Axiom application graph and emits a self-contained page.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "AskTech AS",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@cynodia/axiom-core": "0.
|
|
35
|
-
"@cynodia/axiom-runtime": "0.
|
|
34
|
+
"@cynodia/axiom-core": "0.7.0-alpha.2",
|
|
35
|
+
"@cynodia/axiom-runtime": "0.7.0-alpha.2"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc -b tsconfig.json tsconfig.test.json",
|