@astrale-os/kernel-dsl 1.0.0-beta.7 → 1.0.0-beta.9
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/class/authoring.d.ts +29 -4
- package/dist/v1/builder/class/authoring.js +15 -5
- package/dist/v1/builder/index.d.ts +1 -1
- package/dist/v1/builder/lowering/class.js +12 -2
- 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/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 +23 -0
- package/dist/v1/compiled/admission/semantics/core.js +16 -0
- package/dist/v1/compiled/admission/wire/class.js +24 -4
- package/dist/v1/language/class/inheritance.d.ts +9 -0
- package/dist/v1/language/class/inheritance.js +10 -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 +16 -1
- package/dist/v1/language/class/model/edge.d.ts +22 -6
- package/dist/v1/language/class/unique.d.ts +123 -13
- package/dist/v1/language/class/unique.js +238 -27
- package/dist/v1/language/index.d.ts +5 -5
- package/dist/v1/language/index.js +3 -3
- package/dist/v1/schema/compatibility/api.d.ts +1 -0
- package/dist/v1/schema/compatibility/api.js +1 -0
- package/dist/v1/schema/compatibility/extension.d.ts +11 -0
- package/dist/v1/schema/compatibility/extension.js +22 -0
- package/dist/v1/schema/compatibility/model.d.ts +7 -0
- 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/requirements.js +14 -11
- package/dist/v1/schema/compatibility/versioning/surface.js +13 -8
- package/dist/v1/schema/diagnostic.d.ts +1 -1
- package/dist/v1/schema/normalization/domain.js +15 -4
- package/dist/v1/schema/validation/class.js +70 -1
- package/dist/v1/schema/validation/core.js +50 -0
- package/dist/v1/schema/validation/domain.structure.js +18 -4
- 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
|
@@ -5,7 +5,7 @@ import type { MethodRecord } from '../../language/callable/model/callable.js';
|
|
|
5
5
|
import type { ResolvedMethod } from '../../language/callable/resolved.js';
|
|
6
6
|
import type { Inheritance } from '../../language/class/model/class.js';
|
|
7
7
|
import type { MediaType } from '../../language/class/model/data.js';
|
|
8
|
-
import type { EdgeCount,
|
|
8
|
+
import type { EdgeCount, EdgeLifetime } from '../../language/class/model/edge.js';
|
|
9
9
|
import type { ResolvedClass, ResolvedProperty } from '../../language/class/resolved.js';
|
|
10
10
|
import type { ClassRuleChecks, ClassRuleObject } from '../../language/policy/model/policy.js';
|
|
11
11
|
import type { StateTopology, StateTopologyOf } from '../../language/state/topology.js';
|
|
@@ -26,16 +26,22 @@ export interface DirectedSourceInput {
|
|
|
26
26
|
readonly as: string;
|
|
27
27
|
readonly accepts: readonly NodeClassReferenceInput[];
|
|
28
28
|
readonly outgoing: EdgeCount;
|
|
29
|
+
/** What deleting a Node at this endpoint does to the Edge; derived when omitted. */
|
|
30
|
+
readonly onDelete?: EdgeLifetime;
|
|
29
31
|
}
|
|
30
32
|
export interface DirectedTargetInput {
|
|
31
33
|
readonly as: string;
|
|
32
34
|
readonly accepts: readonly NodeClassReferenceInput[];
|
|
33
35
|
readonly incoming: EdgeCount;
|
|
36
|
+
/** What deleting a Node at this endpoint does to the Edge; derived when omitted. */
|
|
37
|
+
readonly onDelete?: EdgeLifetime;
|
|
34
38
|
}
|
|
35
39
|
export interface IncidentEndpointInput {
|
|
36
40
|
readonly as: string;
|
|
37
41
|
readonly accepts: readonly NodeClassReferenceInput[];
|
|
38
42
|
readonly incident: EdgeCount;
|
|
43
|
+
/** What deleting a Node at this endpoint does to the Edge; derived when omitted. */
|
|
44
|
+
readonly onDelete?: EdgeLifetime;
|
|
39
45
|
}
|
|
40
46
|
type ExactEndpointInput<Input, Shape> = Input & Record<Exclude<keyof Input, keyof Shape>, never>;
|
|
41
47
|
/** One observation slot: a local Policy, or its constant value. Absent means `true`. */
|
|
@@ -69,7 +75,17 @@ export interface NodeClassInput {
|
|
|
69
75
|
readonly icon?: string;
|
|
70
76
|
/** Local observation Policies; inherited by no descendant and forbidden on abstract Classes. */
|
|
71
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[];
|
|
72
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[]];
|
|
73
89
|
export interface EdgeClassOptions {
|
|
74
90
|
readonly abstract?: boolean;
|
|
75
91
|
/** Which Classes may extend this one; omitted, those of its own Domain only (`'internal'`). */
|
|
@@ -84,15 +100,24 @@ export interface EdgeClassOptions {
|
|
|
84
100
|
readonly acyclic?: true;
|
|
85
101
|
};
|
|
86
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[]];
|
|
87
112
|
export interface DirectedEdgeInput extends EdgeClassOptions {
|
|
88
113
|
readonly orientation: 'directed';
|
|
89
114
|
readonly source: DirectedSourceInput;
|
|
90
115
|
readonly target: DirectedTargetInput;
|
|
91
116
|
/**
|
|
92
|
-
* The tuples no two Edges of this Class and of its active concrete descendants share
|
|
93
|
-
* none. A descendant is covered without declaring them again.
|
|
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.
|
|
94
119
|
*/
|
|
95
|
-
readonly unique?: readonly
|
|
120
|
+
readonly unique?: readonly EdgeUniqueTupleInput[];
|
|
96
121
|
}
|
|
97
122
|
export interface UndirectedEdgeInput extends Omit<EdgeClassOptions, 'constraints'> {
|
|
98
123
|
readonly orientation: 'undirected';
|
|
@@ -25,13 +25,21 @@ export function nodeClass(config) {
|
|
|
25
25
|
'methods',
|
|
26
26
|
'icon',
|
|
27
27
|
'policies',
|
|
28
|
+
'unique',
|
|
28
29
|
]);
|
|
29
30
|
if (admitted.policies !== undefined)
|
|
30
31
|
assertKnownKeys(admitted.policies, ['read', 'traverse']);
|
|
31
32
|
const value = authoringValue('definition', {
|
|
32
33
|
kind: 'node',
|
|
33
34
|
});
|
|
34
|
-
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
|
+
});
|
|
35
43
|
return value;
|
|
36
44
|
}
|
|
37
45
|
export const edgeClass = Object.freeze({
|
|
@@ -62,7 +70,9 @@ export const edgeClass = Object.freeze({
|
|
|
62
70
|
// Copied, so the declaration does not follow later changes to the author's arrays.
|
|
63
71
|
...(unique === undefined
|
|
64
72
|
? {}
|
|
65
|
-
: {
|
|
73
|
+
: {
|
|
74
|
+
unique: unique.map((tuple) => tuple.map((field) => typeof field === 'string' ? field : { property: field.property })),
|
|
75
|
+
}),
|
|
66
76
|
...options,
|
|
67
77
|
abstract: options.abstract ?? false,
|
|
68
78
|
});
|
|
@@ -104,13 +114,13 @@ export function classConfiguration(value) {
|
|
|
104
114
|
return input;
|
|
105
115
|
}
|
|
106
116
|
function validateDirectedSourceInput(value) {
|
|
107
|
-
assertKnownKeys(value, ['as', 'accepts', 'outgoing']);
|
|
117
|
+
assertKnownKeys(value, ['as', 'accepts', 'outgoing', 'onDelete']);
|
|
108
118
|
}
|
|
109
119
|
function validateDirectedTargetInput(value) {
|
|
110
|
-
assertKnownKeys(value, ['as', 'accepts', 'incoming']);
|
|
120
|
+
assertKnownKeys(value, ['as', 'accepts', 'incoming', 'onDelete']);
|
|
111
121
|
}
|
|
112
122
|
function validateIncidentEndpointInput(value) {
|
|
113
|
-
assertKnownKeys(value, ['as', 'accepts', 'incident']);
|
|
123
|
+
assertKnownKeys(value, ['as', 'accepts', 'incident', 'onDelete']);
|
|
114
124
|
}
|
|
115
125
|
function validateEdgeClassOptions(value) {
|
|
116
126
|
assertKnownKeys(value, [
|
|
@@ -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';
|
|
@@ -39,6 +39,11 @@ export class V1DefinitionCompiler {
|
|
|
39
39
|
...(data === undefined ? {} : { data }),
|
|
40
40
|
...(input.icon === undefined ? {} : { icon: input.icon }),
|
|
41
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 }),
|
|
42
47
|
};
|
|
43
48
|
}
|
|
44
49
|
edgeClass(value, name, pointer) {
|
|
@@ -54,7 +59,11 @@ export class V1DefinitionCompiler {
|
|
|
54
59
|
target: this.directedTarget(directed.target, `${pointer}/endpoints/target`),
|
|
55
60
|
},
|
|
56
61
|
constraints: directedConstraints(directed),
|
|
57
|
-
|
|
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 }),
|
|
58
67
|
};
|
|
59
68
|
}
|
|
60
69
|
const undirected = classConfiguration(value);
|
|
@@ -129,6 +138,7 @@ export class V1DefinitionCompiler {
|
|
|
129
138
|
accepts: value.accepts
|
|
130
139
|
.map((entry, index) => this.context.resolveNodeClass(entry, `${pointer}/accepts/${index}`))
|
|
131
140
|
.sort(compareDefinitionRefs),
|
|
141
|
+
...(value.onDelete === undefined ? {} : { onDelete: value.onDelete }),
|
|
132
142
|
};
|
|
133
143
|
}
|
|
134
144
|
directedSource(value, pointer) {
|
|
@@ -142,7 +152,7 @@ export class V1DefinitionCompiler {
|
|
|
142
152
|
}
|
|
143
153
|
parents(values, kind, pointer) {
|
|
144
154
|
return (values ?? [])
|
|
145
|
-
.map((entry, index) => this.context.
|
|
155
|
+
.map((entry, index) => this.context.resolveParent(entry, kind, `${pointer}/${index}`))
|
|
146
156
|
.sort(compareDefinitionRefs);
|
|
147
157
|
}
|
|
148
158
|
methods(values, abstractOwner, pointer) {
|
|
@@ -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
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DiagnosticCode as LanguageDiagnosticCode } from '../../language/validation/diagnostic.js';
|
|
2
|
-
export type BuilderDiagnosticCode = LanguageDiagnosticCode | 'BUILDER_ABSTRACT_POLICY' | 'BUILDER_AMBIGUOUS_OWNERSHIP' | 'BUILDER_AUTO_EVENT_INVALID' | 'BUILDER_AUTO_IMMUTABLE' | 'BUILDER_AUTO_NESTED' | 'BUILDER_AUTO_SET_INVALID' | 'BUILDER_CALLABLE_AUTH' | 'BUILDER_CALLABLE_PLACEMENT' | 'BUILDER_CALLABLE_POLICY_AUTH' | 'BUILDER_CORE_CLASS' | 'BUILDER_CORE_EDGE' | 'BUILDER_CORE_NODE' | 'BUILDER_CORE_PROPERTY' | 'BUILDER_CORE_REF' | 'BUILDER_DEFINITION' | 'BUILDER_DEPENDENCY_CONFLICT' | 'BUILDER_DEPENDENCY_ALIAS' | 'BUILDER_DEPENDENCY_DIRECT' | 'BUILDER_DUPLICATE_MEMBER' | 'BUILDER_FOREIGN_POLICY' | 'BUILDER_FULL_MATCH_INVALID' | 'BUILDER_FUNCTION' | 'BUILDER_METHOD' | 'BUILDER_PATH_DUPLICATE' | 'BUILDER_PATH_EMPTY' | 'BUILDER_POLICY' | 'BUILDER_POLICY_COMPOSITION' | 'BUILDER_POLICY_CYCLE' | 'BUILDER_POLICY_EMPTY_COMPOSITION' | 'BUILDER_POLICY_EMPTY_EXISTS' | 'BUILDER_POLICY_INPUT' | 'BUILDER_POLICY_REF' | 'BUILDER_POLICY_SELF' | 'BUILDER_PROPERTY_IMMUTABLE_INVALID' | 'BUILDER_REFERENCE_CYCLE' | 'BUILDER_REFERENCE_KIND' | 'BUILDER_REFERENCE_MISSING' | 'BUILDER_UNKNOWN_FIELD' | 'BUILDER_UNOWNED_REFERENCE' | 'BUILDER_VIEW' | 'ZOD_SCHEMA_NOT_LOSSLESS';
|
|
2
|
+
export type BuilderDiagnosticCode = LanguageDiagnosticCode | 'BUILDER_ABSTRACT_POLICY' | 'BUILDER_AMBIGUOUS_OWNERSHIP' | 'BUILDER_AUTO_EVENT_INVALID' | 'BUILDER_AUTO_IMMUTABLE' | 'BUILDER_AUTO_NESTED' | 'BUILDER_AUTO_SET_INVALID' | 'BUILDER_CALLABLE_AUTH' | 'BUILDER_CALLABLE_PLACEMENT' | 'BUILDER_CALLABLE_POLICY_AUTH' | 'BUILDER_CLASS_INHERITANCE' | 'BUILDER_CORE_CLASS' | 'BUILDER_CORE_EDGE' | 'BUILDER_CORE_NODE' | 'BUILDER_CORE_PROPERTY' | 'BUILDER_CORE_REF' | 'BUILDER_DEFINITION' | 'BUILDER_DEPENDENCY_CONFLICT' | 'BUILDER_DEPENDENCY_ALIAS' | 'BUILDER_DEPENDENCY_DIRECT' | 'BUILDER_DUPLICATE_MEMBER' | 'BUILDER_EDGE_LIFETIME' | 'BUILDER_FOREIGN_POLICY' | 'BUILDER_FULL_MATCH_INVALID' | 'BUILDER_FUNCTION' | 'BUILDER_METHOD' | 'BUILDER_PATH_DUPLICATE' | 'BUILDER_PATH_EMPTY' | 'BUILDER_POLICY' | 'BUILDER_POLICY_COMPOSITION' | 'BUILDER_POLICY_CYCLE' | 'BUILDER_POLICY_EMPTY_COMPOSITION' | 'BUILDER_POLICY_EMPTY_EXISTS' | 'BUILDER_POLICY_INPUT' | 'BUILDER_POLICY_REF' | 'BUILDER_POLICY_SELF' | 'BUILDER_PROPERTY_IMMUTABLE_INVALID' | 'BUILDER_REFERENCE_CYCLE' | 'BUILDER_REFERENCE_KIND' | 'BUILDER_REFERENCE_MISSING' | 'BUILDER_UNIQUE_PROPERTY' | 'BUILDER_UNKNOWN_FIELD' | 'BUILDER_UNOWNED_REFERENCE' | 'BUILDER_VIEW' | 'ZOD_SCHEMA_NOT_LOSSLESS';
|
|
3
3
|
export interface BuilderDiagnostic<out Code extends BuilderDiagnosticCode = BuilderDiagnosticCode> {
|
|
4
4
|
readonly code: Code;
|
|
5
5
|
readonly pointer: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type CompiledSchemaEntry } from '../../model/schema.js';
|
|
2
2
|
import { type SemanticIndex } from './context.js';
|
|
3
3
|
import { type CoreNode, type CoreEdgeValue } from './core.js';
|
|
4
|
-
/** Check normalized Edge
|
|
4
|
+
/** Check normalized Edge identity, keys, noSelf, cycles and complete Core degrees. */
|
|
5
5
|
export declare function validateCoreEdgeSet(context: SemanticIndex, entry: CompiledSchemaEntry, entryIndex: number, nodes: ReadonlyMap<string, CoreNode>, edges: readonly CoreEdgeValue[]): void;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Key } from '../../../addressing/index.js';
|
|
2
2
|
import { compareUnicode } from '../../../addressing/ordering.js';
|
|
3
3
|
import {} from '../../../language/class/model/edge.js';
|
|
4
|
+
import { coveringEdgeUniqueTuples, edgeUniqueKeys } from '../../../language/class/unique.js';
|
|
4
5
|
import {} from '../../model/schema.js';
|
|
5
6
|
import { fail } from '../diagnostic.js';
|
|
6
7
|
import { isCompiledEdge, classSatisfiesAny, } from './context.js';
|
|
7
8
|
import {} from './core.js';
|
|
8
9
|
import { sameIncidentEndpoint, countBounds } from './edge.js';
|
|
9
|
-
/** Check normalized Edge
|
|
10
|
+
/** Check normalized Edge identity, keys, noSelf, cycles and complete Core degrees. */
|
|
10
11
|
export function validateCoreEdgeSet(context, entry, entryIndex, nodes, edges) {
|
|
11
12
|
const seen = new Set();
|
|
12
13
|
const slices = new Map();
|
|
@@ -26,6 +27,20 @@ export function validateCoreEdgeSet(context, entry, entryIndex, nodes, edges) {
|
|
|
26
27
|
if (edge.class.value.constraints.noSelf === true && edge.source.key === edge.target.key)
|
|
27
28
|
fail(edge.pointer, 'Core Edge violates noSelf.');
|
|
28
29
|
}
|
|
30
|
+
// No two Core Edges hold one key under a tuple covering their Classes (DV-EDGE-UNIQUE).
|
|
31
|
+
const resolve = (ref) => context.classes.get(Key.of(ref))?.value;
|
|
32
|
+
const holders = new Set();
|
|
33
|
+
for (const edge of edges) {
|
|
34
|
+
const keys = edgeUniqueKeys(coveringEdgeUniqueTuples(edge.class.value, resolve), {
|
|
35
|
+
source: edge.source.key,
|
|
36
|
+
target: edge.target.key,
|
|
37
|
+
props: edge.value.properties,
|
|
38
|
+
});
|
|
39
|
+
if (keys.some((key) => holders.has(key)))
|
|
40
|
+
fail(edge.pointer, 'Core Edge holds a key another Core Edge holds.');
|
|
41
|
+
for (const key of keys)
|
|
42
|
+
holders.add(key);
|
|
43
|
+
}
|
|
29
44
|
for (const edgeClass of entry.definitions.classes) {
|
|
30
45
|
if (!isCompiledEdge(edgeClass))
|
|
31
46
|
continue;
|
|
@@ -2,6 +2,7 @@ import { json } from '@astrale-os/kernel-dsl/value';
|
|
|
2
2
|
import { Key, PropertyKey as PropertyKeyValue, } from '../../../addressing/index.js';
|
|
3
3
|
import { composeMethods } from '../../../language/callable/composition.js';
|
|
4
4
|
import { isMethodExecutable } from '../../../language/callable/executable.js';
|
|
5
|
+
import { keyableProperty } from '../../../language/class/unique.js';
|
|
5
6
|
import { bindValueProgram } from '../../../language/value/compile/load.js';
|
|
6
7
|
import {} from '../../model/schema.js';
|
|
7
8
|
import { fail } from '../diagnostic.js';
|
|
@@ -116,6 +117,28 @@ export function validateClassFacets(context) {
|
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
}
|
|
120
|
+
if (located.value.unique !== undefined) {
|
|
121
|
+
const properties = context.analyses.get(Key.of(located.value.ref))?.properties;
|
|
122
|
+
const keyable = (key, at) => {
|
|
123
|
+
const property = properties?.get(key)?.declaration;
|
|
124
|
+
if (property === undefined || !keyableProperty(property.ordering, property.auto)) {
|
|
125
|
+
fail(at, 'A key Property is a keyable Property of the Class.');
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
// A Node tuple names Properties only; an Edge tuple names them as `{ property }`.
|
|
129
|
+
const value = located.value;
|
|
130
|
+
if (value.kind === 'node') {
|
|
131
|
+
for (const [t, tuple] of (value.unique ?? []).entries())
|
|
132
|
+
for (const [f, key] of tuple.entries())
|
|
133
|
+
keyable(key, `${located.pointer}/unique/${t}/${f}`);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
for (const [t, tuple] of (value.unique ?? []).entries())
|
|
137
|
+
for (const [f, field] of tuple.entries())
|
|
138
|
+
if (typeof field !== 'string')
|
|
139
|
+
keyable(field.property, `${located.pointer}/unique/${t}/${f}/property`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
119
142
|
if (located.value.kind === 'node') {
|
|
120
143
|
for (const [index, method] of located.value.methods.entries()) {
|
|
121
144
|
if (isMethodExecutable(located.value, method))
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Key } from '../../../addressing/index.js';
|
|
2
2
|
import { isMethodExecutable } from '../../../language/callable/executable.js';
|
|
3
|
+
import { coveringNodeUniqueTuples, nodeUniqueKeys } from '../../../language/class/unique.js';
|
|
3
4
|
import {} from '../../../language/core/model/core.js';
|
|
4
5
|
import { policyObjectOrigin, policyObjectKey, projectedMemberClass, } from '../../../language/index.js';
|
|
5
6
|
import { bindValueProgram } from '../../../language/value/compile/load.js';
|
|
@@ -31,6 +32,7 @@ export function validateCore(candidate, context) {
|
|
|
31
32
|
pointer: path,
|
|
32
33
|
});
|
|
33
34
|
}
|
|
35
|
+
validateCoreNodeKeys(context, nodes);
|
|
34
36
|
const edges = [];
|
|
35
37
|
for (const [index, value] of entry.facts.core.entries()) {
|
|
36
38
|
const path = `/closure/${entryIndex}/facts/core/${index}`;
|
|
@@ -67,6 +69,20 @@ export function validateCore(candidate, context) {
|
|
|
67
69
|
validateCoreEdgeSet(context, entry, entryIndex, nodes, edges);
|
|
68
70
|
}
|
|
69
71
|
}
|
|
72
|
+
/** No two Core Nodes hold one key under a tuple covering their Classes (DV-NODE-UNIQUE). */
|
|
73
|
+
function validateCoreNodeKeys(context, nodes) {
|
|
74
|
+
const resolve = (ref) => context.classes.get(Key.of(ref))?.value;
|
|
75
|
+
const holders = new Set();
|
|
76
|
+
for (const node of nodes.values()) {
|
|
77
|
+
const keys = nodeUniqueKeys(coveringNodeUniqueTuples(node.class.value, resolve), {
|
|
78
|
+
props: node.value.properties,
|
|
79
|
+
});
|
|
80
|
+
if (keys.some((key) => holders.has(key)))
|
|
81
|
+
fail(node.pointer, 'Core Node holds a key another Core Node holds.');
|
|
82
|
+
for (const key of keys)
|
|
83
|
+
holders.add(key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
70
86
|
function validateCoreProperties(values, analysis, path) {
|
|
71
87
|
for (const key of Object.keys(values))
|
|
72
88
|
if (!analysis.properties.has(key))
|