@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.
Files changed (35) hide show
  1. package/dist/v1/addressing/domain-dependency.d.ts +10 -0
  2. package/dist/v1/addressing/domain-dependency.js +6 -1
  3. package/dist/v1/addressing/index.d.ts +1 -1
  4. package/dist/v1/addressing/patterns.d.ts +4 -0
  5. package/dist/v1/addressing/patterns.js +4 -0
  6. package/dist/v1/builder/index.d.ts +1 -1
  7. package/dist/v1/builder/lowering/schema/capture.js +1 -1
  8. package/dist/v1/builder/lowering/schema/compile.js +2 -2
  9. package/dist/v1/builder/lowering/schema/context.d.ts +2 -2
  10. package/dist/v1/builder/lowering/schema/context.js +20 -15
  11. package/dist/v1/builder/schema/define.d.ts +1 -1
  12. package/dist/v1/builder/schema/diagnostic.d.ts +1 -1
  13. package/dist/v1/builder/schema/input.d.ts +1 -1
  14. package/dist/v1/builder/schema/projection.d.ts +13 -2
  15. package/dist/v1/bundle/codec.js +23 -6
  16. package/dist/v1/compiled/admission/accept.js +64 -23
  17. package/dist/v1/compiled/diagnostic.d.ts +1 -1
  18. package/dist/v1/compiled/loader/load.js +9 -7
  19. package/dist/v1/compiled/model/schema.d.ts +2 -2
  20. package/dist/v1/domain/create.d.ts +3 -3
  21. package/dist/v1/domain/create.js +155 -83
  22. package/dist/v1/domain/model/domain.d.ts +7 -1
  23. package/dist/v1/domain/model/input.d.ts +9 -5
  24. package/dist/v1/schema/codec/header.d.ts +2 -2
  25. package/dist/v1/schema/codec/header.js +4 -1
  26. package/dist/v1/schema/diagnostic.d.ts +1 -1
  27. package/dist/v1/schema/model/schema.d.ts +2 -2
  28. package/dist/v1/schema/normalization/domain.js +1 -1
  29. package/dist/v1/schema/resolution/closure.d.ts +1 -1
  30. package/dist/v1/schema/resolution/closure.js +1 -1
  31. package/dist/v1/schema/resolution/context.js +77 -45
  32. package/dist/v1/schema/resolution/domain.js +9 -6
  33. package/dist/v1/schema/resolution/value.registry.js +2 -2
  34. package/dist/v1/schema/validation/domain.structure.js +6 -2
  35. package/package.json +2 -3
@@ -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
+ }>;
@@ -1 +1,6 @@
1
- export {};
1
+ /** Representation-neutral admission budgets shared by source and compiled closures. */
2
+ export const dependencyLimits = Object.freeze({
3
+ direct: 32,
4
+ closure: 256,
5
+ depth: 32,
6
+ });
@@ -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: readonly DomainSchema[]);
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(): readonly DomainDependency[];
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 && previous.dependency.revision !== dependency.revision) {
29
- fail('BUILDER_DEPENDENCY_CONFLICT', '/dependencies', `Dependency ${schema.origin} was supplied at two different revisions.`);
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.usedForeignOrigins]
127
- .map((origin) => {
128
- const source = this.#dependencies.get(origin);
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?: readonly DomainSchema<string, unknown>[];
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;
@@ -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
- for (const [index, dependency] of current.schema.dependencies.entries()) {
122
- const pointer = `${current.pointer}/dependencies/${index}`;
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
- for (const [dependencyIndex, pin] of owner.dependencies.entries()) {
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/${dependencyIndex}`);
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
- .get(origin)
47
- ?.dependencies.forEach(({ origin: dependencyOrigin }) => visit(dependencyOrigin));
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 = array(value.dependencies, 'dependencies', dependenciesPath).map((item, index) => dependency(item, `dependencies[${index}]`, `${dependenciesPath}/${index}`));
80
+ const dependencies = dependencyMap(value.dependencies, dependenciesPath);
71
81
  const dependencyOrigins = new Set();
72
- for (const [index, dependency] of dependencies.entries()) {
82
+ for (const [alias, dependency] of Object.entries(dependencies)) {
73
83
  if (dependencyOrigins.has(dependency.origin))
74
- invalid('duplicate dependency', undefined, `${dependenciesPath}/${index}/origin`);
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 visiting = new Set();
632
+ const active = new Set();
623
633
  const visited = new Set();
624
- const visit = (origin) => {
625
- if (visited.has(origin))
626
- return;
627
- if (visiting.has(origin))
628
- invalid('dependency cycle', 'COMPILED_DEPENDENCY_CYCLE', `/closure/${byOrigin.get(origin)?.index ?? 0}/dependencies`);
629
- visiting.add(origin);
630
- for (const dependency of byOrigin.get(origin)?.entry.dependencies ?? [])
631
- visit(dependency.origin);
632
- visiting.delete(origin);
633
- visited.add(origin);
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
- for (const origin of byOrigin.keys())
636
- visit(origin);
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
- origin: root.entry.origin,
16
- revision: root.entry.revision,
17
- closure: Object.freeze(compiled.closure
18
- .map(({ origin, revision }) => Object.freeze({ origin, revision }))
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: readonly DomainDependency[];
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;