@astrale-os/kernel-dsl 1.0.0-beta.13 → 1.0.0-beta.15
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/domain-dependency.d.ts +0 -6
- package/dist/v1/addressing/domain-dependency.js +1 -6
- 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/authoring.d.ts +43 -0
- package/dist/v1/builder/class/property.d.ts +3 -9
- package/dist/v1/builder/class/property.js +1 -0
- package/dist/v1/builder/index.d.ts +1 -1
- package/dist/v1/builder/lowering/class.js +5 -1
- package/dist/v1/builder/lowering/policy.js +2 -0
- package/dist/v1/builder/lowering/schema/context.d.ts +5 -0
- package/dist/v1/builder/lowering/schema/context.js +22 -0
- package/dist/v1/builder/policy/authoring.d.ts +84 -4
- package/dist/v1/builder/policy/authoring.js +44 -14
- package/dist/v1/builder/schema/projection/class.d.ts +116 -14
- package/dist/v1/bundle/codec.js +9 -9
- 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/compiled/admission/accept.js +3 -3
- package/dist/v1/compiled/admission/closure.js +2 -2
- package/dist/v1/compiled/admission/semantics/core.js +3 -7
- package/dist/v1/compiled/admission/semantics/policy/parity.js +3 -10
- 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/core/facts.d.ts +17 -4
- package/dist/v1/language/core/facts.js +26 -5
- package/dist/v1/language/definition/metadata.d.ts +5 -0
- package/dist/v1/language/definition/metadata.js +7 -0
- package/dist/v1/language/limits.d.ts +64 -0
- package/dist/v1/language/limits.js +60 -0
- package/dist/v1/language/policy/analysis/budgets.d.ts +39 -18
- package/dist/v1/language/policy/analysis/budgets.js +30 -20
- package/dist/v1/language/policy/analysis/checks.js +3 -2
- package/dist/v1/language/policy/analysis/typing.js +4 -3
- 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 +29 -0
- package/dist/v1/language/validation/rule.js +120 -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/regexp/ast.js +6 -7
- package/dist/v1/language/value/regexp/compile.d.ts +5 -11
- package/dist/v1/language/value/regexp/compile.js +14 -12
- package/dist/v1/language/value/regexp/syntax.js +3 -2
- 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/admission/accept.js +3 -14
- package/dist/v1/schema/admission/callable.d.ts +21 -0
- package/dist/v1/schema/admission/callable.js +96 -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 +10 -1
- package/dist/v1/schema/index.js +4 -0
- package/dist/v1/schema/model/schema.d.ts +19 -1
- package/dist/v1/schema/normalization/domain.d.ts +2 -0
- package/dist/v1/schema/normalization/domain.js +2 -1
- package/dist/v1/schema/resolution/closure.js +2 -1
- package/dist/v1/schema/resolution/context.js +10 -8
- package/dist/v1/schema/resolution/domain.d.ts +5 -0
- package/dist/v1/schema/resolution/domain.js +1 -1
- package/dist/v1/schema/rule.d.ts +32 -0
- package/dist/v1/schema/rule.js +428 -0
- package/dist/v1/schema/validation/callable.d.ts +35 -2
- package/dist/v1/schema/validation/callable.js +51 -8
- package/dist/v1/schema/validation/class.js +36 -44
- package/dist/v1/schema/validation/core.js +19 -13
- package/dist/v1/schema/validation/domain.js +2 -2
- 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.route.d.ts +6 -1
- package/dist/v1/schema/validation/domain.route.js +18 -0
- package/dist/v1/schema/validation/domain.structure.d.ts +4020 -0
- package/dist/v1/schema/validation/domain.structure.js +359 -153
- 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/budgets.d.ts +33 -2
- package/dist/v1/schema/validation/policy/budgets.js +89 -16
- package/dist/v1/schema/validation/policy/references.js +6 -21
- package/dist/v1/schema/validation/policy/uses.d.ts +29 -0
- package/dist/v1/schema/validation/policy/uses.js +28 -51
- 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.d.ts +3 -0
- package/dist/v1/schema/validation/value.js +6 -12
- 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 { limits } from '../../language/limits.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(limits.policy.repetition),
|
|
249
|
+
max: number().int().min(0).max(limits.policy.repetition),
|
|
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(limits.policy.variablesPerExists)),
|
|
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
|
-
message: 'Only an authorized method may declare a policy.',
|
|
238
|
-
params: { structureCode: 'STRUCTURE_CALLABLE_POLICY_AUTH' },
|
|
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',
|
|
241
355
|
});
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
356
|
+
const classPolicySchema = union([literal(false), policyRefSchema]).register(documentJsonSchema, {
|
|
357
|
+
id: 'class-policy',
|
|
358
|
+
});
|
|
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,113 @@ 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
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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' });
|
|
431
|
+
const functionContractShape = {
|
|
307
432
|
...callableShape,
|
|
308
433
|
input: union([binaryInputSchema, valueFunctionInputSchema]),
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
});
|
|
434
|
+
};
|
|
435
|
+
const functionDefinitionSchema = strictObject({
|
|
436
|
+
ref: functionRefSchema,
|
|
437
|
+
...functionContractShape,
|
|
438
|
+
})
|
|
439
|
+
.refine((callable) => callable.auth === 'authorized' || callable.policy === undefined, callablePolicyIssue('function'))
|
|
440
|
+
.register(documentJsonSchema, { id: 'function-definition', ...authorizedPolicy });
|
|
441
|
+
// A callable contract has the structure of a Function declaration without its ref, wherever it is.
|
|
442
|
+
const callableContractSchema = strictObject(functionContractShape).refine((callable) => callable.auth === 'authorized' || callable.policy === undefined, callablePolicyIssue('function'));
|
|
319
443
|
const policyExpressionSchema = union([
|
|
320
444
|
strictObject({
|
|
321
445
|
match: policyPatternExpressionSchema,
|
|
322
446
|
}),
|
|
323
|
-
strictObject({
|
|
324
|
-
|
|
325
|
-
}),
|
|
326
|
-
strictObject({
|
|
327
|
-
anyOf: array(policyRefSchema).min(1),
|
|
328
|
-
}),
|
|
447
|
+
strictObject({ allOf: nonEmpty(array(policyRefSchema)) }),
|
|
448
|
+
strictObject({ anyOf: nonEmpty(array(policyRefSchema)) }),
|
|
329
449
|
]);
|
|
330
450
|
const policyDefinitionSchema = strictObject({
|
|
331
451
|
ref: policyRefSchema,
|
|
332
452
|
expression: policyExpressionSchema,
|
|
333
453
|
description: descriptionSchema.optional(),
|
|
454
|
+
}).register(documentJsonSchema, { id: 'policy-definition' });
|
|
455
|
+
const viewTargetSchema = strictObject({ kind: literal('domain') }).register(documentJsonSchema, {
|
|
456
|
+
id: 'view-target',
|
|
457
|
+
description: 'Every View targets its owning Domain.',
|
|
334
458
|
});
|
|
335
|
-
const viewTargetSchema = strictObject({ kind: literal('domain') });
|
|
336
459
|
const viewDefinitionSchema = strictObject({
|
|
337
460
|
ref: viewRefSchema,
|
|
338
461
|
description: descriptionSchema.optional(),
|
|
339
462
|
target: viewTargetSchema,
|
|
463
|
+
}).register(documentJsonSchema, { id: 'view-definition' });
|
|
464
|
+
const propertyValuesSchema = record(propertyKeySchema, jsonValueSchema).register(documentJsonSchema, {
|
|
465
|
+
id: 'property-values',
|
|
340
466
|
});
|
|
341
467
|
const coreNodeSchema = strictObject({
|
|
342
468
|
ref: coreRefSchema,
|
|
343
469
|
class: classRefSchema,
|
|
344
|
-
properties:
|
|
345
|
-
});
|
|
470
|
+
properties: propertyValuesSchema,
|
|
471
|
+
}).register(documentJsonSchema, { id: 'core-definition' });
|
|
346
472
|
const coreEdgeSchema = strictObject({
|
|
347
473
|
class: classRefSchema,
|
|
348
474
|
source: coreEndpointRefSchema,
|
|
349
475
|
target: coreEndpointRefSchema,
|
|
350
|
-
properties:
|
|
351
|
-
});
|
|
476
|
+
properties: propertyValuesSchema,
|
|
477
|
+
}).register(documentJsonSchema, { id: 'core-edge' });
|
|
352
478
|
const coreSchema = strictObject({
|
|
353
479
|
nodes: record(coreSlugSchema, coreNodeSchema),
|
|
354
480
|
edges: array(coreEdgeSchema),
|
|
355
|
-
});
|
|
356
|
-
|
|
481
|
+
}).register(documentJsonSchema, { id: 'core' });
|
|
482
|
+
/** The structure of an authored Domain schema document, the single source of its JSON Schema. */
|
|
483
|
+
export const domainSchemaStructure = strictObject({
|
|
357
484
|
format: literal(DOMAIN_FORMAT),
|
|
358
485
|
version: literal(DOMAIN_VERSION),
|
|
359
486
|
origin: originSchema,
|
|
@@ -366,19 +493,65 @@ const structuralSchema = strictObject({
|
|
|
366
493
|
views: record(memberNameSchema, viewDefinitionSchema),
|
|
367
494
|
core: coreSchema,
|
|
368
495
|
});
|
|
369
|
-
const domainSchemaStructuralSchema = structuralSchema;
|
|
370
496
|
export function parseDomainSchemaStructure(input) {
|
|
371
|
-
const result =
|
|
372
|
-
if (result.success)
|
|
373
|
-
|
|
374
|
-
|
|
497
|
+
const result = domainSchemaStructure.safeParse(input);
|
|
498
|
+
if (!result.success) {
|
|
499
|
+
throw new SchemaError(result.error.issues.flatMap((issue) => diagnosticsForIssue(issue, input)));
|
|
500
|
+
}
|
|
501
|
+
const lost = lostProtoKeys(input, result.data, []);
|
|
502
|
+
if (lost.length > 0)
|
|
503
|
+
throw new SchemaError(lost);
|
|
504
|
+
return modelOf(result.data);
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Each `__proto__` member of the input that the parsed document lost. Zod skips that member in an
|
|
508
|
+
* object or a record, so the structure would accept the document without it; it refuses the
|
|
509
|
+
* document instead. A State's transitions are read in place and keep the member.
|
|
510
|
+
*/
|
|
511
|
+
function lostProtoKeys(input, parsed, path) {
|
|
512
|
+
if (!isObject(input) || !isObject(parsed))
|
|
513
|
+
return [];
|
|
514
|
+
const lost = Object.hasOwn(input, PROTO_KEY) && !Object.hasOwn(parsed, PROTO_KEY)
|
|
515
|
+
? [
|
|
516
|
+
diagnostic('STRUCTURE_PROTO_KEY', pointer([...path, PROTO_KEY]), 'A member named __proto__ is refused: the parsed document cannot keep it.'),
|
|
517
|
+
]
|
|
518
|
+
: [];
|
|
519
|
+
return [
|
|
520
|
+
...lost,
|
|
521
|
+
...Object.keys(parsed).flatMap((key) => lostProtoKeys(Reflect.get(input, key), Reflect.get(parsed, key), [...path, key])),
|
|
522
|
+
];
|
|
523
|
+
}
|
|
524
|
+
/** Parse the structure of one callable contract: a Function declaration without its ref. */
|
|
525
|
+
export function parseCallableContract(input) {
|
|
526
|
+
const result = callableContractSchema.safeParse(input);
|
|
527
|
+
if (!result.success) {
|
|
528
|
+
throw new SchemaError(result.error.issues.flatMap((issue) => diagnosticsForIssue(issue, input)));
|
|
529
|
+
}
|
|
530
|
+
const lost = lostProtoKeys(input, result.data, []);
|
|
531
|
+
if (lost.length > 0)
|
|
532
|
+
throw new SchemaError(lost);
|
|
533
|
+
return result.data;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* The structure admits the document the model types: this parameter checks one direction, and
|
|
537
|
+
* `domain.structure.typecheck.ts` both. The brands are the model's own proofs from here on.
|
|
538
|
+
*/
|
|
539
|
+
function modelOf(document) {
|
|
540
|
+
return document;
|
|
375
541
|
}
|
|
376
542
|
function absoluteRefSchema(kind) {
|
|
377
543
|
return strictObject({
|
|
378
544
|
origin: originSchema,
|
|
379
545
|
kind: literal(kind),
|
|
380
546
|
name: memberNameSchema,
|
|
381
|
-
});
|
|
547
|
+
}).register(documentJsonSchema, { id: `${kind}-ref` });
|
|
548
|
+
}
|
|
549
|
+
function callablePolicyIssue(owner) {
|
|
550
|
+
return {
|
|
551
|
+
path: ['policy'],
|
|
552
|
+
params: { structureCode: 'STRUCTURE_CALLABLE_POLICY_AUTH' },
|
|
553
|
+
error: `Only an authorized ${owner} may declare a policy.`,
|
|
554
|
+
};
|
|
382
555
|
}
|
|
383
556
|
function isStateTransitions(input) {
|
|
384
557
|
if (!isRecord(input))
|
|
@@ -402,9 +575,27 @@ function isStateTransitions(input) {
|
|
|
402
575
|
return true;
|
|
403
576
|
}
|
|
404
577
|
function isRecord(input) {
|
|
405
|
-
return input
|
|
578
|
+
return isObject(input) && !Array.isArray(input);
|
|
579
|
+
}
|
|
580
|
+
function isObject(input) {
|
|
581
|
+
return input !== null && typeof input === 'object';
|
|
582
|
+
}
|
|
583
|
+
/** At least one item, which `min(1)` proves: the output is typed as the non-empty tuple it is. */
|
|
584
|
+
function nonEmpty(list) {
|
|
585
|
+
return list.min(1);
|
|
586
|
+
}
|
|
587
|
+
/** Items distinct by JSON equality, which JSON Schema states as `uniqueItems`. */
|
|
588
|
+
function uniqueArray(element) {
|
|
589
|
+
return distinctArray(element, canonicalJson).register(documentJsonSchema, { uniqueItems: true });
|
|
406
590
|
}
|
|
407
|
-
|
|
591
|
+
/**
|
|
592
|
+
* Tuples distinct as sets of fields. JSON Schema states their JSON equality, `uniqueItems`; the
|
|
593
|
+
* rule `unique-tuple-set` names the rest.
|
|
594
|
+
*/
|
|
595
|
+
function uniqueTupleSet(tuple) {
|
|
596
|
+
return distinctArray(tuple, (fields) => JSON.stringify(fields.map(canonicalJson).sort())).register(documentJsonSchema, { uniqueItems: true, 'x-astrale-rule': 'unique-tuple-set' });
|
|
597
|
+
}
|
|
598
|
+
function distinctArray(element, keyOf) {
|
|
408
599
|
return array(element).superRefine((values, context) => {
|
|
409
600
|
const firstIndexByValue = new Map();
|
|
410
601
|
values.forEach((value, index) => {
|
|
@@ -437,24 +628,40 @@ function diagnosticsForIssue(issue, input) {
|
|
|
437
628
|
if (issue.code === 'unrecognized_keys') {
|
|
438
629
|
return issue.keys.map((key) => diagnostic('STRUCTURE_UNKNOWN_FIELD', pointer([...issue.path, key]), `Unknown field ${JSON.stringify(key)}.`));
|
|
439
630
|
}
|
|
440
|
-
|
|
631
|
+
// A record key refused by a refinement reports that refinement, at the key.
|
|
632
|
+
const refinement = issue.code === 'invalid_key' ? issue.issues.find(isRefinement) : undefined;
|
|
633
|
+
if (refinement !== undefined) {
|
|
634
|
+
return [diagnostic(refinementCode(refinement), pointer(issue.path), refinement.message)];
|
|
635
|
+
}
|
|
441
636
|
return [
|
|
442
|
-
diagnostic(
|
|
637
|
+
diagnostic(structureCode(issue, input), pointer(issue.path), structureMessage(issue, input)),
|
|
443
638
|
];
|
|
444
639
|
}
|
|
445
|
-
function
|
|
446
|
-
|
|
447
|
-
|
|
640
|
+
function isRefinement(issue) {
|
|
641
|
+
return issue.code === 'custom';
|
|
642
|
+
}
|
|
643
|
+
/** Every refinement of the structure names its own code. */
|
|
644
|
+
function refinementCode(issue) {
|
|
645
|
+
const code = issue.params?.structureCode;
|
|
646
|
+
switch (code) {
|
|
647
|
+
case 'STRUCTURE_BINARY_INPUT':
|
|
448
648
|
case 'STRUCTURE_CALLABLE_POLICY_AUTH':
|
|
649
|
+
case 'STRUCTURE_DEPENDENCY_ALIAS':
|
|
650
|
+
case 'STRUCTURE_DOMAIN_NAME':
|
|
449
651
|
case 'STRUCTURE_DUPLICATE_ITEM':
|
|
652
|
+
case 'STRUCTURE_EDGE_BLOB':
|
|
450
653
|
case 'STRUCTURE_EDGE_ORIENTATION':
|
|
451
|
-
|
|
654
|
+
case 'STRUCTURE_PROPERTY_KEY':
|
|
655
|
+
case 'STRUCTURE_STATE_TRANSITIONS':
|
|
656
|
+
return code;
|
|
452
657
|
default:
|
|
453
|
-
|
|
658
|
+
throw new TypeError(`A structure refinement names no Schema code: ${String(code)}.`);
|
|
454
659
|
}
|
|
455
660
|
}
|
|
456
661
|
function structureCode(issue, input) {
|
|
457
662
|
switch (issue.code) {
|
|
663
|
+
case 'custom':
|
|
664
|
+
return refinementCode(issue);
|
|
458
665
|
case 'invalid_type':
|
|
459
666
|
return valueAtPath(input, issue.path) === undefined ? 'STRUCTURE_REQUIRED' : 'STRUCTURE_TYPE';
|
|
460
667
|
case 'invalid_value':
|
|
@@ -469,10 +676,9 @@ function structureCode(issue, input) {
|
|
|
469
676
|
return 'STRUCTURE_MINIMUM';
|
|
470
677
|
case 'too_big':
|
|
471
678
|
return 'STRUCTURE_MAXIMUM';
|
|
472
|
-
case 'not_multiple_of':
|
|
473
|
-
return 'STRUCTURE_MULTIPLE';
|
|
474
679
|
default:
|
|
475
|
-
|
|
680
|
+
// The structure declares no multipleOf, Map or Set.
|
|
681
|
+
throw new TypeError(`The structure raised a ${issue.code} issue it declares no check for.`);
|
|
476
682
|
}
|
|
477
683
|
}
|
|
478
684
|
function structureMessage(issue, input) {
|