@astrale-os/kernel-dsl 0.2.0-beta.7 → 0.2.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/addressing/domain-dependency.d.ts +10 -0
- package/dist/v1/addressing/domain-dependency.js +6 -1
- package/dist/v1/addressing/index.d.ts +1 -1
- package/dist/v1/addressing/patterns.d.ts +4 -0
- package/dist/v1/addressing/patterns.js +4 -0
- package/dist/v1/builder/index.d.ts +1 -1
- package/dist/v1/builder/lowering/schema/capture.js +1 -1
- package/dist/v1/builder/lowering/schema/compile.js +2 -2
- package/dist/v1/builder/lowering/schema/context.d.ts +2 -2
- package/dist/v1/builder/lowering/schema/context.js +20 -15
- package/dist/v1/builder/schema/define.d.ts +1 -1
- package/dist/v1/builder/schema/diagnostic.d.ts +1 -1
- package/dist/v1/builder/schema/input.d.ts +1 -1
- package/dist/v1/builder/schema/projection.d.ts +13 -2
- package/dist/v1/bundle/codec.js +23 -6
- package/dist/v1/compiled/admission/accept.js +64 -23
- package/dist/v1/compiled/diagnostic.d.ts +1 -1
- package/dist/v1/compiled/loader/load.js +9 -7
- package/dist/v1/compiled/model/schema.d.ts +2 -2
- package/dist/v1/domain/create.d.ts +3 -3
- package/dist/v1/domain/create.js +155 -83
- package/dist/v1/domain/model/domain.d.ts +7 -1
- package/dist/v1/domain/model/input.d.ts +9 -5
- package/dist/v1/schema/codec/header.d.ts +2 -2
- package/dist/v1/schema/codec/header.js +4 -1
- package/dist/v1/schema/diagnostic.d.ts +1 -1
- package/dist/v1/schema/model/schema.d.ts +2 -2
- package/dist/v1/schema/normalization/domain.js +1 -1
- package/dist/v1/schema/resolution/closure.d.ts +1 -1
- package/dist/v1/schema/resolution/closure.js +1 -1
- package/dist/v1/schema/resolution/context.js +77 -45
- package/dist/v1/schema/resolution/domain.js +9 -6
- package/dist/v1/schema/resolution/value.registry.js +2 -2
- package/dist/v1/schema/validation/domain.structure.js +6 -2
- package/package.json +2 -3
package/dist/v1/domain/create.js
CHANGED
|
@@ -6,7 +6,7 @@ const admittedDomains = new WeakSet();
|
|
|
6
6
|
export function isDomain(input) {
|
|
7
7
|
return input !== null && typeof input === 'object' && admittedDomains.has(input);
|
|
8
8
|
}
|
|
9
|
-
/** Retain source provenance without losing the admitted Domain identity. */
|
|
9
|
+
/** Retain source provenance without losing the admitted root Domain identity. */
|
|
10
10
|
export function createSourceDomain(domain, source) {
|
|
11
11
|
if (!isDomain(domain))
|
|
12
12
|
throw new TypeError('Source provenance requires an admitted Domain.');
|
|
@@ -14,12 +14,129 @@ export function createSourceDomain(domain, source) {
|
|
|
14
14
|
admittedDomains.add(resolved);
|
|
15
15
|
return resolved;
|
|
16
16
|
}
|
|
17
|
+
/** Construct one resolved Definition universe and one memoized facade per closure Domain. */
|
|
17
18
|
export function createDomain(input) {
|
|
18
|
-
const
|
|
19
|
+
const entries = new Map();
|
|
20
|
+
for (const entry of input.entries) {
|
|
21
|
+
if (entries.has(entry.origin))
|
|
22
|
+
throw new TypeError(`Domain entry ${entry.origin} is duplicated.`);
|
|
23
|
+
entries.set(entry.origin, entry);
|
|
24
|
+
}
|
|
25
|
+
const root = entries.get(input.root.origin);
|
|
26
|
+
if (root === undefined || root.revision !== input.root.revision) {
|
|
27
|
+
throw new TypeError('Domain root is absent or has the wrong revision.');
|
|
28
|
+
}
|
|
29
|
+
const declarations = Object.freeze(input.entries.flatMap(({ definitions }) => definitions));
|
|
30
|
+
const classesByKey = createClasses(declarations, input.validation);
|
|
31
|
+
const definitionsByKey = createDefinitions(declarations, classesByKey, input);
|
|
32
|
+
const callables = createCallables(definitionsByKey, classesByKey);
|
|
33
|
+
const facades = new Map();
|
|
34
|
+
const projecting = new Set();
|
|
35
|
+
const project = (entry) => {
|
|
36
|
+
const cached = facades.get(entry.origin);
|
|
37
|
+
if (cached !== undefined)
|
|
38
|
+
return cached;
|
|
39
|
+
if (projecting.has(entry.origin))
|
|
40
|
+
throw new TypeError(`Dependency cycle reaches ${entry.origin}.`);
|
|
41
|
+
projecting.add(entry.origin);
|
|
42
|
+
const visibleEntries = reachableEntries(entry, entries);
|
|
43
|
+
const visibleOrigins = new Set(visibleEntries.map(({ origin }) => origin));
|
|
44
|
+
const local = [...definitionsByKey.values()]
|
|
45
|
+
.filter(({ ref }) => ref.origin === entry.origin)
|
|
46
|
+
.sort((left, right) => compare(left.key, right.key));
|
|
47
|
+
const classes = namespace(local, 'class');
|
|
48
|
+
const functions = namespace(local, 'function');
|
|
49
|
+
const policies = namespace(local, 'policy');
|
|
50
|
+
const views = namespace(local, 'view');
|
|
51
|
+
const coreNodes = namespace(local, 'core');
|
|
52
|
+
const domainEndpoint = Object.freeze({
|
|
53
|
+
kind: 'domain',
|
|
54
|
+
origin: entry.origin,
|
|
55
|
+
});
|
|
56
|
+
const coreEdges = Object.freeze(entry.coreEdges.map((edge) => {
|
|
57
|
+
const classValue = classesByKey.get(Key.of(edge.class));
|
|
58
|
+
if (classValue?.kind !== 'edge') {
|
|
59
|
+
throw new TypeError(`Core Edge has no Edge Class ${Key.of(edge.class)}.`);
|
|
60
|
+
}
|
|
61
|
+
return Object.freeze({
|
|
62
|
+
class: classValue,
|
|
63
|
+
source: edge.source.kind === 'domain'
|
|
64
|
+
? domainEndpoint
|
|
65
|
+
: requiredCore(definitionsByKey, edge.source),
|
|
66
|
+
target: edge.target.kind === 'domain'
|
|
67
|
+
? domainEndpoint
|
|
68
|
+
: requiredCore(definitionsByKey, edge.target),
|
|
69
|
+
properties: edge.properties,
|
|
70
|
+
});
|
|
71
|
+
}));
|
|
72
|
+
const dependencies = frozenRecord(Object.entries(entry.dependencies).map(([alias, pin]) => {
|
|
73
|
+
const dependency = entries.get(pin.origin);
|
|
74
|
+
if (dependency === undefined || dependency.revision !== pin.revision) {
|
|
75
|
+
throw new TypeError(`Dependency alias ${alias} has no exact ${pin.origin}@${pin.revision} entry.`);
|
|
76
|
+
}
|
|
77
|
+
return [alias, project(dependency)];
|
|
78
|
+
}));
|
|
79
|
+
const definitionLookup = ((value) => {
|
|
80
|
+
const resolved = definitionsByKey.get(typeof value === 'string' ? value : Key.of(value));
|
|
81
|
+
return resolved !== undefined && visibleOrigins.has(resolved.ref.origin)
|
|
82
|
+
? resolved
|
|
83
|
+
: undefined;
|
|
84
|
+
});
|
|
85
|
+
const domain = {
|
|
86
|
+
origin: entry.origin,
|
|
87
|
+
revision: entry.revision,
|
|
88
|
+
closure: Object.freeze(visibleEntries
|
|
89
|
+
.map(({ origin, revision }) => Object.freeze({ origin, revision }))
|
|
90
|
+
.sort((left, right) => compare(left.origin, right.origin))),
|
|
91
|
+
dependencies,
|
|
92
|
+
classes,
|
|
93
|
+
functions,
|
|
94
|
+
policies,
|
|
95
|
+
views,
|
|
96
|
+
core: Object.freeze({ nodes: coreNodes, edges: coreEdges }),
|
|
97
|
+
definition: definitionLookup,
|
|
98
|
+
callable(key) {
|
|
99
|
+
const callable = callables.get(key);
|
|
100
|
+
if (callable === undefined)
|
|
101
|
+
return undefined;
|
|
102
|
+
const origin = 'ref' in callable ? callable.ref.origin : callable.owner.ref.origin;
|
|
103
|
+
return visibleOrigins.has(origin) ? callable : undefined;
|
|
104
|
+
},
|
|
105
|
+
definitions(kind) {
|
|
106
|
+
return kind === undefined ? local : local.filter(({ ref }) => ref.kind === kind);
|
|
107
|
+
},
|
|
108
|
+
viewsFor(target) {
|
|
109
|
+
return Object.freeze(Object.values(views).filter((view) => {
|
|
110
|
+
if (view.target.kind === 'domain')
|
|
111
|
+
return target === 'domain';
|
|
112
|
+
if (target === 'domain')
|
|
113
|
+
return false;
|
|
114
|
+
return view.target.definitions.some((ref) => {
|
|
115
|
+
const selected = classesByKey.get(Key.of(ref));
|
|
116
|
+
if (target.kind === 'node' && selected?.kind === 'node') {
|
|
117
|
+
return target.satisfies(selected);
|
|
118
|
+
}
|
|
119
|
+
if (target.kind === 'edge' && selected?.kind === 'edge') {
|
|
120
|
+
return target.satisfies(selected);
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
});
|
|
124
|
+
}));
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
const admitted = Object.freeze(domain);
|
|
128
|
+
facades.set(entry.origin, admitted);
|
|
129
|
+
admittedDomains.add(admitted);
|
|
130
|
+
projecting.delete(entry.origin);
|
|
131
|
+
return admitted;
|
|
132
|
+
};
|
|
133
|
+
return project(root);
|
|
134
|
+
}
|
|
135
|
+
function createDefinitions(declarations, classesByKey, input) {
|
|
19
136
|
const definitionsByKey = new Map();
|
|
20
137
|
for (const value of classesByKey.values())
|
|
21
138
|
definitionsByKey.set(value.key, value);
|
|
22
|
-
for (const definition of
|
|
139
|
+
for (const definition of declarations) {
|
|
23
140
|
if (definition.ref.kind === 'class')
|
|
24
141
|
continue;
|
|
25
142
|
if (isFunctionDefinition(definition)) {
|
|
@@ -44,35 +161,33 @@ export function createDomain(input) {
|
|
|
44
161
|
}
|
|
45
162
|
if (isPolicyDefinition(definition)) {
|
|
46
163
|
const key = Key.of(definition.ref);
|
|
47
|
-
|
|
164
|
+
definitionsByKey.set(key, Object.freeze({
|
|
48
165
|
ref: definition.ref,
|
|
49
166
|
key,
|
|
50
167
|
expression: definition.expression,
|
|
51
168
|
description: definition.description,
|
|
52
169
|
definition,
|
|
53
|
-
});
|
|
54
|
-
definitionsByKey.set(key, resolved);
|
|
170
|
+
}));
|
|
55
171
|
continue;
|
|
56
172
|
}
|
|
57
173
|
if (isViewDefinition(definition)) {
|
|
58
174
|
const key = Key.of(definition.ref);
|
|
59
|
-
|
|
175
|
+
definitionsByKey.set(key, Object.freeze({
|
|
60
176
|
ref: definition.ref,
|
|
61
177
|
key,
|
|
62
178
|
target: definition.target,
|
|
63
179
|
description: definition.description,
|
|
64
180
|
definition,
|
|
65
|
-
});
|
|
66
|
-
definitionsByKey.set(key, resolved);
|
|
67
|
-
continue;
|
|
181
|
+
}));
|
|
68
182
|
}
|
|
69
183
|
}
|
|
70
|
-
for (const definition of
|
|
184
|
+
for (const definition of declarations) {
|
|
71
185
|
if (!isCoreDefinition(definition))
|
|
72
186
|
continue;
|
|
73
187
|
const classValue = classesByKey.get(Key.of(definition.class));
|
|
74
|
-
if (classValue?.kind !== 'node')
|
|
188
|
+
if (classValue?.kind !== 'node') {
|
|
75
189
|
throw new TypeError(`Core ${Key.of(definition.ref)} has no Node Class.`);
|
|
190
|
+
}
|
|
76
191
|
if (classValue.abstract) {
|
|
77
192
|
throw new TypeError(`Core ${Key.of(definition.ref)} cannot use abstract Class ${classValue.key}.`);
|
|
78
193
|
}
|
|
@@ -85,84 +200,41 @@ export function createDomain(input) {
|
|
|
85
200
|
});
|
|
86
201
|
definitionsByKey.set(resolved.key, resolved);
|
|
87
202
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const classes = namespace(local, 'class');
|
|
92
|
-
const functions = namespace(local, 'function');
|
|
93
|
-
const policies = namespace(local, 'policy');
|
|
94
|
-
const views = namespace(local, 'view');
|
|
95
|
-
const coreNodes = namespace(local, 'core');
|
|
96
|
-
const domainEndpoint = Object.freeze({
|
|
97
|
-
kind: 'domain',
|
|
98
|
-
origin: input.origin,
|
|
99
|
-
});
|
|
100
|
-
const coreEdges = Object.freeze(input.coreEdges.map((edge) => {
|
|
101
|
-
const classValue = classesByKey.get(Key.of(edge.class));
|
|
102
|
-
if (classValue?.kind !== 'edge')
|
|
103
|
-
throw new TypeError(`Core Edge has no Edge Class ${Key.of(edge.class)}.`);
|
|
104
|
-
return Object.freeze({
|
|
105
|
-
class: classValue,
|
|
106
|
-
source: edge.source.kind === 'domain'
|
|
107
|
-
? domainEndpoint
|
|
108
|
-
: requiredCore(definitionsByKey, edge.source),
|
|
109
|
-
target: edge.target.kind === 'domain'
|
|
110
|
-
? domainEndpoint
|
|
111
|
-
: requiredCore(definitionsByKey, edge.target),
|
|
112
|
-
properties: edge.properties,
|
|
113
|
-
});
|
|
114
|
-
}));
|
|
203
|
+
return definitionsByKey;
|
|
204
|
+
}
|
|
205
|
+
function createCallables(definitions, classes) {
|
|
115
206
|
const callables = new Map();
|
|
116
|
-
for (const definition of
|
|
117
|
-
if (definition.ref.kind === 'function')
|
|
207
|
+
for (const definition of definitions.values()) {
|
|
208
|
+
if (definition.ref.kind === 'function') {
|
|
118
209
|
callables.set(definition.key, definition);
|
|
210
|
+
}
|
|
119
211
|
}
|
|
120
|
-
for (const definition of
|
|
212
|
+
for (const definition of classes.values()) {
|
|
121
213
|
if (definition.kind !== 'node')
|
|
122
214
|
continue;
|
|
123
|
-
for (const method of [...definition.methods, ...definition.static.methods])
|
|
215
|
+
for (const method of [...definition.methods, ...definition.static.methods]) {
|
|
124
216
|
callables.set(method.key, method);
|
|
217
|
+
}
|
|
125
218
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return Object.freeze(Object.values(views).filter((view) => {
|
|
146
|
-
if (view.target.kind === 'domain')
|
|
147
|
-
return target === 'domain';
|
|
148
|
-
if (target === 'domain')
|
|
149
|
-
return false;
|
|
150
|
-
return view.target.definitions.some((ref) => {
|
|
151
|
-
const selected = classesByKey.get(Key.of(ref));
|
|
152
|
-
if (target.kind === 'node' && selected?.kind === 'node') {
|
|
153
|
-
return target.satisfies(selected);
|
|
154
|
-
}
|
|
155
|
-
if (target.kind === 'edge' && selected?.kind === 'edge') {
|
|
156
|
-
return target.satisfies(selected);
|
|
157
|
-
}
|
|
158
|
-
return false;
|
|
159
|
-
});
|
|
160
|
-
}));
|
|
161
|
-
},
|
|
162
|
-
};
|
|
163
|
-
const admitted = Object.freeze(domain);
|
|
164
|
-
admittedDomains.add(admitted);
|
|
165
|
-
return admitted;
|
|
219
|
+
return callables;
|
|
220
|
+
}
|
|
221
|
+
function reachableEntries(root, entries) {
|
|
222
|
+
const reachable = new Map();
|
|
223
|
+
const pending = [root];
|
|
224
|
+
while (pending.length > 0) {
|
|
225
|
+
const current = pending.pop();
|
|
226
|
+
if (reachable.has(current.origin))
|
|
227
|
+
continue;
|
|
228
|
+
reachable.set(current.origin, current);
|
|
229
|
+
for (const dependency of Object.values(current.dependencies)) {
|
|
230
|
+
const nested = entries.get(dependency.origin);
|
|
231
|
+
if (nested === undefined || nested.revision !== dependency.revision) {
|
|
232
|
+
throw new TypeError(`Domain universe has no exact ${dependency.origin}@${dependency.revision}.`);
|
|
233
|
+
}
|
|
234
|
+
pending.push(nested);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return [...reachable.values()];
|
|
166
238
|
}
|
|
167
239
|
function namespace(definitions, kind) {
|
|
168
240
|
return frozenRecord(definitions
|
|
@@ -14,12 +14,18 @@ export interface CoreNamespace<Nodes extends Readonly<Record<string, unknown>> =
|
|
|
14
14
|
readonly nodes: Readonly<Nodes>;
|
|
15
15
|
readonly edges: readonly ResolvedCoreEdge[];
|
|
16
16
|
}
|
|
17
|
+
/** Erased recursive dependency projection used when no authored Schema witness is available. */
|
|
18
|
+
export interface DependencyDomains {
|
|
19
|
+
readonly [alias: string]: Domain<Readonly<Record<string, AnyResolvedClass>>, Readonly<Record<string, ResolvedFunction>>, Readonly<Record<string, ResolvedPolicy>>, Readonly<Record<string, ResolvedView>>, Readonly<Record<string, ResolvedCoreDefinition>>, Revision>;
|
|
20
|
+
}
|
|
21
|
+
export type DependencyDomainNamespace<Dependencies extends Readonly<Record<string, unknown>> = DependencyDomains> = Readonly<Dependencies>;
|
|
17
22
|
export interface Domain<Classes extends Readonly<Record<string, unknown>> = Readonly<Record<string, AnyResolvedClass>>, Functions extends Readonly<Record<string, unknown>> = Readonly<Record<string, ResolvedFunction>>, Policies extends Readonly<Record<string, unknown>> = Readonly<Record<string, ResolvedPolicy>>, Views extends Readonly<Record<string, unknown>> = Readonly<Record<string, ResolvedView>>, CoreNodes extends Readonly<Record<string, unknown>> = Readonly<Record<string, ResolvedCoreDefinition>>, RevisionValue extends Revision = Revision> {
|
|
18
23
|
readonly origin: Key.Origin;
|
|
19
24
|
readonly revision: RevisionValue;
|
|
20
25
|
/** Exact admitted root and transitive dependency revisions, ordered by origin. */
|
|
21
26
|
readonly closure: readonly DomainDependency[];
|
|
22
|
-
|
|
27
|
+
/** Exact immutable direct dependency Domains keyed by authored Schema aliases. */
|
|
28
|
+
readonly dependencies: DependencyDomainNamespace;
|
|
23
29
|
readonly classes: ClassNamespace<Classes>;
|
|
24
30
|
readonly functions: FunctionNamespace<Functions>;
|
|
25
31
|
readonly policies: PolicyNamespace<Policies>;
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import type { Key as AddressKey, PropertyKey, Revision } from '../../addressing/index.js';
|
|
2
|
-
import type { DomainDependency } from '../../addressing/index.js';
|
|
2
|
+
import type { DomainDependency, DomainDependencyMap } from '../../addressing/index.js';
|
|
3
3
|
import type { CoreEdge } from '../../language/core/model/core.js';
|
|
4
4
|
import type { Definition } from '../../language/definition.js';
|
|
5
5
|
import type { Diagnostic } from '../../language/validation/diagnostic.js';
|
|
6
6
|
import type { OrderingKind } from '../../language/value/ordering.js';
|
|
7
|
-
/**
|
|
8
|
-
export interface
|
|
7
|
+
/** One admitted Schema entry in a dependency-closed Domain universe. */
|
|
8
|
+
export interface DomainEntryInput {
|
|
9
9
|
readonly origin: AddressKey.Origin;
|
|
10
10
|
readonly revision: Revision;
|
|
11
|
-
readonly
|
|
12
|
-
readonly dependencies: readonly DomainDependency[];
|
|
11
|
+
readonly dependencies: DomainDependencyMap;
|
|
13
12
|
readonly definitions: readonly Definition[];
|
|
14
13
|
readonly coreEdges: readonly CoreEdge[];
|
|
14
|
+
}
|
|
15
|
+
/** Dependency-closed, representation-neutral input to the one Domain universe constructor. */
|
|
16
|
+
export interface DomainInput {
|
|
17
|
+
readonly root: DomainDependency;
|
|
18
|
+
readonly entries: readonly DomainEntryInput[];
|
|
15
19
|
readonly validation: DomainValidation;
|
|
16
20
|
}
|
|
17
21
|
/** Already-bound validation semantics supplied by source compilation or compiled loading. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Key } from '../../addressing/index.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { DomainDependencyMap } from '../../addressing/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* Structurally trusted routing information used to discover an exact
|
|
5
5
|
* dependency closure before whole-Domain semantic acceptance is possible.
|
|
@@ -11,7 +11,7 @@ export interface DomainSchemaHeader {
|
|
|
11
11
|
readonly format: 'astrale.dsl';
|
|
12
12
|
readonly version: 'v1';
|
|
13
13
|
readonly origin: Key.Origin;
|
|
14
|
-
readonly dependencies:
|
|
14
|
+
readonly dependencies: DomainDependencyMap;
|
|
15
15
|
}
|
|
16
16
|
/** Decode detached Schema bytes or text and retain only validated closure-routing identity. */
|
|
17
17
|
export declare function header(input: string | Uint8Array): DomainSchemaHeader;
|
|
@@ -17,7 +17,10 @@ export function header(input) {
|
|
|
17
17
|
return Object.freeze({
|
|
18
18
|
format: structured.format,
|
|
19
19
|
version: structured.version,
|
|
20
|
-
dependencies: Object.freeze(
|
|
20
|
+
dependencies: Object.freeze(Object.fromEntries(Object.entries(structured.dependencies).map(([alias, dependency]) => [
|
|
21
|
+
alias,
|
|
22
|
+
Object.freeze({ ...dependency }),
|
|
23
|
+
]))),
|
|
21
24
|
origin: structured.origin,
|
|
22
25
|
});
|
|
23
26
|
}
|
|
@@ -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' | '
|
|
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';
|
|
2
2
|
export interface SchemaDiagnostic<out Code extends SchemaDiagnosticCode = SchemaDiagnosticCode> {
|
|
3
3
|
readonly code: Code;
|
|
4
4
|
readonly pointer: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Key } from '../../addressing/index.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { DomainDependencyMap } from '../../addressing/index.js';
|
|
3
3
|
import type { Domain, DomainOfSource, DomainSource } from '../../domain/model/domain.js';
|
|
4
4
|
import type { FunctionDefinition } from '../../language/callable/model/callable.js';
|
|
5
5
|
import type { ClassDefinition } from '../../language/class/model/class.js';
|
|
@@ -10,7 +10,7 @@ export interface DomainSchemaIR {
|
|
|
10
10
|
readonly format: 'astrale.dsl';
|
|
11
11
|
readonly version: 'v1';
|
|
12
12
|
readonly origin: Key.Origin;
|
|
13
|
-
readonly dependencies:
|
|
13
|
+
readonly dependencies: DomainDependencyMap;
|
|
14
14
|
readonly classes: Readonly<Record<string, ClassDefinition>>;
|
|
15
15
|
readonly functions: Readonly<Record<string, FunctionDefinition>>;
|
|
16
16
|
readonly policies: Readonly<Record<string, PolicyDefinition>>;
|
|
@@ -11,7 +11,7 @@ export function normalizeDomainSchema(schema) {
|
|
|
11
11
|
format: 'astrale.dsl',
|
|
12
12
|
version: 'v1',
|
|
13
13
|
origin: schema.origin,
|
|
14
|
-
dependencies:
|
|
14
|
+
dependencies: mapRecord(schema.dependencies, (dependency) => ({ ...dependency })),
|
|
15
15
|
classes,
|
|
16
16
|
functions: mapRecord(schema.functions, canonicalizeFunction),
|
|
17
17
|
policies: mapRecord(schema.policies, canonicalizePolicy),
|
|
@@ -6,4 +6,4 @@ export declare function rememberDomainClosure(schema: DomainSchema, dependencies
|
|
|
6
6
|
export declare function knownDomainClosure(schema: DomainSchema): readonly DomainSchemaEntry[] | undefined;
|
|
7
7
|
export declare function requireDomainClosure(schema: DomainSchema): readonly DomainSchemaEntry[];
|
|
8
8
|
export declare function lookupForDomainClosure(schema: DomainSchema): DomainSchemaLookup;
|
|
9
|
-
export declare function lookupEntriesForDependencies(dependencies:
|
|
9
|
+
export declare function lookupEntriesForDependencies(dependencies: Readonly<Record<string, DomainSchema>>): readonly DomainSchemaEntry[];
|
|
@@ -29,7 +29,7 @@ export function lookupForDomainClosure(schema) {
|
|
|
29
29
|
}
|
|
30
30
|
export function lookupEntriesForDependencies(dependencies) {
|
|
31
31
|
const entries = new Map();
|
|
32
|
-
for (const schema of dependencies) {
|
|
32
|
+
for (const schema of Object.values(dependencies)) {
|
|
33
33
|
const revision = revisionOfDomainSchema(schema);
|
|
34
34
|
entries.set(closureKey(schema.origin, revision), { schema, revision });
|
|
35
35
|
for (const entry of requireDomainClosure(schema)) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dependencyLimits } from '../../addressing/domain-dependency.js';
|
|
1
2
|
import { definitionRefIndex } from '../../addressing/ordering.js';
|
|
2
3
|
import { DOMAIN_REVISION_PATTERN } from '../../addressing/patterns.js';
|
|
3
4
|
import { revisionOfDomainSchema } from '../canonical/revision.js';
|
|
@@ -9,8 +10,11 @@ export function buildDomainResolutionContext(candidate, lookup) {
|
|
|
9
10
|
const dependencies = new Map();
|
|
10
11
|
const definitions = new Map();
|
|
11
12
|
const directOrigins = new Set();
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
if (Object.keys(candidate.dependencies).length > dependencyLimits.direct) {
|
|
14
|
+
diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', '/dependencies', `A Domain may declare at most ${dependencyLimits.direct} direct dependency aliases.`));
|
|
15
|
+
}
|
|
16
|
+
for (const [alias, dependency] of Object.entries(candidate.dependencies)) {
|
|
17
|
+
const pointer = `/dependencies/${escapeToken(alias)}`;
|
|
14
18
|
if (!DOMAIN_REVISION_PATTERN.test(dependency.revision))
|
|
15
19
|
continue;
|
|
16
20
|
if (dependency.origin === candidate.origin) {
|
|
@@ -68,48 +72,78 @@ export function buildDomainResolutionContext(candidate, lookup) {
|
|
|
68
72
|
}
|
|
69
73
|
function loadDependencyClosure(candidate, dependencies, lookup, diagnostics) {
|
|
70
74
|
const revisionByOrigin = new Map();
|
|
71
|
-
const visiting = new Set();
|
|
72
75
|
const visited = new Set();
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
const activeOrigins = new Set([candidate.origin]);
|
|
77
|
+
const frames = [
|
|
78
|
+
{
|
|
79
|
+
schema: candidate,
|
|
80
|
+
key: `${candidate.origin}\u0000candidate`,
|
|
81
|
+
depth: 0,
|
|
82
|
+
edges: Object.entries(candidate.dependencies),
|
|
83
|
+
index: 0,
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
let closureLimitReported = false;
|
|
87
|
+
while (frames.length > 0) {
|
|
88
|
+
const frame = frames.at(-1);
|
|
89
|
+
const edge = frame.edges[frame.index++];
|
|
90
|
+
if (edge === undefined) {
|
|
91
|
+
frames.pop();
|
|
92
|
+
activeOrigins.delete(frame.schema.origin);
|
|
93
|
+
visited.add(frame.key);
|
|
94
|
+
continue;
|
|
77
95
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
96
|
+
const [alias, dependency] = edge;
|
|
97
|
+
const pointer = frames.length === 1 ? `/dependencies/${escapeToken(alias)}` : '/dependencies';
|
|
98
|
+
if (activeOrigins.has(dependency.origin)) {
|
|
99
|
+
diagnostics.push(diagnostic('DV_DEPENDENCY_ACYCLIC', pointer, `Dependency cycle reaches ${dependency.origin}.`));
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const previous = revisionByOrigin.get(dependency.origin);
|
|
103
|
+
if (previous !== undefined && previous !== dependency.revision) {
|
|
104
|
+
diagnostics.push(diagnostic('DV_DEPENDENCY_REVISION_COHERENCE', pointer, `Closure contains ${dependency.origin} at both ${previous} and ${dependency.revision}.`));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
revisionByOrigin.set(dependency.origin, dependency.revision);
|
|
108
|
+
}
|
|
109
|
+
const nested = dependencies.get(dependency.origin) ?? lookup.get(dependency.origin, dependency.revision);
|
|
110
|
+
if (nested === undefined) {
|
|
111
|
+
diagnostics.push(diagnostic('DV_DEPENDENCY_EXACT', pointer, `Dependency closure is missing ${dependency.origin}@${dependency.revision}.`));
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (nested.origin !== dependency.origin) {
|
|
115
|
+
diagnostics.push(diagnostic('DV_DEPENDENCY_EXACT', pointer, `Schema lookup value origin ${nested.origin} disagrees with ${dependency.origin}.`));
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (revisionOfDomainSchema(nested) !== dependency.revision) {
|
|
119
|
+
diagnostics.push(diagnostic('DV_DEPENDENCY_EXACT', pointer, `Schema lookup content for ${dependency.origin} disagrees with ${dependency.revision}.`));
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
dependencies.set(nested.origin, nested);
|
|
123
|
+
if (dependencies.size + 1 > dependencyLimits.closure) {
|
|
124
|
+
if (!closureLimitReported) {
|
|
125
|
+
diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', '/dependencies', `A dependency closure may contain at most ${dependencyLimits.closure} Domains.`));
|
|
126
|
+
closureLimitReported = true;
|
|
105
127
|
}
|
|
106
|
-
|
|
107
|
-
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const depth = frame.depth + 1;
|
|
131
|
+
if (depth > dependencyLimits.depth) {
|
|
132
|
+
diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', pointer, `Dependency depth ${depth} exceeds the maximum ${dependencyLimits.depth}.`));
|
|
133
|
+
continue;
|
|
108
134
|
}
|
|
109
|
-
|
|
110
|
-
visited.
|
|
111
|
-
|
|
112
|
-
|
|
135
|
+
const key = `${dependency.origin}\u0000${dependency.revision}`;
|
|
136
|
+
if (visited.has(key))
|
|
137
|
+
continue;
|
|
138
|
+
activeOrigins.add(nested.origin);
|
|
139
|
+
frames.push({
|
|
140
|
+
schema: nested,
|
|
141
|
+
key,
|
|
142
|
+
depth,
|
|
143
|
+
edges: Object.entries(nested.dependencies),
|
|
144
|
+
index: 0,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
113
147
|
}
|
|
114
148
|
function validateDependencyUse(candidate, directOrigins, diagnostics) {
|
|
115
149
|
const used = collectUsedForeignOrigins(candidate);
|
|
@@ -118,9 +152,7 @@ function validateDependencyUse(candidate, directOrigins, diagnostics) {
|
|
|
118
152
|
diagnostics.push(diagnostic('DV_DEPENDENCY_DIRECT', '', `Foreign origin ${origin} is referenced without an exact direct dependency.`));
|
|
119
153
|
}
|
|
120
154
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
}
|
|
155
|
+
}
|
|
156
|
+
function escapeToken(value) {
|
|
157
|
+
return value.replaceAll('~', '~0').replaceAll('/', '~1');
|
|
126
158
|
}
|
|
@@ -29,17 +29,20 @@ function resolveWithLookup(schema, lookup) {
|
|
|
29
29
|
const closure = [schema, ...validation.context.dependencies.values()];
|
|
30
30
|
const registries = new Map(closure.map((owner) => [owner.origin, createDomainValueSchemaRegistry(owner, lookup)]));
|
|
31
31
|
const input = {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
root: Object.freeze({
|
|
33
|
+
origin: schema.origin,
|
|
34
|
+
revision: revisionOfDomainSchema(schema),
|
|
35
|
+
}),
|
|
36
|
+
entries: Object.freeze(closure
|
|
37
|
+
.filter((owner, index, values) => values.findIndex(({ origin }) => origin === owner.origin) === index)
|
|
35
38
|
.map((owner) => Object.freeze({
|
|
36
39
|
origin: owner.origin,
|
|
37
40
|
revision: revisionOfDomainSchema(owner),
|
|
41
|
+
dependencies: owner.dependencies,
|
|
42
|
+
definitions: Object.freeze(definitionsOf(owner)),
|
|
43
|
+
coreEdges: owner.core.edges,
|
|
38
44
|
}))
|
|
39
45
|
.sort((left, right) => compareUnicode(left.origin, right.origin))),
|
|
40
|
-
dependencies: schema.dependencies,
|
|
41
|
-
definitions: Object.freeze(closure.flatMap(definitionsOf)),
|
|
42
|
-
coreEdges: schema.core.edges,
|
|
43
46
|
validation: sourceValidation(closure, registries),
|
|
44
47
|
};
|
|
45
48
|
const domain = createDomain(input);
|
|
@@ -9,7 +9,7 @@ export function createDomainValueSchemaRegistry(schema, lookup = lookupForDomain
|
|
|
9
9
|
export function createValueSchemaRegistry(context) {
|
|
10
10
|
const directDependencies = [];
|
|
11
11
|
const transitiveDependencies = [];
|
|
12
|
-
const directOrigins = new Set(context.candidate.dependencies.map(({ origin }) => origin));
|
|
12
|
+
const directOrigins = new Set(Object.values(context.candidate.dependencies).map(({ origin }) => origin));
|
|
13
13
|
for (const schema of context.dependencies.values()) {
|
|
14
14
|
const entry = {
|
|
15
15
|
...domainValueRegistry(schema),
|
|
@@ -33,7 +33,7 @@ function domainValueRegistry(schema) {
|
|
|
33
33
|
.map(([name]) => ({ origin: schema.origin, kind: 'class', name }));
|
|
34
34
|
return Object.freeze({
|
|
35
35
|
origin: schema.origin,
|
|
36
|
-
dependencies: schema.dependencies,
|
|
36
|
+
dependencies: Object.values(schema.dependencies),
|
|
37
37
|
nodeDefinitions: Object.freeze(nodeDefinitions),
|
|
38
38
|
});
|
|
39
39
|
}
|