@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.
Files changed (103) hide show
  1. package/dist/v1/addressing/domain-dependency.d.ts +0 -6
  2. package/dist/v1/addressing/domain-dependency.js +1 -6
  3. package/dist/v1/addressing/key.js +2 -1
  4. package/dist/v1/addressing/patterns.d.ts +1 -0
  5. package/dist/v1/addressing/patterns.js +2 -1
  6. package/dist/v1/builder/class/authoring.d.ts +43 -0
  7. package/dist/v1/builder/class/property.d.ts +3 -9
  8. package/dist/v1/builder/class/property.js +1 -0
  9. package/dist/v1/builder/index.d.ts +1 -1
  10. package/dist/v1/builder/lowering/class.js +5 -1
  11. package/dist/v1/builder/lowering/policy.js +2 -0
  12. package/dist/v1/builder/lowering/schema/context.d.ts +5 -0
  13. package/dist/v1/builder/lowering/schema/context.js +22 -0
  14. package/dist/v1/builder/policy/authoring.d.ts +84 -4
  15. package/dist/v1/builder/policy/authoring.js +44 -14
  16. package/dist/v1/builder/schema/projection/class.d.ts +116 -14
  17. package/dist/v1/bundle/codec.js +9 -9
  18. package/dist/v1/bundle/diagnostic.d.ts +2 -0
  19. package/dist/v1/bundle/diagnostic.js +6 -0
  20. package/dist/v1/bundle/structure.js +2 -2
  21. package/dist/v1/compiled/admission/accept.js +3 -3
  22. package/dist/v1/compiled/admission/closure.js +2 -2
  23. package/dist/v1/compiled/admission/semantics/core.js +3 -7
  24. package/dist/v1/compiled/admission/semantics/policy/parity.js +3 -10
  25. package/dist/v1/domain/validation/create.d.ts +4 -1
  26. package/dist/v1/domain/validation/create.js +21 -34
  27. package/dist/v1/language/.spec/schemas/value-schema.schema.json +22 -0
  28. package/dist/v1/language/class/model/class.d.ts +4 -4
  29. package/dist/v1/language/class/model/edge.d.ts +2 -2
  30. package/dist/v1/language/class/model/property.d.ts +16 -7
  31. package/dist/v1/language/core/facts.d.ts +17 -4
  32. package/dist/v1/language/core/facts.js +26 -5
  33. package/dist/v1/language/definition/metadata.d.ts +5 -0
  34. package/dist/v1/language/definition/metadata.js +7 -0
  35. package/dist/v1/language/limits.d.ts +64 -0
  36. package/dist/v1/language/limits.js +60 -0
  37. package/dist/v1/language/policy/analysis/budgets.d.ts +39 -18
  38. package/dist/v1/language/policy/analysis/budgets.js +30 -20
  39. package/dist/v1/language/policy/analysis/checks.js +3 -2
  40. package/dist/v1/language/policy/analysis/typing.js +4 -3
  41. package/dist/v1/language/policy/model/policy.d.ts +5 -5
  42. package/dist/v1/language/policy/normalization/normalize.js +17 -16
  43. package/dist/v1/language/validation/diagnostic.d.ts +1 -1
  44. package/dist/v1/language/validation/rule.d.ts +29 -0
  45. package/dist/v1/language/validation/rule.js +120 -0
  46. package/dist/v1/language/value/profile/meta-schema.compiled.js +4 -1
  47. package/dist/v1/language/value/profile/rules.js +13 -37
  48. package/dist/v1/language/value/profile/vocabulary.d.ts +35 -2
  49. package/dist/v1/language/value/profile/vocabulary.js +52 -5
  50. package/dist/v1/language/value/reference/registry.index.js +2 -1
  51. package/dist/v1/language/value/regexp/ast.js +6 -7
  52. package/dist/v1/language/value/regexp/compile.d.ts +5 -11
  53. package/dist/v1/language/value/regexp/compile.js +14 -12
  54. package/dist/v1/language/value/regexp/syntax.js +3 -2
  55. package/dist/v1/language/value/syntax/keywords.d.ts +3 -3
  56. package/dist/v1/language/value/syntax/keywords.js +4 -20
  57. package/dist/v1/language/value/syntax/node-path/validate.js +2 -3
  58. package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +2336 -0
  59. package/dist/v1/schema/admission/accept.js +3 -14
  60. package/dist/v1/schema/admission/callable.d.ts +21 -0
  61. package/dist/v1/schema/admission/callable.js +96 -0
  62. package/dist/v1/schema/analysis/class.js +33 -15
  63. package/dist/v1/schema/compatibility/api.d.ts +1 -1
  64. package/dist/v1/schema/compatibility/coordinate.js +3 -3
  65. package/dist/v1/schema/compatibility/model.d.ts +5 -0
  66. package/dist/v1/schema/diagnostic.d.ts +17 -3
  67. package/dist/v1/schema/diagnostic.js +11 -0
  68. package/dist/v1/schema/index.d.ts +10 -1
  69. package/dist/v1/schema/index.js +4 -0
  70. package/dist/v1/schema/model/schema.d.ts +19 -1
  71. package/dist/v1/schema/normalization/domain.d.ts +2 -0
  72. package/dist/v1/schema/normalization/domain.js +2 -1
  73. package/dist/v1/schema/resolution/closure.js +2 -1
  74. package/dist/v1/schema/resolution/context.js +10 -8
  75. package/dist/v1/schema/resolution/domain.d.ts +5 -0
  76. package/dist/v1/schema/resolution/domain.js +1 -1
  77. package/dist/v1/schema/rule.d.ts +32 -0
  78. package/dist/v1/schema/rule.js +428 -0
  79. package/dist/v1/schema/validation/callable.d.ts +35 -2
  80. package/dist/v1/schema/validation/callable.js +51 -8
  81. package/dist/v1/schema/validation/class.js +36 -44
  82. package/dist/v1/schema/validation/core.js +19 -13
  83. package/dist/v1/schema/validation/domain.js +2 -2
  84. package/dist/v1/schema/validation/domain.json-schema.d.ts +15 -0
  85. package/dist/v1/schema/validation/domain.json-schema.js +183 -0
  86. package/dist/v1/schema/validation/domain.route.d.ts +6 -1
  87. package/dist/v1/schema/validation/domain.route.js +18 -0
  88. package/dist/v1/schema/validation/domain.structure.d.ts +4020 -0
  89. package/dist/v1/schema/validation/domain.structure.js +359 -153
  90. package/dist/v1/schema/validation/json.json-schema.d.ts +24 -0
  91. package/dist/v1/schema/validation/json.json-schema.js +36 -0
  92. package/dist/v1/schema/validation/policy/budgets.d.ts +33 -2
  93. package/dist/v1/schema/validation/policy/budgets.js +89 -16
  94. package/dist/v1/schema/validation/policy/references.js +6 -21
  95. package/dist/v1/schema/validation/policy/uses.d.ts +29 -0
  96. package/dist/v1/schema/validation/policy/uses.js +28 -51
  97. package/dist/v1/schema/validation/value-api.d.ts +12 -1
  98. package/dist/v1/schema/validation/value-api.js +10 -1
  99. package/dist/v1/schema/validation/value.d.ts +3 -0
  100. package/dist/v1/schema/validation/value.js +6 -12
  101. package/dist/v1/schema/validation/value.json-schema.d.ts +51 -0
  102. package/dist/v1/schema/validation/value.json-schema.js +126 -0
  103. 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 originSchema = string().min(3).max(253).regex(DOMAIN_ORIGIN_PATTERN);
10
- const memberNameSchema = string().regex(MEMBER_NAME_PATTERN);
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 !== '__proto__' && value !== 'prototype' && value !== 'constructor', 'Expected a non-reserved dependency alias.');
14
- const propertyKeySchema = string().refine(SemanticPropertyKey.is, 'Expected a canonical PropertyKey.');
15
- const coreSlugSchema = string().min(1).max(100).regex(CORE_SLUG_PATTERN);
16
- const revisionSchema = string().regex(DOMAIN_REVISION_PATTERN);
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
- const iconSchema = string().min(metadata.icon.minLength).max(metadata.icon.maxLength);
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, 'Expected one trimmed line of text.');
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
- const stateTransitionsSchema = custom(isStateTransitions);
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 propertyDefinitionSchema = strictObject({
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'])).min(1),
123
+ on: nonEmpty(uniqueArray(enumeration(['create', 'update']))),
49
124
  set: literal('instant'),
50
- }).optional(),
125
+ })
126
+ .register(documentJsonSchema, { id: 'auto' })
127
+ .optional(),
51
128
  immutable: enumeration(['create', 'once']).optional(),
52
129
  description: descriptionSchema.optional(),
53
- state: stateTopologySchema.optional(),
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 propertyRecordSchema = record(memberNameSchema, propertyDefinitionSchema);
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({ kind: literal('method'), owner: classRefSchema, name: memberNameSchema }),
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).min(1),
87
- onDelete: enumeration(['restrict', 'detach']).optional(),
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
- const directedEdgeEndpointsSchema = strictObject({
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([incidentEndpointSchema, incidentEndpointSchema]);
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 = uniqueArray(uniqueArray(edgeUniqueFieldSchema).min(1), (tuple) =>
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 = uniqueArray(uniqueArray(propertyKeySchema).min(1), (tuple) => JSON.stringify([...tuple].sort())).min(1);
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(3),
144
- max: number().int().min(0).max(3),
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).min(1).max(4),
263
+ nodes: nonEmpty(array(policyVariableSchema).max(limits.policy.variablesPerExists)),
159
264
  where: policyPatternExpressionSchema,
160
265
  }),
161
266
  }),
162
- strictObject({
163
- allOf: array(policyPatternExpressionSchema).min(1),
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', [selfObjectSchema, refObjectSchema]);
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).min(1) }),
187
- strictObject({ anyOf: array(checks).min(1) }),
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]).optional(),
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
- }).superRefine((method, context) => {
233
- if (method.auth !== 'authorized' && method.policy !== undefined) {
234
- context.addIssue({
235
- code: 'custom',
236
- path: ['policy'],
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 methodRecordSchema = record(memberNameSchema, methodDeclarationSchema);
243
- const classPolicySchema = union([literal(false), policyRefSchema]);
244
- const nodeClassPolicySchema = union([classPolicySchema, classRuleChecksSchema]);
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: propertyRecordSchema,
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 edgeClassShape = {
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: propertyRecordSchema,
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
- }).superRefine(validateEdgeOrientationShape);
286
- const classDeclarationSchema = discriminatedUnion('kind', [nodeClassSchema, edgeClassSchema]);
287
- function validateEdgeOrientationShape(value, context) {
288
- if ((value.orientation === 'undirected') !== Array.isArray(value.endpoints)) {
289
- context.addIssue({
290
- code: 'custom',
291
- path: ['endpoints'],
292
- message: `${value.orientation} Edge endpoints have the wrong structural shape.`,
293
- params: { structureCode: 'STRUCTURE_EDGE_ORIENTATION' },
294
- });
295
- }
296
- if (value.orientation === 'undirected' && value.unique !== undefined) {
297
- context.addIssue({
298
- code: 'custom',
299
- path: ['unique'],
300
- message: 'Only a directed Edge Class declares unique tuples.',
301
- params: { structureCode: 'STRUCTURE_EDGE_ORIENTATION' },
302
- });
303
- }
304
- }
305
- const functionDefinitionSchema = strictObject({
306
- ref: functionRefSchema,
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
- }).superRefine((callable, context) => {
310
- if (callable.auth !== 'authorized' && callable.policy !== undefined) {
311
- context.addIssue({
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
- });
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
- allOf: array(policyRefSchema).min(1),
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: record(propertyKeySchema, jsonValueSchema),
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: record(propertyKeySchema, jsonValueSchema),
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
- const structuralSchema = strictObject({
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 = domainSchemaStructuralSchema.safeParse(input);
372
- if (result.success)
373
- return result.data;
374
- throw new SchemaError(result.error.issues.flatMap((issue) => diagnosticsForIssue(issue, input)));
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 !== null && typeof input === 'object' && !Array.isArray(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
- function uniqueArray(element, keyOf = canonicalJson) {
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
- const customCode = issue.code === 'custom' ? customStructureCode(issue.params?.structureCode) : undefined;
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(customCode ?? structureCode(issue, input), pointer(issue.path), structureMessage(issue, input)),
637
+ diagnostic(structureCode(issue, input), pointer(issue.path), structureMessage(issue, input)),
443
638
  ];
444
639
  }
445
- function customStructureCode(input) {
446
- switch (input) {
447
- case 'STRUCTURE_ABSTRACT_POLICY':
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
- return input;
654
+ case 'STRUCTURE_PROPERTY_KEY':
655
+ case 'STRUCTURE_STATE_TRANSITIONS':
656
+ return code;
452
657
  default:
453
- return undefined;
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
- return 'STRUCTURE_INVALID';
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) {