@astrale-os/kernel-dsl 1.0.0-beta.6 → 1.0.0-beta.7
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/compiled/admission/wire/class.js +10 -5
- package/dist/v1/language/class/model/edge.d.ts +4 -2
- package/dist/v1/language/class/unique.d.ts +24 -0
- package/dist/v1/language/class/unique.js +46 -0
- package/dist/v1/language/index.d.ts +2 -0
- package/dist/v1/language/index.js +1 -0
- package/dist/v1/schema/normalization/domain.js +4 -1
- package/dist/v1/schema/validation/domain.structure.js +3 -3
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {} from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import { Key } from '../../../addressing/index.js';
|
|
3
3
|
import { MEDIA_TYPE_PATTERN } from '../../../language/class/model/data.js';
|
|
4
|
+
import { compareEdgeUniqueTuples, edgeUniqueTuple } from '../../../language/class/unique.js';
|
|
4
5
|
import {} from '../../model/schema.js';
|
|
5
6
|
import { invalid } from '../diagnostic.js';
|
|
6
7
|
import { compiledCallable } from './callable.js';
|
|
@@ -119,17 +120,21 @@ function edgeContract(value, path) {
|
|
|
119
120
|
invalid('Edge endpoint roles', undefined, endpointsPath);
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
|
-
/** A nonempty list of
|
|
123
|
+
/** A nonempty list of tuples of `EDGE_UNIQUE_TUPLES`, distinct and in their canonical order. */
|
|
123
124
|
function uniqueTuples(input, path) {
|
|
124
125
|
const tuples = array(input, 'Edge unique tuples', path);
|
|
125
126
|
if (tuples.length === 0)
|
|
126
127
|
invalid('Edge unique tuples', undefined, path);
|
|
127
|
-
|
|
128
|
-
const fields = array(tuple, 'Edge unique tuple', `${path}/${index}`);
|
|
129
|
-
if (fields
|
|
128
|
+
const known = tuples.map((tuple, index) => {
|
|
129
|
+
const fields = edgeUniqueTuple(array(tuple, 'Edge unique tuple', `${path}/${index}`));
|
|
130
|
+
if (fields === undefined)
|
|
130
131
|
invalid('Edge unique tuple', undefined, `${path}/${index}`);
|
|
132
|
+
return fields;
|
|
133
|
+
});
|
|
134
|
+
for (const [index, tuple] of known.entries()) {
|
|
135
|
+
if (index > 0 && compareEdgeUniqueTuples(known[index - 1], tuple) >= 0)
|
|
136
|
+
invalid('Edge unique tuples', undefined, `${path}/${index}`);
|
|
131
137
|
}
|
|
132
|
-
unique(tuples.map((tuple) => JSON.stringify(tuple)), 'Edge unique tuples', path);
|
|
133
138
|
}
|
|
134
139
|
function edgeEndpoint(input, count, label, path) {
|
|
135
140
|
const value = object(input, label, path);
|
|
@@ -22,11 +22,13 @@ export interface UndirectedEdgeConstraints {
|
|
|
22
22
|
readonly noSelf?: true;
|
|
23
23
|
readonly acyclic?: never;
|
|
24
24
|
}
|
|
25
|
+
/** A field a uniqueness tuple names: an endpoint of the Edge, or its slug. */
|
|
26
|
+
export type EdgeUniqueField = 'source' | 'target' | 'slug';
|
|
25
27
|
/**
|
|
26
28
|
* The fields one uniqueness tuple of a directed Edge Class names, which no two Edges of the Classes
|
|
27
|
-
* it covers share:
|
|
29
|
+
* it covers share: one endpoint, then the slug. `EDGE_UNIQUE_TUPLES` lists them in canonical order.
|
|
28
30
|
*/
|
|
29
|
-
export type EdgeUniqueTuple = readonly ['source', 'slug'];
|
|
31
|
+
export type EdgeUniqueTuple = readonly ['source', 'slug'] | readonly ['target', 'slug'];
|
|
30
32
|
export interface DirectedEdgeContract {
|
|
31
33
|
readonly orientation: 'directed';
|
|
32
34
|
readonly endpoints: {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { EdgeUniqueTuple } from './model/edge.js';
|
|
2
|
+
/**
|
|
3
|
+
* Every uniqueness tuple a directed Edge Class may declare, in canonical order: an endpoint, then
|
|
4
|
+
* the slug. The source tuple names entries under their source; the target tuple, under their target.
|
|
5
|
+
*/
|
|
6
|
+
export declare const EDGE_UNIQUE_TUPLES: readonly EdgeUniqueTuple[];
|
|
7
|
+
/** The fields of an Edge a uniqueness tuple reads; the slug is absent from an Edge without one. */
|
|
8
|
+
export interface EdgeUniqueFacts {
|
|
9
|
+
readonly source: string;
|
|
10
|
+
readonly target: string;
|
|
11
|
+
readonly slug?: string;
|
|
12
|
+
}
|
|
13
|
+
/** The tuple `value` spells, or undefined when it spells none. */
|
|
14
|
+
export declare function edgeUniqueTuple(value: unknown): EdgeUniqueTuple | undefined;
|
|
15
|
+
/** Whether `tuples` lists the tuple `value` spells: false when it spells none. */
|
|
16
|
+
export declare function includesEdgeUniqueTuple(tuples: readonly EdgeUniqueTuple[], value: unknown): boolean;
|
|
17
|
+
/** The canonical order of tuples: field by field, source before target before slug. */
|
|
18
|
+
export declare function compareEdgeUniqueTuples(left: EdgeUniqueTuple, right: EdgeUniqueTuple): number;
|
|
19
|
+
/**
|
|
20
|
+
* The value `edge` takes under `tuple`, or undefined when one of its fields is absent, so that an
|
|
21
|
+
* Edge without a slug never collides: two distinct Edges of the Classes a tuple covers collide
|
|
22
|
+
* exactly when they take one value.
|
|
23
|
+
*/
|
|
24
|
+
export declare function edgeUniqueValue(tuple: EdgeUniqueTuple, edge: EdgeUniqueFacts): string | undefined;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every uniqueness tuple a directed Edge Class may declare, in canonical order: an endpoint, then
|
|
3
|
+
* the slug. The source tuple names entries under their source; the target tuple, under their target.
|
|
4
|
+
*/
|
|
5
|
+
export const EDGE_UNIQUE_TUPLES = Object.freeze([
|
|
6
|
+
Object.freeze(['source', 'slug']),
|
|
7
|
+
Object.freeze(['target', 'slug']),
|
|
8
|
+
]);
|
|
9
|
+
/** The fields a tuple names, in canonical order. */
|
|
10
|
+
const FIELDS = Object.freeze(['source', 'target', 'slug']);
|
|
11
|
+
/** The tuple `value` spells, or undefined when it spells none. */
|
|
12
|
+
export function edgeUniqueTuple(value) {
|
|
13
|
+
if (!Array.isArray(value))
|
|
14
|
+
return undefined;
|
|
15
|
+
return EDGE_UNIQUE_TUPLES.find((tuple) => tuple.length === value.length && tuple.every((field, index) => field === value[index]));
|
|
16
|
+
}
|
|
17
|
+
/** Whether `tuples` lists the tuple `value` spells: false when it spells none. */
|
|
18
|
+
export function includesEdgeUniqueTuple(tuples, value) {
|
|
19
|
+
const tuple = edgeUniqueTuple(value);
|
|
20
|
+
return (tuple !== undefined &&
|
|
21
|
+
tuples.some((candidate) => compareEdgeUniqueTuples(candidate, tuple) === 0));
|
|
22
|
+
}
|
|
23
|
+
/** The canonical order of tuples: field by field, source before target before slug. */
|
|
24
|
+
export function compareEdgeUniqueTuples(left, right) {
|
|
25
|
+
for (let index = 0; index < Math.min(left.length, right.length); index += 1) {
|
|
26
|
+
const order = FIELDS.indexOf(left[index]) - FIELDS.indexOf(right[index]);
|
|
27
|
+
if (order !== 0)
|
|
28
|
+
return order;
|
|
29
|
+
}
|
|
30
|
+
return left.length - right.length;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The value `edge` takes under `tuple`, or undefined when one of its fields is absent, so that an
|
|
34
|
+
* Edge without a slug never collides: two distinct Edges of the Classes a tuple covers collide
|
|
35
|
+
* exactly when they take one value.
|
|
36
|
+
*/
|
|
37
|
+
export function edgeUniqueValue(tuple, edge) {
|
|
38
|
+
const values = [];
|
|
39
|
+
for (const field of tuple) {
|
|
40
|
+
const value = edge[field];
|
|
41
|
+
if (value === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
values.push(value);
|
|
44
|
+
}
|
|
45
|
+
return JSON.stringify(values);
|
|
46
|
+
}
|
|
@@ -17,6 +17,8 @@ export type * from './class/model/edge.js';
|
|
|
17
17
|
export { inheritanceOf } from './class/inheritance.js';
|
|
18
18
|
export { edgeLifetimes } from './class/lifetime.js';
|
|
19
19
|
export type { EdgeLifetime } from './class/lifetime.js';
|
|
20
|
+
export { EDGE_UNIQUE_TUPLES, compareEdgeUniqueTuples, edgeUniqueTuple, edgeUniqueValue, includesEdgeUniqueTuple, } from './class/unique.js';
|
|
21
|
+
export type { EdgeUniqueFacts } from './class/unique.js';
|
|
20
22
|
export type * from './class/model/property.js';
|
|
21
23
|
export type * from './class/resolved.js';
|
|
22
24
|
export type * from './class/facets.js';
|
|
@@ -5,5 +5,6 @@ export { valueProgramIncludes } from './value/inclusion/includes.js';
|
|
|
5
5
|
export { stateMachine } from './state/index.js';
|
|
6
6
|
export { inheritanceOf } from './class/inheritance.js';
|
|
7
7
|
export { edgeLifetimes } from './class/lifetime.js';
|
|
8
|
+
export { EDGE_UNIQUE_TUPLES, compareEdgeUniqueTuples, edgeUniqueTuple, edgeUniqueValue, includesEdgeUniqueTuple, } from './class/unique.js';
|
|
8
9
|
export { PropertyValues } from './class/property-values.js';
|
|
9
10
|
export { isClassRuleChecks, policyVariableClass, policyVariableSelector, policyObjectKey, policyObjectOrigin, projectedMemberClass, } from './policy/model/policy.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { json } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import { compareDefinitionRefs, compareUnicode } from '../../addressing/ordering.js';
|
|
3
3
|
import { isBinaryInput } from '../../language/callable/model/callable.js';
|
|
4
|
+
import { compareEdgeUniqueTuples } from '../../language/class/unique.js';
|
|
4
5
|
import { normalizeClassRule, normalizePolicyChecks, normalizePolicyExpression, } from '../../language/policy/normalization/normalize.js';
|
|
5
6
|
import { admitStateTopology } from '../../language/state/topology.js';
|
|
6
7
|
import { canonicalizeValueSchema } from '../../language/value/profile/canonicalize.js';
|
|
@@ -81,7 +82,9 @@ function canonicalizeClass(value) {
|
|
|
81
82
|
endpoints: canonicalizeDirectedEndpoints(value.endpoints),
|
|
82
83
|
policies: value.policies,
|
|
83
84
|
constraints: value.constraints,
|
|
84
|
-
...(value.unique === undefined
|
|
85
|
+
...(value.unique === undefined
|
|
86
|
+
? {}
|
|
87
|
+
: { unique: [...value.unique].sort(compareEdgeUniqueTuples) }),
|
|
85
88
|
}
|
|
86
89
|
: {
|
|
87
90
|
...base,
|
|
@@ -98,9 +98,9 @@ const edgeConstraintsSchema = strictObject({
|
|
|
98
98
|
noSelf: literal(true).optional(),
|
|
99
99
|
acyclic: literal(true).optional(),
|
|
100
100
|
});
|
|
101
|
-
// A tuple names
|
|
102
|
-
// an empty list: one spelling per meaning, one revision.
|
|
103
|
-
const edgeUniqueSchema = uniqueArray(tuple([literal('source'), literal('slug')])).min(1);
|
|
101
|
+
// A tuple names an endpoint, then the slug (`EDGE_UNIQUE_TUPLES`). No tuple is spelled by the
|
|
102
|
+
// absence of the field, never by an empty list: one spelling per meaning, one revision.
|
|
103
|
+
const edgeUniqueSchema = uniqueArray(union([tuple([literal('source'), literal('slug')]), tuple([literal('target'), literal('slug')])])).min(1);
|
|
104
104
|
const policyPatternSubjectSchema = strictObject({ kind: literal('subject') });
|
|
105
105
|
const policyPatternObjectSchema = strictObject({ kind: literal('object') });
|
|
106
106
|
const policyPatternSourceSchema = strictObject({ kind: literal('source') });
|