@astrale-os/kernel-dsl 0.2.0-beta.17 → 0.2.0-beta.19
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 +14 -0
- package/dist/v1/builder/class/authoring.d.ts +19 -9
- package/dist/v1/builder/class/property.d.ts +13 -6
- package/dist/v1/builder/class/property.js +11 -0
- package/dist/v1/builder/index.d.ts +1 -1
- package/dist/v1/builder/index.js +1 -1
- package/dist/v1/builder/lowering/value/compile.js +8 -0
- package/dist/v1/builder/schema/projection.d.ts +8 -6
- package/dist/v1/compiled/admission/accept.js +38 -6
- package/dist/v1/compiled/compiler/compile.js +1 -0
- package/dist/v1/index.d.ts +3 -1
- package/dist/v1/index.js +2 -1
- package/dist/v1/language/class/model/property.d.ts +1 -0
- package/dist/v1/language/class/resolved.d.ts +4 -2
- package/dist/v1/language/index.d.ts +2 -0
- package/dist/v1/language/index.js +1 -0
- package/dist/v1/language/state/index.d.ts +3 -0
- package/dist/v1/language/state/index.js +1 -0
- package/dist/v1/language/state/machine.d.ts +28 -0
- package/dist/v1/language/state/machine.js +40 -0
- package/dist/v1/language/state/topology.d.ts +14 -0
- package/dist/v1/language/state/topology.js +69 -0
- package/dist/v1/schema/analysis/class.d.ts +2 -0
- package/dist/v1/schema/analysis/class.js +1 -0
- package/dist/v1/schema/compatibility/meaning.js +1 -0
- package/dist/v1/schema/diagnostic.d.ts +1 -1
- package/dist/v1/schema/normalization/domain.js +4 -0
- package/dist/v1/schema/validation/class.js +29 -3
- package/dist/v1/schema/validation/domain.structure.js +31 -0
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.0-beta.19](https://github.com/astrale-os/kernel/compare/kernel-dsl-v0.2.0-beta.18...kernel-dsl-v0.2.0-beta.19) (2026-08-29)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **system:** clear specification diagnostic debt ([#539](https://github.com/astrale-os/kernel/issues/539)) ([2e88cf5](https://github.com/astrale-os/kernel/commit/2e88cf5a84c7026122f680249bbea20202b92ac6))
|
|
9
|
+
|
|
10
|
+
## [0.2.0-beta.18](https://github.com/astrale-os/kernel/compare/kernel-dsl-v0.2.0-beta.17...kernel-dsl-v0.2.0-beta.18) (2026-08-29)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **dsl:** make StateProperty topology portable ([#534](https://github.com/astrale-os/kernel/issues/534)) ([f71cf87](https://github.com/astrale-os/kernel/commit/f71cf879e68475ffd002cafe6688be0649eb9c49))
|
|
16
|
+
|
|
3
17
|
## [0.2.0-beta.17](https://github.com/astrale-os/kernel/compare/kernel-dsl-v0.2.0-beta.16...kernel-dsl-v0.2.0-beta.17) (2026-08-29)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -6,12 +6,13 @@ import type { ResolvedMethod } from '../../language/callable/resolved.js';
|
|
|
6
6
|
import type { MediaType } from '../../language/class/model/data.js';
|
|
7
7
|
import type { EdgeCount } from '../../language/class/model/edge.js';
|
|
8
8
|
import type { ResolvedClass, ResolvedProperty } from '../../language/class/resolved.js';
|
|
9
|
+
import type { StateTopology, StateTopologyOf } from '../../language/state/topology.js';
|
|
9
10
|
import type { Method, ProjectionOfCallable } from '../callable/authoring.js';
|
|
10
11
|
import type { Policy } from '../policy/authoring.js';
|
|
11
12
|
import type { Lazy } from '../schema/lazy.js';
|
|
12
13
|
import type { ClassProjectionOfResolved } from '../schema/projection.js';
|
|
13
14
|
import type { TypedValueSchema } from '../value/schema.js';
|
|
14
|
-
import type { AutoProperty, Property, PropertyOptions, ValueSchemaInput } from './property.js';
|
|
15
|
+
import type { AutoProperty, Property, PropertyOptions, PropertyStateMachineOf, ValueSchemaInput } from './property.js';
|
|
15
16
|
import type { PropertyRecordInput } from './property.js';
|
|
16
17
|
import { type AUTHORING } from '../schema/handle.js';
|
|
17
18
|
declare const CLASS_TYPE: unique symbol;
|
|
@@ -88,26 +89,27 @@ export type ClassInput = ({
|
|
|
88
89
|
readonly abstract: boolean;
|
|
89
90
|
} & Omit<UndirectedEdgeInput, 'orientation'>);
|
|
90
91
|
export type Class = NodeClass | EdgeClass;
|
|
91
|
-
export interface AuthoredPropertyProjection<out Input = unknown, out Output = unknown, out Required extends boolean = boolean, out Name extends string = string, out Owner = 'self'> {
|
|
92
|
+
export interface AuthoredPropertyProjection<out Input = unknown, out Output = unknown, out Required extends boolean = boolean, out Name extends string = string, out Owner = 'self', out State extends StateTopology | undefined = StateTopology | undefined> {
|
|
92
93
|
readonly input: Input;
|
|
93
94
|
readonly output: Output;
|
|
94
95
|
readonly required: Required;
|
|
95
96
|
readonly name: Name;
|
|
96
97
|
readonly owner: Owner;
|
|
98
|
+
readonly state?: State;
|
|
97
99
|
}
|
|
98
100
|
export interface AuthoredMethodProjection<out Value extends Method = Method, out Name extends string = string, out Owner = 'self'> {
|
|
99
101
|
readonly value: Value;
|
|
100
102
|
readonly name: Name;
|
|
101
103
|
readonly owner: Owner;
|
|
102
104
|
}
|
|
103
|
-
type PropertySchemaOf<Field> = Field extends AutoProperty<infer Value> ? PropertySchemaOf<Value> : Field extends Property<infer Schema, PropertyOptions> ? Schema : Field;
|
|
104
|
-
type PropertyOptionsOf<Field> = Field extends AutoProperty<infer Value> ? PropertyOptionsOf<Value> : Field extends Property<ValueSchemaInput, infer Options> ? Options : Record<never, never>;
|
|
105
|
+
type PropertySchemaOf<Field> = Field extends AutoProperty<infer Value> ? PropertySchemaOf<Value> : Field extends Property<infer Schema, PropertyOptions, unknown> ? Schema : Field;
|
|
106
|
+
type PropertyOptionsOf<Field> = Field extends AutoProperty<infer Value> ? PropertyOptionsOf<Value> : Field extends Property<ValueSchemaInput, infer Options, unknown> ? Options : Record<never, never>;
|
|
105
107
|
type ValueInputOf<Schema> = Schema extends TypedValueSchema<infer Value> ? Value : Schema extends z.ZodType ? z.input<Schema> : PortableValue;
|
|
106
108
|
type ValueOutputOf<Schema> = Schema extends TypedValueSchema<infer Value> ? Value : Schema extends z.ZodType ? z.output<Schema> : PortableValue;
|
|
107
109
|
type PropertyRequiredOf<Field> = PropertyOptionsOf<Field> extends {
|
|
108
110
|
readonly required: infer Required extends boolean;
|
|
109
111
|
} ? Required : undefined extends ValueInputOf<PropertySchemaOf<Field>> ? false : true;
|
|
110
|
-
type PropertyProjectionOf<Name extends string, Field> = AuthoredPropertyProjection<Field extends AutoProperty ? never : ValueInputOf<PropertySchemaOf<Field>>, ValueOutputOf<PropertySchemaOf<Field>>, PropertyRequiredOf<Field>, Name>;
|
|
112
|
+
type PropertyProjectionOf<Name extends string, Field> = AuthoredPropertyProjection<Field extends AutoProperty ? never : ValueInputOf<PropertySchemaOf<Field>>, ValueOutputOf<PropertySchemaOf<Field>>, PropertyRequiredOf<Field>, Name, 'self', Field extends Property ? StateTopologyOf<PropertyStateMachineOf<Field>> : undefined>;
|
|
111
113
|
type PropertyProjectionsOf<Input> = Input extends Readonly<Record<string, unknown>> ? {
|
|
112
114
|
readonly [Name in keyof Input]: PropertyProjectionOf<Extract<Name, string>, Input[Name]>;
|
|
113
115
|
} : Readonly<Record<never, never>>;
|
|
@@ -124,7 +126,7 @@ type ParentPropertyProjectionsOf<Input> = Input extends {
|
|
|
124
126
|
type EffectivePropertyProjectionsOf<Input> = PropertyProjectionEntriesOf<'properties' extends keyof Input ? Input extends {
|
|
125
127
|
readonly properties?: infer Value;
|
|
126
128
|
} ? NonNullable<Value> : never : never> | ParentPropertyProjectionsOf<Input>;
|
|
127
|
-
type BindPropertyOwners<Entries, Owner> = Entries extends AuthoredPropertyProjection<infer Input, infer Output, infer Required, infer Name, infer ExistingOwner> ? AuthoredPropertyProjection<Input, Output, Required, Name, ExistingOwner extends 'self' ? Owner : ExistingOwner> : Entries;
|
|
129
|
+
type BindPropertyOwners<Entries, Owner> = Entries extends AuthoredPropertyProjection<infer Input, infer Output, infer Required, infer Name, infer ExistingOwner, infer State> ? AuthoredPropertyProjection<Input, Output, Required, Name, ExistingOwner extends 'self' ? Owner : ExistingOwner, State> : Entries;
|
|
128
130
|
type MethodFieldsOf<Input> = 'methods' extends keyof Input ? Input extends {
|
|
129
131
|
readonly methods?: infer Methods extends Readonly<Record<string, Method>>;
|
|
130
132
|
} ? NonNullable<Methods> : Readonly<Record<never, never>> : Readonly<Record<never, never>>;
|
|
@@ -208,7 +210,7 @@ type UndirectedEdgeClassIdentity<Config> = Config extends {
|
|
|
208
210
|
readonly endpoints: Endpoints;
|
|
209
211
|
readonly constraints: Constraints;
|
|
210
212
|
} : never;
|
|
211
|
-
type OwnerErasedPropertyProjection<Entry> = Entry extends AuthoredPropertyProjection<infer Input, infer Output, infer Required, infer Name, unknown> ? AuthoredPropertyProjection<Input, Output, Required, Name, unknown> : Entry extends ResolvedProperty<infer Value, infer Required> & {
|
|
213
|
+
type OwnerErasedPropertyProjection<Entry> = Entry extends AuthoredPropertyProjection<infer Input, infer Output, infer Required, infer Name, unknown, infer State> ? AuthoredPropertyProjection<Input, Output, Required, Name, unknown, State> : Entry extends ResolvedProperty<infer Value, infer Required, infer State> & {
|
|
212
214
|
readonly name: infer Name;
|
|
213
215
|
readonly key: infer Key;
|
|
214
216
|
} ? {
|
|
@@ -216,6 +218,7 @@ type OwnerErasedPropertyProjection<Entry> = Entry extends AuthoredPropertyProjec
|
|
|
216
218
|
readonly required: Required;
|
|
217
219
|
readonly name: Name;
|
|
218
220
|
readonly key: Key;
|
|
221
|
+
readonly state?: State;
|
|
219
222
|
} : never;
|
|
220
223
|
type OwnerErasedMethodProjection<Entry> = Entry extends AuthoredMethodProjection<infer Value, infer Name, unknown> ? AuthoredMethodProjection<Value, Name, unknown> : Entry extends ResolvedMethod<infer Input, infer Output, infer Static, infer Inheritance, infer Auth, infer _Policy, infer Key> & {
|
|
221
224
|
readonly name: infer Name;
|
|
@@ -355,11 +358,18 @@ export declare const edgeClass: Readonly<{
|
|
|
355
358
|
directed<const Input extends Omit<DirectedEdgeInput, 'orientation'>>(input: Input & {
|
|
356
359
|
readonly source: ExactEndpointInput<Input['source'], DirectedSourceInput>;
|
|
357
360
|
readonly target: ExactEndpointInput<Input['target'], DirectedTargetInput>;
|
|
358
|
-
}): DirectedEdgeClass<CompactDirectedInput<Input>>;
|
|
361
|
+
} & WithoutStateProperties<Input>): DirectedEdgeClass<CompactDirectedInput<Input>>;
|
|
359
362
|
undirected<const Input extends Omit<UndirectedEdgeInput, 'orientation'>>(input: Input & {
|
|
360
363
|
readonly endpoints: readonly [ExactEndpointInput<Input['endpoints'][0], IncidentEndpointInput>, ExactEndpointInput<Input['endpoints'][1], IncidentEndpointInput>];
|
|
361
|
-
}): UndirectedEdgeClass<CompactUndirectedInput<Input>>;
|
|
364
|
+
} & WithoutStateProperties<Input>): UndirectedEdgeClass<CompactUndirectedInput<Input>>;
|
|
362
365
|
}>;
|
|
366
|
+
type WithoutStateProperties<Input> = Input extends {
|
|
367
|
+
readonly properties?: infer Properties extends Readonly<Record<string, unknown>>;
|
|
368
|
+
} ? {
|
|
369
|
+
readonly properties?: {
|
|
370
|
+
readonly [Name in keyof Properties]: Properties[Name] extends Property ? [PropertyStateMachineOf<Properties[Name]>] extends [never] ? Properties[Name] : never : Properties[Name];
|
|
371
|
+
};
|
|
372
|
+
} : unknown;
|
|
363
373
|
export type ProjectionOfClass<Value extends Class> = Value extends NodeClass<infer Config> ? Config : Value extends DirectedEdgeClass<infer Input> ? Input : Value extends UndirectedEdgeClass<infer Input> ? Input : never;
|
|
364
374
|
export declare function classConfiguration(value: NodeClass): NodeClassInput;
|
|
365
375
|
export declare function classConfiguration(value: DirectedEdgeClass): Omit<DirectedEdgeInput, 'orientation'>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
2
|
import type { AutoEvent, AutoSet } from '../../language/class/model/property.js';
|
|
3
|
+
import type { StateMachine } from '../../language/state/index.js';
|
|
3
4
|
import type { JsonSchema } from '../../language/value/model/schema.js';
|
|
4
5
|
import type { AUTHORING } from '../schema/handle.js';
|
|
5
6
|
declare const PROPERTY_INPUT: unique symbol;
|
|
@@ -13,15 +14,17 @@ export interface AutoOptions {
|
|
|
13
14
|
readonly on: AutoEvent | readonly AutoEvent[];
|
|
14
15
|
readonly set: AutoSet;
|
|
15
16
|
}
|
|
16
|
-
export interface Property<out Schema extends ValueSchemaInput = ValueSchemaInput, out Options extends PropertyOptions = PropertyOptions> {
|
|
17
|
+
export interface Property<out Schema extends ValueSchemaInput = ValueSchemaInput, out Options extends PropertyOptions = PropertyOptions, out Machine = unknown> {
|
|
17
18
|
readonly [AUTHORING]: 'property';
|
|
18
19
|
readonly [PROPERTY_INPUT]: {
|
|
19
20
|
readonly schema: Schema;
|
|
20
21
|
readonly options: Options;
|
|
22
|
+
readonly machine?: Machine;
|
|
21
23
|
};
|
|
22
24
|
}
|
|
23
|
-
export type PropertySchemaOf<Value extends Property> = Value extends Property<infer Schema, PropertyOptions> ? Schema : never;
|
|
24
|
-
export type PropertyOptionsOf<Value extends Property> = Value extends Property<ValueSchemaInput, infer Options> ? Options : never;
|
|
25
|
+
export type PropertySchemaOf<Value extends Property> = Value extends Property<infer Schema, PropertyOptions, unknown> ? Schema : never;
|
|
26
|
+
export type PropertyOptionsOf<Value extends Property> = Value extends Property<ValueSchemaInput, infer Options, unknown> ? Options : never;
|
|
27
|
+
export type PropertyStateMachineOf<Value extends Property> = Value extends Property<ValueSchemaInput, PropertyOptions, infer Machine> ? Machine : never;
|
|
25
28
|
export interface AutoProperty<out PropertyValue extends Property = Property> {
|
|
26
29
|
readonly [AUTHORING]: 'auto';
|
|
27
30
|
readonly [AUTO_INPUT]: {
|
|
@@ -32,9 +35,10 @@ export interface AutoProperty<out PropertyValue extends Property = Property> {
|
|
|
32
35
|
};
|
|
33
36
|
};
|
|
34
37
|
}
|
|
35
|
-
export type PropertyAuthoringInput<Value extends Property> = Value extends Property<infer Schema, infer Options> ? {
|
|
38
|
+
export type PropertyAuthoringInput<Value extends Property> = Value extends Property<infer Schema, infer Options, infer Machine> ? {
|
|
36
39
|
readonly schema: Schema;
|
|
37
40
|
readonly options: Options;
|
|
41
|
+
readonly machine?: Machine;
|
|
38
42
|
} : never;
|
|
39
43
|
export declare function propertyAuthoringInput<const Value extends Property>(value: Value): PropertyAuthoringInput<Value>;
|
|
40
44
|
export declare function autoPropertyAuthoringInput<const Value extends AutoProperty>(value: Value): Value extends AutoProperty<infer Property> ? {
|
|
@@ -46,6 +50,9 @@ export declare function autoPropertyAuthoringInput<const Value extends AutoPrope
|
|
|
46
50
|
} : never;
|
|
47
51
|
export type PropertyFieldInput = ValueSchemaInput | Property | AutoProperty;
|
|
48
52
|
export type PropertyRecordInput = Readonly<Record<string, PropertyFieldInput>>;
|
|
49
|
-
export declare function property<const Schema extends ValueSchemaInput, const Options extends PropertyOptions = Record<never, never>>(schema: Schema, options?: Options): Property<Schema, Options>;
|
|
50
|
-
export declare function
|
|
53
|
+
export declare function property<const Schema extends ValueSchemaInput, const Options extends PropertyOptions = Record<never, never>>(schema: Schema, options?: Options): Property<Schema, Options, never>;
|
|
54
|
+
export declare function stateProperty<const Machine extends StateMachine, const Options extends Omit<PropertyOptions, 'required'> = Record<never, never>>(machine: Machine, options?: Options & Record<Exclude<keyof Options, 'description'>, never>): Property<Machine['stateSchema'], Readonly<Options & {
|
|
55
|
+
readonly required: true;
|
|
56
|
+
}>, Machine>;
|
|
57
|
+
export declare function auto<const PropertyValue extends Property>(property: [PropertyStateMachineOf<PropertyValue>] extends [never] ? PropertyValue : never, options: AutoOptions): AutoProperty<PropertyValue>;
|
|
51
58
|
export {};
|
|
@@ -18,6 +18,17 @@ export function property(schema, options) {
|
|
|
18
18
|
},
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
+
export function stateProperty(machine, options) {
|
|
22
|
+
const admitted = options ?? {};
|
|
23
|
+
assertKnownKeys(admitted, ['description']);
|
|
24
|
+
return authoringValue('property', {
|
|
25
|
+
[PROPERTY_INPUT]: {
|
|
26
|
+
schema: machine.stateSchema,
|
|
27
|
+
options: { ...admitted, required: true },
|
|
28
|
+
machine,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
21
32
|
export function auto(property, options) {
|
|
22
33
|
if (isAuthoringValue(property, 'auto')) {
|
|
23
34
|
fail('BUILDER_AUTO_NESTED', '', 'auto(...) cannot wrap another auto Property.');
|
|
@@ -4,7 +4,7 @@ export { defineClass, edgeClass, nodeClass } from './class/authoring.js';
|
|
|
4
4
|
export { fullMatch } from './value/full-match.js';
|
|
5
5
|
export { path } from './value/path.js';
|
|
6
6
|
export { policy } from './policy/authoring.js';
|
|
7
|
-
export { auto, property } from './class/property.js';
|
|
7
|
+
export { auto, property, stateProperty } from './class/property.js';
|
|
8
8
|
export { defineSchema } from './schema/define.js';
|
|
9
9
|
export type { DefinedSchema } from './schema/define.js';
|
|
10
10
|
export type { ResolvedDomainOfInput } from './schema/projection.js';
|
package/dist/v1/builder/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { defineClass, edgeClass, nodeClass } from './class/authoring.js';
|
|
|
4
4
|
export { fullMatch } from './value/full-match.js';
|
|
5
5
|
export { path } from './value/path.js';
|
|
6
6
|
export { policy } from './policy/authoring.js';
|
|
7
|
-
export { auto, property } from './class/property.js';
|
|
7
|
+
export { auto, property, stateProperty } from './class/property.js';
|
|
8
8
|
export { defineSchema } from './schema/define.js';
|
|
9
9
|
export { valueSchema } from './value/schema.js';
|
|
10
10
|
export { view } from './view/authoring.js';
|
|
@@ -40,6 +40,14 @@ export class BuilderValueSchemas {
|
|
|
40
40
|
...(authoredInput?.options.description === undefined
|
|
41
41
|
? {}
|
|
42
42
|
: { description: authoredInput.options.description }),
|
|
43
|
+
...(authoredInput?.machine === undefined
|
|
44
|
+
? {}
|
|
45
|
+
: {
|
|
46
|
+
state: {
|
|
47
|
+
initial: authoredInput.machine.initial,
|
|
48
|
+
transitions: authoredInput.machine.transitions,
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
43
51
|
};
|
|
44
52
|
}
|
|
45
53
|
return compiled;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import type { Revision } from '../../addressing/index.js';
|
|
2
2
|
import type * as domain from '../../domain/model/domain.js';
|
|
3
3
|
import type * as language from '../../language/index.js';
|
|
4
|
+
import type { StateTopology } from '../../language/state/index.js';
|
|
4
5
|
import type { DomainOfSchema, DomainSchema } from '../../schema/model/schema.js';
|
|
5
6
|
import type { CallableProjection, Function, Method } from '../callable/authoring.js';
|
|
6
7
|
import type { AuthoredPropertyProjection, AuthoredMethodProjection, Class, ClassIdentityProjection, ClassIdentityProjectionOf, ClassOwnerProjection, ClassOwnerProjectionOf, ClassReferenceProjection, ClassReferenceProjectionOf, DirectedEdgeClass, NodeClass, ProjectionOfClass, UndirectedEdgeClass } from '../class/authoring.js';
|
|
7
8
|
import type { DomainSchemaInput } from './input.js';
|
|
8
|
-
export interface PropertyProjection<Value = unknown, Required extends boolean = boolean, Name extends string = string, Key extends language.PropertyKey = language.PropertyKey> {
|
|
9
|
+
export interface PropertyProjection<Value = unknown, Required extends boolean = boolean, Name extends string = string, Key extends language.PropertyKey = language.PropertyKey, State extends StateTopology | undefined = StateTopology | undefined> {
|
|
9
10
|
readonly value: Value;
|
|
10
11
|
readonly required: Required;
|
|
11
12
|
readonly name: Name;
|
|
12
13
|
readonly key: Key;
|
|
14
|
+
readonly state?: State;
|
|
13
15
|
}
|
|
14
16
|
export interface MethodProjection<Input = unknown, Output = unknown, Static extends boolean = boolean, Inheritance extends language.MethodInheritance = language.MethodInheritance, Auth extends language.FunctionAuth = language.FunctionAuth, Name extends string = string, Key extends language.MethodKey = language.MethodKey> {
|
|
15
17
|
readonly input: Input;
|
|
@@ -256,10 +258,10 @@ type CallableAuthOf<Value extends Method | Function> = CallableConfig<Value>['au
|
|
|
256
258
|
type MethodProjectionOfEntry<Origin extends string, OwnerName extends string, Entry> = Entry extends language.ResolvedMethod ? MethodProjectionOfResolved<Entry> : Entry extends AuthoredMethodEntry<unknown, infer Name, infer Value> ? MethodProjection<CallableInputValue<Value>, CallableOutputValue<Value>, CallableStaticOf<Value>, CallableInheritanceOf<Value>, CallableAuthOf<Value>, Name, language.MethodKey<Origin, OwnerName, Name>> : never;
|
|
257
259
|
type MethodProjectionOfEntries<Origin extends string, OwnerName extends string, Entries> = Entries extends unknown ? MethodProjectionOfEntry<Origin, OwnerName, Entries> : never;
|
|
258
260
|
type MethodCollectionOperation = 'byKey' | 'get';
|
|
259
|
-
type PropertyProjectionOfResolved<Value> = Value extends language.ResolvedProperty<infer PropertyValue, infer Required> & {
|
|
261
|
+
type PropertyProjectionOfResolved<Value> = Value extends language.ResolvedProperty<infer PropertyValue, infer Required, infer State> & {
|
|
260
262
|
readonly name: infer Name extends string;
|
|
261
263
|
readonly key: infer Key extends language.PropertyKey;
|
|
262
|
-
} ? PropertyProjection<PropertyValue, Required, Name, Key> : never;
|
|
264
|
+
} ? PropertyProjection<PropertyValue, Required, Name, Key, State> : never;
|
|
263
265
|
type MethodProjectionOfResolved<Value> = Value extends language.ResolvedMethod<infer Input, infer Output, infer Static, infer Inheritance, infer Auth, infer _Policy, infer Key> & {
|
|
264
266
|
readonly name: infer Name extends string;
|
|
265
267
|
} ? MethodProjection<Input, Output, Static, Inheritance, Auth, Name, Key> : never;
|
|
@@ -288,12 +290,12 @@ type ShallowClassProjectionTupleOfResolved<Parents extends readonly unknown[]> =
|
|
|
288
290
|
type ExactClass<Value> = Value extends {
|
|
289
291
|
readonly key: infer Key extends language.DefinitionKey<string, 'class'>;
|
|
290
292
|
} ? Key extends `${infer Origin}:class.${infer Name}` ? string extends Origin ? never : string extends Name ? never : Value : never : never;
|
|
291
|
-
type OwnPropertyProjectionsOf<Origin extends string, Name extends string, Value> = OwnPropertyEntries<Value> extends infer Entry ? Entry extends AuthoredPropertyEntry<unknown, infer PropertyName, infer Field> ? Field extends AuthoredPropertyProjection<unknown, infer Output, infer Required> ? PropertyProjection<Output, Required, PropertyName, language.PropertyKey<Origin, Name, PropertyName
|
|
293
|
+
type OwnPropertyProjectionsOf<Origin extends string, Name extends string, Value> = OwnPropertyEntries<Value> extends infer Entry ? Entry extends AuthoredPropertyEntry<unknown, infer PropertyName, infer Field> ? Field extends AuthoredPropertyProjection<unknown, infer Output, infer Required, string, unknown, infer State> ? PropertyProjection<Output, Required, PropertyName, language.PropertyKey<Origin, Name, PropertyName>, State> : never : never : never;
|
|
292
294
|
type EffectivePropertyEntriesOfClass<Value> = ClassInputOf<Value> extends {
|
|
293
295
|
readonly effectiveProperties: infer Properties;
|
|
294
296
|
} ? Properties : never;
|
|
295
297
|
type OwnerNameOf<Root extends DomainSchemaInput, CurrentName extends string, Owner> = Owner extends 'self' ? CurrentName : Owner extends ClassOwnerProjection ? Extract<LocalNameOfOwner<Root, Owner>, string> : Owner extends ClassIdentityProjection ? Extract<LocalNameOfIdentity<Root, Owner>, string> : never;
|
|
296
|
-
type EffectivePropertyProjectionOfEntry<Origin extends string, CurrentName extends string, Root extends DomainSchemaInput, Entry> = Entry extends language.ResolvedProperty ? PropertyProjectionOfResolved<Entry> : Entry extends AuthoredPropertyProjection<unknown, infer Output, infer Required, infer PropertyName, infer Owner> ? OwnerNameOf<Root, CurrentName, Owner> extends infer OwnerName extends string ? PropertyProjection<Output, Required, PropertyName, language.PropertyKey<Origin, OwnerName, PropertyName
|
|
298
|
+
type EffectivePropertyProjectionOfEntry<Origin extends string, CurrentName extends string, Root extends DomainSchemaInput, Entry> = Entry extends language.ResolvedProperty ? PropertyProjectionOfResolved<Entry> : Entry extends AuthoredPropertyProjection<unknown, infer Output, infer Required, infer PropertyName, infer Owner, infer State> ? OwnerNameOf<Root, CurrentName, Owner> extends infer OwnerName extends string ? PropertyProjection<Output, Required, PropertyName, language.PropertyKey<Origin, OwnerName, PropertyName>, State> : never : never;
|
|
297
299
|
type EffectivePropertyProjectionsOf<Origin extends string, Name extends string, Value, Root extends DomainSchemaInput> = EffectivePropertyEntriesOfClass<Value> extends infer Entry ? Entry extends unknown ? EffectivePropertyProjectionOfEntry<Origin, Name, Root, Entry> : never : never;
|
|
298
300
|
type OwnInstanceMethodProjectionsOf<Origin extends string, Name extends string, Value> = MethodProjectionOfEntries<Origin, Name, OwnMethodEntries<Value, false>>;
|
|
299
301
|
type OwnStaticMethodProjectionsOf<Origin extends string, Name extends string, Value> = MethodProjectionOfEntries<Origin, Name, OwnMethodEntries<Value, true>>;
|
|
@@ -316,7 +318,7 @@ type ClassProjectionOfAuthoring<Origin extends string, Name extends string, Valu
|
|
|
316
318
|
type FunctionProjectionOfAuthoring<Origin extends string, Name extends string, Value> = Value extends Function ? FunctionProjection<CallableInputValue<Value>, CallableOutputValue<Value>, CallableAuthOf<Value>, language.FunctionRef<Name> & {
|
|
317
319
|
readonly origin: Origin;
|
|
318
320
|
}, language.DefinitionKey<Origin, 'function', Name>> : never;
|
|
319
|
-
type ResolvedPropertyOfProjection<Value> = Value extends PropertyProjection<infer PropertyValue, infer Required, infer Name, infer Key> ? language.ResolvedProperty<PropertyValue, Required> & {
|
|
321
|
+
type ResolvedPropertyOfProjection<Value> = Value extends PropertyProjection<infer PropertyValue, infer Required, infer Name, infer Key, infer State> ? language.ResolvedProperty<PropertyValue, Required, State> & {
|
|
320
322
|
readonly name: Name;
|
|
321
323
|
readonly key: Key;
|
|
322
324
|
} : never;
|
|
@@ -4,6 +4,7 @@ import { isDefinitionRef, Key } from '../../addressing/index.js';
|
|
|
4
4
|
import { DEPENDENCY_ALIAS_PATTERN, DOMAIN_ORIGIN_PATTERN, DOMAIN_REVISION_PATTERN, MEMBER_NAME_PATTERN, } from '../../addressing/patterns.js';
|
|
5
5
|
import { MEDIA_TYPE_PATTERN } from '../../language/class/model/data.js';
|
|
6
6
|
import { PropertyValues } from '../../language/class/property-values.js';
|
|
7
|
+
import { admitStateTopology } from '../../language/state/topology.js';
|
|
7
8
|
import { classifyValueProgramOrdering } from '../../language/value/ordering.js';
|
|
8
9
|
import { invalidCompiled } from '../diagnostic.js';
|
|
9
10
|
import { revisionOfCompiledEntry } from './revision.js';
|
|
@@ -158,10 +159,8 @@ function compiledClass(input, values, path) {
|
|
|
158
159
|
const parents = array(value.extends, 'Class extends', extendsPath).map((parent, index) => definitionRef(parent, 'class', 'Class parent', `${extendsPath}/${index}`));
|
|
159
160
|
unique(parents.map(Key.of), 'Class parents', extendsPath);
|
|
160
161
|
const propertiesPath = `${path}/properties`;
|
|
161
|
-
const properties = array(value.properties, 'Class properties', propertiesPath);
|
|
162
|
-
|
|
163
|
-
compiledProperty(property, values, `${propertiesPath}/${index}`);
|
|
164
|
-
unique(properties.map((property, index) => string(object(property, 'Compiled Property', `${propertiesPath}/${index}`).name, 'Property name', `${propertiesPath}/${index}/name`)), 'Class Properties', propertiesPath);
|
|
162
|
+
const properties = array(value.properties, 'Class properties', propertiesPath).map((property, index) => compiledProperty(property, values, value.kind === 'node', `${propertiesPath}/${index}`));
|
|
163
|
+
unique(properties.map((property) => property.name), 'Class Properties', propertiesPath);
|
|
165
164
|
if (value.kind === 'node') {
|
|
166
165
|
if (value.data !== undefined)
|
|
167
166
|
dataDefinition(value.data, `${path}/data`);
|
|
@@ -177,7 +176,7 @@ function compiledClass(input, values, path) {
|
|
|
177
176
|
classPolicies(value.policies, `${path}/policies`);
|
|
178
177
|
edgeContract(value, path);
|
|
179
178
|
}
|
|
180
|
-
return value;
|
|
179
|
+
return { ...value, properties };
|
|
181
180
|
}
|
|
182
181
|
function compiledFunction(input, values, path) {
|
|
183
182
|
const value = object(input, 'Compiled Function', path);
|
|
@@ -185,12 +184,13 @@ function compiledFunction(input, values, path) {
|
|
|
185
184
|
definitionRef(value.ref, 'function', 'Function ref', `${path}/ref`);
|
|
186
185
|
return value;
|
|
187
186
|
}
|
|
188
|
-
function compiledProperty(input, values, path) {
|
|
187
|
+
function compiledProperty(input, values, node, path) {
|
|
189
188
|
const value = object(input, 'Compiled Property', path);
|
|
190
189
|
exact(value, ['name', 'required', 'value'], 'Compiled Property', path, [
|
|
191
190
|
'auto',
|
|
192
191
|
'description',
|
|
193
192
|
'ordering',
|
|
193
|
+
'state',
|
|
194
194
|
]);
|
|
195
195
|
memberName(value.name, 'Property name', `${path}/name`);
|
|
196
196
|
boolean(value.required, 'Property required', `${path}/required`);
|
|
@@ -198,9 +198,41 @@ function compiledProperty(input, values, path) {
|
|
|
198
198
|
if (value.auto !== undefined)
|
|
199
199
|
propertyAuto(value.auto, `${path}/auto`);
|
|
200
200
|
const valueIndex = index(value.value, values.length, 'Property value', `${path}/value`);
|
|
201
|
+
let state;
|
|
202
|
+
if (value.state !== undefined) {
|
|
203
|
+
if (!node)
|
|
204
|
+
invalid('Property state placement', 'COMPILED_SOURCE_INVALID', `${path}/state`);
|
|
205
|
+
let topology;
|
|
206
|
+
try {
|
|
207
|
+
topology = admitStateTopology(value.state);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
invalid('Property state topology', 'COMPILED_SOURCE_INVALID', `${path}/state`);
|
|
211
|
+
}
|
|
212
|
+
const program = values[valueIndex];
|
|
213
|
+
const schema = program.nodes[program.root].schema;
|
|
214
|
+
const states = Object.keys(topology.transitions);
|
|
215
|
+
const schemaObject = schema;
|
|
216
|
+
const schemaMatches = schema !== null &&
|
|
217
|
+
typeof schema === 'object' &&
|
|
218
|
+
!Array.isArray(schema) &&
|
|
219
|
+
Object.keys(schemaObject).length === 2 &&
|
|
220
|
+
schemaObject.type === 'string' &&
|
|
221
|
+
Array.isArray(schemaObject.enum) &&
|
|
222
|
+
schemaObject.enum.length === states.length &&
|
|
223
|
+
schemaObject.enum.every((state, index) => state === states[index]);
|
|
224
|
+
if (!value.required || value.auto !== undefined || !schemaMatches) {
|
|
225
|
+
invalid('Property state contract', 'COMPILED_SOURCE_INVALID', `${path}/state`);
|
|
226
|
+
}
|
|
227
|
+
state = topology;
|
|
228
|
+
}
|
|
201
229
|
if (value.ordering !== classifyValueProgramOrdering(values[valueIndex])) {
|
|
202
230
|
invalid('Property ordering', 'COMPILED_CANONICAL_MISMATCH', `${path}/ordering`);
|
|
203
231
|
}
|
|
232
|
+
return {
|
|
233
|
+
...value,
|
|
234
|
+
...(state === undefined ? {} : { state }),
|
|
235
|
+
};
|
|
204
236
|
}
|
|
205
237
|
function compiledCallable(input, values, method, path) {
|
|
206
238
|
const value = object(input, 'Compiled Callable', path);
|
|
@@ -39,6 +39,7 @@ function compileEntry(schema, lookup) {
|
|
|
39
39
|
required: property.required,
|
|
40
40
|
...(property.auto === undefined ? {} : { auto: property.auto }),
|
|
41
41
|
...(property.description === undefined ? {} : { description: property.description }),
|
|
42
|
+
...(property.state === undefined ? {} : { state: property.state }),
|
|
42
43
|
...(ordering === undefined ? {} : { ordering }),
|
|
43
44
|
value: add(property.schema, `${pointer}/properties/${name}/schema`),
|
|
44
45
|
};
|
package/dist/v1/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Stable aggregate facade for the V1 language. */
|
|
2
|
-
export { auto, core, defineClass, defineSchema, edgeClass, func, fullMatch, method, nodeClass, output, path, policy, property, valueSchema, view, } from './builder/index.js';
|
|
2
|
+
export { auto, core, defineClass, defineSchema, edgeClass, func, fullMatch, method, nodeClass, output, path, policy, property, stateProperty, valueSchema, view, } from './builder/index.js';
|
|
3
|
+
export { stateMachine } from './language/state/index.js';
|
|
4
|
+
export type { EventOf, StateMachine, StateOf } from './language/state/index.js';
|
|
3
5
|
export { PropertyValues } from './language/index.js';
|
|
4
6
|
export * as metadata from './language/definition/metadata.js';
|
|
5
7
|
export type { AnyResolvedClass, BoundMethodOf, BoundMethodShortcutsOf, CallableResultOf, CallableInputOf, CallableOutputOf, ClassSatisfactionKeysOf, ClassDefinition, CoreDefinition, CoreEdge, FunctionAuth, FunctionDefinition, MethodDefinition, MethodInheritance, MethodOwnerKeyOf, PolicyCheckExpression, PolicyDefinition, PropertyInputOf, PropertyAccessOf, PropertyOutputOf, PropertyShortcutsOf, PropertyValuesOf, ResolvedClass, ResolvedCoreDefinition, ResolvedData, ResolvedFunction, ResolvedMethod, ResolvedMethodCollection, ResolvedPolicy, ResolvedProperty, ResolvedView, MethodAccessOf, SelfMethodsOf, SelfPropertiesOf, SelfStaticMethodsOf, StaticMethodsOf, ViewDefinition, } from './language/index.js';
|
package/dist/v1/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Stable aggregate facade for the V1 language. */
|
|
2
|
-
export { auto, core, defineClass, defineSchema, edgeClass, func, fullMatch, method, nodeClass, output, path, policy, property, valueSchema, view, } from './builder/index.js';
|
|
2
|
+
export { auto, core, defineClass, defineSchema, edgeClass, func, fullMatch, method, nodeClass, output, path, policy, property, stateProperty, valueSchema, view, } from './builder/index.js';
|
|
3
|
+
export { stateMachine } from './language/state/index.js';
|
|
3
4
|
export { PropertyValues } from './language/index.js';
|
|
4
5
|
export * as metadata from './language/definition/metadata.js';
|
|
5
6
|
export { acceptDefinitionRef, callableAddress, ClassKey, equalDefinitionRefs, isDefinitionRef, Key, MethodKey, PropertyKey, } from './addressing/index.js';
|
|
@@ -9,6 +9,7 @@ export interface PropertyDefinition {
|
|
|
9
9
|
readonly required: boolean;
|
|
10
10
|
readonly auto?: PropertyAuto;
|
|
11
11
|
readonly description?: string;
|
|
12
|
+
readonly state?: import('../../state/index.js').StateTopology;
|
|
12
13
|
}
|
|
13
14
|
export type PropertyRecord = Readonly<Record<string, PropertyDefinition>>;
|
|
14
15
|
export interface PropertyFacet {
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import type { Value as PortableValue } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import type { ClassRef, DefinitionKey, Key, PolicyRef, PropertyKey } from '../../addressing/index.js';
|
|
3
3
|
import type { ResolvedMethodCollection } from '../callable/resolved.js';
|
|
4
|
+
import type { StateTopology } from '../state/index.js';
|
|
4
5
|
import type { Diagnostic } from '../validation/diagnostic.js';
|
|
5
6
|
import type { OrderingKind } from '../value/ordering.js';
|
|
6
7
|
import type { ClassDefinition, ClassKind } from './model/class.js';
|
|
7
8
|
import type { DataDefinition } from './model/data.js';
|
|
8
9
|
import type { PropertyDefinition } from './model/property.js';
|
|
9
10
|
import type { PropertyValues } from './property-values.js';
|
|
10
|
-
export interface ResolvedProperty<out Value = PortableValue, out Required extends boolean = boolean> {
|
|
11
|
+
export interface ResolvedProperty<out Value = PortableValue, out Required extends boolean = boolean, out State extends StateTopology | undefined = StateTopology | undefined> {
|
|
11
12
|
readonly owner: AnyResolvedClass;
|
|
12
13
|
readonly name: string;
|
|
13
14
|
readonly key: PropertyKey;
|
|
14
|
-
readonly definition: PropertyDefinition & {
|
|
15
|
+
readonly definition: Omit<PropertyDefinition, 'required' | 'state'> & {
|
|
15
16
|
readonly required: Required;
|
|
17
|
+
readonly state?: State;
|
|
16
18
|
};
|
|
17
19
|
/** The one portable scalar ordering domain, when the Property is orderable. */
|
|
18
20
|
readonly ordering?: OrderingKind;
|
|
@@ -2,6 +2,8 @@ export type { ClassRef, CoreRef, DefinitionBase, DefinitionKey, DefinitionRef, F
|
|
|
2
2
|
export type { Value as PropertyValue } from '@astrale-os/kernel-dsl/value';
|
|
3
3
|
export type { JsonSchema as ValueSchema, JsonSchemaObject as ValueSchemaObject, } from './value/model/schema.js';
|
|
4
4
|
export type { OrderingKind } from './value/ordering.js';
|
|
5
|
+
export { stateMachine } from './state/index.js';
|
|
6
|
+
export type { EventOf, StateMachine, StateOf, StateTopology } from './state/index.js';
|
|
5
7
|
export type * from './callable/model/callable.js';
|
|
6
8
|
export type * from './callable/resolved.js';
|
|
7
9
|
export type * from './class/model/class.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { stateMachine } from './machine.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { EventName, StateName, StateTopology, TransitionRelation, ValidTransitionRelation } from './topology.js';
|
|
3
|
+
export interface StateMachine<Transitions extends TransitionRelation = any, Initial extends StateName<Transitions> = StateName<Transitions>> extends StateTopology<Transitions, Initial> {
|
|
4
|
+
readonly stateSchema: z.ZodType<StateName<Transitions>>;
|
|
5
|
+
readonly eventSchema: z.ZodType<EventName<Transitions>>;
|
|
6
|
+
eventsFrom<State extends StateName<Transitions>>(state: State): readonly EventName<Transitions, State>[];
|
|
7
|
+
decide<State extends StateName<Transitions>, Event extends EventName<Transitions>>(state: State, event: Event): TransitionDecision<Transitions, State, Event>;
|
|
8
|
+
}
|
|
9
|
+
export type StateOf<Machine extends StateMachine> = StateName<Machine['transitions']>;
|
|
10
|
+
export type EventOf<Machine extends StateMachine, State extends StateOf<Machine> = StateOf<Machine>> = EventName<Machine['transitions'], State>;
|
|
11
|
+
type TransitionDecision<Transitions extends TransitionRelation, State extends StateName<Transitions>, Event extends EventName<Transitions>> = State extends StateName<Transitions> ? Event extends EventName<Transitions> ? Event extends keyof Transitions[State] ? Readonly<{
|
|
12
|
+
kind: 'allowed';
|
|
13
|
+
transition: Readonly<{
|
|
14
|
+
from: State;
|
|
15
|
+
event: Event;
|
|
16
|
+
to: Transitions[State][Event];
|
|
17
|
+
}>;
|
|
18
|
+
}> : Readonly<{
|
|
19
|
+
kind: 'rejected';
|
|
20
|
+
code: 'STATE_TRANSITION_ILLEGAL';
|
|
21
|
+
from: State;
|
|
22
|
+
event: Event;
|
|
23
|
+
}> : never : never;
|
|
24
|
+
export declare function stateMachine<const Transitions extends TransitionRelation, const Initial extends StateName<Transitions>>(input: Readonly<{
|
|
25
|
+
initial: Initial;
|
|
26
|
+
transitions: Transitions & ValidTransitionRelation<Transitions>;
|
|
27
|
+
}>): StateMachine<Transitions, Initial>;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { compareUnicode } from '../../addressing/ordering.js';
|
|
3
|
+
import { admitStateTopology } from './topology.js';
|
|
4
|
+
export function stateMachine(input) {
|
|
5
|
+
const topology = admitStateTopology(input);
|
|
6
|
+
const states = Object.keys(topology.transitions);
|
|
7
|
+
const events = [
|
|
8
|
+
...new Set(states.flatMap((state) => Object.keys(topology.transitions[state]))),
|
|
9
|
+
].sort(compareUnicode);
|
|
10
|
+
const stateSet = new Set(states);
|
|
11
|
+
const eventSet = new Set(events);
|
|
12
|
+
return Object.freeze({
|
|
13
|
+
...topology,
|
|
14
|
+
stateSchema: z.enum(states),
|
|
15
|
+
eventSchema: z.enum(events),
|
|
16
|
+
eventsFrom(state) {
|
|
17
|
+
admitState(state, stateSet);
|
|
18
|
+
return Object.freeze(Object.keys(topology.transitions[state]));
|
|
19
|
+
},
|
|
20
|
+
decide(state, event) {
|
|
21
|
+
admitState(state, stateSet);
|
|
22
|
+
admitEvent(event, eventSet);
|
|
23
|
+
const target = topology.transitions[state][event];
|
|
24
|
+
return target === undefined
|
|
25
|
+
? Object.freeze({ kind: 'rejected', code: 'STATE_TRANSITION_ILLEGAL', from: state, event })
|
|
26
|
+
: Object.freeze({
|
|
27
|
+
kind: 'allowed',
|
|
28
|
+
transition: Object.freeze({ from: state, event, to: target }),
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function admitState(state, states) {
|
|
34
|
+
if (!states.has(state))
|
|
35
|
+
throw new TypeError(`State machine does not declare state "${state}".`);
|
|
36
|
+
}
|
|
37
|
+
function admitEvent(event, events) {
|
|
38
|
+
if (!events.has(event))
|
|
39
|
+
throw new TypeError(`State machine does not declare event "${event}".`);
|
|
40
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type TransitionRelation = Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
2
|
+
export type StateName<Transitions extends TransitionRelation> = Extract<keyof Transitions, string>;
|
|
3
|
+
export type EventName<Transitions extends TransitionRelation, State extends StateName<Transitions> = StateName<Transitions>> = State extends StateName<Transitions> ? Extract<keyof Transitions[State], string> : never;
|
|
4
|
+
export type ValidTransitionRelation<Transitions extends TransitionRelation> = Readonly<{
|
|
5
|
+
[State in StateName<Transitions>]: Readonly<{
|
|
6
|
+
[Event in keyof Transitions[State]]: string extends StateName<Transitions> ? string : Exclude<StateName<Transitions>, State>;
|
|
7
|
+
}>;
|
|
8
|
+
}>;
|
|
9
|
+
export interface StateTopology<Transitions extends TransitionRelation = TransitionRelation, Initial extends StateName<Transitions> = StateName<Transitions>> {
|
|
10
|
+
readonly initial: Initial;
|
|
11
|
+
readonly transitions: Transitions;
|
|
12
|
+
}
|
|
13
|
+
export type StateTopologyOf<Value> = Value extends StateTopology<infer Transitions, infer Initial> ? StateTopology<Transitions, Initial> : undefined;
|
|
14
|
+
export declare function admitStateTopology(input: unknown): StateTopology;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { compareUnicode } from '../../addressing/ordering.js';
|
|
2
|
+
export function admitStateTopology(input) {
|
|
3
|
+
if (!isRecord(input) || !exactKeys(input, ['initial', 'transitions']))
|
|
4
|
+
invalidInput();
|
|
5
|
+
const initial = input.initial;
|
|
6
|
+
const authored = input.transitions;
|
|
7
|
+
if (typeof initial !== 'string' || initial.length === 0 || !isRecord(authored))
|
|
8
|
+
invalidInput();
|
|
9
|
+
const states = Object.keys(authored);
|
|
10
|
+
if (states.length === 0)
|
|
11
|
+
throw new TypeError('State topology requires at least one state.');
|
|
12
|
+
if (states.includes(''))
|
|
13
|
+
throw new TypeError('State topology has an empty state name.');
|
|
14
|
+
if (!states.includes(initial)) {
|
|
15
|
+
throw new TypeError(`State topology initial state "${initial}" is not declared.`);
|
|
16
|
+
}
|
|
17
|
+
const declared = new Set(states);
|
|
18
|
+
const transitions = Object.create(null);
|
|
19
|
+
let transitionCount = 0;
|
|
20
|
+
for (const state of states.sort(compareUnicode)) {
|
|
21
|
+
const outgoing = authored[state];
|
|
22
|
+
if (!isRecord(outgoing))
|
|
23
|
+
throw new TypeError(`State "${state}" transitions must be an object.`);
|
|
24
|
+
const accepted = Object.create(null);
|
|
25
|
+
for (const event of Object.keys(outgoing).sort(compareUnicode)) {
|
|
26
|
+
const target = outgoing[event];
|
|
27
|
+
if (event.length === 0)
|
|
28
|
+
throw new TypeError(`State "${state}" has an empty event name.`);
|
|
29
|
+
if (typeof target !== 'string' || !declared.has(target)) {
|
|
30
|
+
throw new TypeError(`State "${state}" event "${event}" targets undeclared state "${String(target)}".`);
|
|
31
|
+
}
|
|
32
|
+
if (target === state) {
|
|
33
|
+
throw new TypeError(`State "${state}" event "${event}" is a self-transition.`);
|
|
34
|
+
}
|
|
35
|
+
accepted[event] = target;
|
|
36
|
+
transitionCount += 1;
|
|
37
|
+
}
|
|
38
|
+
transitions[state] = Object.freeze(accepted);
|
|
39
|
+
}
|
|
40
|
+
if (transitionCount === 0)
|
|
41
|
+
throw new TypeError('State topology requires at least one transition.');
|
|
42
|
+
const reachable = new Set([initial]);
|
|
43
|
+
const pending = [initial];
|
|
44
|
+
while (pending.length > 0) {
|
|
45
|
+
const state = pending.shift();
|
|
46
|
+
for (const target of Object.values(transitions[state])) {
|
|
47
|
+
if (reachable.has(target))
|
|
48
|
+
continue;
|
|
49
|
+
reachable.add(target);
|
|
50
|
+
pending.push(target);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (const state of states) {
|
|
54
|
+
if (!reachable.has(state)) {
|
|
55
|
+
throw new TypeError(`State "${state}" is unreachable from initial "${initial}".`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return Object.freeze({ initial, transitions: Object.freeze(transitions) });
|
|
59
|
+
}
|
|
60
|
+
function invalidInput() {
|
|
61
|
+
throw new TypeError('State topology must contain exactly initial and transitions.');
|
|
62
|
+
}
|
|
63
|
+
function isRecord(input) {
|
|
64
|
+
return input !== null && typeof input === 'object' && !Array.isArray(input);
|
|
65
|
+
}
|
|
66
|
+
function exactKeys(input, expected) {
|
|
67
|
+
const keys = Reflect.ownKeys(input);
|
|
68
|
+
return keys.length === expected.length && keys.every((key) => expected.includes(String(key)));
|
|
69
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { MethodDefinition } from '../../language/callable/model/callable.js
|
|
|
3
3
|
import type { ClassDefinition } from '../../language/class/model/class.js';
|
|
4
4
|
import type { DataDefinition } from '../../language/class/model/data.js';
|
|
5
5
|
import type { PropertyAuto } from '../../language/class/model/property.js';
|
|
6
|
+
import type { StateTopology } from '../../language/state/index.js';
|
|
6
7
|
import type { JsonSchema } from '../../language/value/model/schema.js';
|
|
7
8
|
import type { SchemaDiagnostic } from '../diagnostic.js';
|
|
8
9
|
import type { DomainResolutionContext } from '../resolution/context.js';
|
|
@@ -14,6 +15,7 @@ export interface EffectiveField {
|
|
|
14
15
|
readonly schema: JsonSchema;
|
|
15
16
|
readonly required: boolean;
|
|
16
17
|
readonly auto?: PropertyAuto;
|
|
18
|
+
readonly state?: StateTopology;
|
|
17
19
|
}
|
|
18
20
|
export interface EffectiveMethod {
|
|
19
21
|
readonly identity: string;
|
|
@@ -63,6 +63,7 @@ function ownFields(owner, declaration) {
|
|
|
63
63
|
schema: property.schema,
|
|
64
64
|
required: property.required,
|
|
65
65
|
...(property.auto === undefined ? {} : { auto: property.auto }),
|
|
66
|
+
...(property.state === undefined ? {} : { state: property.state }),
|
|
66
67
|
});
|
|
67
68
|
}
|
|
68
69
|
return result;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type SchemaDiagnosticCode = 'DM_MEMBER_ROOT_INVALID' | 'DM_DEPENDENCY_CONTEXT_INVALID' | 'DM_DEPENDENCY_NOT_RETAINED' | 'DM_MEANING_INCONSISTENT' | 'DM_MEMBER_ORIGIN_MISMATCH' | 'DV_ACCEPTANCE_CONTEXT' | 'DV_CALLABLE_AUTH' | 'DV_CALLABLE_IO' | 'DV_CALLABLE_POLICY_AUTH' | 'DV_CLASS_ABSTRACT' | 'DV_CLASS_ACYCLIC' | 'DV_CLASS_KIND' | 'DV_CORE_EDGE_CLASS' | 'DV_CORE_EDGE_CONSTRAINTS' | 'DV_CORE_EDGE_KEY' | 'DV_CORE_EDGE_PROPERTIES' | 'DV_CORE_NODE_CLASS' | 'DV_CORE_NODE_PROPERTIES' | 'DV_CORE_REF' | 'DV_DATA_COMPOSITION' | 'DV_DEPENDENCY_ACYCLIC' | 'DV_DEPENDENCY_DIRECT' | 'DV_DEPENDENCY_EXACT' | 'DV_DEPENDENCY_LIMITS' | 'DV_DEPENDENCY_REVISION_COHERENCE' | 'DV_DEPENDENCY_UNIQUE' | 'DV_EDGE_ACCEPTS' | 'DV_EDGE_CLASS_COMPOSITION' | 'DV_EDGE_CONSTRAINT' | 'DV_EDGE_ROLE' | 'DV_EDGE_UNDIRECTED' | 'DV_JSON_VALUE' | 'DV_METHOD_ABSTRACT' | 'DV_METHOD_COMPOSITION' | 'DV_PROPERTY_AUTO' | 'DV_PROPERTY_COMPOSITION' | 'DV_REF_RESOLVES' | 'DV_VALUE_SCHEMA' | 'DV_VIEW_TARGET' | 'PL_BUDGET' | 'PL_COMPOSITION' | 'PL_CONNECTIVITY' | 'PL_REF' | 'PL_REPEAT' | 'PL_TARGET' | 'PL_TYPING' | 'PL_USE' | 'PL_VARIABLE_SCOPE' | 'STRUCTURE_ABSTRACT_POLICY' | 'STRUCTURE_CALLABLE_POLICY_AUTH' | 'STRUCTURE_DUPLICATE_ITEM' | 'STRUCTURE_EDGE_ORIENTATION' | 'STRUCTURE_FORMAT' | 'STRUCTURE_INVALID' | 'STRUCTURE_KEY' | 'STRUCTURE_MAXIMUM' | 'STRUCTURE_MINIMUM' | 'STRUCTURE_MULTIPLE' | 'STRUCTURE_REQUIRED' | 'STRUCTURE_TYPE' | 'STRUCTURE_UNION' | 'STRUCTURE_UNKNOWN_FIELD' | 'STRUCTURE_VALUE' | 'UNSUPPORTED_DOCUMENT_FORMAT' | 'UNSUPPORTED_DSL_VERSION';
|
|
1
|
+
export type SchemaDiagnosticCode = 'DM_MEMBER_ROOT_INVALID' | 'DM_DEPENDENCY_CONTEXT_INVALID' | 'DM_DEPENDENCY_NOT_RETAINED' | 'DM_MEANING_INCONSISTENT' | 'DM_MEMBER_ORIGIN_MISMATCH' | 'DV_ACCEPTANCE_CONTEXT' | 'DV_CALLABLE_AUTH' | 'DV_CALLABLE_IO' | 'DV_CALLABLE_POLICY_AUTH' | 'DV_CLASS_ABSTRACT' | 'DV_CLASS_ACYCLIC' | 'DV_CLASS_KIND' | 'DV_CORE_EDGE_CLASS' | 'DV_CORE_EDGE_CONSTRAINTS' | 'DV_CORE_EDGE_KEY' | 'DV_CORE_EDGE_PROPERTIES' | 'DV_CORE_NODE_CLASS' | 'DV_CORE_NODE_PROPERTIES' | 'DV_CORE_REF' | 'DV_DATA_COMPOSITION' | 'DV_DEPENDENCY_ACYCLIC' | 'DV_DEPENDENCY_DIRECT' | 'DV_DEPENDENCY_EXACT' | 'DV_DEPENDENCY_LIMITS' | 'DV_DEPENDENCY_REVISION_COHERENCE' | 'DV_DEPENDENCY_UNIQUE' | 'DV_EDGE_ACCEPTS' | 'DV_EDGE_CLASS_COMPOSITION' | 'DV_EDGE_CONSTRAINT' | 'DV_EDGE_ROLE' | 'DV_EDGE_UNDIRECTED' | 'DV_JSON_VALUE' | 'DV_METHOD_ABSTRACT' | 'DV_METHOD_COMPOSITION' | 'DV_PROPERTY_AUTO' | 'DV_PROPERTY_COMPOSITION' | 'DV_PROPERTY_STATE' | 'DV_REF_RESOLVES' | 'DV_VALUE_SCHEMA' | 'DV_VIEW_TARGET' | 'PL_BUDGET' | 'PL_COMPOSITION' | 'PL_CONNECTIVITY' | 'PL_REF' | 'PL_REPEAT' | 'PL_TARGET' | 'PL_TYPING' | 'PL_USE' | 'PL_VARIABLE_SCOPE' | 'STRUCTURE_ABSTRACT_POLICY' | 'STRUCTURE_CALLABLE_POLICY_AUTH' | 'STRUCTURE_DUPLICATE_ITEM' | 'STRUCTURE_EDGE_ORIENTATION' | 'STRUCTURE_FORMAT' | 'STRUCTURE_INVALID' | 'STRUCTURE_KEY' | 'STRUCTURE_MAXIMUM' | 'STRUCTURE_MINIMUM' | 'STRUCTURE_MULTIPLE' | 'STRUCTURE_REQUIRED' | 'STRUCTURE_TYPE' | 'STRUCTURE_UNION' | 'STRUCTURE_UNKNOWN_FIELD' | 'STRUCTURE_VALUE' | 'UNSUPPORTED_DOCUMENT_FORMAT' | 'UNSUPPORTED_DSL_VERSION';
|
|
2
2
|
export interface SchemaDiagnostic<out Code extends SchemaDiagnosticCode = SchemaDiagnosticCode> {
|
|
3
3
|
readonly code: Code;
|
|
4
4
|
readonly pointer: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { json } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import { compareDefinitionRefs, compareUnicode } from '../../addressing/ordering.js';
|
|
3
3
|
import { normalizePolicyChecks, normalizePolicyExpression, } from '../../language/policy/normalization/normalize.js';
|
|
4
|
+
import { admitStateTopology } from '../../language/state/topology.js';
|
|
4
5
|
import { canonicalizeValueSchema } from '../../language/value/profile/canonicalize.js';
|
|
5
6
|
export function normalizeDomainSchema(schema) {
|
|
6
7
|
const classes = mapRecord(schema.classes, canonicalizeClass);
|
|
@@ -132,6 +133,9 @@ function canonicalizeProperty(value) {
|
|
|
132
133
|
},
|
|
133
134
|
}),
|
|
134
135
|
...(value.description === undefined ? {} : { description: value.description }),
|
|
136
|
+
...(value.state === undefined
|
|
137
|
+
? {}
|
|
138
|
+
: { state: admitStateTopology(value.state) }),
|
|
135
139
|
};
|
|
136
140
|
}
|
|
137
141
|
function canonicalizeDirectedEndpoints(value) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Key } from '../../addressing/key.js';
|
|
2
2
|
import { definitionRefIndex } from '../../addressing/ordering.js';
|
|
3
|
+
import { admitStateTopology } from '../../language/state/topology.js';
|
|
3
4
|
import { classifyOrdering } from '../../language/value/ordering.js';
|
|
4
5
|
import { diagnostic } from '../diagnostic.js';
|
|
5
6
|
import { createValueSchemaRegistry } from '../resolution/value.registry.js';
|
|
@@ -26,15 +27,40 @@ export function validateDefinitions(environment, analysis) {
|
|
|
26
27
|
function validateFacet(environment, declaration, pointer, diagnostics) {
|
|
27
28
|
const valueRegistry = createValueSchemaRegistry(environment);
|
|
28
29
|
for (const [name, property] of Object.entries(declaration.properties)) {
|
|
30
|
+
const propertyPointer = `${pointer}/properties/${escapePointer(name)}`;
|
|
31
|
+
if (property.state !== undefined) {
|
|
32
|
+
if (declaration.kind !== 'node') {
|
|
33
|
+
diagnostics.push(diagnostic('DV_PROPERTY_STATE', `${propertyPointer}/state`, 'A State Property may be declared only on a Node Class.'));
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const topology = admitStateTopology(property.state);
|
|
37
|
+
const expected = Object.keys(topology.transitions).sort();
|
|
38
|
+
const schema = property.schema;
|
|
39
|
+
const schemaMatches = schema !== null &&
|
|
40
|
+
typeof schema === 'object' &&
|
|
41
|
+
!Array.isArray(schema) &&
|
|
42
|
+
Object.keys(schema).length === 2 &&
|
|
43
|
+
schema.type === 'string' &&
|
|
44
|
+
Array.isArray(schema.enum) &&
|
|
45
|
+
schema.enum.length === expected.length &&
|
|
46
|
+
[...schema.enum].sort().every((state, index) => state === expected[index]);
|
|
47
|
+
if (!property.required || property.auto !== undefined || !schemaMatches) {
|
|
48
|
+
diagnostics.push(diagnostic('DV_PROPERTY_STATE', `${propertyPointer}/state`, 'A State Property is required, non-automatic, and has exactly its declared states.'));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
diagnostics.push(diagnostic('DV_PROPERTY_STATE', `${propertyPointer}/state`, error instanceof Error ? error.message : 'Invalid State topology.'));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
29
55
|
if (property.auto === undefined)
|
|
30
56
|
continue;
|
|
31
|
-
const
|
|
57
|
+
const autoPointer = `${propertyPointer}/auto`;
|
|
32
58
|
if (property.required && !property.auto.on.includes('create')) {
|
|
33
|
-
diagnostics.push(diagnostic('DV_PROPERTY_AUTO', `${
|
|
59
|
+
diagnostics.push(diagnostic('DV_PROPERTY_AUTO', `${autoPointer}/on`, 'A required auto Property must run on create.'));
|
|
34
60
|
}
|
|
35
61
|
if (property.auto.set === 'instant' &&
|
|
36
62
|
classifyOrdering(property.schema, valueRegistry) !== 'instant') {
|
|
37
|
-
diagnostics.push(diagnostic('DV_PROPERTY_AUTO', `${
|
|
63
|
+
diagnostics.push(diagnostic('DV_PROPERTY_AUTO', `${autoPointer}/set`, 'The instant setter requires an Instant value schema.'));
|
|
38
64
|
}
|
|
39
65
|
}
|
|
40
66
|
if (declaration.kind !== 'node')
|
|
@@ -34,6 +34,12 @@ const jsonSchemaSchema = z.union([z.boolean(), jsonObjectSchema]);
|
|
|
34
34
|
const dataDeclarationSchema = z.strictObject({
|
|
35
35
|
mediaType: z.string().regex(MEDIA_TYPE_PATTERN),
|
|
36
36
|
});
|
|
37
|
+
const stateNameSchema = z.string().min(1);
|
|
38
|
+
const stateTransitionsSchema = z.custom(isStateTransitions);
|
|
39
|
+
const stateTopologySchema = z.strictObject({
|
|
40
|
+
initial: stateNameSchema,
|
|
41
|
+
transitions: stateTransitionsSchema,
|
|
42
|
+
});
|
|
37
43
|
const propertyDefinitionSchema = z.strictObject({
|
|
38
44
|
schema: jsonSchemaSchema,
|
|
39
45
|
required: z.boolean(),
|
|
@@ -44,6 +50,7 @@ const propertyDefinitionSchema = z.strictObject({
|
|
|
44
50
|
})
|
|
45
51
|
.optional(),
|
|
46
52
|
description: descriptionSchema.optional(),
|
|
53
|
+
state: stateTopologySchema.optional(),
|
|
47
54
|
});
|
|
48
55
|
const propertyRecordSchema = z.record(memberNameSchema, propertyDefinitionSchema);
|
|
49
56
|
const domainDependencySchema = z.strictObject({
|
|
@@ -325,6 +332,30 @@ function absoluteRefSchema(kind) {
|
|
|
325
332
|
name: memberNameSchema,
|
|
326
333
|
});
|
|
327
334
|
}
|
|
335
|
+
function isStateTransitions(input) {
|
|
336
|
+
if (!isRecord(input))
|
|
337
|
+
return false;
|
|
338
|
+
for (const state of Reflect.ownKeys(input)) {
|
|
339
|
+
if (typeof state !== 'string' || state.length === 0)
|
|
340
|
+
return false;
|
|
341
|
+
const outgoing = Reflect.get(input, state);
|
|
342
|
+
if (!isRecord(outgoing))
|
|
343
|
+
return false;
|
|
344
|
+
for (const event of Reflect.ownKeys(outgoing)) {
|
|
345
|
+
const target = Reflect.get(outgoing, event);
|
|
346
|
+
if (typeof event !== 'string' ||
|
|
347
|
+
event.length === 0 ||
|
|
348
|
+
typeof target !== 'string' ||
|
|
349
|
+
target.length === 0) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
function isRecord(input) {
|
|
357
|
+
return input !== null && typeof input === 'object' && !Array.isArray(input);
|
|
358
|
+
}
|
|
328
359
|
function uniqueArray(element) {
|
|
329
360
|
return z.array(element).superRefine((values, context) => {
|
|
330
361
|
const firstIndexByValue = new Map();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrale-os/kernel-dsl",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.19",
|
|
4
4
|
"description": "Astrale Kernel DSL - Schema authoring, inference, and IR serialization",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -59,6 +59,10 @@
|
|
|
59
59
|
"types": "./dist/v1/language/index.d.ts",
|
|
60
60
|
"import": "./dist/v1/language/index.js"
|
|
61
61
|
},
|
|
62
|
+
"./v1/language/state": {
|
|
63
|
+
"types": "./dist/v1/language/state/index.d.ts",
|
|
64
|
+
"import": "./dist/v1/language/state/index.js"
|
|
65
|
+
},
|
|
62
66
|
"./v1/schema": {
|
|
63
67
|
"types": "./dist/v1/schema/index.d.ts",
|
|
64
68
|
"import": "./dist/v1/schema/index.js"
|