@astrale-os/sdk 0.5.0-beta.65 → 0.5.0-beta.67
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/CHANGELOG.md +19 -0
- package/dist/application/mutation/authoring/builder.d.ts +6 -21
- package/dist/application/mutation/authoring/builder.js +4 -54
- package/dist/application/mutation/authoring/transition.d.ts +17 -0
- package/dist/application/mutation/authoring/transition.js +47 -0
- package/dist/application/mutation/define.d.ts +3 -1
- package/dist/application/mutation/execution/execute.js +1 -1
- package/dist/application/mutation/failure.d.ts +10 -2
- package/dist/application/mutation/failure.js +24 -1
- package/dist/application/state/authority.d.ts +3 -0
- package/dist/application/state/authority.js +7 -0
- package/dist/application/state/define.js +4 -1
- package/dist/platform/schema/index.d.ts +2 -0
- package/dist/platform/schema/index.js +1 -0
- package/dist/platform/schema/state-property/authoring.d.ts +15 -0
- package/dist/platform/schema/state-property/authoring.js +49 -0
- package/dist/platform/schema/state-property/edge.d.ts +16 -0
- package/dist/platform/schema/state-property/edge.js +21 -0
- package/dist/platform/schema/state-property/index.d.ts +4 -0
- package/dist/platform/schema/state-property/index.js +3 -0
- package/dist/platform/schema/state-property/model.d.ts +49 -0
- package/dist/platform/schema/state-property/model.js +1 -0
- package/dist/platform/schema/state-property/registry.d.ts +9 -0
- package/dist/platform/schema/state-property/registry.js +74 -0
- package/dist/tooling/linter/implementations/source/mutations.js +174 -114
- package/dist/tooling/linter/implementations/source/schema.js +113 -16
- package/dist/tooling/linter/implementations/source/{lifecycle.d.ts → state-machine.d.ts} +10 -6
- package/dist/tooling/linter/implementations/source/{lifecycle.js → state-machine.js} +82 -10
- package/dist/tooling/linter/implementations/source/states.js +5 -5
- package/dist/tooling/linter/policy/generated.js +3 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.0-beta.67](https://github.com/astrale-os/sdk/compare/sdk-v0.5.0-beta.66...sdk-v0.5.0-beta.67) (2026-08-28)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* make StateProperty transitions intrinsic ([#306](https://github.com/astrale-os/sdk/issues/306)) ([a2b2575](https://github.com/astrale-os/sdk/commit/a2b2575cf19c585c26539839c4777c0c2d413275))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* **state:** ratify intrinsic property transitions ([#303](https://github.com/astrale-os/sdk/issues/303)) ([d484e58](https://github.com/astrale-os/sdk/commit/d484e58bc47f80c2571886b5c85d91d91da52b25))
|
|
14
|
+
|
|
15
|
+
## [0.5.0-beta.66](https://github.com/astrale-os/sdk/compare/sdk-v0.5.0-beta.65...sdk-v0.5.0-beta.66) (2026-08-27)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **state:** use machine transition authority ([#301](https://github.com/astrale-os/sdk/issues/301)) ([6424217](https://github.com/astrale-os/sdk/commit/642421784d391863d33e90e85c0a1c11b1a6ed7b))
|
|
21
|
+
|
|
3
22
|
## [0.5.0-beta.65](https://github.com/astrale-os/sdk/compare/sdk-v0.5.0-beta.64...sdk-v0.5.0-beta.65) (2026-08-27)
|
|
4
23
|
|
|
5
24
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { EdgeSlug } from '@astrale-os/kernel-core/graph/edge';
|
|
2
2
|
import type { MutationBuilder } from '@astrale-os/kernel-core/graph/mutate';
|
|
3
3
|
import type { QueryEdgeAST, QueryNodeAST } from '@astrale-os/kernel-core/graph/query';
|
|
4
|
-
import type { ResolvedClass } from '../../../platform/schema/index.js';
|
|
5
|
-
import type {
|
|
4
|
+
import type { Domain, ResolvedClass } from '../../../platform/schema/index.js';
|
|
5
|
+
import type { EventOf, StateOf } from '../../state/machine.js';
|
|
6
6
|
import type { CreatedNode, ExistingEndpoint, MutationEndpoint } from './endpoint.js';
|
|
7
7
|
import type { QueryPrecondition } from './precondition.js';
|
|
8
|
-
import type { RichPropertyConditions, RichPropertyDelta, RichPropertyInput
|
|
8
|
+
import type { RichPropertyConditions, RichPropertyDelta, RichPropertyInput } from './properties.js';
|
|
9
|
+
import { type StateMachineOf, type StatePropertyName, type StateTransitionInput } from './transition.js';
|
|
9
10
|
export interface RichMutationBuilder {
|
|
10
11
|
readonly expect: {
|
|
11
12
|
node<Class extends ResolvedClass<'node'>>(input: {
|
|
@@ -29,14 +30,7 @@ export interface RichMutationBuilder {
|
|
|
29
30
|
readonly class: Class;
|
|
30
31
|
readonly props: RichPropertyDelta<Class>;
|
|
31
32
|
}): this;
|
|
32
|
-
transition<
|
|
33
|
-
readonly lifecycle: Machine;
|
|
34
|
-
readonly node: ExistingEndpoint;
|
|
35
|
-
readonly class: Class;
|
|
36
|
-
readonly property: Property;
|
|
37
|
-
readonly decision: AllowedTransitionDecision<Machine>;
|
|
38
|
-
readonly props?: RichPropertyConditions<Class>;
|
|
39
|
-
}): this;
|
|
33
|
+
transition<Class extends ResolvedClass<'node'>, Property extends StatePropertyName<Class>, Machine extends StateMachineOf<Class, Property>, From extends StateOf<Machine>, Event extends EventOf<Machine, From>>(input: StateTransitionInput<Class, Property, Machine, From, Event>): this;
|
|
40
34
|
deleteNode<Class extends ResolvedClass<'node'>>(input: {
|
|
41
35
|
readonly node: ExistingEndpoint;
|
|
42
36
|
readonly class: Class;
|
|
@@ -59,13 +53,4 @@ export interface EdgeSelector<Class extends ResolvedClass<'edge'>> {
|
|
|
59
53
|
readonly target: ExistingEndpoint;
|
|
60
54
|
readonly class: Class;
|
|
61
55
|
}
|
|
62
|
-
export declare function richMutationBuilder(core: MutationBuilder): RichMutationBuilder;
|
|
63
|
-
type LifecyclePropertyName<Class extends ResolvedClass<'node'>, Machine extends StateMachine> = {
|
|
64
|
-
[Name in RichPropertyName<Class>]: IsExact<Exclude<RichPropertyInput<Class>[Name], undefined>, StateOf<Machine>> extends true ? Name : never;
|
|
65
|
-
}[RichPropertyName<Class>];
|
|
66
|
-
type AllowedTransitionDecision<Machine extends StateMachine> = Readonly<{
|
|
67
|
-
kind: 'allowed';
|
|
68
|
-
transition: TransitionOf<Machine>;
|
|
69
|
-
}>;
|
|
70
|
-
type IsExact<Left, Right> = [Left] extends [Right] ? ([Right] extends [Left] ? true : false) : false;
|
|
71
|
-
export {};
|
|
56
|
+
export declare function richMutationBuilder(core: MutationBuilder, domain: Domain): RichMutationBuilder;
|
|
@@ -2,8 +2,9 @@ import { LocalBinding } from '@astrale-os/kernel-core/graph/graph';
|
|
|
2
2
|
import { ClassKey } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
3
3
|
import { endpoint, existingEndpoint } from './endpoint.js';
|
|
4
4
|
import { precondition } from './precondition.js';
|
|
5
|
-
import { conditions, delta, properties
|
|
6
|
-
|
|
5
|
+
import { conditions, delta, properties } from './properties.js';
|
|
6
|
+
import { authorStateTransition, } from './transition.js';
|
|
7
|
+
export function richMutationBuilder(core, domain) {
|
|
7
8
|
const expectation = Object.freeze({
|
|
8
9
|
node(input) {
|
|
9
10
|
core.expect.node({
|
|
@@ -47,55 +48,7 @@ export function richMutationBuilder(core) {
|
|
|
47
48
|
return rich;
|
|
48
49
|
},
|
|
49
50
|
transition(input) {
|
|
50
|
-
|
|
51
|
-
if (submitted.kind !== 'allowed' ||
|
|
52
|
-
submitted.transition === undefined ||
|
|
53
|
-
submitted.transition === null ||
|
|
54
|
-
typeof submitted.transition !== 'object' ||
|
|
55
|
-
typeof submitted.transition.from !== 'string' ||
|
|
56
|
-
typeof submitted.transition.event !== 'string' ||
|
|
57
|
-
typeof submitted.transition.to !== 'string') {
|
|
58
|
-
throw new TypeError('Lifecycle transition decision must be allowed.');
|
|
59
|
-
}
|
|
60
|
-
const transition = submitted.transition;
|
|
61
|
-
let decision;
|
|
62
|
-
try {
|
|
63
|
-
decision = input.lifecycle.decide(transition.from, transition.event);
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
invalidTransition(transition);
|
|
67
|
-
}
|
|
68
|
-
if (decision.kind !== 'allowed' || decision.transition.to !== transition.to) {
|
|
69
|
-
invalidTransition(transition);
|
|
70
|
-
}
|
|
71
|
-
const selected = resolvedProperty(input.class, input.property);
|
|
72
|
-
for (const state of [transition.from, transition.to]) {
|
|
73
|
-
if (selected.validate(state).length > 0) {
|
|
74
|
-
throw new TypeError(`Lifecycle property ${String(input.property)} does not admit state ${state}.`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
const from = { [input.property]: transition.from };
|
|
78
|
-
const to = { [input.property]: transition.to };
|
|
79
|
-
const suppliedEquals = input.props?.equals;
|
|
80
|
-
if ((suppliedEquals !== undefined &&
|
|
81
|
-
Object.hasOwn(suppliedEquals, input.property) &&
|
|
82
|
-
suppliedEquals[input.property] !== transition.from) ||
|
|
83
|
-
input.props?.absent?.includes(input.property) === true) {
|
|
84
|
-
throw new TypeError(`Lifecycle transition conditions conflict with property ${String(input.property)}.`);
|
|
85
|
-
}
|
|
86
|
-
core.expect.node({
|
|
87
|
-
node: existingEndpoint(input.node),
|
|
88
|
-
class: classKey(input.class),
|
|
89
|
-
props: conditions(input.class, {
|
|
90
|
-
...input.props,
|
|
91
|
-
equals: { ...suppliedEquals, ...from },
|
|
92
|
-
}),
|
|
93
|
-
});
|
|
94
|
-
core.updateNode({
|
|
95
|
-
node: existingEndpoint(input.node),
|
|
96
|
-
class: classKey(input.class),
|
|
97
|
-
props: delta(input.class, { set: to }),
|
|
98
|
-
});
|
|
51
|
+
authorStateTransition(core, domain, input);
|
|
99
52
|
return rich;
|
|
100
53
|
},
|
|
101
54
|
deleteNode(input) {
|
|
@@ -147,6 +100,3 @@ export function richMutationBuilder(core) {
|
|
|
147
100
|
function classKey(selected) {
|
|
148
101
|
return ClassKey(selected.key);
|
|
149
102
|
}
|
|
150
|
-
function invalidTransition(transition) {
|
|
151
|
-
throw new TypeError(`Lifecycle transition witness is not legal: ${transition.from} + ${transition.event} -> ${transition.to}.`);
|
|
152
|
-
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MutationBuilder } from '@astrale-os/kernel-core/graph/mutate';
|
|
2
|
+
import type { Domain, ResolvedClass } from '../../../platform/schema/index.js';
|
|
3
|
+
import type { StatePropertiesOfClass } from '../../../platform/schema/state-property/index.js';
|
|
4
|
+
import type { EventOf, StateMachine, StateOf } from '../../state/machine.js';
|
|
5
|
+
import type { ExistingEndpoint } from './endpoint.js';
|
|
6
|
+
import type { RichPropertyConditions, RichPropertyName } from './properties.js';
|
|
7
|
+
export type StatePropertyName<Class extends ResolvedClass<'node'>> = Extract<keyof StatePropertiesOfClass<Class>, RichPropertyName<Class>>;
|
|
8
|
+
export type StateMachineOf<Class extends ResolvedClass<'node'>, Property extends StatePropertyName<Class>> = Extract<StatePropertiesOfClass<Class>[Property], StateMachine>;
|
|
9
|
+
export interface StateTransitionInput<Class extends ResolvedClass<'node'>, Property extends StatePropertyName<Class>, Machine extends StateMachineOf<Class, Property>, From extends StateOf<Machine>, Event extends EventOf<Machine, From>> {
|
|
10
|
+
readonly node: ExistingEndpoint;
|
|
11
|
+
readonly class: Class;
|
|
12
|
+
readonly property: Property;
|
|
13
|
+
readonly from: From;
|
|
14
|
+
readonly event: Event;
|
|
15
|
+
readonly props?: RichPropertyConditions<Class>;
|
|
16
|
+
}
|
|
17
|
+
export declare function authorStateTransition<Class extends ResolvedClass<'node'>, Property extends StatePropertyName<Class>, Machine extends StateMachineOf<Class, Property>, From extends StateOf<Machine>, Event extends EventOf<Machine, From>>(core: MutationBuilder, domain: Domain, input: StateTransitionInput<Class, Property, Machine, From, Event>): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ClassKey } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
2
|
+
import { stateMachineFor } from '../../../platform/schema/state-property/registry.js';
|
|
3
|
+
import { stateTransitionFailure } from '../failure.js';
|
|
4
|
+
import { existingEndpoint } from './endpoint.js';
|
|
5
|
+
import { conditions, delta, resolvedProperty } from './properties.js';
|
|
6
|
+
export function authorStateTransition(core, domain, input) {
|
|
7
|
+
const node = existingEndpoint(input.node);
|
|
8
|
+
const selectedClass = input.class;
|
|
9
|
+
const property = input.property;
|
|
10
|
+
const fromState = input.from;
|
|
11
|
+
const event = input.event;
|
|
12
|
+
const suppliedProps = input.props;
|
|
13
|
+
const equals = suppliedProps?.equals;
|
|
14
|
+
const absent = suppliedProps?.absent;
|
|
15
|
+
const suppliedEquals = equals === undefined ? undefined : { ...equals };
|
|
16
|
+
const suppliedAbsent = absent === undefined ? undefined : [...absent];
|
|
17
|
+
const selected = resolvedProperty(selectedClass, property);
|
|
18
|
+
const selectedClassKey = ClassKey(selectedClass.key);
|
|
19
|
+
const machine = stateMachineFor(domain, selected);
|
|
20
|
+
if (machine === undefined) {
|
|
21
|
+
throw new TypeError(`State transition requires StateProperty ${selectedClassKey}.${String(property)} in the selected Domain.`);
|
|
22
|
+
}
|
|
23
|
+
const decision = machine.decide(fromState, event);
|
|
24
|
+
if (decision.kind === 'rejected')
|
|
25
|
+
throw stateTransitionFailure(decision.from, decision.event);
|
|
26
|
+
const from = { [property]: fromState };
|
|
27
|
+
const to = { [property]: decision.transition.to };
|
|
28
|
+
if ((suppliedEquals !== undefined &&
|
|
29
|
+
Object.hasOwn(suppliedEquals, property) &&
|
|
30
|
+
suppliedEquals[property] !== fromState) ||
|
|
31
|
+
suppliedAbsent?.includes(property) === true) {
|
|
32
|
+
throw new TypeError(`StateMachine transition conditions conflict with property ${String(property)}.`);
|
|
33
|
+
}
|
|
34
|
+
core.expect.node({
|
|
35
|
+
node,
|
|
36
|
+
class: selectedClassKey,
|
|
37
|
+
props: conditions(selectedClass, {
|
|
38
|
+
...(suppliedAbsent === undefined ? {} : { absent: suppliedAbsent }),
|
|
39
|
+
equals: { ...suppliedEquals, ...from },
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
core.updateNode({
|
|
43
|
+
node,
|
|
44
|
+
class: selectedClassKey,
|
|
45
|
+
props: delta(selectedClass, { set: to }),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { MutationResult } from '@astrale-os/kernel-core/graph/mutate';
|
|
2
2
|
import type { Domain, schema } from '../../platform/schema/index.js';
|
|
3
|
+
import type { StatefulDomainOf } from '../../platform/schema/state-property/index.js';
|
|
3
4
|
import type { Mutation, MutationDefinition } from './mutation.js';
|
|
4
5
|
type ResolvedDomainOf<Schema extends schema.DomainSchema> = Extract<schema.DomainOf<Schema>, Domain>;
|
|
6
|
+
type AuthoringDomainOf<Schema extends schema.DomainSchema> = StatefulDomainOf<Schema>;
|
|
5
7
|
interface MutationProjector<Schema extends schema.DomainSchema> {
|
|
6
|
-
<Input, Output = MutationResult>(project: (domain:
|
|
8
|
+
<Input, Output = MutationResult>(project: (domain: AuthoringDomainOf<Schema>) => MutationDefinition<Input, Output>): Mutation<ResolvedDomainOf<Schema>, Input, Output>;
|
|
7
9
|
}
|
|
8
10
|
/** Define one inert Mutation recipe projected from its exact loaded Domain. */
|
|
9
11
|
export declare function defineMutation<Schema extends schema.DomainSchema>(): MutationProjector<Schema>;
|
|
@@ -9,7 +9,7 @@ export async function executeMutation(client, domain, definition, input) {
|
|
|
9
9
|
}
|
|
10
10
|
const operation = realizeMutation(definition, domain);
|
|
11
11
|
try {
|
|
12
|
-
const ast = MutationAST.build((builder) => operation.build(input, richMutationBuilder(builder)));
|
|
12
|
+
const ast = MutationAST.build((builder) => operation.build(input, richMutationBuilder(builder, domain)));
|
|
13
13
|
return projectMutation(operation, await client.mutate(ast, { expected: domain.closure }), input);
|
|
14
14
|
}
|
|
15
15
|
catch (cause) {
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
export interface
|
|
2
|
-
readonly code: string
|
|
1
|
+
export interface KernelMutationFailure extends Error {
|
|
2
|
+
readonly code: `MUTATION_${string}`;
|
|
3
3
|
readonly outcomeKnown: boolean;
|
|
4
4
|
}
|
|
5
|
+
export interface StateTransitionFailure extends Error {
|
|
6
|
+
readonly code: 'STATE_TRANSITION_ILLEGAL';
|
|
7
|
+
readonly outcomeKnown: true;
|
|
8
|
+
readonly from: string;
|
|
9
|
+
readonly event: string;
|
|
10
|
+
}
|
|
11
|
+
export type MutationFailure = KernelMutationFailure | StateTransitionFailure;
|
|
12
|
+
export declare function stateTransitionFailure(from: string, event: string): StateTransitionFailure;
|
|
5
13
|
export declare function mutationFailure(input: unknown): MutationFailure | undefined;
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
const UNKNOWN_OUTCOME_CODES = new Set(['MUTATION_OUTCOME_UNKNOWN']);
|
|
2
|
+
const localStateTransitionFailures = new WeakSet();
|
|
3
|
+
export function stateTransitionFailure(from, event) {
|
|
4
|
+
const failure = Object.assign(new Error(`State transition is illegal: ${from} + ${event}.`), {
|
|
5
|
+
code: 'STATE_TRANSITION_ILLEGAL',
|
|
6
|
+
outcomeKnown: true,
|
|
7
|
+
from,
|
|
8
|
+
event,
|
|
9
|
+
});
|
|
10
|
+
localStateTransitionFailures.add(failure);
|
|
11
|
+
return failure;
|
|
12
|
+
}
|
|
2
13
|
export function mutationFailure(input) {
|
|
3
14
|
if (!(input instanceof Error))
|
|
4
15
|
return undefined;
|
|
@@ -8,6 +19,15 @@ export function mutationFailure(input) {
|
|
|
8
19
|
const explicit = input.outcomeKnown;
|
|
9
20
|
if (explicit !== undefined && typeof explicit !== 'boolean')
|
|
10
21
|
return undefined;
|
|
22
|
+
if (code === 'STATE_TRANSITION_ILLEGAL') {
|
|
23
|
+
if (!localStateTransitionFailures.has(input))
|
|
24
|
+
return undefined;
|
|
25
|
+
const from = Reflect.get(input, 'from');
|
|
26
|
+
const event = Reflect.get(input, 'event');
|
|
27
|
+
if (typeof from !== 'string' || typeof event !== 'string' || explicit === false)
|
|
28
|
+
return undefined;
|
|
29
|
+
return Object.assign(input, { code, outcomeKnown: true, from, event });
|
|
30
|
+
}
|
|
11
31
|
return Object.assign(input, {
|
|
12
32
|
code,
|
|
13
33
|
outcomeKnown: explicit !== false && !UNKNOWN_OUTCOME_CODES.has(code),
|
|
@@ -15,9 +35,12 @@ export function mutationFailure(input) {
|
|
|
15
35
|
}
|
|
16
36
|
function mutationCode(input) {
|
|
17
37
|
const value = input;
|
|
38
|
+
if (value.code === 'STATE_TRANSITION_ILLEGAL')
|
|
39
|
+
return value.code;
|
|
18
40
|
for (const candidate of [value.code, nestedCode(value.reason), nestedCode(value.data)]) {
|
|
19
|
-
if (typeof candidate === 'string' && candidate.startsWith('MUTATION_'))
|
|
41
|
+
if (typeof candidate === 'string' && candidate.startsWith('MUTATION_')) {
|
|
20
42
|
return candidate;
|
|
43
|
+
}
|
|
21
44
|
}
|
|
22
45
|
return undefined;
|
|
23
46
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { rememberStateMachine } from './authority.js';
|
|
2
3
|
import { relation } from './relation.js';
|
|
3
4
|
export function stateMachine(input) {
|
|
4
5
|
const admitted = relation(input);
|
|
@@ -10,7 +11,7 @@ export function stateMachine(input) {
|
|
|
10
11
|
const eventSchema = z.enum(events);
|
|
11
12
|
const stateSet = new Set(states);
|
|
12
13
|
const eventSet = new Set(events);
|
|
13
|
-
|
|
14
|
+
const machine = Object.freeze({
|
|
14
15
|
initial: admitted.initial,
|
|
15
16
|
stateSchema,
|
|
16
17
|
eventSchema,
|
|
@@ -40,6 +41,8 @@ export function stateMachine(input) {
|
|
|
40
41
|
});
|
|
41
42
|
},
|
|
42
43
|
});
|
|
44
|
+
rememberStateMachine(machine);
|
|
45
|
+
return machine;
|
|
43
46
|
}
|
|
44
47
|
function admitState(state, states) {
|
|
45
48
|
if (!states.has(state))
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { KernelSchema as CoreKernelSchema } from '@astrale-os/kernel-core/schema';
|
|
2
|
+
export { defineSchema, edgeClass, nodeClass, stateProperty } from './state-property/index.js';
|
|
3
|
+
export type { StateClass, StateProperty, StateSchema } from './state-property/index.js';
|
|
2
4
|
export * from '@astrale-os/kernel-dsl/v1';
|
|
3
5
|
export { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
4
6
|
export type { CallablePolicyBuilder, CallablePolicyContext, CallableProjection, AuthoredMethodProjection, AuthoredPropertyProjection, ClassIdentityProjection, ClassOwnerProjection, CoreEdge, CoreEndpoint, CoreNode, DefinedSchema, DirectedEdgeClass, ClassReferenceProjection, ClassProjection, FunctionProjection, EdgeClass, Function, Method, MethodProjection, Lazy, NodeClass, Policy, Property, PropertyProjection, ResolvedDomainOfInput, View, } from '@astrale-os/kernel-dsl/v1/builder';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { KernelSchema as CoreKernelSchema } from '@astrale-os/kernel-core/schema';
|
|
2
2
|
/** Canonical Schema authoring, compilation, loading, and resolved Domain surface. */
|
|
3
3
|
import { schema as language } from '@astrale-os/kernel-dsl/v1';
|
|
4
|
+
export { defineSchema, edgeClass, nodeClass, stateProperty } from './state-property/index.js';
|
|
4
5
|
export * from '@astrale-os/kernel-dsl/v1';
|
|
5
6
|
export { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
6
7
|
/** Kernel-owned Schema values used by authored product Schemas and graph operations. */
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DefinedSchema, NodeClassInput, PropertyOptions, ResolvedDomainOfInput, SchemaInput as DomainSchemaInput } from '@astrale-os/kernel-dsl/v1/builder';
|
|
2
|
+
import { nodeClass as languageNodeClass } from '@astrale-os/kernel-dsl/v1';
|
|
3
|
+
import type { StateMachine } from '../../../application/state/machine.js';
|
|
4
|
+
import type { StateClass, StateClassesOfInput, StateDependenciesOfInput, StatePropertiesOfConfig, StateProperty, StateSchema } from './model.js';
|
|
5
|
+
interface StatePropertyOptions {
|
|
6
|
+
readonly visibility?: PropertyOptions['visibility'];
|
|
7
|
+
readonly description?: string;
|
|
8
|
+
}
|
|
9
|
+
type RequiredStatePropertyOptions<Options extends StatePropertyOptions> = Readonly<Options & {
|
|
10
|
+
readonly required: true;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function stateProperty<const Machine extends StateMachine, const Options extends StatePropertyOptions = Record<never, never>>(machine: Machine, options?: Options & Record<Exclude<keyof Options, keyof StatePropertyOptions>, never>): StateProperty<Machine, RequiredStatePropertyOptions<Options>>;
|
|
13
|
+
export declare function nodeClass<const Config extends NodeClassInput = Record<never, never>>(config?: Config & Record<Exclude<keyof Config, keyof NodeClassInput>, never>): ReturnType<typeof languageNodeClass<Config>> & StateClass<StatePropertiesOfConfig<Config>>;
|
|
14
|
+
export declare function defineSchema<const Origin extends string, const Input extends DomainSchemaInput>(origin: Origin, input: Input): DefinedSchema<Origin, ResolvedDomainOfInput<Origin, Input>> & StateSchema<StateClassesOfInput<Input>, StateDependenciesOfInput<Input>>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { defineSchema as defineLanguageSchema, nodeClass as languageNodeClass, property, schema, } from '@astrale-os/kernel-dsl/v1';
|
|
2
|
+
import { isStateMachine } from '../../../application/state/authority.js';
|
|
3
|
+
import { rememberStateClass, rememberStateProperty, rememberStateSchema } from './registry.js';
|
|
4
|
+
export function stateProperty(machine, options) {
|
|
5
|
+
if (!isStateMachine(machine))
|
|
6
|
+
throw new TypeError('stateProperty requires an SDK StateMachine.');
|
|
7
|
+
if (options !== undefined &&
|
|
8
|
+
(typeof options !== 'object' ||
|
|
9
|
+
options === null ||
|
|
10
|
+
Array.isArray(options) ||
|
|
11
|
+
Reflect.ownKeys(options).some((key) => key !== 'visibility' && key !== 'description'))) {
|
|
12
|
+
throw new TypeError('stateProperty options are visibility and description only.');
|
|
13
|
+
}
|
|
14
|
+
const value = property(machine.stateSchema, {
|
|
15
|
+
...options,
|
|
16
|
+
required: true,
|
|
17
|
+
});
|
|
18
|
+
rememberStateProperty(value, machine);
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
export function nodeClass(config) {
|
|
22
|
+
const captured = captureNodeClassInput(config);
|
|
23
|
+
const value = languageNodeClass(captured);
|
|
24
|
+
rememberStateClass(value, captured?.properties);
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
export function defineSchema(origin, input) {
|
|
28
|
+
const captured = captureSchemaInput(input);
|
|
29
|
+
const source = defineLanguageSchema(origin, captured);
|
|
30
|
+
rememberStateSchema(source, captured, schema.resolve(source));
|
|
31
|
+
return source;
|
|
32
|
+
}
|
|
33
|
+
function captureNodeClassInput(config) {
|
|
34
|
+
if (config === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
const captured = { ...config };
|
|
37
|
+
return {
|
|
38
|
+
...captured,
|
|
39
|
+
...(captured.properties === undefined ? {} : { properties: { ...captured.properties } }),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function captureSchemaInput(input) {
|
|
43
|
+
const captured = { ...input };
|
|
44
|
+
return {
|
|
45
|
+
...captured,
|
|
46
|
+
...(captured.dependencies === undefined ? {} : { dependencies: { ...captured.dependencies } }),
|
|
47
|
+
...(captured.classes === undefined ? {} : { classes: { ...captured.classes } }),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DirectedEdgeInput, UndirectedEdgeInput } from '@astrale-os/kernel-dsl/v1/builder';
|
|
2
|
+
import { edgeClass as languageEdgeClass } from '@astrale-os/kernel-dsl/v1';
|
|
3
|
+
import type { StateProperty } from './model.js';
|
|
4
|
+
interface EdgeClassAuthoring {
|
|
5
|
+
directed<const Input extends Omit<DirectedEdgeInput, 'orientation'>>(input: Parameters<typeof languageEdgeClass.directed<Input>>[0] & WithoutStateProperties<Input>): ReturnType<typeof languageEdgeClass.directed<Input>>;
|
|
6
|
+
undirected<const Input extends Omit<UndirectedEdgeInput, 'orientation'>>(input: Parameters<typeof languageEdgeClass.undirected<Input>>[0] & WithoutStateProperties<Input>): ReturnType<typeof languageEdgeClass.undirected<Input>>;
|
|
7
|
+
}
|
|
8
|
+
type WithoutStateProperties<Input> = Input extends {
|
|
9
|
+
readonly properties?: infer Properties extends Readonly<Record<string, unknown>>;
|
|
10
|
+
} ? {
|
|
11
|
+
readonly properties?: {
|
|
12
|
+
readonly [Name in keyof Properties]: Properties[Name] extends StateProperty ? never : Properties[Name];
|
|
13
|
+
};
|
|
14
|
+
} : unknown;
|
|
15
|
+
export declare const edgeClass: Readonly<EdgeClassAuthoring>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { edgeClass as languageEdgeClass } from '@astrale-os/kernel-dsl/v1';
|
|
2
|
+
import { isStateProperty } from './registry.js';
|
|
3
|
+
export const edgeClass = Object.freeze({
|
|
4
|
+
directed(input) {
|
|
5
|
+
return languageEdgeClass.directed(captureEdgeInput(input));
|
|
6
|
+
},
|
|
7
|
+
undirected(input) {
|
|
8
|
+
return languageEdgeClass.undirected(captureEdgeInput(input));
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
function captureEdgeInput(input) {
|
|
12
|
+
const captured = { ...input };
|
|
13
|
+
const properties = captured.properties === undefined ? undefined : { ...captured.properties };
|
|
14
|
+
if (Object.values(properties ?? {}).some(isStateProperty)) {
|
|
15
|
+
throw new TypeError('StateProperty is supported only on Node Classes.');
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
...captured,
|
|
19
|
+
...(properties === undefined ? {} : { properties }),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { defineSchema, nodeClass, stateProperty } from './authoring.js';
|
|
2
|
+
export { edgeClass } from './edge.js';
|
|
3
|
+
export type { StateClass, StatePropertiesOfClass, StateProperty, StateSchema, StatefulDomainOf, } from './model.js';
|
|
4
|
+
export { stateMachineFor } from './registry.js';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Domain, ResolvedClass } from '@astrale-os/kernel-dsl/v1';
|
|
2
|
+
import type { NodeClassInput, Property, PropertyOptions, SchemaInput as DomainSchemaInput } from '@astrale-os/kernel-dsl/v1/builder';
|
|
3
|
+
import type * as languageSchema from '@astrale-os/kernel-dsl/v1/schema';
|
|
4
|
+
import type { StateMachine } from '../../../application/state/machine.js';
|
|
5
|
+
declare const STATE_PROPERTY_MACHINE: unique symbol;
|
|
6
|
+
declare const STATE_CLASS_PROPERTIES: unique symbol;
|
|
7
|
+
declare const STATE_SCHEMA_CLASSES: unique symbol;
|
|
8
|
+
export interface StateProperty<Machine extends StateMachine = StateMachine, Options extends PropertyOptions = PropertyOptions> extends Property<Machine['stateSchema'], Options> {
|
|
9
|
+
readonly [STATE_PROPERTY_MACHINE]: Machine;
|
|
10
|
+
}
|
|
11
|
+
export interface StateClass<Properties extends Readonly<Record<string, StateMachine>>> {
|
|
12
|
+
readonly [STATE_CLASS_PROPERTIES]: Properties;
|
|
13
|
+
}
|
|
14
|
+
export interface StateSchema<Classes extends Readonly<Record<string, Readonly<Record<string, StateMachine>>>>, Dependencies extends Readonly<Record<string, languageSchema.DomainSchema>> = Readonly<Record<never, never>>> {
|
|
15
|
+
readonly [STATE_SCHEMA_CLASSES]: {
|
|
16
|
+
readonly classes: Classes;
|
|
17
|
+
readonly dependencies: Dependencies;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export type StatePropertiesOfConfig<Config extends NodeClassInput> = Merge<ParentStateProperties<Config>, OwnStateProperties<Config>>;
|
|
21
|
+
export type StateClassesOfInput<Input extends DomainSchemaInput> = Input extends {
|
|
22
|
+
readonly classes?: infer Classes extends Readonly<Record<string, unknown>>;
|
|
23
|
+
} ? {
|
|
24
|
+
readonly [Name in keyof Classes]: Classes[Name] extends StateClass<infer Properties> ? Properties : Readonly<Record<never, never>>;
|
|
25
|
+
} : Readonly<Record<never, never>>;
|
|
26
|
+
export type StateDependenciesOfInput<Input extends DomainSchemaInput> = Input extends {
|
|
27
|
+
readonly dependencies?: infer Dependencies extends Readonly<Record<string, languageSchema.DomainSchema>>;
|
|
28
|
+
} ? Dependencies : Readonly<Record<never, never>>;
|
|
29
|
+
export type StatefulDomainOf<Schema extends languageSchema.DomainSchema> = Schema extends StateSchema<infer Classes, infer Dependencies> ? Extract<languageSchema.DomainOf<Schema>, Domain> extends infer DomainValue extends Domain ? DomainValue & {
|
|
30
|
+
readonly classes: {
|
|
31
|
+
readonly [Name in keyof DomainValue['classes']]: DomainValue['classes'][Name] & StateClass<Name extends keyof Classes ? Extract<Classes[Name], Readonly<Record<string, StateMachine>>> : Readonly<Record<never, never>>>;
|
|
32
|
+
};
|
|
33
|
+
readonly dependencies: {
|
|
34
|
+
readonly [Name in keyof DomainValue['dependencies']]: Name extends keyof Dependencies ? Dependencies[Name] extends languageSchema.DomainSchema ? StatefulDomainOf<Dependencies[Name]> : DomainValue['dependencies'][Name] : DomainValue['dependencies'][Name];
|
|
35
|
+
};
|
|
36
|
+
} : never : Extract<languageSchema.DomainOf<Schema>, Domain>;
|
|
37
|
+
export type StatePropertiesOfClass<Class extends ResolvedClass<'node'>> = Class extends StateClass<infer Properties> ? Properties : Readonly<Record<never, never>>;
|
|
38
|
+
type OwnStateProperties<Config extends NodeClassInput> = Config extends {
|
|
39
|
+
readonly properties?: infer Properties extends Readonly<Record<string, unknown>>;
|
|
40
|
+
} ? {
|
|
41
|
+
readonly [Name in keyof Properties as Properties[Name] extends StateProperty ? Name : never]: Properties[Name] extends StateProperty<infer Machine> ? Machine : never;
|
|
42
|
+
} : Readonly<Record<never, never>>;
|
|
43
|
+
type ParentStateProperties<Config extends NodeClassInput> = Config extends {
|
|
44
|
+
readonly extends?: readonly (infer Parent)[];
|
|
45
|
+
} ? UnionToIntersection<StatePropertiesOfReference<Parent>> : Readonly<Record<never, never>>;
|
|
46
|
+
type StatePropertiesOfReference<Value, Depth extends readonly unknown[] = []> = Depth['length'] extends 32 ? Readonly<Record<never, never>> : Value extends () => infer Resolved ? StatePropertiesOfReference<Resolved, readonly [...Depth, unknown]> : Value extends StateClass<infer Properties> ? Properties : Readonly<Record<never, never>>;
|
|
47
|
+
type UnionToIntersection<Value> = (Value extends unknown ? (input: Value) => void : never) extends (input: infer Intersection) => void ? Intersection : never;
|
|
48
|
+
type Merge<Parent, Own> = Readonly<Omit<Parent, keyof Own> & Own>;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Domain, ResolvedProperty } from '@astrale-os/kernel-dsl/v1';
|
|
2
|
+
import type { Property, SchemaInput as DomainSchemaInput } from '@astrale-os/kernel-dsl/v1/builder';
|
|
3
|
+
import type * as languageSchema from '@astrale-os/kernel-dsl/v1/schema';
|
|
4
|
+
import type { StateMachine } from '../../../application/state/machine.js';
|
|
5
|
+
export declare function rememberStateProperty(property: Property, machine: StateMachine): void;
|
|
6
|
+
export declare function isStateProperty(value: unknown): boolean;
|
|
7
|
+
export declare function rememberStateClass(selected: object, properties?: Readonly<Record<string, unknown>>): void;
|
|
8
|
+
export declare function rememberStateSchema(source: languageSchema.DomainSchema, input: DomainSchemaInput, domain: Domain): void;
|
|
9
|
+
export declare function stateMachineFor(domain: Domain, selected: ResolvedProperty<unknown>): StateMachine | undefined;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const propertyMachines = new WeakMap();
|
|
2
|
+
const classProperties = new WeakMap();
|
|
3
|
+
const schemaProperties = new WeakMap();
|
|
4
|
+
export function rememberStateProperty(property, machine) {
|
|
5
|
+
propertyMachines.set(property, machine);
|
|
6
|
+
}
|
|
7
|
+
export function isStateProperty(value) {
|
|
8
|
+
return typeof value === 'object' && value !== null && propertyMachines.has(value);
|
|
9
|
+
}
|
|
10
|
+
export function rememberStateClass(selected, properties) {
|
|
11
|
+
const machines = new Map();
|
|
12
|
+
for (const [name, property] of Object.entries(properties ?? {})) {
|
|
13
|
+
if (typeof property !== 'object' || property === null)
|
|
14
|
+
continue;
|
|
15
|
+
const machine = propertyMachines.get(property);
|
|
16
|
+
if (machine !== undefined)
|
|
17
|
+
machines.set(name, machine);
|
|
18
|
+
}
|
|
19
|
+
classProperties.set(selected, machines);
|
|
20
|
+
}
|
|
21
|
+
export function rememberStateSchema(source, input, domain) {
|
|
22
|
+
const byKey = new Map();
|
|
23
|
+
for (const dependency of Object.values(input.dependencies ?? {})) {
|
|
24
|
+
const associated = schemaProperties.get(dependency);
|
|
25
|
+
if (associated === undefined)
|
|
26
|
+
continue;
|
|
27
|
+
for (const [key, machine] of associated.byKey)
|
|
28
|
+
bindKey(byKey, key, machine);
|
|
29
|
+
}
|
|
30
|
+
for (const [className, authored] of Object.entries(input.classes ?? {})) {
|
|
31
|
+
const machines = classProperties.get(authored);
|
|
32
|
+
if (machines === undefined || machines.size === 0)
|
|
33
|
+
continue;
|
|
34
|
+
const resolved = Reflect.get(domain.classes, className);
|
|
35
|
+
if (resolved === undefined)
|
|
36
|
+
continue;
|
|
37
|
+
for (const [propertyName, machine] of machines) {
|
|
38
|
+
const property = Reflect.get(resolved.self.properties, propertyName);
|
|
39
|
+
if (property !== undefined)
|
|
40
|
+
bindKey(byKey, property.key, machine);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const byProperty = new WeakMap();
|
|
44
|
+
bindDomainProperties(domain, byKey, byProperty, new Set());
|
|
45
|
+
schemaProperties.set(source, { byKey, byProperty });
|
|
46
|
+
}
|
|
47
|
+
export function stateMachineFor(domain, selected) {
|
|
48
|
+
const source = Reflect.get(domain, 'source');
|
|
49
|
+
if (typeof source !== 'object' || source === null)
|
|
50
|
+
return undefined;
|
|
51
|
+
return schemaProperties.get(source)?.byProperty.get(selected);
|
|
52
|
+
}
|
|
53
|
+
function bindKey(byKey, key, machine) {
|
|
54
|
+
const existing = byKey.get(key);
|
|
55
|
+
if (existing !== undefined && existing !== machine) {
|
|
56
|
+
throw new TypeError(`StateProperty ${key} has more than one StateMachine authority.`);
|
|
57
|
+
}
|
|
58
|
+
byKey.set(key, machine);
|
|
59
|
+
}
|
|
60
|
+
function bindDomainProperties(domain, byKey, byProperty, seen) {
|
|
61
|
+
if (seen.has(domain))
|
|
62
|
+
return;
|
|
63
|
+
seen.add(domain);
|
|
64
|
+
for (const definition of domain.definitions('class')) {
|
|
65
|
+
for (const property of definition.self.properties) {
|
|
66
|
+
const machine = byKey.get(property.key);
|
|
67
|
+
if (machine !== undefined)
|
|
68
|
+
byProperty.set(property, machine);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const dependency of Object.values(domain.dependencies)) {
|
|
72
|
+
bindDomainProperties(dependency, byKey, byProperty, seen);
|
|
73
|
+
}
|
|
74
|
+
}
|