@cynodia/axiom-core 0.7.0-alpha.2 → 0.8.1-alpha.1
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 +6 -2
- package/dist/authority.d.ts +2 -0
- package/dist/authority.js +25 -0
- package/dist/derive-edges.js +42 -0
- package/dist/diagnostics.d.ts +19 -0
- package/dist/diagnostics.js +20 -0
- package/dist/effect-outcome.d.ts +43 -0
- package/dist/effect-outcome.js +57 -0
- package/dist/events.d.ts +13 -0
- package/dist/events.js +1 -0
- package/dist/graph.js +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/integrations.d.ts +57 -0
- package/dist/integrations.js +2 -0
- package/dist/ir.d.ts +9 -0
- package/dist/nodes.d.ts +68 -1
- package/dist/nodes.js +18 -0
- package/dist/server-ir.d.ts +55 -3
- package/dist/server-ir.js +66 -4
- package/dist/trigger-capabilities.d.ts +28 -0
- package/dist/trigger-capabilities.js +10 -0
- package/dist/triggers.d.ts +53 -0
- package/dist/triggers.js +8 -0
- package/dist/types.d.ts +5 -2
- package/dist/types.js +4 -0
- package/dist/validate-authority.d.ts +2 -1
- package/dist/validate-authority.js +54 -1
- package/dist/validate.d.ts +8 -0
- package/dist/validate.js +194 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,14 +9,18 @@ The Application Graph and its semantic model: nodes, fields, structured types,
|
|
|
9
9
|
expressions, **locations** (addressable writable positions), edge derivation, validation
|
|
10
10
|
and type inference. It also owns the presentation and UX layer — semantic roles, layout and
|
|
11
11
|
spacing tokens, device classes, value formats and the `Theme` — with presentation
|
|
12
|
-
resolution and validation.
|
|
12
|
+
resolution and validation. As of 0.8 it also owns the vocabulary for external
|
|
13
|
+
integrations, effects, triggers and events: `IntegrationDef`, `IntegrationOperationDef`,
|
|
14
|
+
`EventDef`, `TriggerDef`, and the `integration-query`/`integration-effect` operation
|
|
15
|
+
kinds — see [`docs/INTEGRATIONS.md`](https://github.com/cynodia/axiom/blob/main/docs/INTEGRATIONS.md).
|
|
13
16
|
|
|
14
17
|
Presentation lives here because it is part of the canonical graph: it is intent, not
|
|
15
18
|
styling, and it names no colour, length or CSS property.
|
|
16
19
|
|
|
17
20
|
Main exports: `ApplicationGraph`, `TypeRef` builders, expression builders, `Location`
|
|
18
21
|
builders, `Presentation`, `Theme`, `resolvePresentationMap`, `validateGraph`,
|
|
19
|
-
`VALIDATION_CODES`, `ApplicationIR
|
|
22
|
+
`VALIDATION_CODES`, `ApplicationIR`, `ServerIR`, `IntegrationDef`, `IntegrationOperationDef`,
|
|
23
|
+
`EventDef`, `TriggerDef`.
|
|
20
24
|
|
|
21
25
|
## Installation
|
|
22
26
|
|
package/dist/authority.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ export interface AuthorityContext {
|
|
|
43
43
|
export declare function authorityContext(nodes: readonly AnyNode[], principalEntityId?: NodeId): AuthorityContext;
|
|
44
44
|
/** Every state a set of expressions reads, following derived state transitively. */
|
|
45
45
|
export declare function statesReadBy(expressions: readonly Expression[], context: AuthorityContext): Set<NodeId>;
|
|
46
|
+
/** Whether an action calls out to an integration anywhere in its top-level operations. */
|
|
47
|
+
export declare function actionUsesIntegration(action: ActionDef): boolean;
|
|
46
48
|
/** The states an action writes, following `for-each`, `invoke` and declared native effects. */
|
|
47
49
|
export declare function statesWrittenBy(action: ActionDef, context: AuthorityContext, visited?: Set<NodeId>): Set<NodeId>;
|
|
48
50
|
/** The states an action reads: its guards, its values, its selectors, its authorization. */
|
package/dist/authority.js
CHANGED
|
@@ -112,10 +112,23 @@ function operationExpressions(operation) {
|
|
|
112
112
|
found.push(...locationExpressions(operation.resultTarget));
|
|
113
113
|
}
|
|
114
114
|
break;
|
|
115
|
+
case 'integration-query':
|
|
116
|
+
found.push(...Object.values(operation.arguments ?? {}));
|
|
117
|
+
break;
|
|
118
|
+
case 'integration-effect':
|
|
119
|
+
found.push(...Object.values(operation.arguments ?? {}));
|
|
120
|
+
if (operation.idempotencyKey) {
|
|
121
|
+
found.push(operation.idempotencyKey);
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
115
124
|
default:
|
|
116
125
|
}
|
|
117
126
|
return found;
|
|
118
127
|
}
|
|
128
|
+
/** Whether an action calls out to an integration anywhere in its top-level operations. */
|
|
129
|
+
export function actionUsesIntegration(action) {
|
|
130
|
+
return (action.operations ?? []).some((operation) => operation.kind === 'integration-query' || operation.kind === 'integration-effect');
|
|
131
|
+
}
|
|
119
132
|
/** The states an action writes, following `for-each`, `invoke` and declared native effects. */
|
|
120
133
|
export function statesWrittenBy(action, context, visited = new Set()) {
|
|
121
134
|
const found = new Set();
|
|
@@ -152,6 +165,12 @@ export function statesWrittenBy(action, context, visited = new Set()) {
|
|
|
152
165
|
}
|
|
153
166
|
}
|
|
154
167
|
break;
|
|
168
|
+
case 'integration-query':
|
|
169
|
+
case 'integration-effect':
|
|
170
|
+
// Neither writes Axiom state directly: a query's result is a transaction-local
|
|
171
|
+
// scope binding, and an effect's outcome reaches state only through a follow-up
|
|
172
|
+
// action invoked from its success/failure event.
|
|
173
|
+
break;
|
|
155
174
|
default:
|
|
156
175
|
}
|
|
157
176
|
}
|
|
@@ -194,6 +213,12 @@ export function statesReadByAction(action, context, visited = new Set()) {
|
|
|
194
213
|
* cannot disagree with what the action actually does.
|
|
195
214
|
*/
|
|
196
215
|
export function actionAuthority(action, context) {
|
|
216
|
+
// Integrations default server-only (spec §65: secrets, trust, CORS, auditability,
|
|
217
|
+
// deterministic authority), so an action that calls one is unconditionally server —
|
|
218
|
+
// independent of what it writes.
|
|
219
|
+
if (actionUsesIntegration(action)) {
|
|
220
|
+
return 'server';
|
|
221
|
+
}
|
|
197
222
|
for (const stateId of statesWrittenBy(action, context)) {
|
|
198
223
|
const state = context.states.get(stateId);
|
|
199
224
|
if (state && stateAuthority(state) === 'server') {
|
package/dist/derive-edges.js
CHANGED
|
@@ -143,6 +143,27 @@ export function deriveEdges(nodes) {
|
|
|
143
143
|
// bindings, no caller scope. Its parameters resolve to nothing here by design.
|
|
144
144
|
reads(node.id, node.expression, new Map());
|
|
145
145
|
break;
|
|
146
|
+
case 'integration-operation':
|
|
147
|
+
link(node.id, node.integrationId, 'references');
|
|
148
|
+
break;
|
|
149
|
+
case 'trigger':
|
|
150
|
+
link(node.id, node.actionId, 'invokes');
|
|
151
|
+
if (node.when.kind === 'event') {
|
|
152
|
+
link(node.id, node.when.eventId, 'references');
|
|
153
|
+
}
|
|
154
|
+
if (node.when.kind === 'lifecycle' && node.when.routeId) {
|
|
155
|
+
link(node.id, node.when.routeId, 'depends-on');
|
|
156
|
+
}
|
|
157
|
+
for (const argument of Object.values(node.arguments ?? {})) {
|
|
158
|
+
reads(node.id, argument, rootScope);
|
|
159
|
+
}
|
|
160
|
+
if (node.enabledWhen) {
|
|
161
|
+
reads(node.id, node.enabledWhen, rootScope);
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
case 'integration':
|
|
165
|
+
case 'event':
|
|
166
|
+
break;
|
|
146
167
|
default:
|
|
147
168
|
}
|
|
148
169
|
}
|
|
@@ -415,6 +436,27 @@ function linkOperations(actionId, operations, linker, scope) {
|
|
|
415
436
|
}
|
|
416
437
|
}
|
|
417
438
|
break;
|
|
439
|
+
case 'integration-query':
|
|
440
|
+
linker.link(actionId, operation.operationId, 'references');
|
|
441
|
+
for (const argument of Object.values(operation.arguments ?? {})) {
|
|
442
|
+
linker.reads(actionId, argument, scope);
|
|
443
|
+
}
|
|
444
|
+
break;
|
|
445
|
+
case 'integration-effect':
|
|
446
|
+
linker.link(actionId, operation.operationId, 'references');
|
|
447
|
+
for (const argument of Object.values(operation.arguments ?? {})) {
|
|
448
|
+
linker.reads(actionId, argument, scope);
|
|
449
|
+
}
|
|
450
|
+
if (operation.idempotencyKey) {
|
|
451
|
+
linker.reads(actionId, operation.idempotencyKey, scope);
|
|
452
|
+
}
|
|
453
|
+
if (operation.succeededEventId) {
|
|
454
|
+
linker.link(actionId, operation.succeededEventId, 'references');
|
|
455
|
+
}
|
|
456
|
+
if (operation.failedEventId) {
|
|
457
|
+
linker.link(actionId, operation.failedEventId, 'references');
|
|
458
|
+
}
|
|
459
|
+
break;
|
|
418
460
|
default:
|
|
419
461
|
}
|
|
420
462
|
}
|
package/dist/diagnostics.d.ts
CHANGED
|
@@ -88,5 +88,24 @@ export declare const VALIDATION_CODES: {
|
|
|
88
88
|
readonly serverOnlyStateObserved: "SERVER_ONLY_STATE_OBSERVED";
|
|
89
89
|
readonly invalidPrincipalEntity: "INVALID_PRINCIPAL_ENTITY";
|
|
90
90
|
readonly missingActionArgument: "MISSING_ACTION_ARGUMENT";
|
|
91
|
+
/** An `IntegrationOperationDef.integrationId`, or an operation's `operationId`, that does not resolve. */
|
|
92
|
+
readonly unknownIntegration: "UNKNOWN_INTEGRATION";
|
|
93
|
+
readonly unknownIntegrationOperation: "UNKNOWN_INTEGRATION_OPERATION";
|
|
94
|
+
/** An `integration-query` operation naming an effect, or an `integration-effect` naming a query. */
|
|
95
|
+
readonly integrationOperationModeMismatch: "INTEGRATION_OPERATION_MODE_MISMATCH";
|
|
96
|
+
/** A missing required argument, or an argument the operation declares no parameter for. */
|
|
97
|
+
readonly integrationArgumentMismatch: "INTEGRATION_ARGUMENT_MISMATCH";
|
|
98
|
+
readonly triggerActionNotFound: "TRIGGER_ACTION_NOT_FOUND";
|
|
99
|
+
readonly triggerIntervalNotPositive: "TRIGGER_INTERVAL_NOT_POSITIVE";
|
|
100
|
+
/** An event id that does not resolve to an `EventDef` — a trigger's `eventId`, or an effect's success/failure event. */
|
|
101
|
+
readonly unknownEvent: "UNKNOWN_EVENT";
|
|
102
|
+
/** An `event` trigger targeting a client-authority action, or a `route-enter`/`route-leave` trigger targeting a server-authority one. */
|
|
103
|
+
readonly triggerWrongAuthority: "TRIGGER_WRONG_AUTHORITY";
|
|
104
|
+
/** A trigger — which always invokes with `source: 'system'` — targets an action whose `invocation.allowedSources` excludes `'system'`; the trigger could never succeed. */
|
|
105
|
+
readonly triggerTargetSourceMismatch: "TRIGGER_TARGET_SOURCE_MISMATCH";
|
|
106
|
+
/** `ActionDef.invocation.allowedSources` is present but empty — the action could never be invoked at all. */
|
|
107
|
+
readonly invalidInvocationSource: "INVALID_INVOCATION_SOURCE";
|
|
108
|
+
/** A client-authority trigger of a kind the intended trigger runtime does not execute — it would validate and compile but never fire (spec 8.1 §31-36). */
|
|
109
|
+
readonly clientTriggerUnsupported: "CLIENT_TRIGGER_UNSUPPORTED";
|
|
91
110
|
};
|
|
92
111
|
//# sourceMappingURL=diagnostics.d.ts.map
|
package/dist/diagnostics.js
CHANGED
|
@@ -76,4 +76,24 @@ export const VALIDATION_CODES = {
|
|
|
76
76
|
serverOnlyStateObserved: 'SERVER_ONLY_STATE_OBSERVED',
|
|
77
77
|
invalidPrincipalEntity: 'INVALID_PRINCIPAL_ENTITY',
|
|
78
78
|
missingActionArgument: 'MISSING_ACTION_ARGUMENT',
|
|
79
|
+
// Integrations, effects, triggers and events.
|
|
80
|
+
/** An `IntegrationOperationDef.integrationId`, or an operation's `operationId`, that does not resolve. */
|
|
81
|
+
unknownIntegration: 'UNKNOWN_INTEGRATION',
|
|
82
|
+
unknownIntegrationOperation: 'UNKNOWN_INTEGRATION_OPERATION',
|
|
83
|
+
/** An `integration-query` operation naming an effect, or an `integration-effect` naming a query. */
|
|
84
|
+
integrationOperationModeMismatch: 'INTEGRATION_OPERATION_MODE_MISMATCH',
|
|
85
|
+
/** A missing required argument, or an argument the operation declares no parameter for. */
|
|
86
|
+
integrationArgumentMismatch: 'INTEGRATION_ARGUMENT_MISMATCH',
|
|
87
|
+
triggerActionNotFound: 'TRIGGER_ACTION_NOT_FOUND',
|
|
88
|
+
triggerIntervalNotPositive: 'TRIGGER_INTERVAL_NOT_POSITIVE',
|
|
89
|
+
/** An event id that does not resolve to an `EventDef` — a trigger's `eventId`, or an effect's success/failure event. */
|
|
90
|
+
unknownEvent: 'UNKNOWN_EVENT',
|
|
91
|
+
/** An `event` trigger targeting a client-authority action, or a `route-enter`/`route-leave` trigger targeting a server-authority one. */
|
|
92
|
+
triggerWrongAuthority: 'TRIGGER_WRONG_AUTHORITY',
|
|
93
|
+
/** A trigger — which always invokes with `source: 'system'` — targets an action whose `invocation.allowedSources` excludes `'system'`; the trigger could never succeed. */
|
|
94
|
+
triggerTargetSourceMismatch: 'TRIGGER_TARGET_SOURCE_MISMATCH',
|
|
95
|
+
/** `ActionDef.invocation.allowedSources` is present but empty — the action could never be invoked at all. */
|
|
96
|
+
invalidInvocationSource: 'INVALID_INVOCATION_SOURCE',
|
|
97
|
+
/** A client-authority trigger of a kind the intended trigger runtime does not execute — it would validate and compile but never fire (spec 8.1 §31-36). */
|
|
98
|
+
clientTriggerUnsupported: 'CLIENT_TRIGGER_UNSUPPORTED',
|
|
79
99
|
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { FieldId, NodeId } from './ids.js';
|
|
2
|
+
import type { EntityDef } from './nodes.js';
|
|
3
|
+
import type { TypeRef } from './type-ref.js';
|
|
4
|
+
/**
|
|
5
|
+
* Reserved field ids for an `integration-effect`'s `succeededEventId`/`failedEventId`
|
|
6
|
+
* payload — spec 8.1 §37-41's structured envelope, replacing the 0.8.0 shape (the raw
|
|
7
|
+
* adapter result for success, a formatted `"<code>: <message>"` string for failure) that
|
|
8
|
+
* forced a follow-up action to parse text to correlate a failure back to the effect that
|
|
9
|
+
* caused it.
|
|
10
|
+
*
|
|
11
|
+
* One shape covers both outcomes (spec §40's "structured `EffectOutcome` envelope for both
|
|
12
|
+
* success/failure"), rather than a distinct entity per outcome: field ids are unique across
|
|
13
|
+
* a whole graph — like `GROUP_KEY_FIELD`/`GROUP_ITEMS_FIELD` — so two entities could not both
|
|
14
|
+
* declare `effectId`/`operationId`/`integrationId` without colliding. A success dispatch
|
|
15
|
+
* populates `result`; a failure dispatch populates `code`/`message`/`retryable`; neither
|
|
16
|
+
* ever populates both, and none of `result`/`code`/`message`/`retryable` is `required`, so
|
|
17
|
+
* either shape validates.
|
|
18
|
+
*
|
|
19
|
+
* Unlike `GROUP_KEY_FIELD`/`GROUP_ITEMS_FIELD`, these are not runtime-enforced reservations
|
|
20
|
+
* — a payload is validated as an ordinary `entity` type, the same as any other event. They
|
|
21
|
+
* are a naming convention plus the builder below, so every application's effect outcome
|
|
22
|
+
* events share one shape an agent only has to learn once.
|
|
23
|
+
*/
|
|
24
|
+
export declare const EFFECT_ID_FIELD: FieldId;
|
|
25
|
+
export declare const EFFECT_INTEGRATION_ID_FIELD: FieldId;
|
|
26
|
+
export declare const EFFECT_OPERATION_ID_FIELD: FieldId;
|
|
27
|
+
export declare const EFFECT_CODE_FIELD: FieldId;
|
|
28
|
+
export declare const EFFECT_MESSAGE_FIELD: FieldId;
|
|
29
|
+
export declare const EFFECT_RETRYABLE_FIELD: FieldId;
|
|
30
|
+
export declare const EFFECT_IDEMPOTENCY_KEY_FIELD: FieldId;
|
|
31
|
+
export declare const EFFECT_CORRELATION_ID_FIELD: FieldId;
|
|
32
|
+
export declare const EFFECT_RESULT_FIELD: FieldId;
|
|
33
|
+
/**
|
|
34
|
+
* The canonical entity shape for an `integration-effect`'s `succeededEventId` and
|
|
35
|
+
* `failedEventId` payload alike. Declare it once with
|
|
36
|
+
* `graph.addNode(effectOutcomeEntity(ENTITY_ID, resultType))` and reference it from both
|
|
37
|
+
* `EventDef`s with `entityType(ENTITY_ID)`. `resultType` is the effect operation's own
|
|
38
|
+
* declared result type — if a graph's effect operations return incompatible result types,
|
|
39
|
+
* they need either a common supertype here or their own outcome entity each (field ids are
|
|
40
|
+
* graph-global, so at most one `effectOutcomeEntity` may share a given result shape).
|
|
41
|
+
*/
|
|
42
|
+
export declare function effectOutcomeEntity(id: NodeId, resultType: TypeRef): EntityDef;
|
|
43
|
+
//# sourceMappingURL=effect-outcome.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { fieldId } from './ids.js';
|
|
2
|
+
import { primitiveType } from './type-ref.js';
|
|
3
|
+
/**
|
|
4
|
+
* Reserved field ids for an `integration-effect`'s `succeededEventId`/`failedEventId`
|
|
5
|
+
* payload — spec 8.1 §37-41's structured envelope, replacing the 0.8.0 shape (the raw
|
|
6
|
+
* adapter result for success, a formatted `"<code>: <message>"` string for failure) that
|
|
7
|
+
* forced a follow-up action to parse text to correlate a failure back to the effect that
|
|
8
|
+
* caused it.
|
|
9
|
+
*
|
|
10
|
+
* One shape covers both outcomes (spec §40's "structured `EffectOutcome` envelope for both
|
|
11
|
+
* success/failure"), rather than a distinct entity per outcome: field ids are unique across
|
|
12
|
+
* a whole graph — like `GROUP_KEY_FIELD`/`GROUP_ITEMS_FIELD` — so two entities could not both
|
|
13
|
+
* declare `effectId`/`operationId`/`integrationId` without colliding. A success dispatch
|
|
14
|
+
* populates `result`; a failure dispatch populates `code`/`message`/`retryable`; neither
|
|
15
|
+
* ever populates both, and none of `result`/`code`/`message`/`retryable` is `required`, so
|
|
16
|
+
* either shape validates.
|
|
17
|
+
*
|
|
18
|
+
* Unlike `GROUP_KEY_FIELD`/`GROUP_ITEMS_FIELD`, these are not runtime-enforced reservations
|
|
19
|
+
* — a payload is validated as an ordinary `entity` type, the same as any other event. They
|
|
20
|
+
* are a naming convention plus the builder below, so every application's effect outcome
|
|
21
|
+
* events share one shape an agent only has to learn once.
|
|
22
|
+
*/
|
|
23
|
+
export const EFFECT_ID_FIELD = fieldId('field_effect_id');
|
|
24
|
+
export const EFFECT_INTEGRATION_ID_FIELD = fieldId('field_effect_integration_id');
|
|
25
|
+
export const EFFECT_OPERATION_ID_FIELD = fieldId('field_effect_operation_id');
|
|
26
|
+
export const EFFECT_CODE_FIELD = fieldId('field_effect_code');
|
|
27
|
+
export const EFFECT_MESSAGE_FIELD = fieldId('field_effect_message');
|
|
28
|
+
export const EFFECT_RETRYABLE_FIELD = fieldId('field_effect_retryable');
|
|
29
|
+
export const EFFECT_IDEMPOTENCY_KEY_FIELD = fieldId('field_effect_idempotency_key');
|
|
30
|
+
export const EFFECT_CORRELATION_ID_FIELD = fieldId('field_effect_correlation_id');
|
|
31
|
+
export const EFFECT_RESULT_FIELD = fieldId('field_effect_result');
|
|
32
|
+
/**
|
|
33
|
+
* The canonical entity shape for an `integration-effect`'s `succeededEventId` and
|
|
34
|
+
* `failedEventId` payload alike. Declare it once with
|
|
35
|
+
* `graph.addNode(effectOutcomeEntity(ENTITY_ID, resultType))` and reference it from both
|
|
36
|
+
* `EventDef`s with `entityType(ENTITY_ID)`. `resultType` is the effect operation's own
|
|
37
|
+
* declared result type — if a graph's effect operations return incompatible result types,
|
|
38
|
+
* they need either a common supertype here or their own outcome entity each (field ids are
|
|
39
|
+
* graph-global, so at most one `effectOutcomeEntity` may share a given result shape).
|
|
40
|
+
*/
|
|
41
|
+
export function effectOutcomeEntity(id, resultType) {
|
|
42
|
+
return {
|
|
43
|
+
id,
|
|
44
|
+
kind: 'entity',
|
|
45
|
+
fields: [
|
|
46
|
+
{ id: EFFECT_ID_FIELD, valueType: primitiveType('string'), required: true },
|
|
47
|
+
{ id: EFFECT_INTEGRATION_ID_FIELD, valueType: primitiveType('string'), required: true },
|
|
48
|
+
{ id: EFFECT_OPERATION_ID_FIELD, valueType: primitiveType('string'), required: true },
|
|
49
|
+
{ id: EFFECT_RESULT_FIELD, valueType: resultType },
|
|
50
|
+
{ id: EFFECT_CODE_FIELD, valueType: primitiveType('string') },
|
|
51
|
+
{ id: EFFECT_MESSAGE_FIELD, valueType: primitiveType('string') },
|
|
52
|
+
{ id: EFFECT_RETRYABLE_FIELD, valueType: primitiveType('boolean') },
|
|
53
|
+
{ id: EFFECT_IDEMPOTENCY_KEY_FIELD, valueType: primitiveType('string') },
|
|
54
|
+
{ id: EFFECT_CORRELATION_ID_FIELD, valueType: primitiveType('string') },
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NodeBase } from './nodes.js';
|
|
2
|
+
import type { TypeRef } from './type-ref.js';
|
|
3
|
+
/**
|
|
4
|
+
* A semantic fact that occurred — a webhook delivery, an effect's outcome. An event is
|
|
5
|
+
* not work: it names something that happened, with a typed payload, and it is `TriggerDef`
|
|
6
|
+
* that says what happens next. Nothing resolves an event payload as `unknown`; it is
|
|
7
|
+
* validated against `payloadType` before it reaches any action.
|
|
8
|
+
*/
|
|
9
|
+
export interface EventDef extends NodeBase {
|
|
10
|
+
kind: 'event';
|
|
11
|
+
payloadType: TypeRef;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=events.d.ts.map
|
package/dist/events.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/graph.js
CHANGED
|
@@ -23,7 +23,7 @@ export class ApplicationGraph {
|
|
|
23
23
|
/** Bumped by every change, so the derived edge index can never serve stale data. */
|
|
24
24
|
revision = 0;
|
|
25
25
|
semanticIndex;
|
|
26
|
-
constructor(id, name, version = '0.
|
|
26
|
+
constructor(id, name, version = '0.8.1') {
|
|
27
27
|
this.data = { id, name, version, nodes: {}, edges: {} };
|
|
28
28
|
}
|
|
29
29
|
get id() {
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,12 @@ export * from './type-ref.js';
|
|
|
7
7
|
export * from './group.js';
|
|
8
8
|
export * from './expressions.js';
|
|
9
9
|
export * from './nodes.js';
|
|
10
|
+
export * from './integrations.js';
|
|
11
|
+
export * from './effect-outcome.js';
|
|
12
|
+
export * from './events.js';
|
|
13
|
+
export * from './triggers.js';
|
|
10
14
|
export * from './renderer-capabilities.js';
|
|
15
|
+
export * from './trigger-capabilities.js';
|
|
11
16
|
export * from './authoring-metadata.js';
|
|
12
17
|
export * from './ui.js';
|
|
13
18
|
export * from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,12 @@ export * from './type-ref.js';
|
|
|
7
7
|
export * from './group.js';
|
|
8
8
|
export * from './expressions.js';
|
|
9
9
|
export * from './nodes.js';
|
|
10
|
+
export * from './integrations.js';
|
|
11
|
+
export * from './effect-outcome.js';
|
|
12
|
+
export * from './events.js';
|
|
13
|
+
export * from './triggers.js';
|
|
10
14
|
export * from './renderer-capabilities.js';
|
|
15
|
+
export * from './trigger-capabilities.js';
|
|
11
16
|
export * from './authoring-metadata.js';
|
|
12
17
|
export * from './ui.js';
|
|
13
18
|
export * from './types.js';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { NodeId } from './ids.js';
|
|
2
|
+
import type { NodeBase } from './nodes.js';
|
|
3
|
+
import type { TypeRef } from './type-ref.js';
|
|
4
|
+
/**
|
|
5
|
+
* An external capability domain — a shipping provider, a device fleet, a payments
|
|
6
|
+
* processor. It names the capability, never the SDK, host name, secret or HTTP client
|
|
7
|
+
* that eventually implements it: those are supplied by a host adapter, never by the
|
|
8
|
+
* graph.
|
|
9
|
+
*/
|
|
10
|
+
export interface IntegrationDef extends NodeBase {
|
|
11
|
+
kind: 'integration';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A query observes an external system and does not intentionally mutate it. An effect
|
|
15
|
+
* may mutate or otherwise cause an irreversible external consequence. The distinction is
|
|
16
|
+
* load-bearing: a query's result may be used mid-transaction, while an effect is never
|
|
17
|
+
* rollback-capable and is dispatched only after the transaction that requested it commits.
|
|
18
|
+
*/
|
|
19
|
+
export type IntegrationOperationMode = 'query' | 'effect';
|
|
20
|
+
export declare const INTEGRATION_OPERATION_MODES: readonly IntegrationOperationMode[];
|
|
21
|
+
export type RetryPolicyKind = 'none' | 'fixed' | 'exponential';
|
|
22
|
+
export declare const RETRY_POLICY_KINDS: readonly RetryPolicyKind[];
|
|
23
|
+
export interface RetryPolicy {
|
|
24
|
+
policy: RetryPolicyKind;
|
|
25
|
+
maxAttempts?: number;
|
|
26
|
+
delayMs?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface IntegrationOperationParameter {
|
|
29
|
+
id: NodeId;
|
|
30
|
+
name?: string;
|
|
31
|
+
valueType: TypeRef;
|
|
32
|
+
required?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A typed semantic operation an integration exposes.
|
|
36
|
+
*
|
|
37
|
+
* Its result must conform to `resultType` — a provider response that does not is rejected
|
|
38
|
+
* at the adapter/runtime boundary, never handed to the application as `unknown`.
|
|
39
|
+
*/
|
|
40
|
+
export interface IntegrationOperationDef extends NodeBase {
|
|
41
|
+
kind: 'integration-operation';
|
|
42
|
+
integrationId: NodeId;
|
|
43
|
+
mode: IntegrationOperationMode;
|
|
44
|
+
parameters?: IntegrationOperationParameter[];
|
|
45
|
+
resultType: TypeRef;
|
|
46
|
+
/**
|
|
47
|
+
* Whether this operation may be invoked from client-executed code. Absent means
|
|
48
|
+
* server-only, which is the default for every integration (spec §65): client safety is
|
|
49
|
+
* never inferred from the absence of a declared secret, only declared explicitly.
|
|
50
|
+
*/
|
|
51
|
+
clientSafe?: boolean;
|
|
52
|
+
/** Whether invoking this effect twice with the same idempotency key has one effect. */
|
|
53
|
+
idempotent?: boolean;
|
|
54
|
+
/** Retry policy for a failed effect. Absent means `'none'`. Meaningless for a query. */
|
|
55
|
+
retry?: RetryPolicy;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=integrations.d.ts.map
|
package/dist/ir.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { TypeRef } from './type-ref.js';
|
|
|
7
7
|
import type { Authority } from './authority.js';
|
|
8
8
|
import type { ResolvedPresentation } from './presentation.js';
|
|
9
9
|
import type { Theme } from './theme.js';
|
|
10
|
+
import type { TriggerDef } from './triggers.js';
|
|
10
11
|
export interface RouteSegment {
|
|
11
12
|
kind: 'static' | 'parameter';
|
|
12
13
|
value: string;
|
|
@@ -92,5 +93,13 @@ export interface ApplicationIR {
|
|
|
92
93
|
* second renderer remains possible.
|
|
93
94
|
*/
|
|
94
95
|
presentation: Record<NodeId, ResolvedPresentation>;
|
|
96
|
+
/**
|
|
97
|
+
* Client-authority triggers only — `interval`/`delay`/`lifecycle('application-start' |
|
|
98
|
+
* 'runtime-ready')` triggers whose target action executes locally, plus
|
|
99
|
+
* `lifecycle('route-enter' | 'route-leave')` triggers, which are inherently a client
|
|
100
|
+
* concept. No `event`-kind trigger, no integration, and no secret ever reaches this IR
|
|
101
|
+
* (spec §80).
|
|
102
|
+
*/
|
|
103
|
+
triggers: TriggerDef[];
|
|
95
104
|
}
|
|
96
105
|
//# sourceMappingURL=ir.d.ts.map
|
package/dist/nodes.d.ts
CHANGED
|
@@ -174,6 +174,27 @@ export interface ActionGuard {
|
|
|
174
174
|
condition: Expression;
|
|
175
175
|
failureMode?: FailureMode;
|
|
176
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* `'client'` is an ordinary `InvokeRequest`; `'system'` is a trigger-, event- or
|
|
179
|
+
* effect-outcome-originated invocation. Server-computed and client-unforgeable — see
|
|
180
|
+
* `ExecutionContext.source` in `@cynodia/axiom-server`.
|
|
181
|
+
*/
|
|
182
|
+
export type InvocationSource = 'client' | 'system';
|
|
183
|
+
export declare const INVOCATION_SOURCES: readonly InvocationSource[];
|
|
184
|
+
/** Both sources, the default when `ActionDef.invocation` is absent (spec 8.1 §5). */
|
|
185
|
+
export declare const DEFAULT_ALLOWED_INVOCATION_SOURCES: readonly InvocationSource[];
|
|
186
|
+
/**
|
|
187
|
+
* Restricts who may invoke an action, independently of `authorization`'s identity check.
|
|
188
|
+
*
|
|
189
|
+
* `authorization` answers "may this caller invoke this action"; `invocation` answers "may
|
|
190
|
+
* an invocation reaching the authority this way invoke this action at all" — the trust
|
|
191
|
+
* boundary a trigger-only or effect-outcome-only action needs, since otherwise any anonymous
|
|
192
|
+
* client that knows its id can invoke it directly (spec 8.1 §3-9).
|
|
193
|
+
*/
|
|
194
|
+
export interface ActionInvocationPolicy {
|
|
195
|
+
/** Absent means both — identical to omitting `invocation` entirely. */
|
|
196
|
+
allowedSources?: readonly InvocationSource[];
|
|
197
|
+
}
|
|
177
198
|
/**
|
|
178
199
|
* Behaviour expressed as data, executed as a transaction.
|
|
179
200
|
*
|
|
@@ -210,13 +231,21 @@ export interface ActionDef extends NodeBase {
|
|
|
210
231
|
authorization?: Expression;
|
|
211
232
|
/** What the confirmation says, when a plain message is not enough. */
|
|
212
233
|
confirmation?: ConfirmationPresentation;
|
|
234
|
+
/** Restricts which invocation sources may reach this action. Absent means both. */
|
|
235
|
+
invocation?: ActionInvocationPolicy;
|
|
213
236
|
}
|
|
214
237
|
/**
|
|
215
238
|
* The conditions an action checks, however they were written. `guards` pairs each
|
|
216
239
|
* condition with its failure; the older parallel arrays are matched by position.
|
|
217
240
|
*/
|
|
218
241
|
export declare function actionGuards(action: ActionDef): ActionGuard[];
|
|
219
|
-
|
|
242
|
+
/** The invocation sources this action accepts — both, unless `invocation` restricts it. */
|
|
243
|
+
export declare function allowedInvocationSources(action: ActionDef): readonly InvocationSource[];
|
|
244
|
+
/** Whether an ordinary client `InvokeRequest` may name this action at all. */
|
|
245
|
+
export declare function isClientInvocable(action: ActionDef): boolean;
|
|
246
|
+
/** Whether this action accepts only trigger-, event- or effect-outcome-originated calls. */
|
|
247
|
+
export declare function isSystemOnlyAction(action: ActionDef): boolean;
|
|
248
|
+
export type Operation = SetOperation | InsertOperation | RemoveOperation | ForEachOperation | InvokeOperation | NavigateOperation | NativeOperation | IntegrationQueryOperation | IntegrationEffectOperation;
|
|
220
249
|
export type OperationKind = Operation['kind'];
|
|
221
250
|
/** Every operation kind the runtime is required to execute. */
|
|
222
251
|
export declare const OPERATION_KINDS: readonly OperationKind[];
|
|
@@ -290,6 +319,44 @@ export interface NavigateOperation {
|
|
|
290
319
|
/** Keyed by route parameter id. */
|
|
291
320
|
parameters?: Record<string, Expression>;
|
|
292
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Executes an integration query and binds its result into scope for later operations in
|
|
324
|
+
* the same action.
|
|
325
|
+
*
|
|
326
|
+
* A query is awaited mid-transaction — its result may inform the mutations that follow —
|
|
327
|
+
* but evaluating it is not an ordinary Expression, because it is not deterministic over
|
|
328
|
+
* semantic state alone. `bindAs` introduces a scope the way a `for-each`'s `scopeId` does:
|
|
329
|
+
* later operations refer to the whole result as `ref(bindAs)`. Never legal inside
|
|
330
|
+
* `for-each`.
|
|
331
|
+
*/
|
|
332
|
+
export interface IntegrationQueryOperation {
|
|
333
|
+
kind: 'integration-query';
|
|
334
|
+
/** The `IntegrationOperationDef` to call. Must declare `mode: 'query'`. */
|
|
335
|
+
operationId: NodeId;
|
|
336
|
+
arguments?: Record<string, Expression>;
|
|
337
|
+
bindAs: NodeId;
|
|
338
|
+
timeoutMs?: number;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Records intent to perform an external effect. It never calls the adapter during the
|
|
342
|
+
* transaction: reaching this operation only appends an effect intent, discarded on
|
|
343
|
+
* rollback exactly like a mutation is. Dispatch to the adapter happens only after the
|
|
344
|
+
* surrounding transaction commits — the outbox invariant (spec §18) — so an effect is
|
|
345
|
+
* never rollback-capable and never mid-transaction. Never legal inside `for-each`.
|
|
346
|
+
*
|
|
347
|
+
* `succeededEventId`/`failedEventId`, when declared, are dispatched through the ordinary
|
|
348
|
+
* event pipeline once the effect's outcome is known — an effect's result is never folded
|
|
349
|
+
* back into the transaction that requested it.
|
|
350
|
+
*/
|
|
351
|
+
export interface IntegrationEffectOperation {
|
|
352
|
+
kind: 'integration-effect';
|
|
353
|
+
/** The `IntegrationOperationDef` to call. Must declare `mode: 'effect'`. */
|
|
354
|
+
operationId: NodeId;
|
|
355
|
+
arguments?: Record<string, Expression>;
|
|
356
|
+
idempotencyKey?: Expression;
|
|
357
|
+
succeededEventId?: NodeId;
|
|
358
|
+
failedEventId?: NodeId;
|
|
359
|
+
}
|
|
293
360
|
export type NativeEffect = {
|
|
294
361
|
kind: 'reads-state';
|
|
295
362
|
stateId: NodeId;
|
package/dist/nodes.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export const INVOCATION_SOURCES = ['client', 'system'];
|
|
2
|
+
/** Both sources, the default when `ActionDef.invocation` is absent (spec 8.1 §5). */
|
|
3
|
+
export const DEFAULT_ALLOWED_INVOCATION_SOURCES = INVOCATION_SOURCES;
|
|
1
4
|
/**
|
|
2
5
|
* The conditions an action checks, however they were written. `guards` pairs each
|
|
3
6
|
* condition with its failure; the older parallel arrays are matched by position.
|
|
@@ -11,6 +14,19 @@ export function actionGuards(action) {
|
|
|
11
14
|
...(action.failureModes?.[index] ? { failureMode: action.failureModes[index] } : {}),
|
|
12
15
|
}));
|
|
13
16
|
}
|
|
17
|
+
/** The invocation sources this action accepts — both, unless `invocation` restricts it. */
|
|
18
|
+
export function allowedInvocationSources(action) {
|
|
19
|
+
return action.invocation?.allowedSources ?? DEFAULT_ALLOWED_INVOCATION_SOURCES;
|
|
20
|
+
}
|
|
21
|
+
/** Whether an ordinary client `InvokeRequest` may name this action at all. */
|
|
22
|
+
export function isClientInvocable(action) {
|
|
23
|
+
return allowedInvocationSources(action).includes('client');
|
|
24
|
+
}
|
|
25
|
+
/** Whether this action accepts only trigger-, event- or effect-outcome-originated calls. */
|
|
26
|
+
export function isSystemOnlyAction(action) {
|
|
27
|
+
const sources = allowedInvocationSources(action);
|
|
28
|
+
return sources.includes('system') && !sources.includes('client');
|
|
29
|
+
}
|
|
14
30
|
/** Every operation kind the runtime is required to execute. */
|
|
15
31
|
export const OPERATION_KINDS = [
|
|
16
32
|
'set',
|
|
@@ -20,6 +36,8 @@ export const OPERATION_KINDS = [
|
|
|
20
36
|
'invoke',
|
|
21
37
|
'navigate',
|
|
22
38
|
'native',
|
|
39
|
+
'integration-query',
|
|
40
|
+
'integration-effect',
|
|
23
41
|
];
|
|
24
42
|
export function isMutationOperation(operation) {
|
|
25
43
|
return operation.kind === 'set' || operation.kind === 'insert' || operation.kind === 'remove';
|