@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,62 +1,156 @@
|
|
|
1
|
-
import { array, boolean, custom, discriminatedUnion, enum as enumeration, lazy, literal, null as nullSchema, number, record, strictObject, string, tuple, union, } from 'zod';
|
|
1
|
+
import { array, boolean, custom, discriminatedUnion, enum as enumeration, lazy, literal, null as nullSchema, number, record, registry, strictObject, string, tuple, union, } from 'zod';
|
|
2
2
|
import { PropertyKey as SemanticPropertyKey } from '../../addressing/index.js';
|
|
3
|
-
import { CORE_SLUG_PATTERN, DEPENDENCY_ALIAS_PATTERN, DOMAIN_ORIGIN_PATTERN, DOMAIN_REVISION_PATTERN, MEMBER_NAME_PATTERN, } from '../../addressing/patterns.js';
|
|
3
|
+
import { CORE_SLUG_PATTERN, DEPENDENCY_ALIAS_PATTERN, DOMAIN_ORIGIN_PATTERN, DOMAIN_REVISION_PATTERN, MEMBER_NAME_PATTERN, PROPERTY_KEY_PATTERN, } from '../../addressing/patterns.js';
|
|
4
4
|
import { MEDIA_TYPE_PATTERN } from '../../language/class/model/data.js';
|
|
5
5
|
import * as metadata from '../../language/definition/metadata.js';
|
|
6
|
+
import { MAX_REPEAT, MAX_VARIABLES } from '../../language/policy/analysis/budgets.js';
|
|
7
|
+
import { BLOB_VALUE_KIND, isBlobValueSchema } from '../../language/value/model/blob.js';
|
|
6
8
|
import { escapePointerToken } from '../../language/value/syntax/pointer.js';
|
|
7
|
-
import { SchemaError, diagnostic } from '../diagnostic.js';
|
|
9
|
+
import { SchemaError, diagnostic, } from '../diagnostic.js';
|
|
8
10
|
import { DOMAIN_FORMAT, DOMAIN_VERSION } from '../model/format.js';
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
+
export const documentJsonSchema = registry();
|
|
12
|
+
/**
|
|
13
|
+
* The checks of the structure JSON Schema cannot state. The published document names each with the
|
|
14
|
+
* positions it constrains, so that a validator of the document knows what it leaves to the Kernel.
|
|
15
|
+
*/
|
|
16
|
+
export const structureRules = {
|
|
17
|
+
'unique-tuple-set': {
|
|
18
|
+
code: 'STRUCTURE_DUPLICATE_ITEM',
|
|
19
|
+
statement: 'A unique tuple is a set of fields: two tuples naming the same fields in another order are one tuple, repeated.',
|
|
20
|
+
},
|
|
21
|
+
'length-in-utf16-code-units': {
|
|
22
|
+
code: 'STRUCTURE_MAXIMUM',
|
|
23
|
+
statement: 'A maxLength counts UTF-16 code units, where JSON Schema counts code points: a string within the bound in code points and beyond it in code units is refused.',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
// The pattern refuses `__proto__`, which zod skips before any key check (`lostProtoKeys`).
|
|
27
|
+
const RESERVED_DEPENDENCY_ALIASES = ['prototype', 'constructor'];
|
|
28
|
+
const originSchema = string()
|
|
29
|
+
.min(3)
|
|
30
|
+
.max(253)
|
|
31
|
+
.regex(DOMAIN_ORIGIN_PATTERN)
|
|
32
|
+
.register(documentJsonSchema, { id: 'origin' });
|
|
33
|
+
const memberNameSchema = string()
|
|
34
|
+
.regex(MEMBER_NAME_PATTERN)
|
|
35
|
+
.register(documentJsonSchema, { id: 'name' });
|
|
11
36
|
const dependencyAliasSchema = string()
|
|
12
37
|
.regex(DEPENDENCY_ALIAS_PATTERN)
|
|
13
|
-
.refine((value) => value
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
38
|
+
.refine((value) => !RESERVED_DEPENDENCY_ALIASES.includes(value), {
|
|
39
|
+
message: 'Expected a non-reserved dependency alias.',
|
|
40
|
+
params: { structureCode: 'STRUCTURE_DEPENDENCY_ALIAS' },
|
|
41
|
+
})
|
|
42
|
+
.register(documentJsonSchema, {
|
|
43
|
+
id: 'dependency-alias',
|
|
44
|
+
not: { enum: [...RESERVED_DEPENDENCY_ALIASES] },
|
|
45
|
+
});
|
|
46
|
+
const propertyKeySchema = string()
|
|
47
|
+
.refine(SemanticPropertyKey.is, {
|
|
48
|
+
message: 'Expected a canonical PropertyKey.',
|
|
49
|
+
params: { structureCode: 'STRUCTURE_PROPERTY_KEY' },
|
|
50
|
+
})
|
|
51
|
+
.register(documentJsonSchema, { id: 'property-key', pattern: PROPERTY_KEY_PATTERN.source });
|
|
52
|
+
const coreSlugSchema = string()
|
|
53
|
+
.min(1)
|
|
54
|
+
.max(100)
|
|
55
|
+
.regex(CORE_SLUG_PATTERN)
|
|
56
|
+
.register(documentJsonSchema, { id: 'core-slug' });
|
|
57
|
+
const revisionSchema = string()
|
|
58
|
+
.regex(DOMAIN_REVISION_PATTERN)
|
|
59
|
+
.register(documentJsonSchema, { id: 'revision' });
|
|
17
60
|
const descriptionSchema = string()
|
|
18
61
|
.min(metadata.description.minLength)
|
|
19
|
-
.max(metadata.description.maxLength)
|
|
20
|
-
|
|
62
|
+
.max(metadata.description.maxLength)
|
|
63
|
+
.register(documentJsonSchema, { id: 'description' });
|
|
64
|
+
const iconSchema = string()
|
|
65
|
+
.min(metadata.icon.minLength)
|
|
66
|
+
.max(metadata.icon.maxLength)
|
|
67
|
+
.register(documentJsonSchema, { id: 'icon' });
|
|
21
68
|
const domainNameSchema = string()
|
|
22
69
|
.min(metadata.name.minLength)
|
|
23
70
|
.max(metadata.name.maxLength)
|
|
24
|
-
.refine(metadata.isDomainNameText,
|
|
71
|
+
.refine(metadata.isDomainNameText, {
|
|
72
|
+
message: 'Expected one trimmed line of text.',
|
|
73
|
+
params: { structureCode: 'STRUCTURE_DOMAIN_NAME' },
|
|
74
|
+
})
|
|
75
|
+
.register(documentJsonSchema, {
|
|
76
|
+
id: 'domain-name',
|
|
77
|
+
description: 'The display name of the Domain: one trimmed line of text.',
|
|
78
|
+
pattern: metadata.DOMAIN_NAME_TEXT_PATTERN.source,
|
|
79
|
+
});
|
|
80
|
+
// Zod skips a `__proto__` member, which the structure therefore refuses (`lostProtoKeys`). The JSON
|
|
81
|
+
// Schema refuses it by the fields of a closed object and the pattern of a record's names; a record
|
|
82
|
+
// of any string states it here, by its property names.
|
|
83
|
+
const PROTO_KEY = '__proto__';
|
|
84
|
+
const anyKeyRecord = { propertyNames: { not: { const: PROTO_KEY } } };
|
|
25
85
|
const jsonValueSchema = lazy(() => union([
|
|
26
86
|
nullSchema(),
|
|
27
87
|
boolean(),
|
|
28
88
|
number(),
|
|
29
89
|
string(),
|
|
30
90
|
array(jsonValueSchema),
|
|
31
|
-
record(string(), jsonValueSchema),
|
|
32
|
-
]));
|
|
33
|
-
const jsonObjectSchema = record(string(), jsonValueSchema);
|
|
34
|
-
const jsonSchemaSchema = union([boolean(), jsonObjectSchema])
|
|
91
|
+
record(string(), jsonValueSchema).register(documentJsonSchema, anyKeyRecord),
|
|
92
|
+
])).register(documentJsonSchema, { id: 'json-value' });
|
|
93
|
+
const jsonObjectSchema = record(string(), jsonValueSchema).register(documentJsonSchema, anyKeyRecord);
|
|
94
|
+
const jsonSchemaSchema = union([boolean(), jsonObjectSchema]).register(documentJsonSchema, {
|
|
95
|
+
id: 'value-schema',
|
|
96
|
+
description: 'A value schema of the dialect https://schemas.astrale.ai/dsl/v1/value-schema. The structure admits any boolean or object; the language admits its keywords.',
|
|
97
|
+
});
|
|
35
98
|
const dataDeclarationSchema = strictObject({
|
|
36
|
-
mediaType: string().regex(MEDIA_TYPE_PATTERN),
|
|
99
|
+
mediaType: string().regex(MEDIA_TYPE_PATTERN).register(documentJsonSchema, { id: 'media-type' }),
|
|
37
100
|
});
|
|
38
101
|
const stateNameSchema = string().min(1);
|
|
39
|
-
|
|
102
|
+
// A custom check reads the relation in place: a record would copy it and drop a `__proto__` state.
|
|
103
|
+
const stateTransitionsSchema = custom(isStateTransitions, {
|
|
104
|
+
message: 'Expected each non-empty state to map non-empty events to non-empty states.',
|
|
105
|
+
params: { structureCode: 'STRUCTURE_STATE_TRANSITIONS' },
|
|
106
|
+
}).register(documentJsonSchema, {
|
|
107
|
+
type: 'object',
|
|
108
|
+
propertyNames: { type: 'string', minLength: 1 },
|
|
109
|
+
additionalProperties: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
propertyNames: { type: 'string', minLength: 1 },
|
|
112
|
+
additionalProperties: { type: 'string', minLength: 1 },
|
|
113
|
+
},
|
|
114
|
+
});
|
|
40
115
|
const stateTopologySchema = strictObject({
|
|
41
116
|
initial: stateNameSchema,
|
|
42
117
|
transitions: stateTransitionsSchema,
|
|
43
|
-
});
|
|
44
|
-
const
|
|
118
|
+
}).register(documentJsonSchema, { id: 'state-topology' });
|
|
119
|
+
const propertyShape = {
|
|
45
120
|
schema: jsonSchemaSchema,
|
|
46
121
|
required: boolean(),
|
|
47
122
|
auto: strictObject({
|
|
48
|
-
on: uniqueArray(enumeration(['create', 'update']))
|
|
123
|
+
on: nonEmpty(uniqueArray(enumeration(['create', 'update']))),
|
|
49
124
|
set: literal('instant'),
|
|
50
|
-
})
|
|
125
|
+
})
|
|
126
|
+
.register(documentJsonSchema, { id: 'auto' })
|
|
127
|
+
.optional(),
|
|
51
128
|
immutable: enumeration(['create', 'once']).optional(),
|
|
52
129
|
description: descriptionSchema.optional(),
|
|
53
|
-
|
|
130
|
+
};
|
|
131
|
+
const nodePropertyRecordSchema = record(memberNameSchema, strictObject({ ...propertyShape, state: stateTopologySchema.optional() }).register(documentJsonSchema, { id: 'node-property-definition' })).register(documentJsonSchema, { id: 'node-property-record' });
|
|
132
|
+
// An Edge has no State Property, and only a Node holds Blob contents.
|
|
133
|
+
const BLOB_KIND_KEYWORD = 'x-astrale-value-kind';
|
|
134
|
+
const edgePropertySchema = jsonSchemaSchema
|
|
135
|
+
.refine((value) => !isBlobValueSchema(value), {
|
|
136
|
+
message: 'Blob values are held only by Node Properties.',
|
|
137
|
+
path: [BLOB_KIND_KEYWORD],
|
|
138
|
+
params: { structureCode: 'STRUCTURE_EDGE_BLOB' },
|
|
139
|
+
})
|
|
140
|
+
.register(documentJsonSchema, {
|
|
141
|
+
not: {
|
|
142
|
+
type: 'object',
|
|
143
|
+
required: [BLOB_KIND_KEYWORD],
|
|
144
|
+
properties: { [BLOB_KIND_KEYWORD]: { const: BLOB_VALUE_KIND } },
|
|
145
|
+
},
|
|
54
146
|
});
|
|
55
|
-
const
|
|
147
|
+
const edgePropertyRecordSchema = record(memberNameSchema, strictObject({ ...propertyShape, schema: edgePropertySchema }).register(documentJsonSchema, {
|
|
148
|
+
id: 'edge-property-definition',
|
|
149
|
+
})).register(documentJsonSchema, { id: 'edge-property-record' });
|
|
56
150
|
const domainDependencySchema = strictObject({
|
|
57
151
|
origin: originSchema,
|
|
58
152
|
revision: revisionSchema,
|
|
59
|
-
});
|
|
153
|
+
}).register(documentJsonSchema, { id: 'dependency' });
|
|
60
154
|
const classRefSchema = absoluteRefSchema('class');
|
|
61
155
|
const functionRefSchema = absoluteRefSchema('function');
|
|
62
156
|
const policyRefSchema = absoluteRefSchema('policy');
|
|
@@ -65,41 +159,54 @@ const coreRefSchema = strictObject({
|
|
|
65
159
|
origin: originSchema,
|
|
66
160
|
kind: literal('core'),
|
|
67
161
|
name: coreSlugSchema,
|
|
162
|
+
}).register(documentJsonSchema, { id: 'core-ref' });
|
|
163
|
+
const domainRefSchema = strictObject({ kind: literal('domain') }).register(documentJsonSchema, {
|
|
164
|
+
id: 'domain-endpoint',
|
|
68
165
|
});
|
|
69
|
-
const domainRefSchema = strictObject({ kind: literal('domain') });
|
|
70
166
|
/** Every Definition whose installed projection resolves through one exact member Path. */
|
|
71
167
|
const projectedMemberRefOptions = [
|
|
72
|
-
strictObject({
|
|
168
|
+
strictObject({
|
|
169
|
+
kind: literal('method'),
|
|
170
|
+
owner: classRefSchema,
|
|
171
|
+
name: memberNameSchema,
|
|
172
|
+
}).register(documentJsonSchema, { id: 'method-address' }),
|
|
73
173
|
classRefSchema,
|
|
74
174
|
functionRefSchema,
|
|
75
175
|
policyRefSchema,
|
|
76
176
|
viewRefSchema,
|
|
77
177
|
coreRefSchema,
|
|
78
178
|
];
|
|
79
|
-
const policyObjectRefSchema = discriminatedUnion('kind', projectedMemberRefOptions);
|
|
179
|
+
const policyObjectRefSchema = discriminatedUnion('kind', projectedMemberRefOptions).register(documentJsonSchema, { id: 'projected-member-ref' });
|
|
80
180
|
const coreEndpointRefSchema = discriminatedUnion('kind', [
|
|
81
181
|
...projectedMemberRefOptions,
|
|
82
182
|
domainRefSchema,
|
|
83
|
-
]);
|
|
183
|
+
]).register(documentJsonSchema, { id: 'core-endpoint' });
|
|
84
184
|
const edgeEndpointBaseShape = {
|
|
85
185
|
role: memberNameSchema,
|
|
86
|
-
accepts: uniqueArray(classRefSchema)
|
|
87
|
-
onDelete: enumeration(['restrict', 'detach'])
|
|
186
|
+
accepts: nonEmpty(uniqueArray(classRefSchema)),
|
|
187
|
+
onDelete: enumeration(['restrict', 'detach'])
|
|
188
|
+
.register(documentJsonSchema, { id: 'edge-lifetime' })
|
|
189
|
+
.optional(),
|
|
88
190
|
};
|
|
89
|
-
const edgeCountSchema = enumeration(['0..*', '1..*', '0..1', '1'])
|
|
90
|
-
|
|
91
|
-
source: strictObject({ ...edgeEndpointBaseShape, outgoing: edgeCountSchema }),
|
|
92
|
-
target: strictObject({ ...edgeEndpointBaseShape, incoming: edgeCountSchema }),
|
|
191
|
+
const edgeCountSchema = enumeration(['0..*', '1..*', '0..1', '1']).register(documentJsonSchema, {
|
|
192
|
+
id: 'edge-count',
|
|
93
193
|
});
|
|
194
|
+
const directedEdgeEndpointsSchema = strictObject({
|
|
195
|
+
source: strictObject({ ...edgeEndpointBaseShape, outgoing: edgeCountSchema }).register(documentJsonSchema, { id: 'directed-source-endpoint' }),
|
|
196
|
+
target: strictObject({ ...edgeEndpointBaseShape, incoming: edgeCountSchema }).register(documentJsonSchema, { id: 'directed-target-endpoint' }),
|
|
197
|
+
}).register(documentJsonSchema, { id: 'directed-endpoints' });
|
|
94
198
|
const incidentEndpointSchema = strictObject({
|
|
95
199
|
...edgeEndpointBaseShape,
|
|
96
200
|
incident: edgeCountSchema,
|
|
97
|
-
});
|
|
98
|
-
const undirectedEdgeEndpointsSchema = tuple([
|
|
201
|
+
}).register(documentJsonSchema, { id: 'incident-endpoint' });
|
|
202
|
+
const undirectedEdgeEndpointsSchema = tuple([
|
|
203
|
+
incidentEndpointSchema,
|
|
204
|
+
incidentEndpointSchema,
|
|
205
|
+
]).register(documentJsonSchema, { id: 'undirected-endpoints' });
|
|
99
206
|
const edgeConstraintsSchema = strictObject({
|
|
100
207
|
noSelf: literal(true).optional(),
|
|
101
208
|
acyclic: literal(true).optional(),
|
|
102
|
-
});
|
|
209
|
+
}).register(documentJsonSchema, { id: 'edge-constraints' });
|
|
103
210
|
// A tuple names distinct fields: endpoints, the slug and Properties by key. Normalization puts
|
|
104
211
|
// them, and the tuples, in canonical order (`compareEdgeUniqueFields`). No tuple is spelled by the
|
|
105
212
|
// absence of the field, never by an empty list: one spelling per meaning, one revision.
|
|
@@ -108,13 +215,11 @@ const edgeUniqueFieldSchema = union([
|
|
|
108
215
|
literal('target'),
|
|
109
216
|
literal('slug'),
|
|
110
217
|
strictObject({ property: propertyKeySchema }),
|
|
111
|
-
]);
|
|
112
|
-
const edgeUniqueSchema =
|
|
113
|
-
// Two spellings of one set of fields are one tuple, repeated.
|
|
114
|
-
JSON.stringify(tuple.map(canonicalJson).sort())).min(1);
|
|
218
|
+
]).register(documentJsonSchema, { id: 'edge-unique-field' });
|
|
219
|
+
const edgeUniqueSchema = nonEmpty(uniqueTupleSet(nonEmpty(uniqueArray(edgeUniqueFieldSchema))));
|
|
115
220
|
// A Node tuple names distinct Properties by key, which normalization puts in code-point order, as
|
|
116
221
|
// it does the tuples (`compareNodeUniqueTuples`). Two orders of one set of keys are one tuple.
|
|
117
|
-
const nodeUniqueSchema =
|
|
222
|
+
const nodeUniqueSchema = nonEmpty(uniqueTupleSet(nonEmpty(uniqueArray(propertyKeySchema))));
|
|
118
223
|
const policyPatternSubjectSchema = strictObject({ kind: literal('subject') });
|
|
119
224
|
const policyPatternObjectSchema = strictObject({ kind: literal('object') });
|
|
120
225
|
const policyPatternSourceSchema = strictObject({ kind: literal('source') });
|
|
@@ -122,7 +227,7 @@ const policyPatternTargetSchema = strictObject({ kind: literal('target') });
|
|
|
122
227
|
const policyPatternVariableSchema = strictObject({
|
|
123
228
|
kind: literal('variable'),
|
|
124
229
|
id: number().int().min(0),
|
|
125
|
-
});
|
|
230
|
+
}).register(documentJsonSchema, { id: 'policy-pattern-variable' });
|
|
126
231
|
const policyPatternNodeSchema = discriminatedUnion('kind', [
|
|
127
232
|
policyPatternSubjectSchema,
|
|
128
233
|
policyPatternObjectSchema,
|
|
@@ -130,19 +235,19 @@ const policyPatternNodeSchema = discriminatedUnion('kind', [
|
|
|
130
235
|
policyPatternTargetSchema,
|
|
131
236
|
policyPatternVariableSchema,
|
|
132
237
|
strictObject({ kind: literal('ref'), ref: policyObjectRefSchema }),
|
|
133
|
-
]);
|
|
238
|
+
]).register(documentJsonSchema, { id: 'policy-pattern-node' });
|
|
134
239
|
const policyVariableSelectorSchema = discriminatedUnion('kind', [
|
|
135
240
|
strictObject({ kind: literal('any') }),
|
|
136
241
|
strictObject({ kind: literal('satisfies'), class: classRefSchema }),
|
|
137
|
-
]);
|
|
242
|
+
]).register(documentJsonSchema, { id: 'policy-variable-selector' });
|
|
138
243
|
const policyVariableSchema = union([
|
|
139
244
|
strictObject({ variable: policyPatternVariableSchema, class: classRefSchema }),
|
|
140
245
|
strictObject({ variable: policyPatternVariableSchema, selector: policyVariableSelectorSchema }),
|
|
141
|
-
]);
|
|
246
|
+
]).register(documentJsonSchema, { id: 'policy-variable' });
|
|
142
247
|
const policyRepeatSchema = strictObject({
|
|
143
|
-
min: number().int().min(0).max(
|
|
144
|
-
max: number().int().min(0).max(
|
|
145
|
-
});
|
|
248
|
+
min: number().int().min(0).max(MAX_REPEAT),
|
|
249
|
+
max: number().int().min(0).max(MAX_REPEAT),
|
|
250
|
+
}).register(documentJsonSchema, { id: 'policy-repeat' });
|
|
146
251
|
const policyPatternExpressionSchema = lazy(() => union([
|
|
147
252
|
strictObject({
|
|
148
253
|
sameNode: strictObject({ left: policyPatternNodeSchema, right: policyPatternNodeSchema }),
|
|
@@ -155,17 +260,13 @@ const policyPatternExpressionSchema = lazy(() => union([
|
|
|
155
260
|
}),
|
|
156
261
|
strictObject({
|
|
157
262
|
exists: strictObject({
|
|
158
|
-
nodes: array(policyVariableSchema).
|
|
263
|
+
nodes: nonEmpty(array(policyVariableSchema).max(MAX_VARIABLES)),
|
|
159
264
|
where: policyPatternExpressionSchema,
|
|
160
265
|
}),
|
|
161
266
|
}),
|
|
162
|
-
strictObject({
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
strictObject({
|
|
166
|
-
anyOf: array(policyPatternExpressionSchema).min(1),
|
|
167
|
-
}),
|
|
168
|
-
]));
|
|
267
|
+
strictObject({ allOf: nonEmpty(array(policyPatternExpressionSchema)) }),
|
|
268
|
+
strictObject({ anyOf: nonEmpty(array(policyPatternExpressionSchema)) }),
|
|
269
|
+
])).register(documentJsonSchema, { id: 'policy-pattern-expr' });
|
|
169
270
|
const selfObjectSchema = strictObject({ kind: literal('self') });
|
|
170
271
|
const refObjectSchema = strictObject({ kind: literal('ref'), ref: policyObjectRefSchema });
|
|
171
272
|
const policyCheckObjectSchema = discriminatedUnion('kind', [
|
|
@@ -175,21 +276,24 @@ const policyCheckObjectSchema = discriminatedUnion('kind', [
|
|
|
175
276
|
field: memberNameSchema,
|
|
176
277
|
}),
|
|
177
278
|
refObjectSchema,
|
|
178
|
-
]);
|
|
279
|
+
]).register(documentJsonSchema, { id: 'policy-check-object' });
|
|
179
280
|
/** A Node Class rule checks the observed Node or one exact member: it has no input. */
|
|
180
|
-
const classRuleObjectSchema = discriminatedUnion('kind', [
|
|
281
|
+
const classRuleObjectSchema = discriminatedUnion('kind', [
|
|
282
|
+
selfObjectSchema,
|
|
283
|
+
refObjectSchema,
|
|
284
|
+
]).register(documentJsonSchema, { id: 'class-rule-object' });
|
|
181
285
|
/** The recursive Policy check grammar, over the objects one use site admits. */
|
|
182
|
-
function policyChecksSchema(objectSchema) {
|
|
286
|
+
function policyChecksSchema(objectSchema, id) {
|
|
183
287
|
const checks = lazy(() => union([
|
|
184
288
|
strictObject({ sameNode: strictObject({ left: objectSchema, right: objectSchema }) }),
|
|
185
289
|
strictObject({ check: policyRefSchema, object: objectSchema }),
|
|
186
|
-
strictObject({ allOf: array(checks)
|
|
187
|
-
strictObject({ anyOf: array(checks)
|
|
188
|
-
]));
|
|
290
|
+
strictObject({ allOf: nonEmpty(array(checks)) }),
|
|
291
|
+
strictObject({ anyOf: nonEmpty(array(checks)) }),
|
|
292
|
+
])).register(documentJsonSchema, { id });
|
|
189
293
|
return checks;
|
|
190
294
|
}
|
|
191
|
-
const policyCheckExpressionSchema = policyChecksSchema(policyCheckObjectSchema);
|
|
192
|
-
const classRuleChecksSchema = policyChecksSchema(classRuleObjectSchema);
|
|
295
|
+
const policyCheckExpressionSchema = policyChecksSchema(policyCheckObjectSchema, 'policy-check-expr');
|
|
296
|
+
const classRuleChecksSchema = policyChecksSchema(classRuleObjectSchema, 'class-rule-check-expr');
|
|
193
297
|
const callableOutputSchema = discriminatedUnion('mode', [
|
|
194
298
|
strictObject({
|
|
195
299
|
mode: literal('value'),
|
|
@@ -202,20 +306,27 @@ const callableOutputSchema = discriminatedUnion('mode', [
|
|
|
202
306
|
strictObject({
|
|
203
307
|
mode: literal('binary'),
|
|
204
308
|
}),
|
|
205
|
-
]);
|
|
309
|
+
]).register(documentJsonSchema, { id: 'callable-output' });
|
|
206
310
|
const binaryInputSchema = strictObject({
|
|
207
311
|
mode: literal('binary'),
|
|
208
312
|
metadata: jsonSchemaSchema,
|
|
209
313
|
maximumBytes: number().int().min(1).max(Number.MAX_SAFE_INTEGER),
|
|
314
|
+
}).register(documentJsonSchema, { id: 'binary-input' });
|
|
315
|
+
const valueFunctionInputSchema = jsonSchemaSchema
|
|
316
|
+
.refine((value) => typeof value !== 'object' || value === null || value.mode !== 'binary', {
|
|
317
|
+
message: 'Binary input must declare metadata and maximumBytes.',
|
|
318
|
+
params: { structureCode: 'STRUCTURE_BINARY_INPUT' },
|
|
319
|
+
})
|
|
320
|
+
.register(documentJsonSchema, {
|
|
321
|
+
not: { type: 'object', required: ['mode'], properties: { mode: { const: 'binary' } } },
|
|
210
322
|
});
|
|
211
|
-
const valueFunctionInputSchema = jsonSchemaSchema.refine((value) => typeof value !== 'object' || value === null || value.mode !== 'binary', 'Binary input must declare metadata and maximumBytes.');
|
|
212
323
|
const callableExecutionSchema = strictObject({
|
|
213
324
|
durable: boolean(),
|
|
214
|
-
});
|
|
325
|
+
}).register(documentJsonSchema, { id: 'callable-execution' });
|
|
215
326
|
// Its range is language meaning, refused by callable validation with DV_CALLABLE_ADMISSION.
|
|
216
327
|
const callableAdmissionSchema = strictObject({
|
|
217
328
|
maxAge: number(),
|
|
218
|
-
});
|
|
329
|
+
}).register(documentJsonSchema, { id: 'callable-admission' });
|
|
219
330
|
const callableShape = {
|
|
220
331
|
description: descriptionSchema.optional(),
|
|
221
332
|
input: jsonSchemaSchema,
|
|
@@ -223,25 +334,29 @@ const callableShape = {
|
|
|
223
334
|
execution: callableExecutionSchema.optional(),
|
|
224
335
|
admission: callableAdmissionSchema.optional(),
|
|
225
336
|
auth: enumeration(['anonymous', 'authenticated', 'authorized']),
|
|
226
|
-
policy: union([literal(true), policyCheckExpressionSchema])
|
|
337
|
+
policy: union([literal(true), policyCheckExpressionSchema])
|
|
338
|
+
.register(documentJsonSchema, { id: 'callable-policy' })
|
|
339
|
+
.optional(),
|
|
340
|
+
};
|
|
341
|
+
const authorizedPolicy = {
|
|
342
|
+
if: { properties: { auth: { enum: ['anonymous', 'authenticated'] } }, required: ['auth'] },
|
|
343
|
+
// oxlint-disable-next-line unicorn/no-thenable -- the JSON Schema keyword
|
|
344
|
+
then: { properties: { policy: false } },
|
|
227
345
|
};
|
|
228
346
|
const methodDeclarationSchema = strictObject({
|
|
229
347
|
...callableShape,
|
|
230
348
|
static: boolean(),
|
|
231
349
|
abstract: boolean(),
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
350
|
+
})
|
|
351
|
+
.refine((method) => method.auth === 'authorized' || method.policy === undefined, callablePolicyIssue('method'))
|
|
352
|
+
.register(documentJsonSchema, { id: 'method-definition', ...authorizedPolicy });
|
|
353
|
+
const methodRecordSchema = record(memberNameSchema, methodDeclarationSchema).register(documentJsonSchema, {
|
|
354
|
+
id: 'method-record',
|
|
355
|
+
});
|
|
356
|
+
const classPolicySchema = union([literal(false), policyRefSchema]).register(documentJsonSchema, {
|
|
357
|
+
id: 'class-policy',
|
|
241
358
|
});
|
|
242
|
-
const
|
|
243
|
-
const classPolicySchema = union([literal(false), policyRefSchema]);
|
|
244
|
-
const nodeClassPolicySchema = union([classPolicySchema, classRuleChecksSchema]);
|
|
359
|
+
const nodeClassPolicySchema = union([classPolicySchema, classRuleChecksSchema]).register(documentJsonSchema, { id: 'node-class-policy' });
|
|
245
360
|
const nodeClassPoliciesSchema = strictObject({
|
|
246
361
|
read: nodeClassPolicySchema.optional(),
|
|
247
362
|
traverse: nodeClassPolicySchema.optional(),
|
|
@@ -259,101 +374,108 @@ const nodeClassSchema = strictObject({
|
|
|
259
374
|
inheritance: inheritanceSchema.optional(),
|
|
260
375
|
description: descriptionSchema.optional(),
|
|
261
376
|
extends: uniqueArray(classRefSchema),
|
|
262
|
-
properties:
|
|
377
|
+
properties: nodePropertyRecordSchema,
|
|
263
378
|
methods: methodRecordSchema,
|
|
264
379
|
data: dataDeclarationSchema.optional(),
|
|
265
380
|
icon: iconSchema.optional(),
|
|
266
381
|
policies: nodeClassPoliciesSchema,
|
|
267
382
|
unique: nodeUniqueSchema.optional(),
|
|
268
|
-
});
|
|
269
|
-
const
|
|
383
|
+
}).register(documentJsonSchema, { id: 'node-class' });
|
|
384
|
+
const orientationIssue = { params: { structureCode: 'STRUCTURE_EDGE_ORIENTATION' } };
|
|
385
|
+
const edgeClassSchema = strictObject({
|
|
270
386
|
ref: classRefSchema,
|
|
271
387
|
kind: literal('edge'),
|
|
272
388
|
abstract: boolean(),
|
|
273
389
|
inheritance: inheritanceSchema.optional(),
|
|
274
390
|
description: descriptionSchema.optional(),
|
|
275
391
|
extends: uniqueArray(classRefSchema),
|
|
276
|
-
properties:
|
|
392
|
+
properties: edgePropertyRecordSchema,
|
|
277
393
|
constraints: edgeConstraintsSchema,
|
|
278
394
|
unique: edgeUniqueSchema.optional(),
|
|
279
|
-
};
|
|
280
|
-
const edgeClassSchema = strictObject({
|
|
281
|
-
...edgeClassShape,
|
|
282
395
|
orientation: enumeration(['directed', 'undirected']),
|
|
283
396
|
endpoints: union([directedEdgeEndpointsSchema, undirectedEdgeEndpointsSchema]),
|
|
284
397
|
policies: edgeClassPoliciesSchema,
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
|
|
398
|
+
})
|
|
399
|
+
.refine((edge) => (edge.orientation === 'undirected') === Array.isArray(edge.endpoints), {
|
|
400
|
+
...orientationIssue,
|
|
401
|
+
path: ['endpoints'],
|
|
402
|
+
error: 'Edge endpoints do not have the structural shape of its orientation.',
|
|
403
|
+
})
|
|
404
|
+
.refine((edge) => edge.orientation === 'directed' || edge.unique === undefined, {
|
|
405
|
+
...orientationIssue,
|
|
406
|
+
path: ['unique'],
|
|
407
|
+
error: 'Only a directed Edge Class declares unique tuples.',
|
|
408
|
+
})
|
|
409
|
+
.refine((edge) => edge.orientation === 'directed' || edge.constraints.acyclic === undefined, {
|
|
410
|
+
...orientationIssue,
|
|
411
|
+
path: ['constraints', 'acyclic'],
|
|
412
|
+
error: 'Only a directed Edge Class is acyclic.',
|
|
413
|
+
})
|
|
414
|
+
.register(documentJsonSchema, {
|
|
415
|
+
id: 'edge-class',
|
|
416
|
+
if: { properties: { orientation: { const: 'undirected' } }, required: ['orientation'] },
|
|
417
|
+
// oxlint-disable-next-line unicorn/no-thenable -- the JSON Schema keyword
|
|
418
|
+
then: {
|
|
419
|
+
properties: {
|
|
420
|
+
endpoints: { type: 'array' },
|
|
421
|
+
unique: false,
|
|
422
|
+
constraints: { type: 'object', properties: { acyclic: false } },
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
else: { properties: { endpoints: { type: 'object' } } },
|
|
426
|
+
});
|
|
427
|
+
const classDeclarationSchema = discriminatedUnion('kind', [
|
|
428
|
+
nodeClassSchema,
|
|
429
|
+
edgeClassSchema,
|
|
430
|
+
]).register(documentJsonSchema, { id: 'class-definition' });
|
|
305
431
|
const functionDefinitionSchema = strictObject({
|
|
306
432
|
ref: functionRefSchema,
|
|
307
433
|
...callableShape,
|
|
308
434
|
input: union([binaryInputSchema, valueFunctionInputSchema]),
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
code: 'custom',
|
|
313
|
-
path: ['policy'],
|
|
314
|
-
message: 'Only an authorized function may declare a policy.',
|
|
315
|
-
params: { structureCode: 'STRUCTURE_CALLABLE_POLICY_AUTH' },
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
});
|
|
435
|
+
})
|
|
436
|
+
.refine((callable) => callable.auth === 'authorized' || callable.policy === undefined, callablePolicyIssue('function'))
|
|
437
|
+
.register(documentJsonSchema, { id: 'function-definition', ...authorizedPolicy });
|
|
319
438
|
const policyExpressionSchema = union([
|
|
320
439
|
strictObject({
|
|
321
440
|
match: policyPatternExpressionSchema,
|
|
322
441
|
}),
|
|
323
|
-
strictObject({
|
|
324
|
-
|
|
325
|
-
}),
|
|
326
|
-
strictObject({
|
|
327
|
-
anyOf: array(policyRefSchema).min(1),
|
|
328
|
-
}),
|
|
442
|
+
strictObject({ allOf: nonEmpty(array(policyRefSchema)) }),
|
|
443
|
+
strictObject({ anyOf: nonEmpty(array(policyRefSchema)) }),
|
|
329
444
|
]);
|
|
330
445
|
const policyDefinitionSchema = strictObject({
|
|
331
446
|
ref: policyRefSchema,
|
|
332
447
|
expression: policyExpressionSchema,
|
|
333
448
|
description: descriptionSchema.optional(),
|
|
449
|
+
}).register(documentJsonSchema, { id: 'policy-definition' });
|
|
450
|
+
const viewTargetSchema = strictObject({ kind: literal('domain') }).register(documentJsonSchema, {
|
|
451
|
+
id: 'view-target',
|
|
452
|
+
description: 'Every View targets its owning Domain.',
|
|
334
453
|
});
|
|
335
|
-
const viewTargetSchema = strictObject({ kind: literal('domain') });
|
|
336
454
|
const viewDefinitionSchema = strictObject({
|
|
337
455
|
ref: viewRefSchema,
|
|
338
456
|
description: descriptionSchema.optional(),
|
|
339
457
|
target: viewTargetSchema,
|
|
458
|
+
}).register(documentJsonSchema, { id: 'view-definition' });
|
|
459
|
+
const propertyValuesSchema = record(propertyKeySchema, jsonValueSchema).register(documentJsonSchema, {
|
|
460
|
+
id: 'property-values',
|
|
340
461
|
});
|
|
341
462
|
const coreNodeSchema = strictObject({
|
|
342
463
|
ref: coreRefSchema,
|
|
343
464
|
class: classRefSchema,
|
|
344
|
-
properties:
|
|
345
|
-
});
|
|
465
|
+
properties: propertyValuesSchema,
|
|
466
|
+
}).register(documentJsonSchema, { id: 'core-definition' });
|
|
346
467
|
const coreEdgeSchema = strictObject({
|
|
347
468
|
class: classRefSchema,
|
|
348
469
|
source: coreEndpointRefSchema,
|
|
349
470
|
target: coreEndpointRefSchema,
|
|
350
|
-
properties:
|
|
351
|
-
});
|
|
471
|
+
properties: propertyValuesSchema,
|
|
472
|
+
}).register(documentJsonSchema, { id: 'core-edge' });
|
|
352
473
|
const coreSchema = strictObject({
|
|
353
474
|
nodes: record(coreSlugSchema, coreNodeSchema),
|
|
354
475
|
edges: array(coreEdgeSchema),
|
|
355
|
-
});
|
|
356
|
-
|
|
476
|
+
}).register(documentJsonSchema, { id: 'core' });
|
|
477
|
+
/** The structure of an authored Domain schema document, the single source of its JSON Schema. */
|
|
478
|
+
export const domainSchemaStructure = strictObject({
|
|
357
479
|
format: literal(DOMAIN_FORMAT),
|
|
358
480
|
version: literal(DOMAIN_VERSION),
|
|
359
481
|
origin: originSchema,
|
|
@@ -366,19 +488,54 @@ const structuralSchema = strictObject({
|
|
|
366
488
|
views: record(memberNameSchema, viewDefinitionSchema),
|
|
367
489
|
core: coreSchema,
|
|
368
490
|
});
|
|
369
|
-
const domainSchemaStructuralSchema = structuralSchema;
|
|
370
491
|
export function parseDomainSchemaStructure(input) {
|
|
371
|
-
const result =
|
|
372
|
-
if (result.success)
|
|
373
|
-
|
|
374
|
-
|
|
492
|
+
const result = domainSchemaStructure.safeParse(input);
|
|
493
|
+
if (!result.success) {
|
|
494
|
+
throw new SchemaError(result.error.issues.flatMap((issue) => diagnosticsForIssue(issue, input)));
|
|
495
|
+
}
|
|
496
|
+
const lost = lostProtoKeys(input, result.data, []);
|
|
497
|
+
if (lost.length > 0)
|
|
498
|
+
throw new SchemaError(lost);
|
|
499
|
+
return modelOf(result.data);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Each `__proto__` member of the input that the parsed document lost. Zod skips that member in an
|
|
503
|
+
* object or a record, so the structure would accept the document without it; it refuses the
|
|
504
|
+
* document instead. A State's transitions are read in place and keep the member.
|
|
505
|
+
*/
|
|
506
|
+
function lostProtoKeys(input, parsed, path) {
|
|
507
|
+
if (!isObject(input) || !isObject(parsed))
|
|
508
|
+
return [];
|
|
509
|
+
const lost = Object.hasOwn(input, PROTO_KEY) && !Object.hasOwn(parsed, PROTO_KEY)
|
|
510
|
+
? [
|
|
511
|
+
diagnostic('STRUCTURE_PROTO_KEY', pointer([...path, PROTO_KEY]), 'A member named __proto__ is refused: the parsed document cannot keep it.'),
|
|
512
|
+
]
|
|
513
|
+
: [];
|
|
514
|
+
return [
|
|
515
|
+
...lost,
|
|
516
|
+
...Object.keys(parsed).flatMap((key) => lostProtoKeys(Reflect.get(input, key), Reflect.get(parsed, key), [...path, key])),
|
|
517
|
+
];
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* The structure admits the document the model types: this parameter checks one direction, and
|
|
521
|
+
* `domain.structure.typecheck.ts` both. The brands are the model's own proofs from here on.
|
|
522
|
+
*/
|
|
523
|
+
function modelOf(document) {
|
|
524
|
+
return document;
|
|
375
525
|
}
|
|
376
526
|
function absoluteRefSchema(kind) {
|
|
377
527
|
return strictObject({
|
|
378
528
|
origin: originSchema,
|
|
379
529
|
kind: literal(kind),
|
|
380
530
|
name: memberNameSchema,
|
|
381
|
-
});
|
|
531
|
+
}).register(documentJsonSchema, { id: `${kind}-ref` });
|
|
532
|
+
}
|
|
533
|
+
function callablePolicyIssue(owner) {
|
|
534
|
+
return {
|
|
535
|
+
path: ['policy'],
|
|
536
|
+
params: { structureCode: 'STRUCTURE_CALLABLE_POLICY_AUTH' },
|
|
537
|
+
error: `Only an authorized ${owner} may declare a policy.`,
|
|
538
|
+
};
|
|
382
539
|
}
|
|
383
540
|
function isStateTransitions(input) {
|
|
384
541
|
if (!isRecord(input))
|
|
@@ -402,9 +559,27 @@ function isStateTransitions(input) {
|
|
|
402
559
|
return true;
|
|
403
560
|
}
|
|
404
561
|
function isRecord(input) {
|
|
405
|
-
return input
|
|
562
|
+
return isObject(input) && !Array.isArray(input);
|
|
563
|
+
}
|
|
564
|
+
function isObject(input) {
|
|
565
|
+
return input !== null && typeof input === 'object';
|
|
566
|
+
}
|
|
567
|
+
/** At least one item, which `min(1)` proves: the output is typed as the non-empty tuple it is. */
|
|
568
|
+
function nonEmpty(list) {
|
|
569
|
+
return list.min(1);
|
|
570
|
+
}
|
|
571
|
+
/** Items distinct by JSON equality, which JSON Schema states as `uniqueItems`. */
|
|
572
|
+
function uniqueArray(element) {
|
|
573
|
+
return distinctArray(element, canonicalJson).register(documentJsonSchema, { uniqueItems: true });
|
|
406
574
|
}
|
|
407
|
-
|
|
575
|
+
/**
|
|
576
|
+
* Tuples distinct as sets of fields. JSON Schema states their JSON equality, `uniqueItems`; the
|
|
577
|
+
* rule `unique-tuple-set` names the rest.
|
|
578
|
+
*/
|
|
579
|
+
function uniqueTupleSet(tuple) {
|
|
580
|
+
return distinctArray(tuple, (fields) => JSON.stringify(fields.map(canonicalJson).sort())).register(documentJsonSchema, { uniqueItems: true, 'x-astrale-rule': 'unique-tuple-set' });
|
|
581
|
+
}
|
|
582
|
+
function distinctArray(element, keyOf) {
|
|
408
583
|
return array(element).superRefine((values, context) => {
|
|
409
584
|
const firstIndexByValue = new Map();
|
|
410
585
|
values.forEach((value, index) => {
|
|
@@ -437,24 +612,40 @@ function diagnosticsForIssue(issue, input) {
|
|
|
437
612
|
if (issue.code === 'unrecognized_keys') {
|
|
438
613
|
return issue.keys.map((key) => diagnostic('STRUCTURE_UNKNOWN_FIELD', pointer([...issue.path, key]), `Unknown field ${JSON.stringify(key)}.`));
|
|
439
614
|
}
|
|
440
|
-
|
|
615
|
+
// A record key refused by a refinement reports that refinement, at the key.
|
|
616
|
+
const refinement = issue.code === 'invalid_key' ? issue.issues.find(isRefinement) : undefined;
|
|
617
|
+
if (refinement !== undefined) {
|
|
618
|
+
return [diagnostic(refinementCode(refinement), pointer(issue.path), refinement.message)];
|
|
619
|
+
}
|
|
441
620
|
return [
|
|
442
|
-
diagnostic(
|
|
621
|
+
diagnostic(structureCode(issue, input), pointer(issue.path), structureMessage(issue, input)),
|
|
443
622
|
];
|
|
444
623
|
}
|
|
445
|
-
function
|
|
446
|
-
|
|
447
|
-
|
|
624
|
+
function isRefinement(issue) {
|
|
625
|
+
return issue.code === 'custom';
|
|
626
|
+
}
|
|
627
|
+
/** Every refinement of the structure names its own code. */
|
|
628
|
+
function refinementCode(issue) {
|
|
629
|
+
const code = issue.params?.structureCode;
|
|
630
|
+
switch (code) {
|
|
631
|
+
case 'STRUCTURE_BINARY_INPUT':
|
|
448
632
|
case 'STRUCTURE_CALLABLE_POLICY_AUTH':
|
|
633
|
+
case 'STRUCTURE_DEPENDENCY_ALIAS':
|
|
634
|
+
case 'STRUCTURE_DOMAIN_NAME':
|
|
449
635
|
case 'STRUCTURE_DUPLICATE_ITEM':
|
|
636
|
+
case 'STRUCTURE_EDGE_BLOB':
|
|
450
637
|
case 'STRUCTURE_EDGE_ORIENTATION':
|
|
451
|
-
|
|
638
|
+
case 'STRUCTURE_PROPERTY_KEY':
|
|
639
|
+
case 'STRUCTURE_STATE_TRANSITIONS':
|
|
640
|
+
return code;
|
|
452
641
|
default:
|
|
453
|
-
|
|
642
|
+
throw new TypeError(`A structure refinement names no Schema code: ${String(code)}.`);
|
|
454
643
|
}
|
|
455
644
|
}
|
|
456
645
|
function structureCode(issue, input) {
|
|
457
646
|
switch (issue.code) {
|
|
647
|
+
case 'custom':
|
|
648
|
+
return refinementCode(issue);
|
|
458
649
|
case 'invalid_type':
|
|
459
650
|
return valueAtPath(input, issue.path) === undefined ? 'STRUCTURE_REQUIRED' : 'STRUCTURE_TYPE';
|
|
460
651
|
case 'invalid_value':
|
|
@@ -469,10 +660,9 @@ function structureCode(issue, input) {
|
|
|
469
660
|
return 'STRUCTURE_MINIMUM';
|
|
470
661
|
case 'too_big':
|
|
471
662
|
return 'STRUCTURE_MAXIMUM';
|
|
472
|
-
case 'not_multiple_of':
|
|
473
|
-
return 'STRUCTURE_MULTIPLE';
|
|
474
663
|
default:
|
|
475
|
-
|
|
664
|
+
// The structure declares no multipleOf, Map or Set.
|
|
665
|
+
throw new TypeError(`The structure raised a ${issue.code} issue it declares no check for.`);
|
|
476
666
|
}
|
|
477
667
|
}
|
|
478
668
|
function structureMessage(issue, input) {
|