@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.
@@ -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 distinct tuples, each naming the source and then the slug. */
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
- for (const [index, tuple] of tuples.entries()) {
128
- const fields = array(tuple, 'Edge unique tuple', `${path}/${index}`);
129
- if (fields.length !== 2 || fields[0] !== 'source' || fields[1] !== 'slug')
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: its source endpoint and its slug.
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 ? {} : { unique: value.unique }),
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 the source and the slug. No tuple is spelled by the absence of the field, never by
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') });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/kernel-dsl",
3
- "version": "1.0.0-beta.6",
3
+ "version": "1.0.0-beta.7",
4
4
  "description": "Astrale Kernel DSL - Schema authoring, inference, and IR serialization",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {