@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 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 auto<const PropertyValue extends Property>(property: PropertyValue, options: AutoOptions): AutoProperty<PropertyValue>;
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';
@@ -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>> : never : never : never;
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>> : never : never;
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
- for (const [index, property] of properties.entries())
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
  };
@@ -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';
@@ -1 +1,2 @@
1
+ export { stateMachine } from './state/index.js';
1
2
  export { PropertyValues } from './class/property-values.js';
@@ -0,0 +1,3 @@
1
+ export { stateMachine } from './machine.js';
2
+ export type { EventOf, StateMachine, StateOf } from './machine.js';
3
+ export type { StateTopology } from './topology.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;
@@ -371,6 +371,7 @@ function propertyMeaning(field) {
371
371
  name: field.name,
372
372
  schema: semanticValueSchema(field.schema),
373
373
  required: field.required,
374
+ ...(field.state === undefined ? {} : { state: field.state }),
374
375
  };
375
376
  }
376
377
  function methodMeaning(method) {
@@ -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 propertyPointer = `${pointer}/properties/${escapePointer(name)}/auto`;
57
+ const autoPointer = `${propertyPointer}/auto`;
32
58
  if (property.required && !property.auto.on.includes('create')) {
33
- diagnostics.push(diagnostic('DV_PROPERTY_AUTO', `${propertyPointer}/on`, 'A required auto Property must run on create.'));
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', `${propertyPointer}/set`, 'The instant setter requires an Instant value schema.'));
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.17",
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"