@astrale-os/kernel-dsl 0.2.0-beta.7 → 0.2.0-beta.8
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 +1 -1
|
@@ -6,3 +6,13 @@ export type DomainDependency = JsonObject & {
|
|
|
6
6
|
readonly origin: Key.Origin;
|
|
7
7
|
readonly revision: Revision;
|
|
8
8
|
};
|
|
9
|
+
/** Canonical authored name of one exact direct dependency. */
|
|
10
|
+
export type DependencyAlias = string;
|
|
11
|
+
/** Exact direct dependency pins keyed by their authored aliases. */
|
|
12
|
+
export type DomainDependencyMap = Readonly<Record<DependencyAlias, DomainDependency>>;
|
|
13
|
+
/** Representation-neutral admission budgets shared by source and compiled closures. */
|
|
14
|
+
export declare const dependencyLimits: Readonly<{
|
|
15
|
+
direct: 32;
|
|
16
|
+
closure: 256;
|
|
17
|
+
depth: 32;
|
|
18
|
+
}>;
|
|
@@ -9,4 +9,4 @@ export { Key } from './key.js';
|
|
|
9
9
|
export { compareUnicode } from './ordering.js';
|
|
10
10
|
export { PropertyKey } from './property-key.js';
|
|
11
11
|
export type { Revision } from './revision.js';
|
|
12
|
-
export type { DomainDependency } from './domain-dependency.js';
|
|
12
|
+
export type { DependencyAlias, DomainDependency, DomainDependencyMap } from './domain-dependency.js';
|
|
@@ -2,15 +2,19 @@
|
|
|
2
2
|
export type DomainOrigin = string;
|
|
3
3
|
export declare const DOMAIN_ORIGIN_PATTERN: RegExp;
|
|
4
4
|
export declare const MEMBER_NAME_PATTERN: RegExp;
|
|
5
|
+
export declare const DEPENDENCY_ALIAS_PATTERN: RegExp;
|
|
5
6
|
export declare const CORE_SLUG_PATTERN: RegExp;
|
|
6
7
|
export declare const DOMAIN_REVISION_PATTERN: RegExp;
|
|
7
8
|
export declare const DOMAIN_REVISION_FULL_MATCH = "sha256:[0-9a-f]{64}";
|
|
8
9
|
export declare const DOMAIN_ORIGIN_FULL_MATCH = "([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?";
|
|
9
10
|
export declare const MEMBER_NAME_FULL_MATCH = "[A-Za-z][A-Za-z0-9_]*";
|
|
11
|
+
export declare const DEPENDENCY_ALIAS_FULL_MATCH = "[a-z][A-Za-z0-9]*";
|
|
10
12
|
export declare const PROPERTY_KEY_FULL_MATCH = "([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?:class\\.[A-Za-z][A-Za-z0-9_]*\\.property\\.[A-Za-z][A-Za-z0-9_]*";
|
|
11
13
|
export declare const PROPERTY_KEY_PATTERN: RegExp;
|
|
12
14
|
export declare const patterns: Readonly<{
|
|
13
15
|
coreSlug: RegExp;
|
|
16
|
+
dependencyAlias: RegExp;
|
|
17
|
+
dependencyAliasFullMatch: "[a-z][A-Za-z0-9]*";
|
|
14
18
|
memberName: RegExp;
|
|
15
19
|
memberNameFullMatch: "[A-Za-z][A-Za-z0-9_]*";
|
|
16
20
|
origin: RegExp;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
export const DOMAIN_ORIGIN_PATTERN = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u;
|
|
2
2
|
export const MEMBER_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/u;
|
|
3
|
+
export const DEPENDENCY_ALIAS_PATTERN = /^[a-z][A-Za-z0-9]*$/u;
|
|
3
4
|
export const CORE_SLUG_PATTERN = /^[a-z][a-z0-9-]{0,99}$/u;
|
|
4
5
|
export const DOMAIN_REVISION_PATTERN = /^sha256:[0-9a-f]{64}$/u;
|
|
5
6
|
export const DOMAIN_REVISION_FULL_MATCH = 'sha256:[0-9a-f]{64}';
|
|
6
7
|
export const DOMAIN_ORIGIN_FULL_MATCH = '([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?';
|
|
7
8
|
export const MEMBER_NAME_FULL_MATCH = '[A-Za-z][A-Za-z0-9_]*';
|
|
9
|
+
export const DEPENDENCY_ALIAS_FULL_MATCH = '[a-z][A-Za-z0-9]*';
|
|
8
10
|
export const PROPERTY_KEY_FULL_MATCH = '([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?:class\\.[A-Za-z][A-Za-z0-9_]*\\.property\\.[A-Za-z][A-Za-z0-9_]*';
|
|
9
11
|
export const PROPERTY_KEY_PATTERN = new RegExp(`^(?:${PROPERTY_KEY_FULL_MATCH})$`, 'u');
|
|
10
12
|
export const patterns = Object.freeze({
|
|
11
13
|
coreSlug: CORE_SLUG_PATTERN,
|
|
14
|
+
dependencyAlias: DEPENDENCY_ALIAS_PATTERN,
|
|
15
|
+
dependencyAliasFullMatch: DEPENDENCY_ALIAS_FULL_MATCH,
|
|
12
16
|
memberName: MEMBER_NAME_PATTERN,
|
|
13
17
|
memberNameFullMatch: MEMBER_NAME_FULL_MATCH,
|
|
14
18
|
origin: DOMAIN_ORIGIN_PATTERN,
|
|
@@ -7,7 +7,7 @@ export { policy } from './policy/authoring.js';
|
|
|
7
7
|
export { auto, property } from './class/property.js';
|
|
8
8
|
export { defineSchema } from './schema/define.js';
|
|
9
9
|
export type { DefinedSchema } from './schema/define.js';
|
|
10
|
-
export type { DomainOfInput } from './schema/projection.js';
|
|
10
|
+
export type { DirectDependenciesOf, DomainOfInput } from './schema/projection.js';
|
|
11
11
|
export { valueSchema } from './value/schema.js';
|
|
12
12
|
export { view } from './view/authoring.js';
|
|
13
13
|
export { BuilderError as Error } from './schema/diagnostic.js';
|
|
@@ -11,7 +11,7 @@ export function captureSchema(input, context) {
|
|
|
11
11
|
captureCore(input.core, context);
|
|
12
12
|
}
|
|
13
13
|
export function commitSchemaOwnership(input, origin) {
|
|
14
|
-
const context = new BuilderContext(origin, input.dependencies ??
|
|
14
|
+
const context = new BuilderContext(origin, input.dependencies ?? {});
|
|
15
15
|
captureSchema(input, context);
|
|
16
16
|
context.ownership.commit();
|
|
17
17
|
}
|
|
@@ -21,7 +21,7 @@ import { BuilderContext } from './context.js';
|
|
|
21
21
|
* This is the private implementation behind defineSchema.
|
|
22
22
|
*/
|
|
23
23
|
export function compileSchema(origin, input) {
|
|
24
|
-
const lookup = new MemoryDomainSchemaLookup(lookupEntriesForDependencies(input.dependencies ??
|
|
24
|
+
const lookup = new MemoryDomainSchemaLookup(lookupEntriesForDependencies(input.dependencies ?? {}));
|
|
25
25
|
const candidate = assembleDomainSchema(origin, input, lookup);
|
|
26
26
|
const accepted = acceptDomainSchema(candidate, lookup);
|
|
27
27
|
commitSchemaOwnership(input, origin);
|
|
@@ -32,7 +32,7 @@ function assembleDomainSchema(origin, input, lookup) {
|
|
|
32
32
|
assertKnownKeys(input, ['dependencies', 'classes', 'functions', 'policies', 'views', 'core']);
|
|
33
33
|
if (input.core !== undefined)
|
|
34
34
|
assertKnownKeys(input.core, ['nodes', 'edges'], '/core');
|
|
35
|
-
const context = new BuilderContext(origin, input.dependencies ??
|
|
35
|
+
const context = new BuilderContext(origin, input.dependencies ?? {});
|
|
36
36
|
captureSchema(input, context);
|
|
37
37
|
const values = new BuilderValueSchemas(context);
|
|
38
38
|
const policyCompiler = new V1PolicyCompiler(context);
|
|
@@ -25,7 +25,7 @@ export declare class BuilderContext {
|
|
|
25
25
|
readonly policyObjects: Map<object, PolicyObjectRef>;
|
|
26
26
|
readonly policyObjectRefs: Set<string>;
|
|
27
27
|
readonly usedForeignOrigins: Set<string>;
|
|
28
|
-
constructor(origin: string, dependencies:
|
|
28
|
+
constructor(origin: string, dependencies: Readonly<Record<string, DomainSchema>>);
|
|
29
29
|
captureDefinition(value: Class, ref: ClassRef, pointer: string): void;
|
|
30
30
|
capturePolicy(value: Policy, ref: PolicyRef, pointer: string): void;
|
|
31
31
|
captureCoreNode(value: CoreNode, slug: string, pointer: string): void;
|
|
@@ -37,7 +37,7 @@ export declare class BuilderContext {
|
|
|
37
37
|
resolvePolicy(input: Lazy<unknown>, pointer: string): PolicyRef;
|
|
38
38
|
resolvePolicyObject(input: PolicyObjectInput | PolicyObjectRef, pointer: string): PolicyObjectRef;
|
|
39
39
|
resolveCoreEndpoint(value: CoreNode | CoreEndpointRef, pointer: string): CoreEndpointRef;
|
|
40
|
-
dependencies():
|
|
40
|
+
dependencies(): Readonly<Record<string, DomainDependency>>;
|
|
41
41
|
valueRegistry(): ValueSchemaRegistry;
|
|
42
42
|
noteDomainEndpointReferences(schema: DomainSchemaIR): void;
|
|
43
43
|
private assertVisible;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { compareDefinitionRefs, compareUnicode, definitionRefIndex, } from '../../../addressing/ordering.js';
|
|
2
|
+
import { DEPENDENCY_ALIAS_PATTERN } from '../../../addressing/patterns.js';
|
|
2
3
|
import { graphNodePathSchema } from '../../../language/value/syntax/node-path/schema.js';
|
|
3
4
|
import { revisionOfDomainSchema } from '../../../schema/canonical/revision.js';
|
|
4
5
|
import { requireDomainClosure } from '../../../schema/resolution/closure.js';
|
|
@@ -19,16 +20,23 @@ export class BuilderContext {
|
|
|
19
20
|
#dependencies = new Map();
|
|
20
21
|
constructor(origin, dependencies) {
|
|
21
22
|
this.origin = origin;
|
|
22
|
-
for (const schema of dependencies) {
|
|
23
|
+
for (const [alias, schema] of Object.entries(dependencies)) {
|
|
24
|
+
const pointer = `/dependencies/${escapePointer(alias)}`;
|
|
25
|
+
if (!DEPENDENCY_ALIAS_PATTERN.test(alias)) {
|
|
26
|
+
fail('BUILDER_DEPENDENCY_ALIAS', pointer, `Dependency alias ${JSON.stringify(alias)} must be an ASCII lower-camel identifier.`);
|
|
27
|
+
}
|
|
28
|
+
if (alias === '__proto__' || alias === 'prototype' || alias === 'constructor') {
|
|
29
|
+
fail('BUILDER_DEPENDENCY_ALIAS', pointer, `Dependency alias ${JSON.stringify(alias)} is reserved.`);
|
|
30
|
+
}
|
|
23
31
|
const dependency = {
|
|
24
32
|
origin: schema.origin,
|
|
25
33
|
revision: revisionOfDomainSchema(schema),
|
|
26
34
|
};
|
|
27
35
|
const previous = this.#dependencies.get(schema.origin);
|
|
28
|
-
if (previous !== undefined
|
|
29
|
-
fail('BUILDER_DEPENDENCY_CONFLICT',
|
|
36
|
+
if (previous !== undefined) {
|
|
37
|
+
fail('BUILDER_DEPENDENCY_CONFLICT', pointer, `Dependency origin ${schema.origin} is already declared as alias ${previous.alias}.`);
|
|
30
38
|
}
|
|
31
|
-
this.#dependencies.set(schema.origin, { schema, dependency });
|
|
39
|
+
this.#dependencies.set(schema.origin, { alias, schema, dependency });
|
|
32
40
|
}
|
|
33
41
|
}
|
|
34
42
|
captureDefinition(value, ref, pointer) {
|
|
@@ -75,11 +83,11 @@ export class BuilderContext {
|
|
|
75
83
|
if (ref.kind !== 'class') {
|
|
76
84
|
fail('BUILDER_REFERENCE_KIND', pointer, `Expected class, received ${ref.kind}.`);
|
|
77
85
|
}
|
|
86
|
+
this.assertVisible(ref, pointer);
|
|
78
87
|
const resolvedKind = this.classKind(ref);
|
|
79
88
|
if (resolvedKind !== kind) {
|
|
80
89
|
fail('BUILDER_REFERENCE_KIND', pointer, `Expected ${kind} Class, received ${resolvedKind ?? 'an unresolved Class'}.`);
|
|
81
90
|
}
|
|
82
|
-
this.assertVisible(ref, pointer);
|
|
83
91
|
return { ...ref };
|
|
84
92
|
}
|
|
85
93
|
resolveNodeClass(input, pointer) {
|
|
@@ -123,15 +131,9 @@ export class BuilderContext {
|
|
|
123
131
|
return { ...ref };
|
|
124
132
|
}
|
|
125
133
|
dependencies() {
|
|
126
|
-
return [...this.
|
|
127
|
-
.
|
|
128
|
-
|
|
129
|
-
if (source === undefined) {
|
|
130
|
-
fail('BUILDER_DEPENDENCY_DIRECT', '/dependencies', `Foreign origin ${origin} is not supplied as an exact direct dependency.`);
|
|
131
|
-
}
|
|
132
|
-
return source.dependency;
|
|
133
|
-
})
|
|
134
|
-
.sort((left, right) => compareUnicode(left.origin, right.origin));
|
|
134
|
+
return Object.fromEntries([...this.#dependencies.values()]
|
|
135
|
+
.sort((left, right) => compareUnicode(left.alias, right.alias))
|
|
136
|
+
.map(({ alias, dependency }) => [alias, dependency]));
|
|
135
137
|
}
|
|
136
138
|
valueRegistry() {
|
|
137
139
|
const directOrigins = new Set(this.#dependencies.keys());
|
|
@@ -145,7 +147,7 @@ export class BuilderContext {
|
|
|
145
147
|
const registryDomain = (schema, revision) => ({
|
|
146
148
|
origin: schema.origin,
|
|
147
149
|
...(revision === undefined ? {} : { revision }),
|
|
148
|
-
dependencies: schema.dependencies,
|
|
150
|
+
dependencies: Object.values(schema.dependencies),
|
|
149
151
|
nodeDefinitions: definitionRefsOf(schema),
|
|
150
152
|
});
|
|
151
153
|
return {
|
|
@@ -192,6 +194,9 @@ export class BuilderContext {
|
|
|
192
194
|
this.policyObjectRefs.add(definitionRefIndex(ref));
|
|
193
195
|
}
|
|
194
196
|
}
|
|
197
|
+
function escapePointer(value) {
|
|
198
|
+
return value.replaceAll('~', '~0').replaceAll('/', '~1');
|
|
199
|
+
}
|
|
195
200
|
function isResolvedClass(value) {
|
|
196
201
|
if (typeof value !== 'object' || value === null)
|
|
197
202
|
return false;
|
|
@@ -7,4 +7,4 @@ export interface DefinedSchema<Origin extends string, Input extends DomainSchema
|
|
|
7
7
|
}
|
|
8
8
|
/** Build and semantically accept one immutable V1 DomainSchema. */
|
|
9
9
|
export declare function defineSchema<const Origin extends string, const Input extends DomainSchemaInput>(origin: Origin, input: Input): DefinedSchema<Origin, Input>;
|
|
10
|
-
export type { DomainOfInput } from './projection.js';
|
|
10
|
+
export type { DirectDependenciesOf, DomainOfInput } from './projection.js';
|
|
@@ -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_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_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_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_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_REFERENCE_CYCLE' | 'BUILDER_REFERENCE_KIND' | 'BUILDER_REFERENCE_MISSING' | '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;
|
|
@@ -6,7 +6,7 @@ import type { Policy } from '../policy/authoring.js';
|
|
|
6
6
|
import type { View } from '../view/authoring.js';
|
|
7
7
|
/** Complete authoring input accepted by defineSchema. */
|
|
8
8
|
export interface DomainSchemaInput {
|
|
9
|
-
readonly dependencies?:
|
|
9
|
+
readonly dependencies?: Readonly<Record<string, DomainSchema<string, unknown>>>;
|
|
10
10
|
readonly classes?: Readonly<Record<string, Class>>;
|
|
11
11
|
readonly functions?: Readonly<Record<string, Function>>;
|
|
12
12
|
readonly policies?: Readonly<Record<string, Policy>>;
|
|
@@ -3,12 +3,13 @@ import type { z } from 'zod';
|
|
|
3
3
|
import type { Revision } from '../../addressing/index.js';
|
|
4
4
|
import type * as domain from '../../domain/model/domain.js';
|
|
5
5
|
import type * as language from '../../language/index.js';
|
|
6
|
+
import type { DomainOfSchema, DomainSchema } from '../../schema/model/schema.js';
|
|
6
7
|
import type { Function, Method, StreamOutput, BinaryOutput } from '../callable/authoring.js';
|
|
7
8
|
import type { Class, DirectedEdgeClass, NodeClass, UndirectedEdgeClass } from '../class/authoring.js';
|
|
8
9
|
import type { AutoProperty, Property, PropertyOptions, ValueSchemaInput } from '../class/property.js';
|
|
9
10
|
import type { TypedValueSchema } from '../value/schema.js';
|
|
10
11
|
import type { DomainSchemaInput } from './input.js';
|
|
11
|
-
export type DomainOfInput<Origin extends string, Input extends DomainSchemaInput> = domain.Domain<{
|
|
12
|
+
export type DomainOfInput<Origin extends string, Input extends DomainSchemaInput> = Omit<domain.Domain<{
|
|
12
13
|
readonly [Name in keyof ClassesInputOf<Input>]: ResolvedClassOfAuthoring<Origin, Extract<Name, string>, ClassesInputOf<Input>[Name], Input>;
|
|
13
14
|
}, {
|
|
14
15
|
readonly [Name in keyof FunctionsInputOf<Input>]: ResolvedFunctionOfAuthoring<Origin, Extract<Name, string>, FunctionsInputOf<Input>[Name]>;
|
|
@@ -33,7 +34,17 @@ export type DomainOfInput<Origin extends string, Input extends DomainSchemaInput
|
|
|
33
34
|
};
|
|
34
35
|
readonly key: language.DefinitionKey<Origin, 'core', Extract<Name, string>>;
|
|
35
36
|
};
|
|
36
|
-
}, Revision
|
|
37
|
+
}, Revision>, 'dependencies'> & {
|
|
38
|
+
readonly dependencies: domain.DependencyDomainNamespace<DirectDependenciesOf<Input>>;
|
|
39
|
+
};
|
|
40
|
+
type DependenciesInputOf<Input extends DomainSchemaInput> = RecordField<Input, 'dependencies'>;
|
|
41
|
+
/**
|
|
42
|
+
* Project exact direct witnesses already carried by dependency Schemas.
|
|
43
|
+
* This intentionally never reopens their authored inputs or computes a typed closure.
|
|
44
|
+
*/
|
|
45
|
+
export type DirectDependenciesOf<Input extends DomainSchemaInput> = {
|
|
46
|
+
readonly [Alias in keyof DependenciesInputOf<Input>]: DependenciesInputOf<Input>[Alias] extends infer Dependency extends DomainSchema ? DomainOfSchema<Dependency> : never;
|
|
47
|
+
};
|
|
37
48
|
export type ResolvedClassOfAuthoring<Origin extends string, Name extends string, Value, Root extends DomainSchemaInput, Seen = Value> = Value extends NodeClass ? language.ResolvedClass<'node', PropertyCollectionOf<Origin, EffectivePropertyEntries<Value>, Root>, MethodCollectionOf<Origin, EffectiveMethodEntries<Value, false>, Root>, MethodCollectionOf<Origin, EffectiveMethodEntries<Value, true>, Root>, PropertyCollectionOf<Origin, OwnPropertyEntries<Value>, Root>, MethodCollectionOf<Origin, OwnMethodEntries<Value, false>, Root>, MethodCollectionOf<Origin, OwnMethodEntries<Value, true>, Root>, Extract<ProjectedClassDefinition<Origin, Name, Value>, language.NodeClassDefinition>, ResolvedParentsOf<Origin, Value, Root, Seen>> & {
|
|
38
49
|
readonly ref: language.ClassRef<Name> & {
|
|
39
50
|
readonly origin: Origin;
|
package/dist/v1/bundle/codec.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { accept as acceptJsonValue, BoundaryError as JsonBoundaryError, json, } from '@astrale-os/kernel-dsl/value';
|
|
2
|
+
import { dependencyLimits } from '../addressing/domain-dependency.js';
|
|
2
3
|
import { compareUnicode } from '../addressing/ordering.js';
|
|
3
4
|
import { acceptDomainSchema } from '../schema/admission/accept.js';
|
|
4
5
|
import { canonicalDomainSchemaAfterValidation } from '../schema/canonical/revision.js';
|
|
@@ -8,6 +9,9 @@ import { SchemaError } from '../schema/diagnostic.js';
|
|
|
8
9
|
import { BundleError, diagnostic } from './diagnostic.js';
|
|
9
10
|
import { asDomainSchemaBundle, } from './model.js';
|
|
10
11
|
import { parseDomainSchemaBundleStructure } from './structure.js';
|
|
12
|
+
function escapeToken(value) {
|
|
13
|
+
return value.replaceAll('~', '~0').replaceAll('/', '~1');
|
|
14
|
+
}
|
|
11
15
|
export function acceptDomainSchemaBundle(input) {
|
|
12
16
|
let admitted;
|
|
13
17
|
try {
|
|
@@ -44,6 +48,14 @@ function acceptBundleValue(input) {
|
|
|
44
48
|
const closure = structure.closure.map((schema, index) => candidate(schema, `/closure/${index}`));
|
|
45
49
|
const diagnostics = [];
|
|
46
50
|
const candidates = new Map();
|
|
51
|
+
if (closure.length + 1 > dependencyLimits.closure) {
|
|
52
|
+
diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', '/closure', `A dependency closure may contain at most ${dependencyLimits.closure} Domains.`));
|
|
53
|
+
}
|
|
54
|
+
for (const entry of [root, ...closure]) {
|
|
55
|
+
if (Object.keys(entry.schema.dependencies).length > dependencyLimits.direct) {
|
|
56
|
+
diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', `${entry.pointer}/dependencies`, `A Domain may declare at most ${dependencyLimits.direct} direct dependency aliases.`));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
47
59
|
for (const entry of closure) {
|
|
48
60
|
if (entry.key === root.key) {
|
|
49
61
|
diagnostics.push(diagnostic('BUNDLE_ROOT_DUPLICATED', entry.pointer, 'The root schema must not also appear in the bundle closure.'));
|
|
@@ -57,7 +69,7 @@ function acceptBundleValue(input) {
|
|
|
57
69
|
candidates.set(entry.key, entry);
|
|
58
70
|
}
|
|
59
71
|
const reachable = new Set();
|
|
60
|
-
collectReachable(root, root, candidates, reachable, new Set(), new Map(), diagnostics);
|
|
72
|
+
collectReachable(root, root, candidates, reachable, new Set(), new Map(), diagnostics, 0);
|
|
61
73
|
for (const entry of candidates.values()) {
|
|
62
74
|
if (!reachable.has(entry.key)) {
|
|
63
75
|
diagnostics.push(diagnostic('BUNDLE_CLOSURE_EXTRA', entry.pointer, `Schema ${entry.schema.origin}@${entry.revision} is not reachable from the root.`));
|
|
@@ -70,7 +82,7 @@ function acceptBundleValue(input) {
|
|
|
70
82
|
const existing = accepted.get(entry.key);
|
|
71
83
|
if (existing !== undefined)
|
|
72
84
|
return existing.schema;
|
|
73
|
-
for (const dependency of entry.schema.dependencies) {
|
|
85
|
+
for (const dependency of Object.values(entry.schema.dependencies)) {
|
|
74
86
|
const nested = candidates.get(keyOf(dependency.origin, dependency.revision));
|
|
75
87
|
if (nested !== undefined)
|
|
76
88
|
acceptCandidate(nested);
|
|
@@ -112,14 +124,19 @@ function candidate(schema, pointer) {
|
|
|
112
124
|
pointer,
|
|
113
125
|
};
|
|
114
126
|
}
|
|
115
|
-
function collectReachable(current, root, candidates, reachable, active, revisions, diagnostics) {
|
|
127
|
+
function collectReachable(current, root, candidates, reachable, active, revisions, diagnostics, depth) {
|
|
116
128
|
if (active.has(current.key)) {
|
|
117
129
|
diagnostics.push(diagnostic('BUNDLE_CLOSURE_CYCLE', current.pointer, `Bundle dependency cycle reaches ${current.schema.origin}@${current.revision}.`));
|
|
118
130
|
return;
|
|
119
131
|
}
|
|
120
132
|
active.add(current.key);
|
|
121
|
-
|
|
122
|
-
|
|
133
|
+
if (depth > dependencyLimits.depth) {
|
|
134
|
+
diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', current.pointer, `Dependency depth ${depth} exceeds the maximum ${dependencyLimits.depth}.`));
|
|
135
|
+
active.delete(current.key);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
for (const [alias, dependency] of Object.entries(current.schema.dependencies)) {
|
|
139
|
+
const pointer = `${current.pointer}/dependencies/${escapeToken(alias)}`;
|
|
123
140
|
const dependencyKey = keyOf(dependency.origin, dependency.revision);
|
|
124
141
|
if (dependencyKey === root.key) {
|
|
125
142
|
diagnostics.push(diagnostic('BUNDLE_CLOSURE_CYCLE', pointer, 'Bundle dependency closure returns to its root.'));
|
|
@@ -138,7 +155,7 @@ function collectReachable(current, root, candidates, reachable, active, revision
|
|
|
138
155
|
continue;
|
|
139
156
|
}
|
|
140
157
|
reachable.add(nested.key);
|
|
141
|
-
collectReachable(nested, root, candidates, reachable, active, revisions, diagnostics);
|
|
158
|
+
collectReachable(nested, root, candidates, reachable, active, revisions, diagnostics, depth + 1);
|
|
142
159
|
}
|
|
143
160
|
active.delete(current.key);
|
|
144
161
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { accept as acceptPortable } from '@astrale-os/kernel-dsl/value';
|
|
2
|
+
import { dependencyLimits } from '../../addressing/domain-dependency.js';
|
|
2
3
|
import { isDefinitionRef, Key } from '../../addressing/index.js';
|
|
3
|
-
import { DOMAIN_ORIGIN_PATTERN, DOMAIN_REVISION_PATTERN, MEMBER_NAME_PATTERN, } from '../../addressing/patterns.js';
|
|
4
|
+
import { DEPENDENCY_ALIAS_PATTERN, DOMAIN_ORIGIN_PATTERN, DOMAIN_REVISION_PATTERN, MEMBER_NAME_PATTERN, } from '../../addressing/patterns.js';
|
|
4
5
|
import { MEDIA_TYPE_PATTERN } from '../../language/class/model/data.js';
|
|
5
6
|
import { PropertyValues } from '../../language/class/property-values.js';
|
|
6
7
|
import { classifyValueProgramOrdering } from '../../language/value/ordering.js';
|
|
@@ -21,6 +22,9 @@ export function acceptCompiledSchema(input) {
|
|
|
21
22
|
invalid('version', undefined, '/version');
|
|
22
23
|
const rootPin = dependency(root.root, 'root', '/root');
|
|
23
24
|
const closure = array(root.closure, 'closure', '/closure').map((value, index) => entry(value, index, `/closure/${index}`));
|
|
25
|
+
if (closure.length > dependencyLimits.closure) {
|
|
26
|
+
invalid('dependency closure limit', 'COMPILED_DEPENDENCY_LIMITS', '/closure');
|
|
27
|
+
}
|
|
24
28
|
const closureOrigins = new Set();
|
|
25
29
|
for (const [index, value] of closure.entries()) {
|
|
26
30
|
if (closureOrigins.has(value.origin))
|
|
@@ -32,9 +36,12 @@ export function acceptCompiledSchema(input) {
|
|
|
32
36
|
if (rootEntry?.revision !== rootPin.revision)
|
|
33
37
|
invalid('root pin', undefined, '/root');
|
|
34
38
|
for (const [entryIndex, owner] of closure.entries()) {
|
|
35
|
-
|
|
39
|
+
if (Object.keys(owner.dependencies).length > dependencyLimits.direct) {
|
|
40
|
+
invalid('direct dependency limit', 'COMPILED_DEPENDENCY_LIMITS', `/closure/${entryIndex}/dependencies`);
|
|
41
|
+
}
|
|
42
|
+
for (const [alias, pin] of Object.entries(owner.dependencies)) {
|
|
36
43
|
if (byOrigin.get(pin.origin)?.revision !== pin.revision)
|
|
37
|
-
invalid('dependency closure', undefined, `/closure/${entryIndex}/dependencies
|
|
44
|
+
invalid('dependency closure', undefined, pointer(`/closure/${entryIndex}/dependencies`, alias));
|
|
38
45
|
}
|
|
39
46
|
}
|
|
40
47
|
const reachable = new Set();
|
|
@@ -42,9 +49,12 @@ export function acceptCompiledSchema(input) {
|
|
|
42
49
|
if (reachable.has(origin))
|
|
43
50
|
return;
|
|
44
51
|
reachable.add(origin);
|
|
45
|
-
byOrigin
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
const owner = byOrigin.get(origin);
|
|
53
|
+
if (owner !== undefined) {
|
|
54
|
+
for (const { origin: dependencyOrigin } of Object.values(owner.dependencies)) {
|
|
55
|
+
visit(dependencyOrigin);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
48
58
|
};
|
|
49
59
|
visit(rootPin.origin);
|
|
50
60
|
if (reachable.size !== closure.length) {
|
|
@@ -67,11 +77,11 @@ function entry(input, entryIndex, path) {
|
|
|
67
77
|
const origin = originValue(value.origin, 'entry origin', `${path}/origin`);
|
|
68
78
|
const revision = revisionValue(value.revision, 'entry revision', `${path}/revision`);
|
|
69
79
|
const dependenciesPath = `${path}/dependencies`;
|
|
70
|
-
const dependencies =
|
|
80
|
+
const dependencies = dependencyMap(value.dependencies, dependenciesPath);
|
|
71
81
|
const dependencyOrigins = new Set();
|
|
72
|
-
for (const [
|
|
82
|
+
for (const [alias, dependency] of Object.entries(dependencies)) {
|
|
73
83
|
if (dependencyOrigins.has(dependency.origin))
|
|
74
|
-
invalid('duplicate dependency', undefined, `${dependenciesPath}
|
|
84
|
+
invalid('duplicate dependency', undefined, `${pointer(dependenciesPath, alias)}/origin`);
|
|
75
85
|
dependencyOrigins.add(dependency.origin);
|
|
76
86
|
}
|
|
77
87
|
const valuesPath = `${path}/values`;
|
|
@@ -530,7 +540,7 @@ function validateClosure(candidate) {
|
|
|
530
540
|
for (const [entryIndex, entry] of candidate.closure.entries()) {
|
|
531
541
|
const visibleOrigins = new Set([
|
|
532
542
|
entry.origin,
|
|
533
|
-
...entry.dependencies.map(({ origin }) => origin),
|
|
543
|
+
...Object.values(entry.dependencies).map(({ origin }) => origin),
|
|
534
544
|
]);
|
|
535
545
|
const collections = [
|
|
536
546
|
['definitions/classes', entry.definitions.classes],
|
|
@@ -619,21 +629,37 @@ function validateClosure(candidate) {
|
|
|
619
629
|
}
|
|
620
630
|
function validateDependencyCycles(candidate) {
|
|
621
631
|
const byOrigin = new Map(candidate.closure.map((entry, index) => [entry.origin, { entry, index }]));
|
|
622
|
-
const
|
|
632
|
+
const active = new Set();
|
|
623
633
|
const visited = new Set();
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
+
const frames = [];
|
|
635
|
+
const push = (origin, depth) => {
|
|
636
|
+
if (depth > dependencyLimits.depth) {
|
|
637
|
+
invalid('dependency depth limit', 'COMPILED_DEPENDENCY_LIMITS', `/closure/${byOrigin.get(origin)?.index ?? 0}/dependencies`);
|
|
638
|
+
}
|
|
639
|
+
active.add(origin);
|
|
640
|
+
frames.push({
|
|
641
|
+
origin,
|
|
642
|
+
depth,
|
|
643
|
+
dependencies: Object.values(byOrigin.get(origin)?.entry.dependencies ?? {}),
|
|
644
|
+
index: 0,
|
|
645
|
+
});
|
|
634
646
|
};
|
|
635
|
-
|
|
636
|
-
|
|
647
|
+
push(candidate.root.origin, 0);
|
|
648
|
+
while (frames.length > 0) {
|
|
649
|
+
const frame = frames.at(-1);
|
|
650
|
+
const dependency = frame.dependencies[frame.index++];
|
|
651
|
+
if (dependency === undefined) {
|
|
652
|
+
frames.pop();
|
|
653
|
+
active.delete(frame.origin);
|
|
654
|
+
visited.add(frame.origin);
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
if (active.has(dependency.origin)) {
|
|
658
|
+
invalid('dependency cycle', 'COMPILED_DEPENDENCY_CYCLE', `/closure/${byOrigin.get(dependency.origin)?.index ?? 0}/dependencies`);
|
|
659
|
+
}
|
|
660
|
+
if (!visited.has(dependency.origin))
|
|
661
|
+
push(dependency.origin, frame.depth + 1);
|
|
662
|
+
}
|
|
637
663
|
}
|
|
638
664
|
function walk(input, path, visit) {
|
|
639
665
|
if (isDefinitionRef(input)) {
|
|
@@ -737,6 +763,21 @@ function dependency(input, label, path) {
|
|
|
737
763
|
revision: revisionValue(value.revision, label, `${path}/revision`),
|
|
738
764
|
};
|
|
739
765
|
}
|
|
766
|
+
function dependencyMap(input, path) {
|
|
767
|
+
const value = object(input, 'dependencies', path);
|
|
768
|
+
const accepted = Object.create(null);
|
|
769
|
+
for (const [alias, inputDependency] of Object.entries(value).sort(([left], [right]) => compare(left, right))) {
|
|
770
|
+
const aliasPath = pointer(path, alias);
|
|
771
|
+
if (!DEPENDENCY_ALIAS_PATTERN.test(alias) ||
|
|
772
|
+
alias === '__proto__' ||
|
|
773
|
+
alias === 'prototype' ||
|
|
774
|
+
alias === 'constructor') {
|
|
775
|
+
invalid('dependency alias', undefined, aliasPath);
|
|
776
|
+
}
|
|
777
|
+
accepted[alias] = dependency(inputDependency, `dependency ${alias}`, aliasPath);
|
|
778
|
+
}
|
|
779
|
+
return accepted;
|
|
780
|
+
}
|
|
740
781
|
function originValue(input, label, path) {
|
|
741
782
|
if (typeof input !== 'string' || !DOMAIN_ORIGIN_PATTERN.test(input))
|
|
742
783
|
invalid(label, undefined, path);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type CompiledDiagnosticCode = 'COMPILED_CANONICAL_MISMATCH' | 'COMPILED_DEPENDENCY_CYCLE' | 'COMPILED_SOURCE_INVALID' | 'COMPILED_STRUCTURE_INVALID';
|
|
1
|
+
export type CompiledDiagnosticCode = 'COMPILED_CANONICAL_MISMATCH' | 'COMPILED_DEPENDENCY_CYCLE' | 'COMPILED_DEPENDENCY_LIMITS' | 'COMPILED_SOURCE_INVALID' | 'COMPILED_STRUCTURE_INVALID';
|
|
2
2
|
export interface CompiledDiagnostic<out Code extends CompiledDiagnosticCode = CompiledDiagnosticCode> {
|
|
3
3
|
readonly code: Code;
|
|
4
4
|
readonly pointer: string;
|
|
@@ -12,14 +12,16 @@ export function load(input) {
|
|
|
12
12
|
throw new TypeError('Compiled root is absent or has the wrong revision.');
|
|
13
13
|
}
|
|
14
14
|
return createDomain({
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
root: compiled.root,
|
|
16
|
+
entries: Object.freeze([...entries.values()]
|
|
17
|
+
.map(({ entry, definitions }) => Object.freeze({
|
|
18
|
+
origin: entry.origin,
|
|
19
|
+
revision: entry.revision,
|
|
20
|
+
dependencies: entry.dependencies,
|
|
21
|
+
definitions,
|
|
22
|
+
coreEdges: entry.facts.core,
|
|
23
|
+
}))
|
|
19
24
|
.sort((left, right) => compareUnicode(left.origin, right.origin))),
|
|
20
|
-
dependencies: root.entry.dependencies,
|
|
21
|
-
definitions: Object.freeze([...entries.values()].flatMap(({ definitions }) => definitions)),
|
|
22
|
-
coreEdges: root.entry.facts.core,
|
|
23
25
|
validation: combineDomainValidation([...entries.values()].map(({ validation }) => validation)),
|
|
24
26
|
});
|
|
25
27
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Key } from '../../addressing/index.js';
|
|
2
|
-
import type { DomainDependency, Revision } from '../../addressing/index.js';
|
|
2
|
+
import type { DomainDependency, DomainDependencyMap, Revision } from '../../addressing/index.js';
|
|
3
3
|
import type { Domain } from '../../domain/model/domain.js';
|
|
4
4
|
import type { MethodDefinition } from '../../language/callable/model/callable.js';
|
|
5
5
|
import type { ClassDefinition } from '../../language/class/model/class.js';
|
|
@@ -49,7 +49,7 @@ export type CompiledCoreEdge = CoreEdge;
|
|
|
49
49
|
export interface CompiledSchemaEntry {
|
|
50
50
|
readonly origin: Key.Origin;
|
|
51
51
|
readonly revision: Revision;
|
|
52
|
-
readonly dependencies:
|
|
52
|
+
readonly dependencies: DomainDependencyMap;
|
|
53
53
|
readonly definitions: {
|
|
54
54
|
readonly classes: readonly CompiledClass[];
|
|
55
55
|
readonly functions: readonly CompiledFunction[];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { Domain } from './model/domain.js';
|
|
2
|
-
import type { SourceDomain } from './model/domain.js';
|
|
1
|
+
import type { Domain, SourceDomain } from './model/domain.js';
|
|
3
2
|
import type { DomainInput } from './model/input.js';
|
|
4
3
|
/** True only for an immutable Domain created by DSL resolution or compiled loading. */
|
|
5
4
|
export declare function isDomain(input: unknown): input is Domain;
|
|
6
|
-
/** Retain source provenance without losing the admitted Domain identity. */
|
|
5
|
+
/** Retain source provenance without losing the admitted root Domain identity. */
|
|
7
6
|
export declare function createSourceDomain<Source, Resolved extends Domain>(domain: Resolved, source: Source): SourceDomain<Source, Resolved>;
|
|
7
|
+
/** Construct one resolved Definition universe and one memoized facade per closure Domain. */
|
|
8
8
|
export declare function createDomain(input: DomainInput): Domain;
|