@astrale-os/kernel-dsl 1.0.0-beta.1 → 1.0.0-beta.10
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/dist/v1/builder/callable/authoring.d.ts +10 -4
- package/dist/v1/builder/callable/authoring.js +2 -0
- package/dist/v1/builder/class/authoring.d.ts +36 -1
- package/dist/v1/builder/class/authoring.js +25 -5
- package/dist/v1/builder/index.d.ts +1 -1
- package/dist/v1/builder/lowering/callable.js +1 -0
- package/dist/v1/builder/lowering/class.js +18 -1
- package/dist/v1/builder/lowering/property-keys.d.ts +27 -0
- package/dist/v1/builder/lowering/property-keys.js +108 -0
- package/dist/v1/builder/lowering/schema/compile.js +8 -3
- package/dist/v1/builder/lowering/schema/context.d.ts +2 -0
- package/dist/v1/builder/lowering/schema/context.js +12 -0
- package/dist/v1/builder/lowering/schema/lifetime.d.ts +11 -0
- package/dist/v1/builder/lowering/schema/lifetime.js +55 -0
- package/dist/v1/builder/schema/diagnostic.d.ts +1 -1
- package/dist/v1/builder/schema/projection/callable.d.ts +7 -5
- package/dist/v1/builder/schema/projection/model.d.ts +4 -2
- package/dist/v1/builder/schema/projection/resolved-class.d.ts +2 -1
- package/dist/v1/compiled/admission/semantics/cardinality.d.ts +1 -1
- package/dist/v1/compiled/admission/semantics/cardinality.js +16 -1
- package/dist/v1/compiled/admission/semantics/class.js +31 -1
- package/dist/v1/compiled/admission/semantics/core.js +16 -0
- package/dist/v1/compiled/admission/semantics/policy/parity.js +3 -2
- package/dist/v1/compiled/admission/wire/callable.js +11 -1
- package/dist/v1/compiled/admission/wire/class.js +50 -3
- package/dist/v1/compiled/compiler/compile.js +2 -0
- package/dist/v1/domain/create.js +1 -0
- package/dist/v1/domain/projection/classes.js +3 -0
- package/dist/v1/index.d.ts +2 -1
- package/dist/v1/language/callable/admission.d.ts +15 -0
- package/dist/v1/language/callable/admission.js +43 -0
- package/dist/v1/language/callable/model/callable.d.ts +10 -0
- package/dist/v1/language/callable/resolved.d.ts +5 -1
- package/dist/v1/language/class/inheritance.d.ts +18 -5
- package/dist/v1/language/class/inheritance.js +14 -1
- package/dist/v1/language/class/lifetime.d.ts +50 -11
- package/dist/v1/language/class/lifetime.js +89 -20
- package/dist/v1/language/class/model/class.d.ts +27 -3
- package/dist/v1/language/class/model/edge.d.ts +30 -1
- package/dist/v1/language/class/unique.d.ts +143 -0
- package/dist/v1/language/class/unique.js +264 -0
- package/dist/v1/language/index.d.ts +6 -2
- package/dist/v1/language/index.js +4 -1
- package/dist/v1/language/value/evaluate/validate.js +4 -0
- package/dist/v1/language/value/evaluate/validity.optimization.d.ts +14 -0
- package/dist/v1/language/value/evaluate/validity.optimization.js +342 -0
- package/dist/v1/language/value/profile/canonicalize.js +6 -5
- package/dist/v1/schema/compatibility/api.d.ts +1 -0
- package/dist/v1/schema/compatibility/api.js +1 -0
- package/dist/v1/schema/compatibility/comparators/classes.d.ts +9 -4
- package/dist/v1/schema/compatibility/comparators/classes.js +25 -4
- package/dist/v1/schema/compatibility/comparators/index.d.ts +1 -1
- package/dist/v1/schema/compatibility/extension.d.ts +11 -0
- package/dist/v1/schema/compatibility/extension.js +22 -0
- package/dist/v1/schema/compatibility/fields.d.ts +20 -4
- package/dist/v1/schema/compatibility/fields.js +13 -3
- package/dist/v1/schema/compatibility/model.d.ts +11 -3
- package/dist/v1/schema/compatibility/scope.d.ts +2 -0
- package/dist/v1/schema/compatibility/scope.js +4 -1
- package/dist/v1/schema/compatibility/structure/index.js +6 -4
- package/dist/v1/schema/compatibility/structure/requirements.js +32 -1
- package/dist/v1/schema/compatibility/versioning/surface.d.ts +1 -1
- package/dist/v1/schema/compatibility/versioning/surface.js +30 -2
- package/dist/v1/schema/diagnostic.d.ts +1 -1
- package/dist/v1/schema/normalization/domain.js +18 -0
- package/dist/v1/schema/validation/callable.d.ts +1 -1
- package/dist/v1/schema/validation/callable.js +17 -2
- package/dist/v1/schema/validation/class.js +80 -2
- package/dist/v1/schema/validation/core.js +50 -0
- package/dist/v1/schema/validation/domain.structure.js +45 -10
- package/dist/v1/schema/validation/policy/uses.js +5 -5
- package/dist/value/canonical.d.ts +5 -0
- package/dist/value/canonical.js +12 -0
- package/dist/value/json.d.ts +1 -1
- package/dist/value/json.js +1 -1
- package/package.json +1 -1
- package/dist/v1/builder/lowering/core-properties.d.ts +0 -10
- package/dist/v1/builder/lowering/core-properties.js +0 -76
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Value as PortableValue } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import type { z } from 'zod';
|
|
3
3
|
import type { PolicyRef } from '../../addressing/index.js';
|
|
4
|
-
import type { BinaryInputProjection, CallableExecution, FunctionAuth } from '../../language/callable/model/callable.js';
|
|
4
|
+
import type { BinaryInputProjection, CallableAdmission, CallableExecution, FunctionAuth } from '../../language/callable/model/callable.js';
|
|
5
5
|
import type { PolicyCheckExpression, PolicyCheckObject, PolicyObjectRef } from '../../language/policy/model/policy.js';
|
|
6
6
|
import type { Class } from '../class/authoring.js';
|
|
7
7
|
import type { ValueSchemaInput } from '../class/property.js';
|
|
@@ -62,6 +62,8 @@ export interface CallableInputBase<Input extends FunctionInputSchema = ValueSche
|
|
|
62
62
|
readonly output: CallableOutputInput;
|
|
63
63
|
/** `{ durable: true }` makes an invocation return an Execution reference. */
|
|
64
64
|
readonly execution?: CallableExecution;
|
|
65
|
+
/** `{ maxAge }`: how old, in seconds, the admission a call is served under may be. */
|
|
66
|
+
readonly admission?: CallableAdmission;
|
|
65
67
|
}
|
|
66
68
|
type ConcreteCallableInput<Input extends FunctionInputSchema = ValueSchemaInput> = CallableInputBase<Input> & ({
|
|
67
69
|
readonly auth: 'anonymous';
|
|
@@ -83,7 +85,7 @@ export type MethodInput = ConcreteCallableInput & {
|
|
|
83
85
|
readonly abstract?: boolean;
|
|
84
86
|
};
|
|
85
87
|
export type CallableInput = FunctionInput | MethodInput;
|
|
86
|
-
export interface CallableProjection<out Input = unknown, out Output = unknown, out Auth extends FunctionAuth = FunctionAuth, out Static extends boolean = boolean, out Abstract extends boolean = boolean, out Execution extends CallableExecution | undefined = CallableExecution | undefined> {
|
|
88
|
+
export interface CallableProjection<out Input = unknown, out Output = unknown, out Auth extends FunctionAuth = FunctionAuth, out Static extends boolean = boolean, out Abstract extends boolean = boolean, out Execution extends CallableExecution | undefined = CallableExecution | undefined, out Admission extends CallableAdmission | undefined = CallableAdmission | undefined> {
|
|
87
89
|
readonly [CALLABLE_PROJECTION_VALUE]: true;
|
|
88
90
|
readonly input: Input;
|
|
89
91
|
readonly output: Output;
|
|
@@ -91,6 +93,7 @@ export interface CallableProjection<out Input = unknown, out Output = unknown, o
|
|
|
91
93
|
readonly static: Static;
|
|
92
94
|
readonly abstract: Abstract;
|
|
93
95
|
readonly execution: Execution;
|
|
96
|
+
readonly admission: Admission;
|
|
94
97
|
}
|
|
95
98
|
type ValueInputOf<Schema> = Schema extends TypedValueSchema<infer Value> ? Value : Schema extends z.ZodType ? z.input<Schema> : PortableValue;
|
|
96
99
|
type FunctionInputOf<Schema> = Schema extends BinaryInputAuthoring<infer Metadata> ? BinaryInputProjection<ValueInputOf<Metadata>> : ValueInputOf<Schema>;
|
|
@@ -98,6 +101,9 @@ type ValueOutputOf<Schema> = Schema extends TypedValueSchema<infer Value> ? Valu
|
|
|
98
101
|
type ExecutionOfConfig<Config extends CallableInput> = Config extends {
|
|
99
102
|
readonly execution: infer Execution extends CallableExecution;
|
|
100
103
|
} ? Execution : undefined;
|
|
104
|
+
type AdmissionOfConfig<Config extends CallableInput> = Config extends {
|
|
105
|
+
readonly admission: infer Admission extends CallableAdmission;
|
|
106
|
+
} ? Admission : undefined;
|
|
101
107
|
type CallableOutputOf<Input extends CallableOutputInput> = Input extends StreamOutput<infer Item> ? {
|
|
102
108
|
readonly mode: 'stream';
|
|
103
109
|
readonly item: ValueOutputOf<Item>;
|
|
@@ -120,12 +126,12 @@ export type ProjectionOfCallable<Value extends Function | Method> = Value extend
|
|
|
120
126
|
export declare function callableConfiguration(value: Function | Method): CallableInput;
|
|
121
127
|
type PolicyObjectAuthoring = Class | Function | Method | Policy | View | CoreNode;
|
|
122
128
|
export type PolicyObjectInput = Lazy<PolicyObjectAuthoring | PolicyObjectRef>;
|
|
123
|
-
export declare function func<const Config extends FunctionInput, Input = FunctionInputOf<Config['input']>, Output = CallableOutputOf<Config['output']>, Auth extends FunctionAuth = Config['auth'], Execution extends CallableExecution | undefined = ExecutionOfConfig<Config>>(config: Config): Function<CallableProjection<Input, Output, Auth, false, false, Execution>>;
|
|
129
|
+
export declare function func<const Config extends FunctionInput, Input = FunctionInputOf<Config['input']>, Output = CallableOutputOf<Config['output']>, Auth extends FunctionAuth = Config['auth'], Execution extends CallableExecution | undefined = ExecutionOfConfig<Config>, Admission extends CallableAdmission | undefined = AdmissionOfConfig<Config>>(config: Config): Function<CallableProjection<Input, Output, Auth, false, false, Execution, Admission>>;
|
|
124
130
|
export declare function method<const Config extends MethodInput, Input = ValueInputOf<Config['input']>, Output = CallableOutputOf<Config['output']>, Auth extends FunctionAuth = Config['auth'], Static extends boolean = Config extends {
|
|
125
131
|
readonly static: true;
|
|
126
132
|
} ? true : false, Abstract extends boolean = Config extends {
|
|
127
133
|
readonly abstract: infer Value extends boolean;
|
|
128
|
-
} ? Value : false, Execution extends CallableExecution | undefined = ExecutionOfConfig<Config>>(config: Config): Method<CallableProjection<Input, Output, Auth, Static, Abstract, Execution>>;
|
|
134
|
+
} ? Value : false, Execution extends CallableExecution | undefined = ExecutionOfConfig<Config>, Admission extends CallableAdmission | undefined = AdmissionOfConfig<Config>>(config: Config): Method<CallableProjection<Input, Output, Auth, Static, Abstract, Execution, Admission>>;
|
|
129
135
|
export declare function callableInputField(name: string): Extract<PolicyCheckObject, {
|
|
130
136
|
readonly kind: 'input';
|
|
131
137
|
}>;
|
|
@@ -18,6 +18,7 @@ export function func(config) {
|
|
|
18
18
|
'static',
|
|
19
19
|
'abstract',
|
|
20
20
|
'execution',
|
|
21
|
+
'admission',
|
|
21
22
|
]);
|
|
22
23
|
const value = authoringValue('function', {});
|
|
23
24
|
callableInputs.set(value, { ...config });
|
|
@@ -33,6 +34,7 @@ export function method(config) {
|
|
|
33
34
|
'static',
|
|
34
35
|
'abstract',
|
|
35
36
|
'execution',
|
|
37
|
+
'admission',
|
|
36
38
|
]);
|
|
37
39
|
const value = authoringValue('method', {});
|
|
38
40
|
callableInputs.set(value, { ...config });
|
|
@@ -3,8 +3,9 @@ import type { z } from 'zod';
|
|
|
3
3
|
import type { ClassRef } from '../../addressing/index.js';
|
|
4
4
|
import type { MethodRecord } from '../../language/callable/model/callable.js';
|
|
5
5
|
import type { ResolvedMethod } from '../../language/callable/resolved.js';
|
|
6
|
+
import type { Inheritance } from '../../language/class/model/class.js';
|
|
6
7
|
import type { MediaType } from '../../language/class/model/data.js';
|
|
7
|
-
import type { EdgeCount } from '../../language/class/model/edge.js';
|
|
8
|
+
import type { EdgeCount, EdgeLifetime } from '../../language/class/model/edge.js';
|
|
8
9
|
import type { ResolvedClass, ResolvedProperty } from '../../language/class/resolved.js';
|
|
9
10
|
import type { ClassRuleChecks, ClassRuleObject } from '../../language/policy/model/policy.js';
|
|
10
11
|
import type { StateTopology, StateTopologyOf } from '../../language/state/topology.js';
|
|
@@ -25,16 +26,22 @@ export interface DirectedSourceInput {
|
|
|
25
26
|
readonly as: string;
|
|
26
27
|
readonly accepts: readonly NodeClassReferenceInput[];
|
|
27
28
|
readonly outgoing: EdgeCount;
|
|
29
|
+
/** What deleting a Node at this endpoint does to the Edge; derived when omitted. */
|
|
30
|
+
readonly onDelete?: EdgeLifetime;
|
|
28
31
|
}
|
|
29
32
|
export interface DirectedTargetInput {
|
|
30
33
|
readonly as: string;
|
|
31
34
|
readonly accepts: readonly NodeClassReferenceInput[];
|
|
32
35
|
readonly incoming: EdgeCount;
|
|
36
|
+
/** What deleting a Node at this endpoint does to the Edge; derived when omitted. */
|
|
37
|
+
readonly onDelete?: EdgeLifetime;
|
|
33
38
|
}
|
|
34
39
|
export interface IncidentEndpointInput {
|
|
35
40
|
readonly as: string;
|
|
36
41
|
readonly accepts: readonly NodeClassReferenceInput[];
|
|
37
42
|
readonly incident: EdgeCount;
|
|
43
|
+
/** What deleting a Node at this endpoint does to the Edge; derived when omitted. */
|
|
44
|
+
readonly onDelete?: EdgeLifetime;
|
|
38
45
|
}
|
|
39
46
|
type ExactEndpointInput<Input, Shape> = Input & Record<Exclude<keyof Input, keyof Shape>, never>;
|
|
40
47
|
/** One observation slot: a local Policy, or its constant value. Absent means `true`. */
|
|
@@ -58,6 +65,8 @@ export interface NodeClassPoliciesInput {
|
|
|
58
65
|
}
|
|
59
66
|
export interface NodeClassInput {
|
|
60
67
|
readonly abstract?: boolean;
|
|
68
|
+
/** Which Classes may extend this one; omitted, those of its own Domain only (`'internal'`). */
|
|
69
|
+
readonly inheritance?: Inheritance;
|
|
61
70
|
readonly description?: string;
|
|
62
71
|
readonly extends?: readonly NodeClassReferenceInput[];
|
|
63
72
|
readonly properties?: PropertyRecordInput;
|
|
@@ -66,9 +75,21 @@ export interface NodeClassInput {
|
|
|
66
75
|
readonly icon?: string;
|
|
67
76
|
/** Local observation Policies; inherited by no descendant and forbidden on abstract Classes. */
|
|
68
77
|
readonly policies?: NodeClassPoliciesInput;
|
|
78
|
+
/**
|
|
79
|
+
* The tuples no two Nodes of this Class and of its active concrete descendants share while every
|
|
80
|
+
* Property is present; omitted, none. A descendant is covered without declaring them again.
|
|
81
|
+
*/
|
|
82
|
+
readonly unique?: readonly NodeUniqueTupleInput[];
|
|
69
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* The distinct Properties of one Node key, in any order, each a Property of the Class, own or
|
|
86
|
+
* inherited, by its name or, when names collide, by its full key.
|
|
87
|
+
*/
|
|
88
|
+
export type NodeUniqueTupleInput = readonly [string, ...string[]];
|
|
70
89
|
export interface EdgeClassOptions {
|
|
71
90
|
readonly abstract?: boolean;
|
|
91
|
+
/** Which Classes may extend this one; omitted, those of its own Domain only (`'internal'`). */
|
|
92
|
+
readonly inheritance?: Inheritance;
|
|
72
93
|
readonly description?: string;
|
|
73
94
|
readonly extends?: readonly EdgeClassReferenceInput[];
|
|
74
95
|
readonly properties?: PropertyRecordInput;
|
|
@@ -79,10 +100,24 @@ export interface EdgeClassOptions {
|
|
|
79
100
|
readonly acyclic?: true;
|
|
80
101
|
};
|
|
81
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* A key field as authored: an endpoint, the slug, or a Property of the Class, own or inherited, by
|
|
105
|
+
* its name or, when names collide, by its full key.
|
|
106
|
+
*/
|
|
107
|
+
export type EdgeUniqueFieldInput = 'source' | 'target' | 'slug' | {
|
|
108
|
+
readonly property: string;
|
|
109
|
+
};
|
|
110
|
+
/** The distinct fields of one key, in any order. */
|
|
111
|
+
export type EdgeUniqueTupleInput = readonly [EdgeUniqueFieldInput, ...EdgeUniqueFieldInput[]];
|
|
82
112
|
export interface DirectedEdgeInput extends EdgeClassOptions {
|
|
83
113
|
readonly orientation: 'directed';
|
|
84
114
|
readonly source: DirectedSourceInput;
|
|
85
115
|
readonly target: DirectedTargetInput;
|
|
116
|
+
/**
|
|
117
|
+
* The tuples no two Edges of this Class and of its active concrete descendants share while every
|
|
118
|
+
* field is present; omitted, none. A descendant is covered without declaring them again.
|
|
119
|
+
*/
|
|
120
|
+
readonly unique?: readonly EdgeUniqueTupleInput[];
|
|
86
121
|
}
|
|
87
122
|
export interface UndirectedEdgeInput extends Omit<EdgeClassOptions, 'constraints'> {
|
|
88
123
|
readonly orientation: 'undirected';
|
|
@@ -17,6 +17,7 @@ export function nodeClass(config) {
|
|
|
17
17
|
const admitted = config ?? {};
|
|
18
18
|
assertKnownKeys(admitted, [
|
|
19
19
|
'abstract',
|
|
20
|
+
'inheritance',
|
|
20
21
|
'description',
|
|
21
22
|
'extends',
|
|
22
23
|
'properties',
|
|
@@ -24,13 +25,21 @@ export function nodeClass(config) {
|
|
|
24
25
|
'methods',
|
|
25
26
|
'icon',
|
|
26
27
|
'policies',
|
|
28
|
+
'unique',
|
|
27
29
|
]);
|
|
28
30
|
if (admitted.policies !== undefined)
|
|
29
31
|
assertKnownKeys(admitted.policies, ['read', 'traverse']);
|
|
30
32
|
const value = authoringValue('definition', {
|
|
31
33
|
kind: 'node',
|
|
32
34
|
});
|
|
33
|
-
classInputs.set(value, {
|
|
35
|
+
classInputs.set(value, {
|
|
36
|
+
...admitted,
|
|
37
|
+
// Copied, so the declaration does not follow later changes to the author's arrays.
|
|
38
|
+
...(admitted.unique === undefined
|
|
39
|
+
? {}
|
|
40
|
+
: { unique: admitted.unique.map((tuple) => [...tuple]) }),
|
|
41
|
+
abstract: admitted.abstract ?? false,
|
|
42
|
+
});
|
|
34
43
|
return value;
|
|
35
44
|
}
|
|
36
45
|
export const edgeClass = Object.freeze({
|
|
@@ -39,15 +48,17 @@ export const edgeClass = Object.freeze({
|
|
|
39
48
|
'source',
|
|
40
49
|
'target',
|
|
41
50
|
'abstract',
|
|
51
|
+
'inheritance',
|
|
42
52
|
'description',
|
|
43
53
|
'extends',
|
|
44
54
|
'properties',
|
|
45
55
|
'policies',
|
|
46
56
|
'constraints',
|
|
57
|
+
'unique',
|
|
47
58
|
]);
|
|
48
59
|
validateDirectedSourceInput(input.source);
|
|
49
60
|
validateDirectedTargetInput(input.target);
|
|
50
|
-
const { source, target, ...options } = input;
|
|
61
|
+
const { source, target, unique, ...options } = input;
|
|
51
62
|
validateEdgeClassOptions(options);
|
|
52
63
|
const value = authoringValue('definition', {
|
|
53
64
|
kind: 'edge',
|
|
@@ -56,6 +67,12 @@ export const edgeClass = Object.freeze({
|
|
|
56
67
|
classInputs.set(value, {
|
|
57
68
|
source: { ...source },
|
|
58
69
|
target: { ...target },
|
|
70
|
+
// Copied, so the declaration does not follow later changes to the author's arrays.
|
|
71
|
+
...(unique === undefined
|
|
72
|
+
? {}
|
|
73
|
+
: {
|
|
74
|
+
unique: unique.map((tuple) => tuple.map((field) => typeof field === 'string' ? field : { property: field.property })),
|
|
75
|
+
}),
|
|
59
76
|
...options,
|
|
60
77
|
abstract: options.abstract ?? false,
|
|
61
78
|
});
|
|
@@ -65,6 +82,7 @@ export const edgeClass = Object.freeze({
|
|
|
65
82
|
assertKnownKeys(input, [
|
|
66
83
|
'endpoints',
|
|
67
84
|
'abstract',
|
|
85
|
+
'inheritance',
|
|
68
86
|
'description',
|
|
69
87
|
'extends',
|
|
70
88
|
'properties',
|
|
@@ -96,17 +114,18 @@ export function classConfiguration(value) {
|
|
|
96
114
|
return input;
|
|
97
115
|
}
|
|
98
116
|
function validateDirectedSourceInput(value) {
|
|
99
|
-
assertKnownKeys(value, ['as', 'accepts', 'outgoing']);
|
|
117
|
+
assertKnownKeys(value, ['as', 'accepts', 'outgoing', 'onDelete']);
|
|
100
118
|
}
|
|
101
119
|
function validateDirectedTargetInput(value) {
|
|
102
|
-
assertKnownKeys(value, ['as', 'accepts', 'incoming']);
|
|
120
|
+
assertKnownKeys(value, ['as', 'accepts', 'incoming', 'onDelete']);
|
|
103
121
|
}
|
|
104
122
|
function validateIncidentEndpointInput(value) {
|
|
105
|
-
assertKnownKeys(value, ['as', 'accepts', 'incident']);
|
|
123
|
+
assertKnownKeys(value, ['as', 'accepts', 'incident', 'onDelete']);
|
|
106
124
|
}
|
|
107
125
|
function validateEdgeClassOptions(value) {
|
|
108
126
|
assertKnownKeys(value, [
|
|
109
127
|
'abstract',
|
|
128
|
+
'inheritance',
|
|
110
129
|
'description',
|
|
111
130
|
'extends',
|
|
112
131
|
'properties',
|
|
@@ -121,6 +140,7 @@ function validateEdgeClassOptions(value) {
|
|
|
121
140
|
function validateUndirectedEdgeClassOptions(value) {
|
|
122
141
|
assertKnownKeys(value, [
|
|
123
142
|
'abstract',
|
|
143
|
+
'inheritance',
|
|
124
144
|
'description',
|
|
125
145
|
'extends',
|
|
126
146
|
'properties',
|
|
@@ -14,7 +14,7 @@ export { BuilderError as Error } from './schema/diagnostic.js';
|
|
|
14
14
|
export type { BuilderDiagnostic as Diagnostic, BuilderDiagnosticCode as DiagnosticCode, } from './schema/diagnostic.js';
|
|
15
15
|
export type { CallableInputBase, CallableInput, BinaryInputAuthoring, CallableOutputInput, CallableProjection, CallablePolicyBuilder, CallablePolicyContext, PolicyCheckContext, BinaryOutput, Function, FunctionInput, Method, MethodInput, PolicyObjectInput, StreamOutput, } from './callable/authoring.js';
|
|
16
16
|
export type { CoreEdge, CoreEndpoint, CoreInput, CoreNode } from './core/authoring.js';
|
|
17
|
-
export type { AuthoredPropertyProjection, AuthoredMethodProjection, Class, ClassIdentityProjection, ClassOwnerProjection, ClassReferenceProjection, ClassReferenceInput as ClassReference, ClassRuleBuilder, ClassRuleContext, DirectedEdgeInput, DirectedEdgeClass, DirectedSourceInput, DirectedTargetInput, EdgeClassCommon, EdgeClassInput, EdgeClass, EdgeClassReferenceInput as EdgeClassReference, IncidentEndpointInput, NodeClass, NodeClassInput, NodeClassPoliciesInput, NodeClassPolicyInput, NodeClassReferenceInput as NodeClassReference, UndirectedEdgeInput, UndirectedEdgeClass, } from './class/authoring.js';
|
|
17
|
+
export type { AuthoredPropertyProjection, AuthoredMethodProjection, Class, ClassIdentityProjection, ClassOwnerProjection, ClassReferenceProjection, ClassReferenceInput as ClassReference, ClassRuleBuilder, ClassRuleContext, DirectedEdgeInput, DirectedEdgeClass, DirectedSourceInput, DirectedTargetInput, EdgeClassCommon, EdgeClassInput, EdgeClass, EdgeClassReferenceInput as EdgeClassReference, EdgeUniqueFieldInput, EdgeUniqueTupleInput, IncidentEndpointInput, NodeClass, NodeClassInput, NodeClassPoliciesInput, NodeClassPolicyInput, NodeClassReferenceInput as NodeClassReference, NodeUniqueTupleInput, UndirectedEdgeInput, UndirectedEdgeClass, } from './class/authoring.js';
|
|
18
18
|
export type { PathAuthoring } from './value/path.js';
|
|
19
19
|
export type { Lazy } from './schema/lazy.js';
|
|
20
20
|
export type { MatchPolicyInput, Policy, PolicyBuilder as PolicyFactory, PolicyEdgeInput, PolicyExistsContext, PolicyInput, PolicyNodeBinder, PolicyPatternContext, } from './policy/authoring.js';
|
|
@@ -42,6 +42,7 @@ export class V1CallableCompiler {
|
|
|
42
42
|
input,
|
|
43
43
|
output,
|
|
44
44
|
...(config.execution === undefined ? {} : { execution: config.execution }),
|
|
45
|
+
...(config.admission === undefined ? {} : { admission: config.admission }),
|
|
45
46
|
};
|
|
46
47
|
const contract = auth === 'authorized'
|
|
47
48
|
? { ...base, auth, ...(policy === undefined ? {} : { policy }) }
|
|
@@ -31,6 +31,7 @@ export class V1DefinitionCompiler {
|
|
|
31
31
|
ref: { origin: this.context.origin, kind: 'class', name },
|
|
32
32
|
kind: 'node',
|
|
33
33
|
abstract: input.abstract ?? false,
|
|
34
|
+
...declaredInheritance(input.inheritance),
|
|
34
35
|
...(input.description === undefined ? {} : { description: input.description }),
|
|
35
36
|
extends: this.parents(input.extends, 'node', `${pointer}/extends`),
|
|
36
37
|
properties: facet,
|
|
@@ -38,6 +39,11 @@ export class V1DefinitionCompiler {
|
|
|
38
39
|
...(data === undefined ? {} : { data }),
|
|
39
40
|
...(input.icon === undefined ? {} : { icon: input.icon }),
|
|
40
41
|
policies: this.classPolicies(input, 'node', `${pointer}/policies`),
|
|
42
|
+
// Property names stay as authored until `canonicalizeUniquePropertyKeys` qualifies them
|
|
43
|
+
// against the Class's effective Properties, once every Class is lowered.
|
|
44
|
+
...(input.unique === undefined
|
|
45
|
+
? {}
|
|
46
|
+
: { unique: input.unique }),
|
|
41
47
|
};
|
|
42
48
|
}
|
|
43
49
|
edgeClass(value, name, pointer) {
|
|
@@ -53,6 +59,11 @@ export class V1DefinitionCompiler {
|
|
|
53
59
|
target: this.directedTarget(directed.target, `${pointer}/endpoints/target`),
|
|
54
60
|
},
|
|
55
61
|
constraints: directedConstraints(directed),
|
|
62
|
+
// Property names stay as authored until `canonicalizeUniquePropertyKeys` qualifies them
|
|
63
|
+
// against the Class's effective Properties, once every Class is lowered.
|
|
64
|
+
...(directed.unique === undefined
|
|
65
|
+
? {}
|
|
66
|
+
: { unique: directed.unique }),
|
|
56
67
|
};
|
|
57
68
|
}
|
|
58
69
|
const undirected = classConfiguration(value);
|
|
@@ -69,6 +80,7 @@ export class V1DefinitionCompiler {
|
|
|
69
80
|
ref: { origin: this.context.origin, kind: 'class', name },
|
|
70
81
|
kind: 'edge',
|
|
71
82
|
abstract: input.abstract ?? false,
|
|
83
|
+
...declaredInheritance(input.inheritance),
|
|
72
84
|
...(input.description === undefined ? {} : { description: input.description }),
|
|
73
85
|
extends: this.parents(input.extends, 'edge', `${pointer}/extends`),
|
|
74
86
|
properties: facet,
|
|
@@ -126,6 +138,7 @@ export class V1DefinitionCompiler {
|
|
|
126
138
|
accepts: value.accepts
|
|
127
139
|
.map((entry, index) => this.context.resolveNodeClass(entry, `${pointer}/accepts/${index}`))
|
|
128
140
|
.sort(compareDefinitionRefs),
|
|
141
|
+
...(value.onDelete === undefined ? {} : { onDelete: value.onDelete }),
|
|
129
142
|
};
|
|
130
143
|
}
|
|
131
144
|
directedSource(value, pointer) {
|
|
@@ -139,7 +152,7 @@ export class V1DefinitionCompiler {
|
|
|
139
152
|
}
|
|
140
153
|
parents(values, kind, pointer) {
|
|
141
154
|
return (values ?? [])
|
|
142
|
-
.map((entry, index) => this.context.
|
|
155
|
+
.map((entry, index) => this.context.resolveParent(entry, kind, `${pointer}/${index}`))
|
|
143
156
|
.sort(compareDefinitionRefs);
|
|
144
157
|
}
|
|
145
158
|
methods(values, abstractOwner, pointer) {
|
|
@@ -158,6 +171,10 @@ export class V1DefinitionCompiler {
|
|
|
158
171
|
}));
|
|
159
172
|
}
|
|
160
173
|
}
|
|
174
|
+
/** The default `'internal'` is never declared, so a Class that keeps it keeps its revision. */
|
|
175
|
+
function declaredInheritance(inheritance) {
|
|
176
|
+
return inheritance === undefined || inheritance === 'internal' ? {} : { inheritance };
|
|
177
|
+
}
|
|
161
178
|
function dataDeclaration(mediaType) {
|
|
162
179
|
return mediaType === undefined ? undefined : { mediaType };
|
|
163
180
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ClassDefinition } from '../../language/class/model/class.js';
|
|
2
|
+
import type { CoreContent } from '../../language/core/model/core.js';
|
|
3
|
+
import type { DefinitionAnalysis } from '../../schema/analysis/class.js';
|
|
4
|
+
import type { DomainSchemaLookup } from '../../schema/dependency/lookup.js';
|
|
5
|
+
import type { DomainSchemaIR } from '../../schema/model/schema.js';
|
|
6
|
+
import type { UnqualifiedCoreContent } from './core.js';
|
|
7
|
+
type UnqualifiedDomainSchema = Omit<DomainSchemaIR, 'core'> & {
|
|
8
|
+
readonly core: UnqualifiedCoreContent;
|
|
9
|
+
};
|
|
10
|
+
/** The effective Properties of every Class `candidate` resolves, by `definitionRefIndex`. */
|
|
11
|
+
export type PropertyOwners = ReadonlyMap<string, DefinitionAnalysis>;
|
|
12
|
+
/**
|
|
13
|
+
* Analyze the already-canonical Definition surface of `candidate` once, so Property shorthand
|
|
14
|
+
* resolves against each Class's own and inherited Properties. Core values and key tuples take no
|
|
15
|
+
* part in Class composition, so they are left out of the analysis.
|
|
16
|
+
*/
|
|
17
|
+
export declare function analyzePropertyOwners(candidate: UnqualifiedDomainSchema, lookup: DomainSchemaLookup): PropertyOwners;
|
|
18
|
+
/** Lower rich builder shorthand to the canonical full-key Core representation. */
|
|
19
|
+
export declare function canonicalizeCorePropertyKeys(core: UnqualifiedCoreContent, owners: PropertyOwners): CoreContent;
|
|
20
|
+
/**
|
|
21
|
+
* Lower each Property a key tuple names by its name to its full key among the Class's own and
|
|
22
|
+
* inherited Properties; a full key stays as authored. Ambiguity and absence are refused here, so a
|
|
23
|
+
* tuple never names a Property the author did not mean. A Node tuple names Properties only; an Edge
|
|
24
|
+
* tuple names them as `{ property }` beside its structural fields.
|
|
25
|
+
*/
|
|
26
|
+
export declare function canonicalizeUniquePropertyKeys(classes: Readonly<Record<string, ClassDefinition>>, origin: string, owners: PropertyOwners): Readonly<Record<string, ClassDefinition>>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { PropertyKey as admitPropertyKey } from '../../addressing/index.js';
|
|
2
|
+
import { definitionRefIndex } from '../../addressing/ordering.js';
|
|
3
|
+
import { createPropertyValues } from '../../language/class/property-values.js';
|
|
4
|
+
import { analyzeDefinitions } from '../../schema/analysis/class.js';
|
|
5
|
+
import { buildDomainResolutionContext } from '../../schema/resolution/context.js';
|
|
6
|
+
import { escapeToken, fail } from '../schema/diagnostic.js';
|
|
7
|
+
/**
|
|
8
|
+
* Analyze the already-canonical Definition surface of `candidate` once, so Property shorthand
|
|
9
|
+
* resolves against each Class's own and inherited Properties. Core values and key tuples take no
|
|
10
|
+
* part in Class composition, so they are left out of the analysis.
|
|
11
|
+
*/
|
|
12
|
+
export function analyzePropertyOwners(candidate, lookup) {
|
|
13
|
+
const analysisCandidate = {
|
|
14
|
+
...candidate,
|
|
15
|
+
core: { nodes: {}, edges: [] },
|
|
16
|
+
};
|
|
17
|
+
return analyzeDefinitions(buildDomainResolutionContext(analysisCandidate, lookup)).definitions;
|
|
18
|
+
}
|
|
19
|
+
/** Lower rich builder shorthand to the canonical full-key Core representation. */
|
|
20
|
+
export function canonicalizeCorePropertyKeys(core, owners) {
|
|
21
|
+
const nodes = Object.fromEntries(Object.entries(core.nodes).map(([slug, node]) => {
|
|
22
|
+
const definition = owners.get(definitionRefIndex(node.class));
|
|
23
|
+
if (definition === undefined) {
|
|
24
|
+
fail('BUILDER_CORE_PROPERTY', `/core/nodes/${escapeToken(slug)}/class`, 'Core property shorthand requires a resolvable concrete Class.');
|
|
25
|
+
}
|
|
26
|
+
return [
|
|
27
|
+
slug,
|
|
28
|
+
{
|
|
29
|
+
...node,
|
|
30
|
+
properties: qualify(node.properties, definition, `/core/nodes/${escapeToken(slug)}/properties`),
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
}));
|
|
34
|
+
const edges = core.edges.map((edge, index) => {
|
|
35
|
+
const definition = owners.get(definitionRefIndex(edge.class));
|
|
36
|
+
if (definition === undefined) {
|
|
37
|
+
fail('BUILDER_CORE_PROPERTY', `/core/edges/${index}/class`, 'Core property shorthand requires a resolvable concrete Class.');
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
...edge,
|
|
41
|
+
properties: qualify(edge.properties, definition, `/core/edges/${index}/properties`),
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
return { nodes, edges };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Lower each Property a key tuple names by its name to its full key among the Class's own and
|
|
48
|
+
* inherited Properties; a full key stays as authored. Ambiguity and absence are refused here, so a
|
|
49
|
+
* tuple never names a Property the author did not mean. A Node tuple names Properties only; an Edge
|
|
50
|
+
* tuple names them as `{ property }` beside its structural fields.
|
|
51
|
+
*/
|
|
52
|
+
export function canonicalizeUniquePropertyKeys(classes, origin, owners) {
|
|
53
|
+
return Object.fromEntries(Object.entries(classes).map(([name, declaration]) => {
|
|
54
|
+
if (declaration.kind === 'edge' && declaration.orientation !== 'directed') {
|
|
55
|
+
return [name, declaration];
|
|
56
|
+
}
|
|
57
|
+
if (declaration.unique === undefined)
|
|
58
|
+
return [name, declaration];
|
|
59
|
+
const ref = { origin, kind: 'class', name };
|
|
60
|
+
const definition = owners.get(definitionRefIndex(ref));
|
|
61
|
+
const pointer = `/classes/${escapeToken(name)}/unique`;
|
|
62
|
+
const resolve = (input, at) => {
|
|
63
|
+
if (definition === undefined) {
|
|
64
|
+
fail('BUILDER_UNIQUE_PROPERTY', at, 'A key Property requires a resolvable Class.');
|
|
65
|
+
}
|
|
66
|
+
return resolveInputKey(input, definition, 'BUILDER_UNIQUE_PROPERTY', at);
|
|
67
|
+
};
|
|
68
|
+
if (declaration.kind === 'node') {
|
|
69
|
+
const unique = declaration.unique.map((tuple, t) => tuple.map((key, f) => resolve(key, `${pointer}/${t}/${f}`)));
|
|
70
|
+
return [name, { ...declaration, unique }];
|
|
71
|
+
}
|
|
72
|
+
const unique = declaration.unique.map((tuple, t) => tuple.map((field, f) => typeof field === 'string'
|
|
73
|
+
? field
|
|
74
|
+
: { property: resolve(field.property, `${pointer}/${t}/${f}/property`) }));
|
|
75
|
+
return [name, { ...declaration, unique }];
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
function qualify(input, definition, pointer) {
|
|
79
|
+
const result = {};
|
|
80
|
+
const sourceByKey = new Map();
|
|
81
|
+
for (const [inputKey, value] of Object.entries(input)) {
|
|
82
|
+
const key = resolveInputKey(inputKey, definition, 'BUILDER_CORE_PROPERTY', `${pointer}/${escapeToken(inputKey)}`);
|
|
83
|
+
const previous = sourceByKey.get(key);
|
|
84
|
+
if (previous !== undefined) {
|
|
85
|
+
fail('BUILDER_CORE_PROPERTY', `${pointer}/${escapeToken(inputKey)}`, `Property is supplied twice through ${JSON.stringify(previous)} and ${JSON.stringify(inputKey)}.`);
|
|
86
|
+
}
|
|
87
|
+
sourceByKey.set(key, inputKey);
|
|
88
|
+
result[key] = value;
|
|
89
|
+
}
|
|
90
|
+
return createPropertyValues(Object.entries(result).map(([key, value]) => [admitPropertyKey(key), value]));
|
|
91
|
+
}
|
|
92
|
+
/** The full key of the Property `input` names on `definition`: its full key, or its unique name. */
|
|
93
|
+
function resolveInputKey(input, definition, code, pointer) {
|
|
94
|
+
if (admitPropertyKey.is(input)) {
|
|
95
|
+
const key = admitPropertyKey(input);
|
|
96
|
+
if (!definition.properties.has(key)) {
|
|
97
|
+
fail(code, pointer, `Property ${input} is not present on the Class.`);
|
|
98
|
+
}
|
|
99
|
+
return key;
|
|
100
|
+
}
|
|
101
|
+
const candidates = [...definition.properties].flatMap(([key, field]) => field.name === input ? [key] : []);
|
|
102
|
+
if (candidates.length === 1)
|
|
103
|
+
return candidates[0];
|
|
104
|
+
if (candidates.length === 0) {
|
|
105
|
+
fail(code, pointer, `Property ${JSON.stringify(input)} does not exist.`);
|
|
106
|
+
}
|
|
107
|
+
fail(code, pointer, `Property ${JSON.stringify(input)} is ambiguous; use one of: ${candidates.join(', ')}.`);
|
|
108
|
+
}
|
|
@@ -7,13 +7,14 @@ import { parseDomainSchemaStructure } from '../../../schema/validation/domain.st
|
|
|
7
7
|
import { assertKnownKeys, escapeToken } from '../../schema/diagnostic.js';
|
|
8
8
|
import { V1CallableCompiler } from '../callable.js';
|
|
9
9
|
import { V1DefinitionCompiler } from '../class.js';
|
|
10
|
-
import { canonicalizeCorePropertyKeys } from '../core-properties.js';
|
|
11
10
|
import { compileCore } from '../core.js';
|
|
12
11
|
import { assertPolicyCompositionAcyclic, V1PolicyCompiler } from '../policy.js';
|
|
12
|
+
import { analyzePropertyOwners, canonicalizeCorePropertyKeys, canonicalizeUniquePropertyKeys, } from '../property-keys.js';
|
|
13
13
|
import { BuilderValueSchemas } from '../value/compile.js';
|
|
14
14
|
import { V1ViewCompiler } from '../view.js';
|
|
15
15
|
import { captureSchema, commitSchemaOwnership } from './capture.js';
|
|
16
16
|
import { BuilderContext } from './context.js';
|
|
17
|
+
import { assertAuthoredLifetimes } from './lifetime.js';
|
|
17
18
|
/**
|
|
18
19
|
* Compile the V1 TypeScript builder graph through the same semantic acceptance
|
|
19
20
|
* path used for untrusted decoded schemas.
|
|
@@ -21,9 +22,11 @@ import { BuilderContext } from './context.js';
|
|
|
21
22
|
* This is the private implementation behind defineSchema.
|
|
22
23
|
*/
|
|
23
24
|
export function compileSchema(origin, input) {
|
|
24
|
-
const
|
|
25
|
+
const dependencies = lookupEntriesForDependencies(input.dependencies ?? {});
|
|
26
|
+
const lookup = new MemoryDomainSchemaLookup(dependencies);
|
|
25
27
|
const candidate = assembleDomainSchema(origin, input, lookup);
|
|
26
28
|
const accepted = acceptDomainSchema(candidate, lookup);
|
|
29
|
+
assertAuthoredLifetimes(accepted, dependencies);
|
|
27
30
|
commitSchemaOwnership(input, origin);
|
|
28
31
|
return accepted;
|
|
29
32
|
}
|
|
@@ -75,9 +78,11 @@ function assembleDomainSchema(origin, input, lookup) {
|
|
|
75
78
|
views,
|
|
76
79
|
core,
|
|
77
80
|
};
|
|
81
|
+
const owners = analyzePropertyOwners(semanticContent, lookup);
|
|
78
82
|
const qualifiedContent = {
|
|
79
83
|
...semanticContent,
|
|
80
|
-
|
|
84
|
+
classes: canonicalizeUniquePropertyKeys(classes, origin, owners),
|
|
85
|
+
core: canonicalizeCorePropertyKeys(core, owners),
|
|
81
86
|
};
|
|
82
87
|
context.noteDomainEndpointReferences(qualifiedContent);
|
|
83
88
|
const candidate = {
|
|
@@ -29,6 +29,8 @@ export declare class BuilderContext {
|
|
|
29
29
|
captureFunction(value: Function, ref: FunctionRef, pointer: string): void;
|
|
30
30
|
captureView(value: View, ref: ViewRef, pointer: string): void;
|
|
31
31
|
resolveClass(input: ClassReferenceInput, kind: ClassKind, pointer: string): ClassRef;
|
|
32
|
+
/** A parent of another Domain must be open to it in the dependency revision authored against. */
|
|
33
|
+
resolveParent(input: ClassReferenceInput, kind: ClassKind, pointer: string): ClassRef;
|
|
32
34
|
resolveNodeClass(input: ClassReferenceInput, pointer: string): ClassRef;
|
|
33
35
|
resolveEdgeClass(input: ClassReferenceInput, pointer: string): ClassRef;
|
|
34
36
|
resolvePolicy(input: Lazy<unknown>, pointer: string): PolicyRef;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { compareDefinitionRefs, compareUnicode, definitionRefIndex, } from '../../../addressing/ordering.js';
|
|
2
2
|
import { DEPENDENCY_ALIAS_PATTERN } from '../../../addressing/patterns.js';
|
|
3
3
|
import { isMethodExecutable } from '../../../language/callable/executable.js';
|
|
4
|
+
import { admitsForeignSubclass, inheritanceOf } from '../../../language/class/inheritance.js';
|
|
4
5
|
import { policyObjectKey, policyObjectOrigin } from '../../../language/index.js';
|
|
5
6
|
import { graphNodePathSchema } from '../../../language/value/syntax/node-path/schema.js';
|
|
6
7
|
import { revisionOfDomainSchema } from '../../../schema/canonical/revision.js';
|
|
@@ -90,6 +91,17 @@ export class BuilderContext {
|
|
|
90
91
|
}
|
|
91
92
|
return { ...ref };
|
|
92
93
|
}
|
|
94
|
+
/** A parent of another Domain must be open to it in the dependency revision authored against. */
|
|
95
|
+
resolveParent(input, kind, pointer) {
|
|
96
|
+
const ref = this.resolveClass(input, kind, pointer);
|
|
97
|
+
if (ref.origin === this.origin)
|
|
98
|
+
return ref;
|
|
99
|
+
const parent = this.#dependencies.get(ref.origin).schema.classes[ref.name];
|
|
100
|
+
if (!admitsForeignSubclass(parent)) {
|
|
101
|
+
fail('BUILDER_CLASS_INHERITANCE', pointer, `Class ${ref.origin}:class.${ref.name} is ${inheritanceOf(parent)}: only an open Class admits a subclass of another Domain.`);
|
|
102
|
+
}
|
|
103
|
+
return ref;
|
|
104
|
+
}
|
|
93
105
|
resolveNodeClass(input, pointer) {
|
|
94
106
|
return this.resolveClass(input, 'node', pointer);
|
|
95
107
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DomainSchemaEntry } from '../../../schema/dependency/lookup.js';
|
|
2
|
+
import type { DomainSchema } from '../../../schema/model/schema.js';
|
|
3
|
+
/**
|
|
4
|
+
* Authoring refuses two Edge Classes that acceptance keeps, because a stored Schema must decode
|
|
5
|
+
* again with the lifetimes it derives (BUILDER-EDGE-LIFETIMES):
|
|
6
|
+
* - one with an endpoint that can hold a Node of another Domain while an endpoint requires an Edge.
|
|
7
|
+
* It can neither restrict that Domain's deletions nor detach below the required count, so the
|
|
8
|
+
* required endpoint must become optional;
|
|
9
|
+
* - one whose lifetimes differ from those of an Edge Class it extends, the root Edge aside.
|
|
10
|
+
*/
|
|
11
|
+
export declare function assertAuthoredLifetimes(schema: DomainSchema, dependencies: readonly DomainSchemaEntry[]): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Key } from '../../../addressing/key.js';
|
|
2
|
+
import { definitionRefIndex } from '../../../addressing/ordering.js';
|
|
3
|
+
import { isGraphRoot } from '../../../language/class/inheritance.js';
|
|
4
|
+
import { contractEndpoints, edgeLifetimes, lifetimeScope, reachesAnotherDomain, requiredEndpoint, } from '../../../language/class/lifetime.js';
|
|
5
|
+
import { BuilderError, diagnostic, escapeToken } from '../../schema/diagnostic.js';
|
|
6
|
+
/**
|
|
7
|
+
* Authoring refuses two Edge Classes that acceptance keeps, because a stored Schema must decode
|
|
8
|
+
* again with the lifetimes it derives (BUILDER-EDGE-LIFETIMES):
|
|
9
|
+
* - one with an endpoint that can hold a Node of another Domain while an endpoint requires an Edge.
|
|
10
|
+
* It can neither restrict that Domain's deletions nor detach below the required count, so the
|
|
11
|
+
* required endpoint must become optional;
|
|
12
|
+
* - one whose lifetimes differ from those of an Edge Class it extends, the root Edge aside.
|
|
13
|
+
*/
|
|
14
|
+
export function assertAuthoredLifetimes(schema, dependencies) {
|
|
15
|
+
const classes = [
|
|
16
|
+
...Object.values(schema.classes),
|
|
17
|
+
...dependencies.flatMap((entry) => Object.values(entry.schema.classes)),
|
|
18
|
+
];
|
|
19
|
+
const scope = lifetimeScope(classes);
|
|
20
|
+
const declarations = new Map(classes.map((declaration) => [definitionRefIndex(declaration.ref), declaration]));
|
|
21
|
+
const diagnostics = Object.entries(schema.classes).flatMap(([name, declaration]) => {
|
|
22
|
+
if (declaration.kind !== 'edge')
|
|
23
|
+
return [];
|
|
24
|
+
const pointer = `/classes/${escapeToken(name)}`;
|
|
25
|
+
const endpoints = contractEndpoints(declaration);
|
|
26
|
+
const keys = declaration.orientation === 'directed' ? ['source', 'target'] : ['0', '1'];
|
|
27
|
+
const counts = declaration.orientation === 'directed' ? ['outgoing', 'incoming'] : ['incident', 'incident'];
|
|
28
|
+
const found = [];
|
|
29
|
+
const required = requiredEndpoint(declaration);
|
|
30
|
+
const reaching = endpoints.find(({ endpoint }) => reachesAnotherDomain(schema.origin, endpoint, scope));
|
|
31
|
+
if (required !== undefined && reaching !== undefined)
|
|
32
|
+
found.push(diagnostic('BUILDER_EDGE_LIFETIME', `${pointer}/endpoints/${keys[required.index]}/${counts[required.index]}`, `${Key.of(declaration.ref)} endpoint ${reaching.endpoint.role} can hold Nodes of another ` +
|
|
33
|
+
`Domain, so it cannot restrict their deletion, and endpoint ${required.endpoint.role} ` +
|
|
34
|
+
`requires ${required.count} Edges, so it cannot detach: make ${required.endpoint.role} optional.`));
|
|
35
|
+
const lifetimes = edgeLifetimes(declaration, scope);
|
|
36
|
+
declaration.extends.forEach((ref, index) => {
|
|
37
|
+
const parent = declarations.get(definitionRefIndex(ref));
|
|
38
|
+
if (parent?.kind !== 'edge' || isGraphRoot(parent))
|
|
39
|
+
return;
|
|
40
|
+
const inherited = edgeLifetimes(parent, scope);
|
|
41
|
+
const same = (order) => order.every((at, position) => lifetimes[position] === inherited[at]);
|
|
42
|
+
if (same([0, 1]) || (declaration.orientation === 'undirected' && same([1, 0])))
|
|
43
|
+
return;
|
|
44
|
+
found.push(diagnostic('BUILDER_EDGE_LIFETIME', `${pointer}/extends/${index}`, `${Key.of(declaration.ref)} has lifetimes ${spell(endpoints, lifetimes)}, and ` +
|
|
45
|
+
`${Key.of(parent.ref)} it extends has ${spell(contractEndpoints(parent), inherited)}: ` +
|
|
46
|
+
`an Edge Class keeps the lifetimes of the Classes it extends.`));
|
|
47
|
+
});
|
|
48
|
+
return found;
|
|
49
|
+
});
|
|
50
|
+
if (diagnostics.length > 0)
|
|
51
|
+
throw new BuilderError(diagnostics);
|
|
52
|
+
}
|
|
53
|
+
function spell(endpoints, lifetimes) {
|
|
54
|
+
return `${endpoints[0].endpoint.role} ${lifetimes[0]} and ${endpoints[1].endpoint.role} ${lifetimes[1]}`;
|
|
55
|
+
}
|