@astrale-os/kernel-dsl 1.0.0-beta.13 → 1.0.0-beta.14
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/key.js +2 -1
- package/dist/v1/addressing/patterns.d.ts +1 -0
- package/dist/v1/addressing/patterns.js +2 -1
- package/dist/v1/builder/class/property.d.ts +3 -9
- package/dist/v1/builder/class/property.js +1 -0
- package/dist/v1/builder/lowering/class.js +5 -1
- package/dist/v1/builder/lowering/policy.js +1 -0
- package/dist/v1/builder/policy/authoring.js +3 -2
- package/dist/v1/bundle/codec.js +2 -2
- package/dist/v1/bundle/diagnostic.d.ts +2 -0
- package/dist/v1/bundle/diagnostic.js +6 -0
- package/dist/v1/bundle/structure.js +2 -2
- package/dist/v1/domain/validation/create.d.ts +4 -1
- package/dist/v1/domain/validation/create.js +21 -34
- package/dist/v1/language/.spec/schemas/value-schema.schema.json +22 -0
- package/dist/v1/language/class/model/class.d.ts +4 -4
- package/dist/v1/language/class/model/edge.d.ts +2 -2
- package/dist/v1/language/class/model/property.d.ts +16 -7
- package/dist/v1/language/definition/metadata.d.ts +5 -0
- package/dist/v1/language/definition/metadata.js +7 -0
- package/dist/v1/language/policy/model/policy.d.ts +5 -5
- package/dist/v1/language/policy/normalization/normalize.js +17 -16
- package/dist/v1/language/validation/diagnostic.d.ts +1 -1
- package/dist/v1/language/validation/rule.d.ts +26 -0
- package/dist/v1/language/validation/rule.js +116 -0
- package/dist/v1/language/value/profile/meta-schema.compiled.js +4 -1
- package/dist/v1/language/value/profile/rules.js +13 -37
- package/dist/v1/language/value/profile/vocabulary.d.ts +35 -2
- package/dist/v1/language/value/profile/vocabulary.js +52 -5
- package/dist/v1/language/value/reference/registry.index.js +2 -1
- package/dist/v1/language/value/syntax/keywords.d.ts +3 -3
- package/dist/v1/language/value/syntax/keywords.js +4 -20
- package/dist/v1/language/value/syntax/node-path/validate.js +2 -3
- package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +2336 -0
- package/dist/v1/schema/analysis/class.js +33 -15
- package/dist/v1/schema/compatibility/api.d.ts +1 -1
- package/dist/v1/schema/compatibility/coordinate.js +3 -3
- package/dist/v1/schema/compatibility/model.d.ts +5 -0
- package/dist/v1/schema/diagnostic.d.ts +17 -3
- package/dist/v1/schema/diagnostic.js +11 -0
- package/dist/v1/schema/index.d.ts +3 -1
- package/dist/v1/schema/index.js +1 -0
- package/dist/v1/schema/model/schema.d.ts +19 -1
- package/dist/v1/schema/normalization/domain.js +1 -0
- package/dist/v1/schema/resolution/closure.js +2 -1
- package/dist/v1/schema/resolution/context.js +3 -1
- package/dist/v1/schema/rule.d.ts +29 -0
- package/dist/v1/schema/rule.js +415 -0
- package/dist/v1/schema/validation/callable.d.ts +4 -1
- package/dist/v1/schema/validation/callable.js +10 -1
- package/dist/v1/schema/validation/class.js +36 -44
- package/dist/v1/schema/validation/core.js +3 -3
- package/dist/v1/schema/validation/domain.json-schema.d.ts +15 -0
- package/dist/v1/schema/validation/domain.json-schema.js +183 -0
- package/dist/v1/schema/validation/domain.structure.d.ts +4018 -0
- package/dist/v1/schema/validation/domain.structure.js +341 -151
- package/dist/v1/schema/validation/json.json-schema.d.ts +24 -0
- package/dist/v1/schema/validation/json.json-schema.js +36 -0
- package/dist/v1/schema/validation/policy/references.js +5 -21
- package/dist/v1/schema/validation/policy/uses.js +0 -3
- package/dist/v1/schema/validation/value-api.d.ts +12 -1
- package/dist/v1/schema/validation/value-api.js +10 -1
- package/dist/v1/schema/validation/value.js +3 -3
- package/dist/v1/schema/validation/value.json-schema.d.ts +51 -0
- package/dist/v1/schema/validation/value.json-schema.js +126 -0
- package/package.json +3 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { acceptDefinitionRef, } from './definition-ref.js';
|
|
2
2
|
import { DOMAIN_ORIGIN_PATTERN } from './patterns.js';
|
|
3
3
|
const DEFINITION_KEY = /^(?:class|function|policy|view)\.([A-Za-z][A-Za-z0-9_]*)$/u;
|
|
4
|
-
|
|
4
|
+
// The slug is the whole rest of the coordinate, its own `.` included: no member follows a Core key.
|
|
5
|
+
const CORE_KEY = /^core\.((?=.{1,100}$)[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*)$/u;
|
|
5
6
|
const MEMBER_KEY = /^class\.([A-Za-z][A-Za-z0-9_]*)\.(property|method)\.([A-Za-z][A-Za-z0-9_]*)$/u;
|
|
6
7
|
/** The keys of frozen Definition coordinates, derived once each. */
|
|
7
8
|
const derived = new WeakMap();
|
|
@@ -3,6 +3,7 @@ export type DomainOrigin = string;
|
|
|
3
3
|
export declare const DOMAIN_ORIGIN_PATTERN: RegExp;
|
|
4
4
|
export declare const MEMBER_NAME_PATTERN: RegExp;
|
|
5
5
|
export declare const DEPENDENCY_ALIAS_PATTERN: RegExp;
|
|
6
|
+
/** Labels of `[a-z][a-z0-9-]*` joined by `.`, 100 code points at most: `graph.reader`. */
|
|
6
7
|
export declare const CORE_SLUG_PATTERN: RegExp;
|
|
7
8
|
export declare const DOMAIN_REVISION_PATTERN: RegExp;
|
|
8
9
|
export declare const PROPERTY_KEY_PATTERN: RegExp;
|
|
@@ -1,7 +1,8 @@
|
|
|
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
3
|
export const DEPENDENCY_ALIAS_PATTERN = /^[a-z][A-Za-z0-9]*$/u;
|
|
4
|
-
|
|
4
|
+
/** Labels of `[a-z][a-z0-9-]*` joined by `.`, 100 code points at most: `graph.reader`. */
|
|
5
|
+
export const CORE_SLUG_PATTERN = /^(?=.{1,100}$)[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*$/u;
|
|
5
6
|
export const DOMAIN_REVISION_PATTERN = /^sha256:[0-9a-f]{64}$/u;
|
|
6
7
|
const DOMAIN_REVISION_FULL_MATCH = 'sha256:[0-9a-f]{64}';
|
|
7
8
|
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])?';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
|
-
import type { AutoEvent, AutoSet, PropertyImmutability } from '../../language/class/model/property.js';
|
|
2
|
+
import type { AutoEvent, AutoSet, PropertyAuto, PropertyImmutability } from '../../language/class/model/property.js';
|
|
3
3
|
import type { StateMachine } from '../../language/state/index.js';
|
|
4
4
|
import type { JsonSchema } from '../../language/value/model/schema.js';
|
|
5
5
|
import type { AUTHORING } from '../schema/handle.js';
|
|
@@ -31,10 +31,7 @@ export interface AutoProperty<out PropertyValue extends Property = Property> {
|
|
|
31
31
|
readonly [AUTHORING]: 'auto';
|
|
32
32
|
readonly [AUTO_INPUT]: {
|
|
33
33
|
readonly property: PropertyValue;
|
|
34
|
-
readonly options:
|
|
35
|
-
readonly on: readonly AutoEvent[];
|
|
36
|
-
readonly set: AutoSet;
|
|
37
|
-
};
|
|
34
|
+
readonly options: PropertyAuto;
|
|
38
35
|
};
|
|
39
36
|
}
|
|
40
37
|
type PropertyAuthoringInput<Value extends Property> = Value extends Property<infer Schema, infer Options, infer Machine> ? {
|
|
@@ -45,10 +42,7 @@ type PropertyAuthoringInput<Value extends Property> = Value extends Property<inf
|
|
|
45
42
|
export declare function propertyAuthoringInput<const Value extends Property>(value: Value): PropertyAuthoringInput<Value>;
|
|
46
43
|
export declare function autoPropertyAuthoringInput<const Value extends AutoProperty>(value: Value): Value extends AutoProperty<infer Property> ? {
|
|
47
44
|
readonly property: Property;
|
|
48
|
-
readonly options:
|
|
49
|
-
readonly on: readonly AutoEvent[];
|
|
50
|
-
readonly set: AutoSet;
|
|
51
|
-
};
|
|
45
|
+
readonly options: PropertyAuto;
|
|
52
46
|
} : never;
|
|
53
47
|
export type PropertyFieldInput = ValueSchemaInput | Property | AutoProperty;
|
|
54
48
|
export type PropertyRecordInput = Readonly<Record<string, PropertyFieldInput>>;
|
|
@@ -48,6 +48,7 @@ export class V1DefinitionCompiler {
|
|
|
48
48
|
}
|
|
49
49
|
edgeClass(value, name, pointer) {
|
|
50
50
|
const input = classConfiguration(value);
|
|
51
|
+
// The authoring types give an Edge no State Property; the structure refuses an erased one.
|
|
51
52
|
const facet = this.values.properties(input.properties, `${pointer}/properties`);
|
|
52
53
|
const contract = (() => {
|
|
53
54
|
if (value.orientation === 'directed') {
|
|
@@ -63,7 +64,9 @@ export class V1DefinitionCompiler {
|
|
|
63
64
|
// against the Class's effective Properties, once every Class is lowered.
|
|
64
65
|
...(directed.unique === undefined
|
|
65
66
|
? {}
|
|
66
|
-
: {
|
|
67
|
+
: {
|
|
68
|
+
unique: directed.unique,
|
|
69
|
+
}),
|
|
67
70
|
};
|
|
68
71
|
}
|
|
69
72
|
const undirected = classConfiguration(value);
|
|
@@ -135,6 +138,7 @@ export class V1DefinitionCompiler {
|
|
|
135
138
|
endpointBase(value, pointer) {
|
|
136
139
|
return {
|
|
137
140
|
role: value.as,
|
|
141
|
+
// An empty list stays as authored: the structure refuses it when it judges the candidate.
|
|
138
142
|
accepts: value.accepts
|
|
139
143
|
.map((entry, index) => this.context.resolveNodeClass(entry, `${pointer}/accepts/${index}`))
|
|
140
144
|
.sort(compareDefinitionRefs),
|
|
@@ -14,6 +14,7 @@ export class V1PolicyCompiler {
|
|
|
14
14
|
const input = policyDeclaration(value);
|
|
15
15
|
const ref = { origin: this.context.origin, kind: 'policy', name };
|
|
16
16
|
if ('operator' in input) {
|
|
17
|
+
// One ref for each Policy of a non-empty composition.
|
|
17
18
|
const refs = input.policies.map((policy, index) => this.context.resolvePolicy(policy, `${pointer}/${input.operator}/${index}`));
|
|
18
19
|
return {
|
|
19
20
|
ref,
|
|
@@ -86,10 +86,11 @@ export function createPatternContext(options) {
|
|
|
86
86
|
});
|
|
87
87
|
Object.freeze(node);
|
|
88
88
|
const where = build({ node });
|
|
89
|
-
|
|
89
|
+
const [first, ...rest] = nodes;
|
|
90
|
+
if (first === undefined) {
|
|
90
91
|
fail('BUILDER_POLICY_EMPTY_EXISTS', `${options.pointer}/match/exists/nodes`, 'exists(...) must declare at least one variable Node.');
|
|
91
92
|
}
|
|
92
|
-
return { exists: { nodes, where } };
|
|
93
|
+
return { exists: { nodes: [first, ...rest], where } };
|
|
93
94
|
},
|
|
94
95
|
allOf(...expressions) {
|
|
95
96
|
nonEmpty(expressions, `${options.pointer}/match/allOf`);
|
package/dist/v1/bundle/codec.js
CHANGED
|
@@ -7,7 +7,7 @@ import { canonicalDomainSchemaAfterValidation } from '../schema/canonical/revisi
|
|
|
7
7
|
import { revisionOfDomainSchema } from '../schema/canonical/revision.js';
|
|
8
8
|
import { MemoryDomainSchemaLookup } from '../schema/dependency/lookup.js';
|
|
9
9
|
import { SchemaError } from '../schema/diagnostic.js';
|
|
10
|
-
import { BundleError, diagnostic } from './diagnostic.js';
|
|
10
|
+
import { BundleError, diagnostic, schemaDiagnosticAt } from './diagnostic.js';
|
|
11
11
|
import { asDomainSchemaBundle, } from './model.js';
|
|
12
12
|
import { parseDomainSchemaBundleStructure } from './structure.js';
|
|
13
13
|
export function acceptDomainSchemaBundle(input) {
|
|
@@ -99,7 +99,7 @@ function acceptBundleValue(input) {
|
|
|
99
99
|
catch (error) {
|
|
100
100
|
if (!(error instanceof SchemaError))
|
|
101
101
|
throw error;
|
|
102
|
-
throw new BundleError(error.diagnostics.map((item) =>
|
|
102
|
+
throw new BundleError(error.diagnostics.map((item) => schemaDiagnosticAt(entry.pointer, item)));
|
|
103
103
|
}
|
|
104
104
|
};
|
|
105
105
|
for (const entry of candidates.values()) {
|
|
@@ -4,6 +4,8 @@ export type BundleDiagnostic<Code extends BundleDiagnosticCode = BundleDiagnosti
|
|
|
4
4
|
readonly code: Code;
|
|
5
5
|
};
|
|
6
6
|
export declare function diagnostic<const Code extends BundleDiagnosticCode>(code: Code, pointer: string, message: string, ref?: string): BundleDiagnostic<Code>;
|
|
7
|
+
/** A refusal of one bundled schema, its pointer rebased under the schema's place in the bundle. */
|
|
8
|
+
export declare function schemaDiagnosticAt(pointer: string, refusal: SchemaDiagnostic): BundleDiagnostic;
|
|
7
9
|
export declare class BundleError extends Error {
|
|
8
10
|
readonly diagnostics: readonly BundleDiagnostic[];
|
|
9
11
|
constructor(diagnostics: readonly BundleDiagnostic[]);
|
|
@@ -2,6 +2,12 @@ import { formatDiagnostics, orderDiagnostics } from '../language/validation/diag
|
|
|
2
2
|
export function diagnostic(code, pointer, message, ref) {
|
|
3
3
|
return Object.freeze({ code, pointer, message, ...(ref === undefined ? {} : { ref }) });
|
|
4
4
|
}
|
|
5
|
+
/** A refusal of one bundled schema, its pointer rebased under the schema's place in the bundle. */
|
|
6
|
+
export function schemaDiagnosticAt(pointer, refusal) {
|
|
7
|
+
// Acceptance refuses a document with Schema codes only, never with comparison request codes.
|
|
8
|
+
const code = refusal.code;
|
|
9
|
+
return Object.freeze({ ...refusal, code, pointer: `${pointer}${refusal.pointer}` });
|
|
10
|
+
}
|
|
5
11
|
export class BundleError extends Error {
|
|
6
12
|
diagnostics;
|
|
7
13
|
constructor(diagnostics) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { escapePointerToken } from '../language/value/syntax/pointer.js';
|
|
2
2
|
import { SchemaError } from '../schema/diagnostic.js';
|
|
3
3
|
import { routeDomainSchema } from '../schema/validation/domain.route.js';
|
|
4
|
-
import { BundleError, diagnostic } from './diagnostic.js';
|
|
4
|
+
import { BundleError, diagnostic, schemaDiagnosticAt } from './diagnostic.js';
|
|
5
5
|
import { DOMAIN_BUNDLE_FORMAT, DOMAIN_BUNDLE_VERSION } from './model.js';
|
|
6
6
|
export function parseDomainSchemaBundleStructure(input) {
|
|
7
7
|
if (!isObject(input)) {
|
|
@@ -57,7 +57,7 @@ function routeAt(value, pointer, diagnostics) {
|
|
|
57
57
|
catch (error) {
|
|
58
58
|
if (!(error instanceof SchemaError))
|
|
59
59
|
throw error;
|
|
60
|
-
diagnostics.push(...error.diagnostics.map((entry) =>
|
|
60
|
+
diagnostics.push(...error.diagnostics.map((entry) => schemaDiagnosticAt(pointer, entry)));
|
|
61
61
|
return undefined;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -14,6 +14,9 @@ export interface CallableValidation {
|
|
|
14
14
|
validateInputField(field: string, input: unknown): readonly Diagnostic[];
|
|
15
15
|
validateOutput(output: unknown): readonly Diagnostic[];
|
|
16
16
|
}
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Build the one Domain-level router shared by source resolution and compiled loading. The Domain
|
|
19
|
+
* built from the same entries asks only for the Properties and Callables they declare.
|
|
20
|
+
*/
|
|
18
21
|
export declare function createDomainValidation(properties: ReadonlyMap<PropertyKey, PropertyValidation>, callables: ReadonlyMap<Key.Callable, CallableValidation>): DomainValidation;
|
|
19
22
|
export declare function unknownInputField(field: string): readonly Diagnostic[];
|
|
@@ -1,34 +1,30 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Build the one Domain-level router shared by source resolution and compiled loading. The Domain
|
|
3
|
+
* built from the same entries asks only for the Properties and Callables they declare.
|
|
4
|
+
*/
|
|
2
5
|
export function createDomainValidation(properties, callables) {
|
|
6
|
+
const propertyOf = (key) => {
|
|
7
|
+
const target = properties.get(key);
|
|
8
|
+
if (target === undefined)
|
|
9
|
+
throw new TypeError(`Property ${key} is absent.`);
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
const callableOf = (key) => {
|
|
13
|
+
const target = callables.get(key);
|
|
14
|
+
if (target === undefined)
|
|
15
|
+
throw new TypeError(`Callable ${key} is absent.`);
|
|
16
|
+
return target;
|
|
17
|
+
};
|
|
3
18
|
const validation = {
|
|
4
19
|
propertyExists: (key) => properties.has(key),
|
|
5
|
-
property(key, input)
|
|
6
|
-
|
|
7
|
-
return target === undefined ? unknownTarget('Property', key) : target.validate(input);
|
|
8
|
-
},
|
|
9
|
-
propertyProgram(key) {
|
|
10
|
-
const target = properties.get(key);
|
|
11
|
-
if (target === undefined)
|
|
12
|
-
throw new TypeError(`Property ${key} is absent.`);
|
|
13
|
-
return target.program();
|
|
14
|
-
},
|
|
20
|
+
property: (key, input) => propertyOf(key).validate(input),
|
|
21
|
+
propertyProgram: (key) => propertyOf(key).program(),
|
|
15
22
|
propertyOrdering: (key) => properties.get(key)?.ordering,
|
|
16
23
|
callableExists: (key) => callables.has(key),
|
|
17
|
-
callableInput(key, input)
|
|
18
|
-
const target = callables.get(key);
|
|
19
|
-
return target === undefined ? unknownTarget('Callable', key) : target.validateInput(input);
|
|
20
|
-
},
|
|
24
|
+
callableInput: (key, input) => callableOf(key).validateInput(input),
|
|
21
25
|
callableFields: (key) => callables.get(key)?.fields,
|
|
22
|
-
callableInputField(key, field, input)
|
|
23
|
-
|
|
24
|
-
return target === undefined
|
|
25
|
-
? unknownTarget('Callable', key)
|
|
26
|
-
: target.validateInputField(field, input);
|
|
27
|
-
},
|
|
28
|
-
callableOutput(key, output) {
|
|
29
|
-
const target = callables.get(key);
|
|
30
|
-
return target === undefined ? unknownTarget('Callable', key) : target.validateOutput(output);
|
|
31
|
-
},
|
|
26
|
+
callableInputField: (key, field, input) => callableOf(key).validateInputField(field, input),
|
|
27
|
+
callableOutput: (key, output) => callableOf(key).validateOutput(output),
|
|
32
28
|
};
|
|
33
29
|
return Object.freeze(validation);
|
|
34
30
|
}
|
|
@@ -41,12 +37,3 @@ export function unknownInputField(field) {
|
|
|
41
37
|
}),
|
|
42
38
|
];
|
|
43
39
|
}
|
|
44
|
-
function unknownTarget(kind, key) {
|
|
45
|
-
return [
|
|
46
|
-
Object.freeze({
|
|
47
|
-
code: 'DOMAIN_VALIDATION_TARGET_UNKNOWN',
|
|
48
|
-
pointer: '',
|
|
49
|
-
message: `${kind} ${key} is absent.`,
|
|
50
|
-
}),
|
|
51
|
-
];
|
|
52
|
-
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://schemas.astrale.ai/dsl/v1/value-schema",
|
|
4
|
+
"$dynamicAnchor": "meta",
|
|
5
|
+
"$vocabulary": {
|
|
6
|
+
"https://json-schema.org/draft/2020-12/vocab/core": true,
|
|
7
|
+
"https://json-schema.org/draft/2020-12/vocab/applicator": true,
|
|
8
|
+
"https://json-schema.org/draft/2020-12/vocab/unevaluated": true,
|
|
9
|
+
"https://json-schema.org/draft/2020-12/vocab/validation": true,
|
|
10
|
+
"https://json-schema.org/draft/2020-12/vocab/meta-data": true,
|
|
11
|
+
"https://json-schema.org/draft/2020-12/vocab/format-annotation": true,
|
|
12
|
+
"https://json-schema.org/draft/2020-12/vocab/content": true,
|
|
13
|
+
"https://schemas.astrale.ai/dsl/v1/value-schema/vocabulary": true
|
|
14
|
+
},
|
|
15
|
+
"title": "Astrale V1 value schema",
|
|
16
|
+
"$comment": "The meta-schema of the V1 value-schema dialect: Draft 2020-12 and the required Astrale vocabulary (fullMatch, x-astrale-path, x-astrale-value-kind). Its $dynamicAnchor extends Draft 2020-12 at every subschema, not only the root. The rules of the profile beyond it are published as schema.values.RULES.",
|
|
17
|
+
"allOf": [{ "$ref": "https://json-schema.org/draft/2020-12/schema" }],
|
|
18
|
+
"properties": {
|
|
19
|
+
"fullMatch": { "type": "string" },
|
|
20
|
+
"x-astrale-path": { "type": "object" }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -3,7 +3,7 @@ import type { MethodRecord } from '../../callable/model/callable.js';
|
|
|
3
3
|
import type { ClassPolicy, NodeClassPolicy } from '../../policy/model/policy.js';
|
|
4
4
|
import type { DataFacet } from './data.js';
|
|
5
5
|
import type { EdgeContract } from './edge.js';
|
|
6
|
-
import type { PropertyFacet } from './property.js';
|
|
6
|
+
import type { EdgePropertyDefinition, NodePropertyDefinition, PropertyFacet } from './property.js';
|
|
7
7
|
export type ClassKind = 'node' | 'edge';
|
|
8
8
|
/**
|
|
9
9
|
* Which Classes may extend a Class: `'open'`, those of its Domain and of every Domain that depends
|
|
@@ -30,7 +30,7 @@ export type NodeUniqueTuple = readonly [PropertyKey, ...PropertyKey[]];
|
|
|
30
30
|
* backend announces it to enforce Node keys, so a tuple is never ignored where it is not.
|
|
31
31
|
*/
|
|
32
32
|
export type NodeUniqueForm = readonly ['property'];
|
|
33
|
-
export interface NodeClassDefinition extends DefinitionBase<ClassRef>, PropertyFacet
|
|
33
|
+
export interface NodeClassDefinition extends DefinitionBase<ClassRef>, PropertyFacet<NodePropertyDefinition>, DataFacet {
|
|
34
34
|
readonly kind: 'node';
|
|
35
35
|
readonly abstract: boolean;
|
|
36
36
|
/** Absent when `'internal'`, the default, so a Class that keeps the default keeps its revision. */
|
|
@@ -44,9 +44,9 @@ export interface NodeClassDefinition extends DefinitionBase<ClassRef>, PropertyF
|
|
|
44
44
|
* The tuples no two Nodes of this Class and of its active concrete descendants share while every
|
|
45
45
|
* Property is present; absent, none. A descendant is covered without declaring them again.
|
|
46
46
|
*/
|
|
47
|
-
readonly unique?: readonly NodeUniqueTuple[];
|
|
47
|
+
readonly unique?: readonly [NodeUniqueTuple, ...NodeUniqueTuple[]];
|
|
48
48
|
}
|
|
49
|
-
export type EdgeClassDefinition = DefinitionBase<ClassRef> & PropertyFacet & EdgeContract & {
|
|
49
|
+
export type EdgeClassDefinition = DefinitionBase<ClassRef> & PropertyFacet<EdgePropertyDefinition> & EdgeContract & {
|
|
50
50
|
readonly kind: 'edge';
|
|
51
51
|
readonly abstract: boolean;
|
|
52
52
|
/** Absent when `'internal'`, the default, so a Class that keeps the default keeps its revision. */
|
|
@@ -8,7 +8,7 @@ export type EdgeOrientation = 'directed' | 'undirected';
|
|
|
8
8
|
export type EdgeLifetime = 'restrict' | 'detach';
|
|
9
9
|
export interface EdgeEndpointBase {
|
|
10
10
|
readonly role: string;
|
|
11
|
-
readonly accepts: readonly ClassRef[];
|
|
11
|
+
readonly accepts: readonly [ClassRef, ...ClassRef[]];
|
|
12
12
|
/** Declared only when authored; otherwise derived (`edgeLifetimes`). */
|
|
13
13
|
readonly onDelete?: EdgeLifetime;
|
|
14
14
|
}
|
|
@@ -56,7 +56,7 @@ export interface DirectedEdgeContract {
|
|
|
56
56
|
* The tuples no two Edges of this Class and of its active concrete descendants share. Absent
|
|
57
57
|
* when the Class declares none, so a Class without one keeps its revision; never empty.
|
|
58
58
|
*/
|
|
59
|
-
readonly unique?: readonly EdgeUniqueTuple[];
|
|
59
|
+
readonly unique?: readonly [EdgeUniqueTuple, ...EdgeUniqueTuple[]];
|
|
60
60
|
}
|
|
61
61
|
export interface UndirectedEdgeContract {
|
|
62
62
|
readonly orientation: 'undirected';
|
|
@@ -1,20 +1,29 @@
|
|
|
1
|
+
import type { StateTopology } from '../../state/index.js';
|
|
2
|
+
import type { JsonSchema } from '../../value/model/schema.js';
|
|
1
3
|
export type AutoEvent = 'create' | 'update';
|
|
2
4
|
export type AutoSet = 'instant';
|
|
3
5
|
/** `create`: written only by node or edge creation. `once`: written while absent, never unset. */
|
|
4
6
|
export type PropertyImmutability = 'create' | 'once';
|
|
5
7
|
export interface PropertyAuto {
|
|
6
|
-
readonly on: readonly AutoEvent[];
|
|
8
|
+
readonly on: readonly [AutoEvent, ...AutoEvent[]];
|
|
7
9
|
readonly set: AutoSet;
|
|
8
10
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
/** A Property of a Node Class, which may be a State Property. */
|
|
12
|
+
export interface NodePropertyDefinition {
|
|
13
|
+
readonly schema: JsonSchema;
|
|
11
14
|
readonly required: boolean;
|
|
12
15
|
readonly auto?: PropertyAuto;
|
|
13
16
|
readonly immutable?: PropertyImmutability;
|
|
14
17
|
readonly description?: string;
|
|
15
|
-
readonly state?:
|
|
18
|
+
readonly state?: StateTopology;
|
|
16
19
|
}
|
|
17
|
-
|
|
18
|
-
export interface
|
|
19
|
-
readonly
|
|
20
|
+
/** A Property of an Edge Class: an Edge has no State Property. */
|
|
21
|
+
export interface EdgePropertyDefinition extends Omit<NodePropertyDefinition, 'state'> {
|
|
22
|
+
readonly state?: never;
|
|
23
|
+
}
|
|
24
|
+
/** A Property of a Class of either kind. */
|
|
25
|
+
export type PropertyDefinition = NodePropertyDefinition | EdgePropertyDefinition;
|
|
26
|
+
export type PropertyRecord<Definition extends PropertyDefinition = PropertyDefinition> = Readonly<Record<string, Definition>>;
|
|
27
|
+
export interface PropertyFacet<Definition extends PropertyDefinition = PropertyDefinition> {
|
|
28
|
+
readonly properties: PropertyRecord<Definition>;
|
|
20
29
|
}
|
|
@@ -18,3 +18,8 @@ export declare const name: Readonly<{
|
|
|
18
18
|
}>;
|
|
19
19
|
/** The common source and compiled grammar for a name displayed exactly as authored. */
|
|
20
20
|
export declare function isDomainNameText(value: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The same grammar as a portable pattern: no control character, and neither first nor last a
|
|
23
|
+
* character `trim` removes, spelled out since `\s` differs between regular expression engines.
|
|
24
|
+
*/
|
|
25
|
+
export declare const DOMAIN_NAME_TEXT_PATTERN: RegExp;
|
|
@@ -22,3 +22,10 @@ const DOMAIN_NAME_CONTROL = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/u;
|
|
|
22
22
|
export function isDomainNameText(value) {
|
|
23
23
|
return value.trim() === value && !DOMAIN_NAME_CONTROL.test(value);
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* The same grammar as a portable pattern: no control character, and neither first nor last a
|
|
27
|
+
* character `trim` removes, spelled out since `\s` differs between regular expression engines.
|
|
28
|
+
*/
|
|
29
|
+
export const DOMAIN_NAME_TEXT_PATTERN =
|
|
30
|
+
// oxlint-disable-next-line no-control-regex
|
|
31
|
+
/^(?:[^\u0000-\u001f\u007f-\u009f\u0020\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff](?:[^\u0000-\u001f\u007f-\u009f\u2028\u2029]*[^\u0000-\u001f\u007f-\u009f\u0020\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff])?)?$/u;
|
|
@@ -65,20 +65,20 @@ export interface PolicyEdgePattern {
|
|
|
65
65
|
}
|
|
66
66
|
export type PolicyPatternExpression = PolicyEdgePattern | PolicySameNode<PolicyPatternTerm> | {
|
|
67
67
|
readonly exists: {
|
|
68
|
-
readonly nodes: readonly PolicyVariable[];
|
|
68
|
+
readonly nodes: readonly [PolicyVariable, ...PolicyVariable[]];
|
|
69
69
|
readonly where: PolicyPatternExpression;
|
|
70
70
|
};
|
|
71
71
|
} | {
|
|
72
|
-
readonly allOf: readonly PolicyPatternExpression[];
|
|
72
|
+
readonly allOf: readonly [PolicyPatternExpression, ...PolicyPatternExpression[]];
|
|
73
73
|
} | {
|
|
74
|
-
readonly anyOf: readonly PolicyPatternExpression[];
|
|
74
|
+
readonly anyOf: readonly [PolicyPatternExpression, ...PolicyPatternExpression[]];
|
|
75
75
|
};
|
|
76
76
|
export type PolicyExpression = {
|
|
77
77
|
readonly match: PolicyPatternExpression;
|
|
78
78
|
} | {
|
|
79
|
-
readonly allOf: readonly PolicyRef[];
|
|
79
|
+
readonly allOf: readonly [PolicyRef, ...PolicyRef[]];
|
|
80
80
|
} | {
|
|
81
|
-
readonly anyOf: readonly PolicyRef[];
|
|
81
|
+
readonly anyOf: readonly [PolicyRef, ...PolicyRef[]];
|
|
82
82
|
};
|
|
83
83
|
export interface PolicyDefinition extends DefinitionBase<PolicyRef> {
|
|
84
84
|
readonly expression: PolicyExpression;
|
|
@@ -6,9 +6,9 @@ export function normalizePolicyExpression(value) {
|
|
|
6
6
|
return { match: normalizePolicyPattern(value.match) };
|
|
7
7
|
}
|
|
8
8
|
if ('allOf' in value) {
|
|
9
|
-
return { allOf: uniqueRefs(value.allOf) };
|
|
9
|
+
return { allOf: operands(uniqueRefs(value.allOf)) };
|
|
10
10
|
}
|
|
11
|
-
return { anyOf: uniqueRefs(value.anyOf) };
|
|
11
|
+
return { anyOf: operands(uniqueRefs(value.anyOf)) };
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* Passes after the first, while one still changes the Pattern. A pass numbers variables by the
|
|
@@ -66,8 +66,7 @@ function unwrapSingleOperands(value) {
|
|
|
66
66
|
}));
|
|
67
67
|
if (children.length === 1)
|
|
68
68
|
return children[0];
|
|
69
|
-
|
|
70
|
-
return all ? { allOf: operands } : { anyOf: operands };
|
|
69
|
+
return all ? { allOf: operands(children) } : { anyOf: operands(children) };
|
|
71
70
|
}
|
|
72
71
|
export function normalizePolicyChecks(value) {
|
|
73
72
|
if ('check' in value)
|
|
@@ -79,17 +78,19 @@ export function normalizePolicyChecks(value) {
|
|
|
79
78
|
const normalized = normalizePolicyChecks(child);
|
|
80
79
|
return 'allOf' in normalized ? normalized.allOf : [normalized];
|
|
81
80
|
});
|
|
82
|
-
return { allOf:
|
|
81
|
+
return { allOf: operands(uniqueJson(children)) };
|
|
83
82
|
}
|
|
84
83
|
const children = value.anyOf.flatMap((child) => {
|
|
85
84
|
const normalized = normalizePolicyChecks(child);
|
|
86
85
|
return 'anyOf' in normalized ? normalized.anyOf : [normalized];
|
|
87
86
|
});
|
|
88
|
-
return { anyOf:
|
|
87
|
+
return { anyOf: operands(uniqueJson(children)) };
|
|
89
88
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
/**
|
|
90
|
+
* The operands of a non-empty list, mapped, flattened, deduplicated or sorted one by one: still at
|
|
91
|
+
* least one. The structure and compiled admission refuse an empty list.
|
|
92
|
+
*/
|
|
93
|
+
function operands(values) {
|
|
93
94
|
return values;
|
|
94
95
|
}
|
|
95
96
|
function normalizePatternNode(value) {
|
|
@@ -106,7 +107,7 @@ function normalizePatternNode(value) {
|
|
|
106
107
|
if ('exists' in value) {
|
|
107
108
|
return {
|
|
108
109
|
exists: {
|
|
109
|
-
nodes: [...value.exists.nodes].sort((left, right) => left.variable.id - right.variable.id),
|
|
110
|
+
nodes: operands([...value.exists.nodes].sort((left, right) => left.variable.id - right.variable.id)),
|
|
110
111
|
where: normalizePatternNode(value.exists.where),
|
|
111
112
|
},
|
|
112
113
|
};
|
|
@@ -116,13 +117,13 @@ function normalizePatternNode(value) {
|
|
|
116
117
|
const normalized = normalizePatternNode(child);
|
|
117
118
|
return 'allOf' in normalized ? normalized.allOf : [normalized];
|
|
118
119
|
});
|
|
119
|
-
return { allOf: uniqueJson(children) };
|
|
120
|
+
return { allOf: operands(uniqueJson(children)) };
|
|
120
121
|
}
|
|
121
122
|
const children = value.anyOf.flatMap((child) => {
|
|
122
123
|
const normalized = normalizePatternNode(child);
|
|
123
124
|
return 'anyOf' in normalized ? normalized.anyOf : [normalized];
|
|
124
125
|
});
|
|
125
|
-
return { anyOf: uniqueJson(children) };
|
|
126
|
+
return { anyOf: operands(uniqueJson(children)) };
|
|
126
127
|
}
|
|
127
128
|
function canonicalVariableIds(value) {
|
|
128
129
|
const declarations = new Map();
|
|
@@ -297,7 +298,7 @@ function rewriteVariables(value, ids) {
|
|
|
297
298
|
if ('exists' in value) {
|
|
298
299
|
return {
|
|
299
300
|
exists: {
|
|
300
|
-
nodes: value.exists.nodes.map((node) => {
|
|
301
|
+
nodes: operands(value.exists.nodes.map((node) => {
|
|
301
302
|
const variable = {
|
|
302
303
|
kind: 'variable',
|
|
303
304
|
id: ids.get(node.variable.id) ?? node.variable.id,
|
|
@@ -305,14 +306,14 @@ function rewriteVariables(value, ids) {
|
|
|
305
306
|
return 'class' in node
|
|
306
307
|
? { variable, class: node.class }
|
|
307
308
|
: { variable, selector: node.selector };
|
|
308
|
-
}),
|
|
309
|
+
})),
|
|
309
310
|
where: rewriteVariables(value.exists.where, ids),
|
|
310
311
|
},
|
|
311
312
|
};
|
|
312
313
|
}
|
|
313
314
|
if ('allOf' in value)
|
|
314
|
-
return { allOf: value.allOf.map((child) => rewriteVariables(child, ids)) };
|
|
315
|
-
return { anyOf: value.anyOf.map((child) => rewriteVariables(child, ids)) };
|
|
315
|
+
return { allOf: operands(value.allOf.map((child) => rewriteVariables(child, ids))) };
|
|
316
|
+
return { anyOf: operands(value.anyOf.map((child) => rewriteVariables(child, ids))) };
|
|
316
317
|
}
|
|
317
318
|
function selectorSignature(node) {
|
|
318
319
|
const selector = policyVariableSelector(node);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type DiagnosticCode = 'CALLABLE_INPUT_FIELD_UNKNOWN' | 'DOMAIN_CLASS_ABSTRACT' | 'DOMAIN_PROPERTY_INPUT_INVALID' | 'DOMAIN_PROPERTY_REQUIRED' | 'DOMAIN_PROPERTY_UNKNOWN' | '
|
|
1
|
+
export type DiagnosticCode = 'CALLABLE_INPUT_FIELD_UNKNOWN' | 'DOMAIN_CLASS_ABSTRACT' | 'DOMAIN_PROPERTY_INPUT_INVALID' | 'DOMAIN_PROPERTY_REQUIRED' | 'DOMAIN_PROPERTY_UNKNOWN' | 'VALUE_SCHEMA_INSTANCE_INVALID' | 'VS_DIALECT' | 'VS_DYNAMIC_FORBIDDEN' | 'VS_KEYWORD_CLOSED' | 'VS_META_SCHEMA' | 'VS_NUMBER_DOMAIN' | 'VS_PATH_EXTENSION' | 'VS_RECURSION_PRODUCTIVE' | 'VS_REFERENCE_FORM' | 'VS_REFERENCE_RESOLUTION' | 'VS_REGEX_PORTABLE' | 'VS_RESOURCE_ID' | 'VS_VALUE_KIND' | 'VS_VALUE_KIND_JSON';
|
|
2
2
|
export interface Diagnostic<out Code extends DiagnosticCode = DiagnosticCode> {
|
|
3
3
|
readonly code: Code;
|
|
4
4
|
readonly pointer: string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DiagnosticCode } from './diagnostic.js';
|
|
2
|
+
/**
|
|
3
|
+
* What a rule reads to decide. `schema`: a value schema alone, with its place in the declaration
|
|
4
|
+
* that holds it (a Property, a callable input or output). `context`: a value schema and what its
|
|
5
|
+
* Domain can reach, the Node Classes of the Domain and of its direct dependencies and the exact
|
|
6
|
+
* dependency closure. `value`: a value, under an accepted schema or a contract of the Domain.
|
|
7
|
+
* `warning`: a value schema, which the rule reports without refusing it.
|
|
8
|
+
*/
|
|
9
|
+
export type ValueRuleStage = 'schema' | 'context' | 'value' | 'warning';
|
|
10
|
+
/** The rule one value diagnostic code reports. */
|
|
11
|
+
export interface ValueRule {
|
|
12
|
+
readonly stage: ValueRuleStage;
|
|
13
|
+
/**
|
|
14
|
+
* The positions the rule constrains, which its diagnostics point at, relative to the value
|
|
15
|
+
* schema, the value or the Domain input it checks: JSON Pointer templates in which a `{name}`
|
|
16
|
+
* token stands for one token of the kind it names and `{/path*}` for any number of tokens.
|
|
17
|
+
*/
|
|
18
|
+
readonly positions: readonly [string, ...string[]];
|
|
19
|
+
/** The rule, in one plain sentence. */
|
|
20
|
+
readonly statement: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Every value diagnostic code with the rule it reports. A code added to the language does not
|
|
24
|
+
* compile until it is described here.
|
|
25
|
+
*/
|
|
26
|
+
export declare const VALUE_RULES: Readonly<Record<DiagnosticCode, ValueRule>>;
|