@astrale-os/kernel-dsl 1.0.0-beta.10 → 1.0.0-beta.12

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 (70) hide show
  1. package/dist/v1/addressing/class-key.js +3 -3
  2. package/dist/v1/addressing/key.d.ts +4 -1
  3. package/dist/v1/addressing/key.js +21 -2
  4. package/dist/v1/builder/callable/authoring.d.ts +1 -1
  5. package/dist/v1/builder/class/authoring.d.ts +6 -256
  6. package/dist/v1/builder/class/handle.d.ts +27 -0
  7. package/dist/v1/builder/class/handle.js +1 -0
  8. package/dist/v1/builder/class/input.d.ts +129 -0
  9. package/dist/v1/builder/class/input.js +1 -0
  10. package/dist/v1/builder/class/projection.d.ts +129 -0
  11. package/dist/v1/builder/class/projection.js +1 -0
  12. package/dist/v1/builder/core/authoring.d.ts +1 -1
  13. package/dist/v1/builder/index.d.ts +3 -1
  14. package/dist/v1/builder/lowering/class.d.ts +1 -1
  15. package/dist/v1/builder/lowering/schema/context.d.ts +2 -1
  16. package/dist/v1/builder/lowering/value/path.d.ts +1 -1
  17. package/dist/v1/builder/policy/authoring.d.ts +1 -1
  18. package/dist/v1/builder/schema/input.d.ts +1 -1
  19. package/dist/v1/builder/schema/projection/class.d.ts +2 -1
  20. package/dist/v1/builder/schema/projection/core-input.d.ts +2 -1
  21. package/dist/v1/builder/schema/projection/identity.d.ts +2 -1
  22. package/dist/v1/builder/schema/projection/property.d.ts +1 -1
  23. package/dist/v1/builder/value/path-metadata.d.ts +1 -1
  24. package/dist/v1/builder/value/path.d.ts +1 -1
  25. package/dist/v1/language/index.d.ts +1 -0
  26. package/dist/v1/language/index.js +1 -0
  27. package/dist/v1/language/validation/diagnostic.d.ts +1 -1
  28. package/dist/v1/language/value/inclusion/arrays.d.ts +3 -0
  29. package/dist/v1/language/value/inclusion/arrays.js +85 -0
  30. package/dist/v1/language/value/inclusion/assertions.d.ts +18 -0
  31. package/dist/v1/language/value/inclusion/assertions.js +29 -0
  32. package/dist/v1/language/value/inclusion/combinators.d.ts +3 -0
  33. package/dist/v1/language/value/inclusion/combinators.js +67 -0
  34. package/dist/v1/language/value/inclusion/equivalence.d.ts +4 -0
  35. package/dist/v1/language/value/inclusion/equivalence.js +74 -0
  36. package/dist/v1/language/value/inclusion/evidence.d.ts +34 -0
  37. package/dist/v1/language/value/inclusion/evidence.js +138 -0
  38. package/dist/v1/language/value/inclusion/includes.d.ts +17 -0
  39. package/dist/v1/language/value/inclusion/includes.js +25 -719
  40. package/dist/v1/language/value/inclusion/keywords.d.ts +5 -0
  41. package/dist/v1/language/value/inclusion/keywords.js +31 -0
  42. package/dist/v1/language/value/inclusion/kinds.d.ts +17 -0
  43. package/dist/v1/language/value/inclusion/kinds.js +57 -0
  44. package/dist/v1/language/value/inclusion/location.d.ts +24 -0
  45. package/dist/v1/language/value/inclusion/location.js +63 -0
  46. package/dist/v1/language/value/inclusion/numbers.d.ts +3 -0
  47. package/dist/v1/language/value/inclusion/numbers.js +41 -0
  48. package/dist/v1/language/value/inclusion/objects.d.ts +3 -0
  49. package/dist/v1/language/value/inclusion/objects.js +190 -0
  50. package/dist/v1/language/value/inclusion/strings.d.ts +3 -0
  51. package/dist/v1/language/value/inclusion/strings.js +29 -0
  52. package/dist/v1/language/value/inclusion/types.d.ts +3 -0
  53. package/dist/v1/language/value/inclusion/types.js +11 -0
  54. package/dist/v1/language/value/index.d.ts +1 -1
  55. package/dist/v1/language/value/index.js +1 -1
  56. package/dist/v1/language/value/ordering.d.ts +9 -0
  57. package/dist/v1/language/value/ordering.js +72 -9
  58. package/dist/v1/schema/validation/value-api.d.ts +9 -0
  59. package/dist/v1/schema/validation/value-api.js +23 -1
  60. package/dist/value/canonical.d.ts +3 -2
  61. package/dist/value/canonical.js +30 -6
  62. package/dist/value/decode.d.ts +7 -0
  63. package/dist/value/decode.js +159 -9
  64. package/dist/value/index.d.ts +1 -1
  65. package/dist/value/json.d.ts +1 -1
  66. package/dist/value/json.js +1 -1
  67. package/dist/value/types.d.ts +6 -0
  68. package/dist/value/unicode.d.ts +2 -1
  69. package/dist/value/unicode.js +2 -0
  70. package/package.json +1 -1
@@ -0,0 +1,5 @@
1
+ import type { JsonSchema } from '../model/schema.js';
2
+ /** Keywords the evaluator never asserts. */
3
+ export declare const INERT: ReadonlySet<string>;
4
+ /** Whether a schema asserts nothing. */
5
+ export declare function isTop(schema: JsonSchema): boolean;
@@ -0,0 +1,31 @@
1
+ /** Keywords the evaluator never asserts. */
2
+ export const INERT = new Set([
3
+ '$anchor',
4
+ '$comment',
5
+ '$defs',
6
+ '$id',
7
+ '$schema',
8
+ 'contentEncoding',
9
+ 'contentMediaType',
10
+ 'contentSchema',
11
+ 'default',
12
+ 'deprecated',
13
+ 'description',
14
+ 'examples',
15
+ 'format',
16
+ 'readOnly',
17
+ 'title',
18
+ 'writeOnly',
19
+ 'x-astrale-path',
20
+ ]);
21
+ /** Whether a schema asserts nothing. */
22
+ export function isTop(schema) {
23
+ if (schema === true)
24
+ return true;
25
+ if (schema === false)
26
+ return false;
27
+ return Object.keys(schema).every((keyword) => INERT.has(keyword) ||
28
+ ((keyword === 'then' || keyword === 'else') && !Object.hasOwn(schema, 'if')) ||
29
+ ((keyword === 'minContains' || keyword === 'maxContains') &&
30
+ !Object.hasOwn(schema, 'contains')));
31
+ }
@@ -0,0 +1,17 @@
1
+ import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
2
+ /** Bit masks over the JSON kinds a schema may admit; integers and fractions split `number`. */
3
+ export declare const NULL = 1;
4
+ export declare const BOOLEAN = 2;
5
+ export declare const OBJECT = 4;
6
+ export declare const ARRAY = 8;
7
+ export declare const STRING = 16;
8
+ export declare const INTEGER = 32;
9
+ export declare const FRACTION = 64;
10
+ export declare const NUMBER: number;
11
+ export declare const ANY: number;
12
+ /** The kinds a `type` keyword value admits; an absent `type` admits every kind. */
13
+ export declare function typeMask(type: JsonValue | undefined): number;
14
+ /** The single kind of one JSON value. */
15
+ export declare function kindOf(value: JsonValue): number;
16
+ /** The finite value set of a kind mask made only of null and booleans. */
17
+ export declare function finiteKinds(kinds: number): readonly JsonValue[] | undefined;
@@ -0,0 +1,57 @@
1
+ /** Bit masks over the JSON kinds a schema may admit; integers and fractions split `number`. */
2
+ export const NULL = 1;
3
+ export const BOOLEAN = 2;
4
+ export const OBJECT = 4;
5
+ export const ARRAY = 8;
6
+ export const STRING = 16;
7
+ export const INTEGER = 32;
8
+ export const FRACTION = 64;
9
+ export const NUMBER = INTEGER | FRACTION;
10
+ export const ANY = NULL | BOOLEAN | OBJECT | ARRAY | STRING | NUMBER;
11
+ /** The kinds a `type` keyword value admits; an absent `type` admits every kind. */
12
+ export function typeMask(type) {
13
+ if (type === undefined)
14
+ return ANY;
15
+ const names = typeof type === 'string' ? [type] : Array.isArray(type) ? type : [];
16
+ return names.reduce((mask, name) => {
17
+ switch (name) {
18
+ case 'null':
19
+ return mask | NULL;
20
+ case 'boolean':
21
+ return mask | BOOLEAN;
22
+ case 'object':
23
+ return mask | OBJECT;
24
+ case 'array':
25
+ return mask | ARRAY;
26
+ case 'string':
27
+ return mask | STRING;
28
+ case 'integer':
29
+ return mask | INTEGER;
30
+ case 'number':
31
+ return mask | NUMBER;
32
+ default:
33
+ return mask;
34
+ }
35
+ }, 0);
36
+ }
37
+ /** The single kind of one JSON value. */
38
+ export function kindOf(value) {
39
+ if (value === null)
40
+ return NULL;
41
+ if (typeof value === 'boolean')
42
+ return BOOLEAN;
43
+ if (typeof value === 'string')
44
+ return STRING;
45
+ if (typeof value === 'number')
46
+ return Number.isInteger(value) ? INTEGER : FRACTION;
47
+ return Array.isArray(value) ? ARRAY : OBJECT;
48
+ }
49
+ /** The finite value set of a kind mask made only of null and booleans. */
50
+ export function finiteKinds(kinds) {
51
+ if ((kinds & ~(NULL | BOOLEAN)) !== 0)
52
+ return undefined;
53
+ return [
54
+ ...((kinds & NULL) !== 0 ? [null] : []),
55
+ ...((kinds & BOOLEAN) !== 0 ? [true, false] : []),
56
+ ];
57
+ }
@@ -0,0 +1,24 @@
1
+ import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
2
+ import type { ValueProgram } from '../compile/model.js';
3
+ import type { JsonSchema } from '../model/schema.js';
4
+ /** One schema location inside one admitted program. */
5
+ export interface At {
6
+ readonly program: ValueProgram;
7
+ readonly index: number;
8
+ }
9
+ /** The location `index` of `program`. */
10
+ export declare function at(program: ValueProgram, index: number): At;
11
+ /** The schema written at a location. */
12
+ export declare function schemaOf(location: At): JsonSchema;
13
+ /** The subschema under `keyword`, or under its `member`, when the program compiled one. */
14
+ export declare function childOf(location: At, keyword: string, member?: string | number): At | undefined;
15
+ /** The subschemas of an array-valued `keyword`, in order. */
16
+ export declare function childrenOf(location: At, keyword: string): readonly At[];
17
+ /** The resolved `$ref` target of a location. */
18
+ export declare function referenceOf(location: At): At | undefined;
19
+ /** The location with its `$ref` targets and `allOf` branches; admission makes this acyclic. */
20
+ export declare function conjunctsOf(location: At): readonly At[];
21
+ /** Whether `name` matches `pattern` under the program's regular-expression dialect. */
22
+ export declare function matches(location: At, pattern: string, name: string): boolean;
23
+ /** Whether the V1 evaluator accepts `value` at a location. */
24
+ export declare function accepts(location: At, value: JsonValue): boolean;
@@ -0,0 +1,63 @@
1
+ import { loadValueProgram } from '../compile/load.js';
2
+ import { evaluateInstance } from '../evaluate/evaluator.js';
3
+ import { isValueObject as isJsonObject } from '../model/object.js';
4
+ import { appendPointer } from '../syntax/pointer.js';
5
+ /** The location `index` of `program`. */
6
+ export function at(program, index) {
7
+ return { program, index };
8
+ }
9
+ /** The schema written at a location. */
10
+ export function schemaOf(location) {
11
+ const node = location.program.nodes[location.index];
12
+ if (node === undefined)
13
+ throw new TypeError('Value program location is absent.');
14
+ return node.schema;
15
+ }
16
+ /** The subschema under `keyword`, or under its `member`, when the program compiled one. */
17
+ export function childOf(location, keyword, member) {
18
+ const base = appendPointer('', keyword);
19
+ const key = (member === undefined ? base : appendPointer(base, member)).slice(1);
20
+ const index = location.program.nodes[location.index]?.children[key];
21
+ return index === undefined ? undefined : at(location.program, index);
22
+ }
23
+ /** The subschemas of an array-valued `keyword`, in order. */
24
+ export function childrenOf(location, keyword) {
25
+ const schema = schemaOf(location);
26
+ if (!isJsonObject(schema) || !Array.isArray(schema[keyword]))
27
+ return [];
28
+ return schema[keyword].flatMap((_, index) => {
29
+ const child = childOf(location, keyword, index);
30
+ return child === undefined ? [] : [child];
31
+ });
32
+ }
33
+ /** The resolved `$ref` target of a location. */
34
+ export function referenceOf(location) {
35
+ const index = location.program.nodes[location.index]?.reference;
36
+ return index === undefined ? undefined : at(location.program, index);
37
+ }
38
+ /** The location with its `$ref` targets and `allOf` branches; admission makes this acyclic. */
39
+ export function conjunctsOf(location) {
40
+ const seen = new Set();
41
+ const conjuncts = [];
42
+ const visit = (current) => {
43
+ if (seen.has(current.index))
44
+ return;
45
+ seen.add(current.index);
46
+ conjuncts.push(current);
47
+ const reference = referenceOf(current);
48
+ if (reference !== undefined)
49
+ visit(reference);
50
+ for (const branch of childrenOf(current, 'allOf'))
51
+ visit(branch);
52
+ };
53
+ visit(location);
54
+ return conjuncts;
55
+ }
56
+ /** Whether `name` matches `pattern` under the program's regular-expression dialect. */
57
+ export function matches(location, pattern, name) {
58
+ return loadValueProgram(location.program).matcher(pattern).test(name);
59
+ }
60
+ /** Whether the V1 evaluator accepts `value` at a location. */
61
+ export function accepts(location, value) {
62
+ return evaluateInstance(loadValueProgram(location.program, location.index), value).valid;
63
+ }
@@ -0,0 +1,3 @@
1
+ import type { Rule } from './assertions.js';
2
+ /** Rules for the numeric bound and divisibility keywords. */
3
+ export declare const numberRules: ReadonlyMap<string, Rule>;
@@ -0,0 +1,41 @@
1
+ import { isMultipleOf } from '../evaluate/assertions.js';
2
+ import { has } from './evidence.js';
3
+ import { FRACTION, NUMBER } from './kinds.js';
4
+ /** Rules for the numeric bound and divisibility keywords. */
5
+ export const numberRules = new Map([
6
+ [
7
+ 'maximum',
8
+ ({ value, evidence }) => typeof value !== 'number' ||
9
+ !has(evidence, NUMBER) ||
10
+ (evidence.upper !== undefined && evidence.upper.value <= value),
11
+ ],
12
+ [
13
+ 'exclusiveMaximum',
14
+ ({ value, evidence }) => typeof value !== 'number' ||
15
+ !has(evidence, NUMBER) ||
16
+ (evidence.upper !== undefined &&
17
+ (evidence.upper.value < value ||
18
+ (evidence.upper.exclusive && evidence.upper.value <= value))),
19
+ ],
20
+ [
21
+ 'minimum',
22
+ ({ value, evidence }) => typeof value !== 'number' ||
23
+ !has(evidence, NUMBER) ||
24
+ (evidence.lower !== undefined && evidence.lower.value >= value),
25
+ ],
26
+ [
27
+ 'exclusiveMinimum',
28
+ ({ value, evidence }) => typeof value !== 'number' ||
29
+ !has(evidence, NUMBER) ||
30
+ (evidence.lower !== undefined &&
31
+ (evidence.lower.value > value ||
32
+ (evidence.lower.exclusive && evidence.lower.value >= value))),
33
+ ],
34
+ [
35
+ 'multipleOf',
36
+ ({ value, evidence }) => typeof value !== 'number' ||
37
+ !has(evidence, NUMBER) ||
38
+ evidence.multipleOf.some((divisor) => isMultipleOf(divisor, value)) ||
39
+ (!has(evidence, FRACTION) && isMultipleOf(1, value)),
40
+ ],
41
+ ]);
@@ -0,0 +1,3 @@
1
+ import type { Rule } from './assertions.js';
2
+ /** Rules for the object size, required-name and property-subschema keywords. */
3
+ export declare const objectRules: ReadonlyMap<string, Rule>;
@@ -0,0 +1,190 @@
1
+ import { isValueObject as isJsonObject } from '../model/object.js';
2
+ import { has } from './evidence.js';
3
+ import { isTop } from './keywords.js';
4
+ import { OBJECT } from './kinds.js';
5
+ import { accepts, childOf, matches, schemaOf } from './location.js';
6
+ /** Rules for the object size, required-name and property-subschema keywords. */
7
+ export const objectRules = new Map([
8
+ [
9
+ 'maxProperties',
10
+ ({ inclusion, value, evidence }) => typeof value !== 'number' ||
11
+ !has(evidence, OBJECT) ||
12
+ (evidence.maxProperties !== undefined && evidence.maxProperties <= value) ||
13
+ evidence.conjuncts.some((conjunct) => {
14
+ const names = closedNames(inclusion, conjunct);
15
+ return names !== undefined && names.length <= value;
16
+ }),
17
+ ],
18
+ [
19
+ 'minProperties',
20
+ ({ value, evidence }) => typeof value !== 'number' ||
21
+ !has(evidence, OBJECT) ||
22
+ value <= 0 ||
23
+ evidence.minProperties >= value ||
24
+ evidence.required.size >= value,
25
+ ],
26
+ [
27
+ 'required',
28
+ ({ value, evidence }) => !Array.isArray(value) ||
29
+ !has(evidence, OBJECT) ||
30
+ value.every((name) => typeof name !== 'string' || evidence.required.has(name)),
31
+ ],
32
+ ['dependentRequired', dependentRequiredHolds],
33
+ ['properties', propertiesHold],
34
+ ['patternProperties', patternPropertiesHold],
35
+ ['additionalProperties', additionalHolds],
36
+ ['propertyNames', propertyNamesHold],
37
+ ['dependentSchemas', dependentSchemasHold],
38
+ ]);
39
+ function dependentRequiredHolds({ inclusion, value, evidence }) {
40
+ return (!isJsonObject(value) ||
41
+ !has(evidence, OBJECT) ||
42
+ Object.entries(value).every(([name, dependencies]) => !Array.isArray(dependencies) ||
43
+ forbids(inclusion, evidence, name) ||
44
+ dependencies.every((dependency) => typeof dependency !== 'string' ||
45
+ evidence.required.has(dependency) ||
46
+ evidence.dependentRequired.get(name)?.has(dependency) === true)));
47
+ }
48
+ function propertiesHold({ inclusion, value, outer, evidence }) {
49
+ return (!isJsonObject(value) ||
50
+ !has(evidence, OBJECT) ||
51
+ Object.keys(value).every((name) => {
52
+ const member = childOf(outer, 'properties', name);
53
+ return (member === undefined ||
54
+ isTop(schemaOf(member)) ||
55
+ memberConstraints(evidence, name).some((constraint) => inclusion.includes(member, constraint)));
56
+ }));
57
+ }
58
+ function patternPropertiesHold({ inclusion, value, outer, evidence }) {
59
+ return (!isJsonObject(value) ||
60
+ !has(evidence, OBJECT) ||
61
+ Object.keys(value).every((pattern) => {
62
+ const member = childOf(outer, 'patternProperties', pattern);
63
+ if (member === undefined || isTop(schemaOf(member)))
64
+ return true;
65
+ return evidence.conjuncts.some((conjunct) => {
66
+ const same = childOf(conjunct, 'patternProperties', pattern);
67
+ if (same !== undefined && inclusion.includes(member, same))
68
+ return true;
69
+ return coversNames(inclusion, conjunct, member, (name) => matches(outer, pattern, name));
70
+ });
71
+ }));
72
+ }
73
+ function additionalHolds({ inclusion, outer, schema, evidence }) {
74
+ const additional = childOf(outer, 'additionalProperties');
75
+ if (additional === undefined || isTop(schemaOf(additional)) || !has(evidence, OBJECT)) {
76
+ return true;
77
+ }
78
+ const declared = isJsonObject(schema.properties) ? schema.properties : {};
79
+ const patterns = isJsonObject(schema.patternProperties)
80
+ ? Object.keys(schema.patternProperties)
81
+ : [];
82
+ const isAdditional = (name) => !Object.hasOwn(declared, name) && !patterns.some((pattern) => matches(outer, pattern, name));
83
+ return evidence.conjuncts.some((conjunct) => coversNames(inclusion, conjunct, additional, isAdditional, patterns));
84
+ }
85
+ function propertyNamesHold({ inclusion, outer, evidence }) {
86
+ const names = childOf(outer, 'propertyNames');
87
+ if (names === undefined || isTop(schemaOf(names)) || !has(evidence, OBJECT))
88
+ return true;
89
+ return evidence.conjuncts.some((conjunct) => {
90
+ const own = childOf(conjunct, 'propertyNames');
91
+ if (own !== undefined && inclusion.includes(names, own))
92
+ return true;
93
+ const closed = closedNames(inclusion, conjunct);
94
+ return closed !== undefined && closed.every((name) => accepts(names, name));
95
+ });
96
+ }
97
+ function dependentSchemasHold({ inclusion, value, outer, evidence }) {
98
+ return (!isJsonObject(value) ||
99
+ !has(evidence, OBJECT) ||
100
+ Object.keys(value).every((name) => {
101
+ const dependent = childOf(outer, 'dependentSchemas', name);
102
+ if (dependent === undefined || isTop(schemaOf(dependent)))
103
+ return true;
104
+ if (forbids(inclusion, evidence, name))
105
+ return true;
106
+ if (evidence.conjuncts.some((conjunct) => inclusion.includes(dependent, conjunct))) {
107
+ return true;
108
+ }
109
+ return evidence.conjuncts.some((conjunct) => {
110
+ const own = childOf(conjunct, 'dependentSchemas', name);
111
+ return own !== undefined && inclusion.includes(dependent, own);
112
+ });
113
+ }));
114
+ }
115
+ /**
116
+ * Whether every property of a `conjunct`-admitted object whose name is `relevant` satisfies
117
+ * `required`. Names matching a pattern in `settled` are never relevant.
118
+ */
119
+ function coversNames(inclusion, conjunct, required, relevant, settled = []) {
120
+ const schema = schemaOf(conjunct);
121
+ if (!isJsonObject(schema))
122
+ return false;
123
+ const rest = childOf(conjunct, 'additionalProperties');
124
+ if (rest === undefined || !inclusion.includes(required, rest))
125
+ return false;
126
+ const declared = isJsonObject(schema.properties) ? Object.keys(schema.properties) : [];
127
+ const patterns = isJsonObject(schema.patternProperties)
128
+ ? Object.keys(schema.patternProperties)
129
+ : [];
130
+ return (declared.every((name) => {
131
+ if (!relevant(name))
132
+ return true;
133
+ const member = childOf(conjunct, 'properties', name);
134
+ return member !== undefined && inclusion.includes(required, member);
135
+ }) &&
136
+ patterns.every((pattern) => {
137
+ if (settled.includes(pattern))
138
+ return true;
139
+ const member = childOf(conjunct, 'patternProperties', pattern);
140
+ return member !== undefined && inclusion.includes(required, member);
141
+ }));
142
+ }
143
+ /** Locations one conjunct applies to the value of property `name`. */
144
+ function memberConstraints(evidence, name) {
145
+ return evidence.conjuncts.flatMap((conjunct) => {
146
+ const schema = schemaOf(conjunct);
147
+ if (!isJsonObject(schema))
148
+ return [];
149
+ const constraints = [];
150
+ const declared = isJsonObject(schema.properties) && Object.hasOwn(schema.properties, name);
151
+ if (declared) {
152
+ const member = childOf(conjunct, 'properties', name);
153
+ if (member !== undefined)
154
+ constraints.push(member);
155
+ }
156
+ let matched = false;
157
+ if (isJsonObject(schema.patternProperties)) {
158
+ for (const pattern of Object.keys(schema.patternProperties)) {
159
+ if (!matches(conjunct, pattern, name))
160
+ continue;
161
+ matched = true;
162
+ const member = childOf(conjunct, 'patternProperties', pattern);
163
+ if (member !== undefined)
164
+ constraints.push(member);
165
+ }
166
+ }
167
+ if (!declared && !matched) {
168
+ const rest = childOf(conjunct, 'additionalProperties');
169
+ if (rest !== undefined)
170
+ constraints.push(rest);
171
+ }
172
+ return constraints;
173
+ });
174
+ }
175
+ /** Whether an admitted object never has property `name`. */
176
+ function forbids(inclusion, evidence, name) {
177
+ return memberConstraints(evidence, name).some((constraint) => inclusion.evidence(constraint).kinds === 0);
178
+ }
179
+ /** The complete property-name set of a closed object conjunct, when it is one. */
180
+ function closedNames(inclusion, conjunct) {
181
+ const schema = schemaOf(conjunct);
182
+ if (!isJsonObject(schema))
183
+ return undefined;
184
+ if (isJsonObject(schema.patternProperties) && Object.keys(schema.patternProperties).length > 0)
185
+ return undefined;
186
+ const rest = childOf(conjunct, 'additionalProperties');
187
+ if (rest === undefined || inclusion.evidence(rest).kinds !== 0)
188
+ return undefined;
189
+ return isJsonObject(schema.properties) ? Object.keys(schema.properties) : [];
190
+ }
@@ -0,0 +1,3 @@
1
+ import type { Rule } from './assertions.js';
2
+ /** Rules for the string length and pattern keywords. */
3
+ export declare const stringRules: ReadonlyMap<string, Rule>;
@@ -0,0 +1,29 @@
1
+ import { has } from './evidence.js';
2
+ import { STRING } from './kinds.js';
3
+ /** Rules for the string length and pattern keywords. */
4
+ export const stringRules = new Map([
5
+ [
6
+ 'maxLength',
7
+ ({ value, evidence }) => typeof value !== 'number' ||
8
+ !has(evidence, STRING) ||
9
+ (evidence.maxLength !== undefined && evidence.maxLength <= value),
10
+ ],
11
+ [
12
+ 'minLength',
13
+ ({ value, evidence }) => typeof value !== 'number' ||
14
+ !has(evidence, STRING) ||
15
+ value <= 0 ||
16
+ evidence.minLength >= value,
17
+ ],
18
+ [
19
+ 'pattern',
20
+ ({ value, evidence }) => typeof value !== 'string' ||
21
+ !has(evidence, STRING) ||
22
+ evidence.patterns.has(value) ||
23
+ evidence.fullMatches.has(value),
24
+ ],
25
+ [
26
+ 'fullMatch',
27
+ ({ value, evidence }) => typeof value !== 'string' || !has(evidence, STRING) || evidence.fullMatches.has(value),
28
+ ],
29
+ ]);
@@ -0,0 +1,3 @@
1
+ import type { Rule } from './assertions.js';
2
+ /** Rules for the keywords that restrict the kind or the exact value. */
3
+ export declare const typeRules: ReadonlyMap<string, Rule>;
@@ -0,0 +1,11 @@
1
+ import { typeMask } from './kinds.js';
2
+ /** Rules for the keywords that restrict the kind or the exact value. */
3
+ export const typeRules = new Map([
4
+ ['type', ({ value, evidence }) => (evidence.kinds & ~typeMask(value)) === 0],
5
+ ['const', () => false],
6
+ ['enum', () => false],
7
+ [
8
+ 'x-astrale-value-kind',
9
+ ({ value, evidence }) => typeof value === 'string' && evidence.valueKinds.has(value),
10
+ ],
11
+ ]);
@@ -2,7 +2,7 @@
2
2
  export type { JsonSchema as ValueSchema, JsonSchemaObject as ValueSchemaObject, PathAnnotation, PropertyObjectSchema, ValueSchemaContext, } from './model/schema.js';
3
3
  export { JSON_SCHEMA_DIALECT, VALUE_SCHEMA_DIALECT, VALUE_SCHEMA_VOCABULARY, } from './model/schema.js';
4
4
  export { instant, isInstantValue } from './model/instant.js';
5
- export { classifyOrdering, type OrderingKind } from './ordering.js';
5
+ export { classifyOrdering, classifyValueKind, type OrderingKind } from './ordering.js';
6
6
  export { canonicalizeValueSchema } from './profile/canonicalize.js';
7
7
  export { validateValueSchema } from './profile/validate.js';
8
8
  export { validateJsonInstance } from './evaluate/validate.js';
@@ -1,7 +1,7 @@
1
1
  /** Complete V1 inline value language. Named Type Definitions do not exist. */
2
2
  export { JSON_SCHEMA_DIALECT, VALUE_SCHEMA_DIALECT, VALUE_SCHEMA_VOCABULARY, } from './model/schema.js';
3
3
  export { instant, isInstantValue } from './model/instant.js';
4
- export { classifyOrdering } from './ordering.js';
4
+ export { classifyOrdering, classifyValueKind } from './ordering.js';
5
5
  export { canonicalizeValueSchema } from './profile/canonicalize.js';
6
6
  export { validateValueSchema } from './profile/validate.js';
7
7
  export { validateJsonInstance } from './evaluate/validate.js';
@@ -1,3 +1,4 @@
1
+ import type { ValueKind } from '@astrale-os/kernel-dsl/value';
1
2
  import type { ValueProgram } from './compile/model.js';
2
3
  import type { CompiledInstanceSchema } from './evaluate/types.js';
3
4
  import type { JsonSchema } from './model/schema.js';
@@ -13,3 +14,11 @@ export declare function classifyOrdering(contract: JsonSchema, registry: ValueSc
13
14
  /** Recompute the scalar ordering classification from one admitted portable value program. */
14
15
  export declare function classifyValueProgramOrdering(program: ValueProgram): OrderingKind | undefined;
15
16
  export declare function classifyPreparedOrdering(compiled: CompiledInstanceSchema): OrderingKind | undefined;
17
+ /**
18
+ * The value kind of a property schema (page "Fondations Query v7", § Atoms et value kinds, Y4):
19
+ * the one kind its non-null values admit, `null` being a point of every kind. An annotated
20
+ * instant is `instant`, a finite set of strings `enum`, an array of unique items `set`; an
21
+ * ambiguous union, an object, an array with repetitions or a domain without values is `json`.
22
+ */
23
+ export declare function classifyValueKind(contract: JsonSchema, registry: ValueSchemaRegistry): ValueKind;
24
+ export declare function classifyPreparedValueKind(compiled: CompiledInstanceSchema): ValueKind;
@@ -25,7 +25,8 @@ export function classifyValueProgramOrdering(program) {
25
25
  return classifyPreparedOrdering(loadValueProgram(program));
26
26
  }
27
27
  export function classifyPreparedOrdering(compiled) {
28
- const candidates = new OrderingClassifier(compiled).classify(compiled.root);
28
+ // An enum orders as the strings it lists; a set is an array.
29
+ const candidates = new Set([...new OrderingClassifier(compiled).classify(compiled.root)].map((kind) => kind === 'enum' ? 'string' : kind === 'set' ? 'array' : kind));
29
30
  if (candidates.size !== 1)
30
31
  return undefined;
31
32
  const [kind] = candidates;
@@ -33,6 +34,37 @@ export function classifyPreparedOrdering(compiled) {
33
34
  ? kind
34
35
  : undefined;
35
36
  }
37
+ /**
38
+ * The value kind of a property schema (page "Fondations Query v7", § Atoms et value kinds, Y4):
39
+ * the one kind its non-null values admit, `null` being a point of every kind. An annotated
40
+ * instant is `instant`, a finite set of strings `enum`, an array of unique items `set`; an
41
+ * ambiguous union, an object, an array with repetitions or a domain without values is `json`.
42
+ */
43
+ export function classifyValueKind(contract, registry) {
44
+ const prepared = prepareValueSchema(contract, registry, '');
45
+ if (!prepared.compiled)
46
+ return 'json';
47
+ return classifyPreparedValueKind(prepared.value);
48
+ }
49
+ export function classifyPreparedValueKind(compiled) {
50
+ const candidates = new Set(new OrderingClassifier(compiled).classify(compiled.root));
51
+ candidates.delete('null');
52
+ if (candidates.size !== 1)
53
+ return 'json';
54
+ const [kind] = candidates;
55
+ switch (kind) {
56
+ case 'string':
57
+ return 'text';
58
+ case 'number':
59
+ case 'boolean':
60
+ case 'instant':
61
+ case 'enum':
62
+ case 'set':
63
+ return kind;
64
+ default:
65
+ return 'json';
66
+ }
67
+ }
36
68
  class OrderingClassifier {
37
69
  compiled;
38
70
  active = new Set();
@@ -72,6 +104,9 @@ class OrderingClassifier {
72
104
  const declaredTypes = typesOf(schema.type);
73
105
  if (declaredTypes !== undefined)
74
106
  candidates = intersect(candidates, declaredTypes);
107
+ if (schema.uniqueItems === true) {
108
+ candidates = new Set([...candidates].map((kind) => (kind === 'array' ? 'set' : kind)));
109
+ }
75
110
  if (Object.hasOwn(schema, 'const')) {
76
111
  candidates = intersect(candidates, new Set([kindOfValue(schema.const)]));
77
112
  }
@@ -130,6 +165,7 @@ function typeSet(types) {
130
165
  }
131
166
  return result;
132
167
  }
168
+ /** The kind of one listed value: a listed string belongs to a finite set of strings. */
133
169
  function kindOfValue(value) {
134
170
  if (value === null)
135
171
  return 'null';
@@ -141,18 +177,35 @@ function kindOfValue(value) {
141
177
  return 'boolean';
142
178
  if (typeof value === 'number')
143
179
  return 'number';
144
- return 'string';
180
+ return 'enum';
145
181
  }
182
+ /** The kind both admit: equal kinds, or the narrower of a kind and one of its subsets. */
183
+ function meet(left, right) {
184
+ if (left === right)
185
+ return left;
186
+ for (const [narrow, wide] of NARROWER) {
187
+ if ((left === narrow && right === wide) || (left === wide && right === narrow))
188
+ return narrow;
189
+ }
190
+ return undefined;
191
+ }
192
+ /**
193
+ * The narrower kind of two that meet: a subset and its kind, and an enum of instants, which
194
+ * orders as instants.
195
+ */
196
+ const NARROWER = [
197
+ ['instant', 'string'],
198
+ ['enum', 'string'],
199
+ ['instant', 'enum'],
200
+ ['set', 'array'],
201
+ ];
146
202
  function intersect(left, right) {
147
203
  const result = new Set();
148
204
  for (const leftKind of left) {
149
205
  for (const rightKind of right) {
150
- if (leftKind === rightKind)
151
- result.add(leftKind);
152
- else if ((leftKind === 'string' && rightKind === 'instant') ||
153
- (leftKind === 'instant' && rightKind === 'string')) {
154
- result.add('instant');
155
- }
206
+ const kind = meet(leftKind, rightKind);
207
+ if (kind !== undefined)
208
+ result.add(kind);
156
209
  }
157
210
  }
158
211
  return normalize(result);
@@ -160,9 +213,19 @@ function intersect(left, right) {
160
213
  function union(left, right) {
161
214
  return normalize(new Set([...left, ...right]));
162
215
  }
216
+ /** A kind absorbs its subsets; strings of two subsets are strings. */
163
217
  function normalize(input) {
164
- if (input.has('string'))
218
+ if (input.has('enum') && input.has('instant')) {
219
+ input.delete('enum');
220
+ input.delete('instant');
221
+ input.add('string');
222
+ }
223
+ if (input.has('string')) {
165
224
  input.delete('instant');
225
+ input.delete('enum');
226
+ }
227
+ if (input.has('array'))
228
+ input.delete('set');
166
229
  return input;
167
230
  }
168
231
  function schemaList(input) {