@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.
@@ -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) => referencedIds(expression).some((id) => hiddenStateIds.has(id)));
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
- for (const node of graph.listNodes()) {
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 { SERVER_IR_CONTRACT, actionAuthority, actionGuards, authorityContext, isObservable, serverStateClosure, stateAuthority, validateGraph, } from '@cynodia/axiom-core';
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
- return {
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);
@@ -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.6.3-alpha.1",
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.6.3-alpha.1",
35
- "@cynodia/axiom-runtime": "0.6.3-alpha.1"
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",