@astrale-os/kernel-dsl 1.0.0-beta.12 → 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.
Files changed (152) hide show
  1. package/dist/v1/addressing/definition-ref.js +21 -37
  2. package/dist/v1/addressing/key.js +2 -1
  3. package/dist/v1/addressing/ordering.d.ts +2 -0
  4. package/dist/v1/addressing/ordering.js +4 -0
  5. package/dist/v1/addressing/patterns.d.ts +1 -1
  6. package/dist/v1/addressing/patterns.js +2 -6
  7. package/dist/v1/builder/class/authoring.js +5 -28
  8. package/dist/v1/builder/class/property.d.ts +3 -9
  9. package/dist/v1/builder/class/property.js +1 -0
  10. package/dist/v1/builder/lowering/class.js +5 -1
  11. package/dist/v1/builder/lowering/policy.js +1 -0
  12. package/dist/v1/builder/lowering/schema/context.js +2 -4
  13. package/dist/v1/builder/lowering/value/path.js +2 -4
  14. package/dist/v1/builder/lowering/value/zod.js +1 -3
  15. package/dist/v1/builder/policy/authoring.js +3 -2
  16. package/dist/v1/builder/schema/define.d.ts +0 -1
  17. package/dist/v1/builder/schema/diagnostic.d.ts +2 -1
  18. package/dist/v1/builder/schema/diagnostic.js +5 -10
  19. package/dist/v1/builder/schema/projection.d.ts +1 -1
  20. package/dist/v1/builder/value/full-match.d.ts +0 -1
  21. package/dist/v1/builder/value/full-match.js +0 -1
  22. package/dist/v1/bundle/codec.js +4 -6
  23. package/dist/v1/bundle/diagnostic.d.ts +2 -0
  24. package/dist/v1/bundle/diagnostic.js +9 -7
  25. package/dist/v1/bundle/structure.js +4 -6
  26. package/dist/v1/compiled/admission/diagnostic.d.ts +1 -1
  27. package/dist/v1/compiled/admission/diagnostic.js +1 -3
  28. package/dist/v1/compiled/admission/revision.d.ts +9 -1
  29. package/dist/v1/compiled/admission/revision.js +6 -3
  30. package/dist/v1/compiled/admission/semantics/class.d.ts +4 -1
  31. package/dist/v1/compiled/admission/semantics/class.js +4 -8
  32. package/dist/v1/compiled/admission/semantics/core.d.ts +1 -24
  33. package/dist/v1/compiled/admission/semantics/core.js +54 -34
  34. package/dist/v1/compiled/admission/semantics/edge.d.ts +2 -4
  35. package/dist/v1/compiled/admission/semantics/edge.js +6 -65
  36. package/dist/v1/compiled/admission/semantics/policy/callable.js +0 -2
  37. package/dist/v1/compiled/admission/semantics/policy/contracts.d.ts +5 -20
  38. package/dist/v1/compiled/admission/semantics/policy/contracts.js +8 -21
  39. package/dist/v1/compiled/admission/semantics/policy/parity.js +13 -22
  40. package/dist/v1/compiled/admission/semantics/policy/pattern.d.ts +4 -1
  41. package/dist/v1/compiled/admission/semantics/policy/pattern.js +4 -16
  42. package/dist/v1/compiled/admission/semantics/policy/typing.d.ts +2 -24
  43. package/dist/v1/compiled/admission/semantics/policy/typing.js +19 -201
  44. package/dist/v1/compiled/compiler/compile.js +6 -8
  45. package/dist/v1/compiled/diagnostic.js +2 -3
  46. package/dist/v1/compiled/loader/load.d.ts +3 -2
  47. package/dist/v1/compiled/loader/load.js +10 -44
  48. package/dist/v1/domain/create.js +3 -5
  49. package/dist/v1/domain/namespace/member-collection.js +4 -6
  50. package/dist/v1/domain/validation/create.d.ts +4 -2
  51. package/dist/v1/domain/validation/create.js +21 -69
  52. package/dist/v1/language/.spec/schemas/value-schema.schema.json +22 -0
  53. package/dist/v1/language/class/inheritance.d.ts +18 -0
  54. package/dist/v1/language/class/inheritance.js +73 -0
  55. package/dist/v1/language/class/model/class.d.ts +4 -4
  56. package/dist/v1/language/class/model/edge.d.ts +2 -2
  57. package/dist/v1/language/class/model/property.d.ts +16 -7
  58. package/dist/v1/language/class/property-values.js +2 -4
  59. package/dist/v1/language/core/facts.d.ts +72 -0
  60. package/dist/v1/language/core/facts.js +95 -0
  61. package/dist/v1/language/definition/metadata.d.ts +5 -0
  62. package/dist/v1/language/definition/metadata.js +7 -0
  63. package/dist/v1/language/policy/analysis/branches.d.ts +8 -0
  64. package/dist/v1/language/policy/analysis/branches.js +2 -1
  65. package/dist/v1/language/policy/analysis/budgets.d.ts +13 -1
  66. package/dist/v1/language/policy/analysis/budgets.js +104 -0
  67. package/dist/v1/language/policy/analysis/typing.d.ts +48 -0
  68. package/dist/v1/language/policy/analysis/typing.js +338 -0
  69. package/dist/v1/language/policy/model/policy.d.ts +5 -5
  70. package/dist/v1/language/policy/normalization/normalize.js +17 -16
  71. package/dist/v1/language/validation/diagnostic.d.ts +15 -1
  72. package/dist/v1/language/validation/diagnostic.js +21 -0
  73. package/dist/v1/language/validation/rule.d.ts +26 -0
  74. package/dist/v1/language/validation/rule.js +116 -0
  75. package/dist/v1/language/value/compile/fields.js +4 -8
  76. package/dist/v1/language/value/profile/meta-schema.compiled.js +4 -1
  77. package/dist/v1/language/value/profile/rules.js +17 -43
  78. package/dist/v1/language/value/profile/validate.js +1 -10
  79. package/dist/v1/language/value/profile/vocabulary.d.ts +35 -2
  80. package/dist/v1/language/value/profile/vocabulary.js +52 -5
  81. package/dist/v1/language/value/reference/recursion.js +2 -22
  82. package/dist/v1/language/value/reference/registry.index.js +2 -1
  83. package/dist/v1/language/value/syntax/keywords.d.ts +3 -3
  84. package/dist/v1/language/value/syntax/keywords.js +4 -20
  85. package/dist/v1/language/value/syntax/node-path/validate.js +2 -3
  86. package/dist/v1/language/value/syntax/pointer.d.ts +2 -0
  87. package/dist/v1/language/value/syntax/pointer.js +5 -2
  88. package/dist/v1/language/value/syntax/walk.d.ts +2 -0
  89. package/dist/v1/language/value/syntax/walk.js +19 -20
  90. package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +2336 -0
  91. package/dist/v1/schema/analysis/class.js +33 -15
  92. package/dist/v1/schema/compatibility/api.d.ts +1 -1
  93. package/dist/v1/schema/compatibility/coordinate.js +3 -3
  94. package/dist/v1/schema/compatibility/diff.js +2 -4
  95. package/dist/v1/schema/compatibility/meaning/footprint.js +16 -50
  96. package/dist/v1/schema/compatibility/model.d.ts +5 -0
  97. package/dist/v1/schema/compatibility/structure/requirements.js +26 -58
  98. package/dist/v1/schema/compatibility/walk.d.ts +17 -0
  99. package/dist/v1/schema/compatibility/walk.js +41 -0
  100. package/dist/v1/schema/diagnostic.d.ts +17 -3
  101. package/dist/v1/schema/diagnostic.js +14 -11
  102. package/dist/v1/schema/index.d.ts +3 -1
  103. package/dist/v1/schema/index.js +1 -0
  104. package/dist/v1/schema/model/schema.d.ts +19 -1
  105. package/dist/v1/schema/normalization/domain.js +1 -0
  106. package/dist/v1/schema/resolution/closure.js +2 -1
  107. package/dist/v1/schema/resolution/context.js +6 -6
  108. package/dist/v1/schema/rule.d.ts +29 -0
  109. package/dist/v1/schema/rule.js +415 -0
  110. package/dist/v1/schema/validation/callable.d.ts +4 -1
  111. package/dist/v1/schema/validation/callable.js +13 -6
  112. package/dist/v1/schema/validation/class.js +46 -96
  113. package/dist/v1/schema/validation/core.js +68 -202
  114. package/dist/v1/schema/validation/domain.js +1 -10
  115. package/dist/v1/schema/validation/domain.json-schema.d.ts +15 -0
  116. package/dist/v1/schema/validation/domain.json-schema.js +183 -0
  117. package/dist/v1/schema/validation/domain.structure.d.ts +4018 -0
  118. package/dist/v1/schema/validation/domain.structure.js +343 -155
  119. package/dist/v1/schema/validation/json.json-schema.d.ts +24 -0
  120. package/dist/v1/schema/validation/json.json-schema.js +36 -0
  121. package/dist/v1/schema/validation/policy/budgets.js +5 -112
  122. package/dist/v1/schema/validation/policy/context.d.ts +5 -3
  123. package/dist/v1/schema/validation/policy/context.js +5 -3
  124. package/dist/v1/schema/validation/policy/objects.d.ts +1 -1
  125. package/dist/v1/schema/validation/policy/objects.js +6 -14
  126. package/dist/v1/schema/validation/policy/references.js +10 -27
  127. package/dist/v1/schema/validation/policy/typing.d.ts +3 -18
  128. package/dist/v1/schema/validation/policy/typing.js +22 -356
  129. package/dist/v1/schema/validation/policy/uses.d.ts +1 -1
  130. package/dist/v1/schema/validation/policy/uses.js +9 -25
  131. package/dist/v1/schema/validation/policy/validate.js +3 -2
  132. package/dist/v1/schema/validation/value-api.d.ts +12 -1
  133. package/dist/v1/schema/validation/value-api.js +12 -3
  134. package/dist/v1/schema/validation/value.js +8 -10
  135. package/dist/v1/schema/validation/value.json-schema.d.ts +51 -0
  136. package/dist/v1/schema/validation/value.json-schema.js +126 -0
  137. package/dist/v1/schema/validation/view.js +4 -8
  138. package/package.json +3 -1
  139. package/dist/v1/compiled/admission/semantics/cardinality.d.ts +0 -5
  140. package/dist/v1/compiled/admission/semantics/cardinality.js +0 -128
  141. package/dist/v1/compiled/admission/semantics/policy/equality.d.ts +0 -8
  142. package/dist/v1/compiled/admission/semantics/policy/equality.js +0 -27
  143. package/dist/v1/compiled/admission/semantics/policy/measure.d.ts +0 -9
  144. package/dist/v1/compiled/admission/semantics/policy/measure.js +0 -81
  145. package/dist/v1/compiled/admission/semantics/policy/variants.d.ts +0 -10
  146. package/dist/v1/compiled/admission/semantics/policy/variants.js +0 -65
  147. package/dist/v1/language/class/index.d.ts +0 -8
  148. package/dist/v1/language/class/index.js +0 -1
  149. package/dist/v1/language/core/index.d.ts +0 -2
  150. package/dist/v1/language/core/index.js +0 -1
  151. package/dist/v1/language/value/index.d.ts +0 -11
  152. package/dist/v1/language/value/index.js +0 -8
@@ -1,61 +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 { SchemaError, diagnostic } from '../diagnostic.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';
8
+ import { escapePointerToken } from '../../language/value/syntax/pointer.js';
9
+ import { SchemaError, diagnostic, } from '../diagnostic.js';
7
10
  import { DOMAIN_FORMAT, DOMAIN_VERSION } from '../model/format.js';
8
- const originSchema = string().min(3).max(253).regex(DOMAIN_ORIGIN_PATTERN);
9
- 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' });
10
36
  const dependencyAliasSchema = string()
11
37
  .regex(DEPENDENCY_ALIAS_PATTERN)
12
- .refine((value) => value !== '__proto__' && value !== 'prototype' && value !== 'constructor', 'Expected a non-reserved dependency alias.');
13
- const propertyKeySchema = string().refine(SemanticPropertyKey.is, 'Expected a canonical PropertyKey.');
14
- const coreSlugSchema = string().min(1).max(100).regex(CORE_SLUG_PATTERN);
15
- 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' });
16
60
  const descriptionSchema = string()
17
61
  .min(metadata.description.minLength)
18
- .max(metadata.description.maxLength);
19
- 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' });
20
68
  const domainNameSchema = string()
21
69
  .min(metadata.name.minLength)
22
70
  .max(metadata.name.maxLength)
23
- .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 } } };
24
85
  const jsonValueSchema = lazy(() => union([
25
86
  nullSchema(),
26
87
  boolean(),
27
88
  number(),
28
89
  string(),
29
90
  array(jsonValueSchema),
30
- record(string(), jsonValueSchema),
31
- ]));
32
- const jsonObjectSchema = record(string(), jsonValueSchema);
33
- 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
+ });
34
98
  const dataDeclarationSchema = strictObject({
35
- mediaType: string().regex(MEDIA_TYPE_PATTERN),
99
+ mediaType: string().regex(MEDIA_TYPE_PATTERN).register(documentJsonSchema, { id: 'media-type' }),
36
100
  });
37
101
  const stateNameSchema = string().min(1);
38
- 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
+ });
39
115
  const stateTopologySchema = strictObject({
40
116
  initial: stateNameSchema,
41
117
  transitions: stateTransitionsSchema,
42
- });
43
- const propertyDefinitionSchema = strictObject({
118
+ }).register(documentJsonSchema, { id: 'state-topology' });
119
+ const propertyShape = {
44
120
  schema: jsonSchemaSchema,
45
121
  required: boolean(),
46
122
  auto: strictObject({
47
- on: uniqueArray(enumeration(['create', 'update'])).min(1),
123
+ on: nonEmpty(uniqueArray(enumeration(['create', 'update']))),
48
124
  set: literal('instant'),
49
- }).optional(),
125
+ })
126
+ .register(documentJsonSchema, { id: 'auto' })
127
+ .optional(),
50
128
  immutable: enumeration(['create', 'once']).optional(),
51
129
  description: descriptionSchema.optional(),
52
- 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
+ },
53
146
  });
54
- 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' });
55
150
  const domainDependencySchema = strictObject({
56
151
  origin: originSchema,
57
152
  revision: revisionSchema,
58
- });
153
+ }).register(documentJsonSchema, { id: 'dependency' });
59
154
  const classRefSchema = absoluteRefSchema('class');
60
155
  const functionRefSchema = absoluteRefSchema('function');
61
156
  const policyRefSchema = absoluteRefSchema('policy');
@@ -64,41 +159,54 @@ const coreRefSchema = strictObject({
64
159
  origin: originSchema,
65
160
  kind: literal('core'),
66
161
  name: coreSlugSchema,
162
+ }).register(documentJsonSchema, { id: 'core-ref' });
163
+ const domainRefSchema = strictObject({ kind: literal('domain') }).register(documentJsonSchema, {
164
+ id: 'domain-endpoint',
67
165
  });
68
- const domainRefSchema = strictObject({ kind: literal('domain') });
69
166
  /** Every Definition whose installed projection resolves through one exact member Path. */
70
167
  const projectedMemberRefOptions = [
71
- 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' }),
72
173
  classRefSchema,
73
174
  functionRefSchema,
74
175
  policyRefSchema,
75
176
  viewRefSchema,
76
177
  coreRefSchema,
77
178
  ];
78
- const policyObjectRefSchema = discriminatedUnion('kind', projectedMemberRefOptions);
179
+ const policyObjectRefSchema = discriminatedUnion('kind', projectedMemberRefOptions).register(documentJsonSchema, { id: 'projected-member-ref' });
79
180
  const coreEndpointRefSchema = discriminatedUnion('kind', [
80
181
  ...projectedMemberRefOptions,
81
182
  domainRefSchema,
82
- ]);
183
+ ]).register(documentJsonSchema, { id: 'core-endpoint' });
83
184
  const edgeEndpointBaseShape = {
84
185
  role: memberNameSchema,
85
- accepts: uniqueArray(classRefSchema).min(1),
86
- onDelete: enumeration(['restrict', 'detach']).optional(),
186
+ accepts: nonEmpty(uniqueArray(classRefSchema)),
187
+ onDelete: enumeration(['restrict', 'detach'])
188
+ .register(documentJsonSchema, { id: 'edge-lifetime' })
189
+ .optional(),
87
190
  };
88
- const edgeCountSchema = enumeration(['0..*', '1..*', '0..1', '1']);
89
- const directedEdgeEndpointsSchema = strictObject({
90
- source: strictObject({ ...edgeEndpointBaseShape, outgoing: edgeCountSchema }),
91
- target: strictObject({ ...edgeEndpointBaseShape, incoming: edgeCountSchema }),
191
+ const edgeCountSchema = enumeration(['0..*', '1..*', '0..1', '1']).register(documentJsonSchema, {
192
+ id: 'edge-count',
92
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' });
93
198
  const incidentEndpointSchema = strictObject({
94
199
  ...edgeEndpointBaseShape,
95
200
  incident: edgeCountSchema,
96
- });
97
- 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' });
98
206
  const edgeConstraintsSchema = strictObject({
99
207
  noSelf: literal(true).optional(),
100
208
  acyclic: literal(true).optional(),
101
- });
209
+ }).register(documentJsonSchema, { id: 'edge-constraints' });
102
210
  // A tuple names distinct fields: endpoints, the slug and Properties by key. Normalization puts
103
211
  // them, and the tuples, in canonical order (`compareEdgeUniqueFields`). No tuple is spelled by the
104
212
  // absence of the field, never by an empty list: one spelling per meaning, one revision.
@@ -107,13 +215,11 @@ const edgeUniqueFieldSchema = union([
107
215
  literal('target'),
108
216
  literal('slug'),
109
217
  strictObject({ property: propertyKeySchema }),
110
- ]);
111
- const edgeUniqueSchema = uniqueArray(uniqueArray(edgeUniqueFieldSchema).min(1), (tuple) =>
112
- // Two spellings of one set of fields are one tuple, repeated.
113
- JSON.stringify(tuple.map(canonicalJson).sort())).min(1);
218
+ ]).register(documentJsonSchema, { id: 'edge-unique-field' });
219
+ const edgeUniqueSchema = nonEmpty(uniqueTupleSet(nonEmpty(uniqueArray(edgeUniqueFieldSchema))));
114
220
  // A Node tuple names distinct Properties by key, which normalization puts in code-point order, as
115
221
  // it does the tuples (`compareNodeUniqueTuples`). Two orders of one set of keys are one tuple.
116
- const nodeUniqueSchema = uniqueArray(uniqueArray(propertyKeySchema).min(1), (tuple) => JSON.stringify([...tuple].sort())).min(1);
222
+ const nodeUniqueSchema = nonEmpty(uniqueTupleSet(nonEmpty(uniqueArray(propertyKeySchema))));
117
223
  const policyPatternSubjectSchema = strictObject({ kind: literal('subject') });
118
224
  const policyPatternObjectSchema = strictObject({ kind: literal('object') });
119
225
  const policyPatternSourceSchema = strictObject({ kind: literal('source') });
@@ -121,7 +227,7 @@ const policyPatternTargetSchema = strictObject({ kind: literal('target') });
121
227
  const policyPatternVariableSchema = strictObject({
122
228
  kind: literal('variable'),
123
229
  id: number().int().min(0),
124
- });
230
+ }).register(documentJsonSchema, { id: 'policy-pattern-variable' });
125
231
  const policyPatternNodeSchema = discriminatedUnion('kind', [
126
232
  policyPatternSubjectSchema,
127
233
  policyPatternObjectSchema,
@@ -129,19 +235,19 @@ const policyPatternNodeSchema = discriminatedUnion('kind', [
129
235
  policyPatternTargetSchema,
130
236
  policyPatternVariableSchema,
131
237
  strictObject({ kind: literal('ref'), ref: policyObjectRefSchema }),
132
- ]);
238
+ ]).register(documentJsonSchema, { id: 'policy-pattern-node' });
133
239
  const policyVariableSelectorSchema = discriminatedUnion('kind', [
134
240
  strictObject({ kind: literal('any') }),
135
241
  strictObject({ kind: literal('satisfies'), class: classRefSchema }),
136
- ]);
242
+ ]).register(documentJsonSchema, { id: 'policy-variable-selector' });
137
243
  const policyVariableSchema = union([
138
244
  strictObject({ variable: policyPatternVariableSchema, class: classRefSchema }),
139
245
  strictObject({ variable: policyPatternVariableSchema, selector: policyVariableSelectorSchema }),
140
- ]);
246
+ ]).register(documentJsonSchema, { id: 'policy-variable' });
141
247
  const policyRepeatSchema = strictObject({
142
- min: number().int().min(0).max(3),
143
- max: number().int().min(0).max(3),
144
- });
248
+ min: number().int().min(0).max(MAX_REPEAT),
249
+ max: number().int().min(0).max(MAX_REPEAT),
250
+ }).register(documentJsonSchema, { id: 'policy-repeat' });
145
251
  const policyPatternExpressionSchema = lazy(() => union([
146
252
  strictObject({
147
253
  sameNode: strictObject({ left: policyPatternNodeSchema, right: policyPatternNodeSchema }),
@@ -154,17 +260,13 @@ const policyPatternExpressionSchema = lazy(() => union([
154
260
  }),
155
261
  strictObject({
156
262
  exists: strictObject({
157
- nodes: array(policyVariableSchema).min(1).max(4),
263
+ nodes: nonEmpty(array(policyVariableSchema).max(MAX_VARIABLES)),
158
264
  where: policyPatternExpressionSchema,
159
265
  }),
160
266
  }),
161
- strictObject({
162
- allOf: array(policyPatternExpressionSchema).min(1),
163
- }),
164
- strictObject({
165
- anyOf: array(policyPatternExpressionSchema).min(1),
166
- }),
167
- ]));
267
+ strictObject({ allOf: nonEmpty(array(policyPatternExpressionSchema)) }),
268
+ strictObject({ anyOf: nonEmpty(array(policyPatternExpressionSchema)) }),
269
+ ])).register(documentJsonSchema, { id: 'policy-pattern-expr' });
168
270
  const selfObjectSchema = strictObject({ kind: literal('self') });
169
271
  const refObjectSchema = strictObject({ kind: literal('ref'), ref: policyObjectRefSchema });
170
272
  const policyCheckObjectSchema = discriminatedUnion('kind', [
@@ -174,21 +276,24 @@ const policyCheckObjectSchema = discriminatedUnion('kind', [
174
276
  field: memberNameSchema,
175
277
  }),
176
278
  refObjectSchema,
177
- ]);
279
+ ]).register(documentJsonSchema, { id: 'policy-check-object' });
178
280
  /** A Node Class rule checks the observed Node or one exact member: it has no input. */
179
- const classRuleObjectSchema = discriminatedUnion('kind', [selfObjectSchema, refObjectSchema]);
281
+ const classRuleObjectSchema = discriminatedUnion('kind', [
282
+ selfObjectSchema,
283
+ refObjectSchema,
284
+ ]).register(documentJsonSchema, { id: 'class-rule-object' });
180
285
  /** The recursive Policy check grammar, over the objects one use site admits. */
181
- function policyChecksSchema(objectSchema) {
286
+ function policyChecksSchema(objectSchema, id) {
182
287
  const checks = lazy(() => union([
183
288
  strictObject({ sameNode: strictObject({ left: objectSchema, right: objectSchema }) }),
184
289
  strictObject({ check: policyRefSchema, object: objectSchema }),
185
- strictObject({ allOf: array(checks).min(1) }),
186
- strictObject({ anyOf: array(checks).min(1) }),
187
- ]));
290
+ strictObject({ allOf: nonEmpty(array(checks)) }),
291
+ strictObject({ anyOf: nonEmpty(array(checks)) }),
292
+ ])).register(documentJsonSchema, { id });
188
293
  return checks;
189
294
  }
190
- const policyCheckExpressionSchema = policyChecksSchema(policyCheckObjectSchema);
191
- const classRuleChecksSchema = policyChecksSchema(classRuleObjectSchema);
295
+ const policyCheckExpressionSchema = policyChecksSchema(policyCheckObjectSchema, 'policy-check-expr');
296
+ const classRuleChecksSchema = policyChecksSchema(classRuleObjectSchema, 'class-rule-check-expr');
192
297
  const callableOutputSchema = discriminatedUnion('mode', [
193
298
  strictObject({
194
299
  mode: literal('value'),
@@ -201,20 +306,27 @@ const callableOutputSchema = discriminatedUnion('mode', [
201
306
  strictObject({
202
307
  mode: literal('binary'),
203
308
  }),
204
- ]);
309
+ ]).register(documentJsonSchema, { id: 'callable-output' });
205
310
  const binaryInputSchema = strictObject({
206
311
  mode: literal('binary'),
207
312
  metadata: jsonSchemaSchema,
208
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' } } },
209
322
  });
210
- const valueFunctionInputSchema = jsonSchemaSchema.refine((value) => typeof value !== 'object' || value === null || value.mode !== 'binary', 'Binary input must declare metadata and maximumBytes.');
211
323
  const callableExecutionSchema = strictObject({
212
324
  durable: boolean(),
213
- });
325
+ }).register(documentJsonSchema, { id: 'callable-execution' });
214
326
  // Its range is language meaning, refused by callable validation with DV_CALLABLE_ADMISSION.
215
327
  const callableAdmissionSchema = strictObject({
216
328
  maxAge: number(),
217
- });
329
+ }).register(documentJsonSchema, { id: 'callable-admission' });
218
330
  const callableShape = {
219
331
  description: descriptionSchema.optional(),
220
332
  input: jsonSchemaSchema,
@@ -222,25 +334,29 @@ const callableShape = {
222
334
  execution: callableExecutionSchema.optional(),
223
335
  admission: callableAdmissionSchema.optional(),
224
336
  auth: enumeration(['anonymous', 'authenticated', 'authorized']),
225
- 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 } },
226
345
  };
227
346
  const methodDeclarationSchema = strictObject({
228
347
  ...callableShape,
229
348
  static: boolean(),
230
349
  abstract: boolean(),
231
- }).superRefine((method, context) => {
232
- if (method.auth !== 'authorized' && method.policy !== undefined) {
233
- context.addIssue({
234
- code: 'custom',
235
- path: ['policy'],
236
- message: 'Only an authorized method may declare a policy.',
237
- params: { structureCode: 'STRUCTURE_CALLABLE_POLICY_AUTH' },
238
- });
239
- }
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',
240
358
  });
241
- const methodRecordSchema = record(memberNameSchema, methodDeclarationSchema);
242
- const classPolicySchema = union([literal(false), policyRefSchema]);
243
- const nodeClassPolicySchema = union([classPolicySchema, classRuleChecksSchema]);
359
+ const nodeClassPolicySchema = union([classPolicySchema, classRuleChecksSchema]).register(documentJsonSchema, { id: 'node-class-policy' });
244
360
  const nodeClassPoliciesSchema = strictObject({
245
361
  read: nodeClassPolicySchema.optional(),
246
362
  traverse: nodeClassPolicySchema.optional(),
@@ -258,101 +374,108 @@ const nodeClassSchema = strictObject({
258
374
  inheritance: inheritanceSchema.optional(),
259
375
  description: descriptionSchema.optional(),
260
376
  extends: uniqueArray(classRefSchema),
261
- properties: propertyRecordSchema,
377
+ properties: nodePropertyRecordSchema,
262
378
  methods: methodRecordSchema,
263
379
  data: dataDeclarationSchema.optional(),
264
380
  icon: iconSchema.optional(),
265
381
  policies: nodeClassPoliciesSchema,
266
382
  unique: nodeUniqueSchema.optional(),
267
- });
268
- const edgeClassShape = {
383
+ }).register(documentJsonSchema, { id: 'node-class' });
384
+ const orientationIssue = { params: { structureCode: 'STRUCTURE_EDGE_ORIENTATION' } };
385
+ const edgeClassSchema = strictObject({
269
386
  ref: classRefSchema,
270
387
  kind: literal('edge'),
271
388
  abstract: boolean(),
272
389
  inheritance: inheritanceSchema.optional(),
273
390
  description: descriptionSchema.optional(),
274
391
  extends: uniqueArray(classRefSchema),
275
- properties: propertyRecordSchema,
392
+ properties: edgePropertyRecordSchema,
276
393
  constraints: edgeConstraintsSchema,
277
394
  unique: edgeUniqueSchema.optional(),
278
- };
279
- const edgeClassSchema = strictObject({
280
- ...edgeClassShape,
281
395
  orientation: enumeration(['directed', 'undirected']),
282
396
  endpoints: union([directedEdgeEndpointsSchema, undirectedEdgeEndpointsSchema]),
283
397
  policies: edgeClassPoliciesSchema,
284
- }).superRefine(validateEdgeOrientationShape);
285
- const classDeclarationSchema = discriminatedUnion('kind', [nodeClassSchema, edgeClassSchema]);
286
- function validateEdgeOrientationShape(value, context) {
287
- if ((value.orientation === 'undirected') !== Array.isArray(value.endpoints)) {
288
- context.addIssue({
289
- code: 'custom',
290
- path: ['endpoints'],
291
- message: `${value.orientation} Edge endpoints have the wrong structural shape.`,
292
- params: { structureCode: 'STRUCTURE_EDGE_ORIENTATION' },
293
- });
294
- }
295
- if (value.orientation === 'undirected' && value.unique !== undefined) {
296
- context.addIssue({
297
- code: 'custom',
298
- path: ['unique'],
299
- message: 'Only a directed Edge Class declares unique tuples.',
300
- params: { structureCode: 'STRUCTURE_EDGE_ORIENTATION' },
301
- });
302
- }
303
- }
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' });
304
431
  const functionDefinitionSchema = strictObject({
305
432
  ref: functionRefSchema,
306
433
  ...callableShape,
307
434
  input: union([binaryInputSchema, valueFunctionInputSchema]),
308
- }).superRefine((callable, context) => {
309
- if (callable.auth !== 'authorized' && callable.policy !== undefined) {
310
- context.addIssue({
311
- code: 'custom',
312
- path: ['policy'],
313
- message: 'Only an authorized function may declare a policy.',
314
- params: { structureCode: 'STRUCTURE_CALLABLE_POLICY_AUTH' },
315
- });
316
- }
317
- });
435
+ })
436
+ .refine((callable) => callable.auth === 'authorized' || callable.policy === undefined, callablePolicyIssue('function'))
437
+ .register(documentJsonSchema, { id: 'function-definition', ...authorizedPolicy });
318
438
  const policyExpressionSchema = union([
319
439
  strictObject({
320
440
  match: policyPatternExpressionSchema,
321
441
  }),
322
- strictObject({
323
- allOf: array(policyRefSchema).min(1),
324
- }),
325
- strictObject({
326
- anyOf: array(policyRefSchema).min(1),
327
- }),
442
+ strictObject({ allOf: nonEmpty(array(policyRefSchema)) }),
443
+ strictObject({ anyOf: nonEmpty(array(policyRefSchema)) }),
328
444
  ]);
329
445
  const policyDefinitionSchema = strictObject({
330
446
  ref: policyRefSchema,
331
447
  expression: policyExpressionSchema,
332
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.',
333
453
  });
334
- const viewTargetSchema = strictObject({ kind: literal('domain') });
335
454
  const viewDefinitionSchema = strictObject({
336
455
  ref: viewRefSchema,
337
456
  description: descriptionSchema.optional(),
338
457
  target: viewTargetSchema,
458
+ }).register(documentJsonSchema, { id: 'view-definition' });
459
+ const propertyValuesSchema = record(propertyKeySchema, jsonValueSchema).register(documentJsonSchema, {
460
+ id: 'property-values',
339
461
  });
340
462
  const coreNodeSchema = strictObject({
341
463
  ref: coreRefSchema,
342
464
  class: classRefSchema,
343
- properties: record(propertyKeySchema, jsonValueSchema),
344
- });
465
+ properties: propertyValuesSchema,
466
+ }).register(documentJsonSchema, { id: 'core-definition' });
345
467
  const coreEdgeSchema = strictObject({
346
468
  class: classRefSchema,
347
469
  source: coreEndpointRefSchema,
348
470
  target: coreEndpointRefSchema,
349
- properties: record(propertyKeySchema, jsonValueSchema),
350
- });
471
+ properties: propertyValuesSchema,
472
+ }).register(documentJsonSchema, { id: 'core-edge' });
351
473
  const coreSchema = strictObject({
352
474
  nodes: record(coreSlugSchema, coreNodeSchema),
353
475
  edges: array(coreEdgeSchema),
354
- });
355
- const structuralSchema = strictObject({
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({
356
479
  format: literal(DOMAIN_FORMAT),
357
480
  version: literal(DOMAIN_VERSION),
358
481
  origin: originSchema,
@@ -365,19 +488,54 @@ const structuralSchema = strictObject({
365
488
  views: record(memberNameSchema, viewDefinitionSchema),
366
489
  core: coreSchema,
367
490
  });
368
- const domainSchemaStructuralSchema = structuralSchema;
369
491
  export function parseDomainSchemaStructure(input) {
370
- const result = domainSchemaStructuralSchema.safeParse(input);
371
- if (result.success)
372
- return result.data;
373
- throw new SchemaError(result.error.issues.flatMap((issue) => diagnosticsForIssue(issue, input)));
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;
374
525
  }
375
526
  function absoluteRefSchema(kind) {
376
527
  return strictObject({
377
528
  origin: originSchema,
378
529
  kind: literal(kind),
379
530
  name: memberNameSchema,
380
- });
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
+ };
381
539
  }
382
540
  function isStateTransitions(input) {
383
541
  if (!isRecord(input))
@@ -401,9 +559,27 @@ function isStateTransitions(input) {
401
559
  return true;
402
560
  }
403
561
  function isRecord(input) {
404
- return input !== null && typeof input === 'object' && !Array.isArray(input);
562
+ return isObject(input) && !Array.isArray(input);
405
563
  }
406
- function uniqueArray(element, keyOf = canonicalJson) {
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 });
574
+ }
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) {
407
583
  return array(element).superRefine((values, context) => {
408
584
  const firstIndexByValue = new Map();
409
585
  values.forEach((value, index) => {
@@ -436,24 +612,40 @@ function diagnosticsForIssue(issue, input) {
436
612
  if (issue.code === 'unrecognized_keys') {
437
613
  return issue.keys.map((key) => diagnostic('STRUCTURE_UNKNOWN_FIELD', pointer([...issue.path, key]), `Unknown field ${JSON.stringify(key)}.`));
438
614
  }
439
- const customCode = issue.code === 'custom' ? customStructureCode(issue.params?.structureCode) : undefined;
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
+ }
440
620
  return [
441
- diagnostic(customCode ?? structureCode(issue, input), pointer(issue.path), structureMessage(issue, input)),
621
+ diagnostic(structureCode(issue, input), pointer(issue.path), structureMessage(issue, input)),
442
622
  ];
443
623
  }
444
- function customStructureCode(input) {
445
- switch (input) {
446
- case 'STRUCTURE_ABSTRACT_POLICY':
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':
447
632
  case 'STRUCTURE_CALLABLE_POLICY_AUTH':
633
+ case 'STRUCTURE_DEPENDENCY_ALIAS':
634
+ case 'STRUCTURE_DOMAIN_NAME':
448
635
  case 'STRUCTURE_DUPLICATE_ITEM':
636
+ case 'STRUCTURE_EDGE_BLOB':
449
637
  case 'STRUCTURE_EDGE_ORIENTATION':
450
- return input;
638
+ case 'STRUCTURE_PROPERTY_KEY':
639
+ case 'STRUCTURE_STATE_TRANSITIONS':
640
+ return code;
451
641
  default:
452
- return undefined;
642
+ throw new TypeError(`A structure refinement names no Schema code: ${String(code)}.`);
453
643
  }
454
644
  }
455
645
  function structureCode(issue, input) {
456
646
  switch (issue.code) {
647
+ case 'custom':
648
+ return refinementCode(issue);
457
649
  case 'invalid_type':
458
650
  return valueAtPath(input, issue.path) === undefined ? 'STRUCTURE_REQUIRED' : 'STRUCTURE_TYPE';
459
651
  case 'invalid_value':
@@ -468,10 +660,9 @@ function structureCode(issue, input) {
468
660
  return 'STRUCTURE_MINIMUM';
469
661
  case 'too_big':
470
662
  return 'STRUCTURE_MAXIMUM';
471
- case 'not_multiple_of':
472
- return 'STRUCTURE_MULTIPLE';
473
663
  default:
474
- return 'STRUCTURE_INVALID';
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.`);
475
666
  }
476
667
  }
477
668
  function structureMessage(issue, input) {
@@ -498,8 +689,5 @@ function valueAtPath(input, path) {
498
689
  function pointer(path) {
499
690
  if (path.length === 0)
500
691
  return '';
501
- return `/${path.map((segment) => escapePointerSegment(String(segment))).join('/')}`;
502
- }
503
- function escapePointerSegment(segment) {
504
- return segment.replaceAll('~', '~0').replaceAll('/', '~1');
692
+ return `/${path.map((segment) => escapePointerToken(String(segment))).join('/')}`;
505
693
  }