@json-schema-engine/core 0.0.1

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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/dist/coverage.d.ts +30 -0
  4. package/dist/coverage.d.ts.map +1 -0
  5. package/dist/coverage.js +81 -0
  6. package/dist/coverage.js.map +1 -0
  7. package/dist/cursor.d.ts +16 -0
  8. package/dist/cursor.d.ts.map +1 -0
  9. package/dist/cursor.js +28 -0
  10. package/dist/cursor.js.map +1 -0
  11. package/dist/dialect.d.ts +316 -0
  12. package/dist/dialect.d.ts.map +1 -0
  13. package/dist/dialect.js +135 -0
  14. package/dist/dialect.js.map +1 -0
  15. package/dist/engine.d.ts +203 -0
  16. package/dist/engine.d.ts.map +1 -0
  17. package/dist/engine.js +493 -0
  18. package/dist/engine.js.map +1 -0
  19. package/dist/index.d.ts +182 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +370 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/json.d.ts +49 -0
  24. package/dist/json.d.ts.map +1 -0
  25. package/dist/json.js +128 -0
  26. package/dist/json.js.map +1 -0
  27. package/dist/keywords/applicator.d.ts +57 -0
  28. package/dist/keywords/applicator.d.ts.map +1 -0
  29. package/dist/keywords/applicator.js +789 -0
  30. package/dist/keywords/applicator.js.map +1 -0
  31. package/dist/keywords/core.d.ts +42 -0
  32. package/dist/keywords/core.d.ts.map +1 -0
  33. package/dist/keywords/core.js +139 -0
  34. package/dist/keywords/core.js.map +1 -0
  35. package/dist/keywords/format.d.ts +43 -0
  36. package/dist/keywords/format.d.ts.map +1 -0
  37. package/dist/keywords/format.js +101 -0
  38. package/dist/keywords/format.js.map +1 -0
  39. package/dist/keywords/metaschemas2019.d.ts +3 -0
  40. package/dist/keywords/metaschemas2019.d.ts.map +1 -0
  41. package/dist/keywords/metaschemas2019.js +406 -0
  42. package/dist/keywords/metaschemas2019.js.map +1 -0
  43. package/dist/keywords/metaschemas2020.d.ts +3 -0
  44. package/dist/keywords/metaschemas2020.d.ts.map +1 -0
  45. package/dist/keywords/metaschemas2020.js +441 -0
  46. package/dist/keywords/metaschemas2020.js.map +1 -0
  47. package/dist/keywords/metaschemas6.d.ts +3 -0
  48. package/dist/keywords/metaschemas6.d.ts.map +1 -0
  49. package/dist/keywords/metaschemas6.js +221 -0
  50. package/dist/keywords/metaschemas6.js.map +1 -0
  51. package/dist/keywords/metaschemas7.d.ts +3 -0
  52. package/dist/keywords/metaschemas7.d.ts.map +1 -0
  53. package/dist/keywords/metaschemas7.js +248 -0
  54. package/dist/keywords/metaschemas7.js.map +1 -0
  55. package/dist/keywords/unevaluated.d.ts +14 -0
  56. package/dist/keywords/unevaluated.d.ts.map +1 -0
  57. package/dist/keywords/unevaluated.js +296 -0
  58. package/dist/keywords/unevaluated.js.map +1 -0
  59. package/dist/keywords/validation.d.ts +21 -0
  60. package/dist/keywords/validation.d.ts.map +1 -0
  61. package/dist/keywords/validation.js +332 -0
  62. package/dist/keywords/validation.js.map +1 -0
  63. package/dist/keywords/vocab2019.d.ts +36 -0
  64. package/dist/keywords/vocab2019.d.ts.map +1 -0
  65. package/dist/keywords/vocab2019.js +585 -0
  66. package/dist/keywords/vocab2019.js.map +1 -0
  67. package/dist/keywords/vocab2020.d.ts +20 -0
  68. package/dist/keywords/vocab2020.d.ts.map +1 -0
  69. package/dist/keywords/vocab2020.js +55 -0
  70. package/dist/keywords/vocab2020.js.map +1 -0
  71. package/dist/keywords/vocab7.d.ts +46 -0
  72. package/dist/keywords/vocab7.d.ts.map +1 -0
  73. package/dist/keywords/vocab7.js +305 -0
  74. package/dist/keywords/vocab7.js.map +1 -0
  75. package/dist/loader.d.ts +41 -0
  76. package/dist/loader.d.ts.map +1 -0
  77. package/dist/loader.js +13 -0
  78. package/dist/loader.js.map +1 -0
  79. package/dist/lowering.d.ts +371 -0
  80. package/dist/lowering.d.ts.map +1 -0
  81. package/dist/lowering.js +59 -0
  82. package/dist/lowering.js.map +1 -0
  83. package/dist/output.d.ts +251 -0
  84. package/dist/output.d.ts.map +1 -0
  85. package/dist/output.js +357 -0
  86. package/dist/output.js.map +1 -0
  87. package/dist/records.d.ts +71 -0
  88. package/dist/records.d.ts.map +1 -0
  89. package/dist/records.js +150 -0
  90. package/dist/records.js.map +1 -0
  91. package/dist/ref.d.ts +10 -0
  92. package/dist/ref.d.ts.map +1 -0
  93. package/dist/ref.js +5 -0
  94. package/dist/ref.js.map +1 -0
  95. package/dist/regex.d.ts +45 -0
  96. package/dist/regex.d.ts.map +1 -0
  97. package/dist/regex.js +163 -0
  98. package/dist/regex.js.map +1 -0
  99. package/dist/registry.d.ts +141 -0
  100. package/dist/registry.d.ts.map +1 -0
  101. package/dist/registry.js +407 -0
  102. package/dist/registry.js.map +1 -0
  103. package/dist/result.d.ts +109 -0
  104. package/dist/result.d.ts.map +1 -0
  105. package/dist/result.js +112 -0
  106. package/dist/result.js.map +1 -0
  107. package/dist/uri.d.ts +18 -0
  108. package/dist/uri.d.ts.map +1 -0
  109. package/dist/uri.js +27 -0
  110. package/dist/uri.js.map +1 -0
  111. package/dist/walk.d.ts +25 -0
  112. package/dist/walk.d.ts.map +1 -0
  113. package/dist/walk.js +65 -0
  114. package/dist/walk.js.map +1 -0
  115. package/package.json +32 -0
  116. package/src/coverage.ts +88 -0
  117. package/src/cursor.ts +42 -0
  118. package/src/dialect.ts +428 -0
  119. package/src/engine.ts +741 -0
  120. package/src/index.ts +699 -0
  121. package/src/json.ts +137 -0
  122. package/src/keywords/applicator.ts +895 -0
  123. package/src/keywords/core.ts +168 -0
  124. package/src/keywords/format.ts +143 -0
  125. package/src/keywords/metaschemas2019.ts +410 -0
  126. package/src/keywords/metaschemas2020.ts +443 -0
  127. package/src/keywords/metaschemas6.ts +226 -0
  128. package/src/keywords/metaschemas7.ts +253 -0
  129. package/src/keywords/unevaluated.ts +329 -0
  130. package/src/keywords/validation.ts +627 -0
  131. package/src/keywords/vocab2019.ts +676 -0
  132. package/src/keywords/vocab2020.ts +82 -0
  133. package/src/keywords/vocab7.ts +393 -0
  134. package/src/loader.ts +60 -0
  135. package/src/lowering.ts +414 -0
  136. package/src/output.ts +651 -0
  137. package/src/records.ts +226 -0
  138. package/src/ref.ts +14 -0
  139. package/src/regex.ts +171 -0
  140. package/src/registry.ts +481 -0
  141. package/src/result.ts +252 -0
  142. package/src/uri.ts +35 -0
  143. package/src/walk.ts +88 -0
@@ -0,0 +1,253 @@
1
+ // The draft-07 metaschema, vendored verbatim from
2
+ // https://json-schema.org/draft-07/schema (JSON Schema specification
3
+ // artifact, BSD-licensed). Same role as metaschemas2020.ts; the $id's empty
4
+ // fragment is stripped to the resource URI.
5
+ import { JsonValue } from "../json.js";
6
+
7
+ export const METASCHEMAS_DRAFT_07: readonly (readonly [string, JsonValue])[] = [
8
+ ["http://json-schema.org/draft-07/schema", {
9
+ "$schema": "http://json-schema.org/draft-07/schema#",
10
+ "$id": "http://json-schema.org/draft-07/schema#",
11
+ "title": "Core schema meta-schema",
12
+ "definitions": {
13
+ "schemaArray": {
14
+ "type": "array",
15
+ "minItems": 1,
16
+ "items": {
17
+ "$ref": "#"
18
+ }
19
+ },
20
+ "nonNegativeInteger": {
21
+ "type": "integer",
22
+ "minimum": 0
23
+ },
24
+ "nonNegativeIntegerDefault0": {
25
+ "allOf": [
26
+ {
27
+ "$ref": "#/definitions/nonNegativeInteger"
28
+ },
29
+ {
30
+ "default": 0
31
+ }
32
+ ]
33
+ },
34
+ "simpleTypes": {
35
+ "enum": [
36
+ "array",
37
+ "boolean",
38
+ "integer",
39
+ "null",
40
+ "number",
41
+ "object",
42
+ "string"
43
+ ]
44
+ },
45
+ "stringArray": {
46
+ "type": "array",
47
+ "items": {
48
+ "type": "string"
49
+ },
50
+ "uniqueItems": true,
51
+ "default": []
52
+ }
53
+ },
54
+ "type": [
55
+ "object",
56
+ "boolean"
57
+ ],
58
+ "properties": {
59
+ "$id": {
60
+ "type": "string",
61
+ "format": "uri-reference"
62
+ },
63
+ "$schema": {
64
+ "type": "string",
65
+ "format": "uri"
66
+ },
67
+ "$ref": {
68
+ "type": "string",
69
+ "format": "uri-reference"
70
+ },
71
+ "$comment": {
72
+ "type": "string"
73
+ },
74
+ "title": {
75
+ "type": "string"
76
+ },
77
+ "description": {
78
+ "type": "string"
79
+ },
80
+ "default": true,
81
+ "readOnly": {
82
+ "type": "boolean",
83
+ "default": false
84
+ },
85
+ "writeOnly": {
86
+ "type": "boolean",
87
+ "default": false
88
+ },
89
+ "examples": {
90
+ "type": "array",
91
+ "items": true
92
+ },
93
+ "multipleOf": {
94
+ "type": "number",
95
+ "exclusiveMinimum": 0
96
+ },
97
+ "maximum": {
98
+ "type": "number"
99
+ },
100
+ "exclusiveMaximum": {
101
+ "type": "number"
102
+ },
103
+ "minimum": {
104
+ "type": "number"
105
+ },
106
+ "exclusiveMinimum": {
107
+ "type": "number"
108
+ },
109
+ "maxLength": {
110
+ "$ref": "#/definitions/nonNegativeInteger"
111
+ },
112
+ "minLength": {
113
+ "$ref": "#/definitions/nonNegativeIntegerDefault0"
114
+ },
115
+ "pattern": {
116
+ "type": "string",
117
+ "format": "regex"
118
+ },
119
+ "additionalItems": {
120
+ "$ref": "#"
121
+ },
122
+ "items": {
123
+ "anyOf": [
124
+ {
125
+ "$ref": "#"
126
+ },
127
+ {
128
+ "$ref": "#/definitions/schemaArray"
129
+ }
130
+ ],
131
+ "default": true
132
+ },
133
+ "maxItems": {
134
+ "$ref": "#/definitions/nonNegativeInteger"
135
+ },
136
+ "minItems": {
137
+ "$ref": "#/definitions/nonNegativeIntegerDefault0"
138
+ },
139
+ "uniqueItems": {
140
+ "type": "boolean",
141
+ "default": false
142
+ },
143
+ "contains": {
144
+ "$ref": "#"
145
+ },
146
+ "maxProperties": {
147
+ "$ref": "#/definitions/nonNegativeInteger"
148
+ },
149
+ "minProperties": {
150
+ "$ref": "#/definitions/nonNegativeIntegerDefault0"
151
+ },
152
+ "required": {
153
+ "$ref": "#/definitions/stringArray"
154
+ },
155
+ "additionalProperties": {
156
+ "$ref": "#"
157
+ },
158
+ "definitions": {
159
+ "type": "object",
160
+ "additionalProperties": {
161
+ "$ref": "#"
162
+ },
163
+ "default": {}
164
+ },
165
+ "properties": {
166
+ "type": "object",
167
+ "additionalProperties": {
168
+ "$ref": "#"
169
+ },
170
+ "default": {}
171
+ },
172
+ "patternProperties": {
173
+ "type": "object",
174
+ "additionalProperties": {
175
+ "$ref": "#"
176
+ },
177
+ "propertyNames": {
178
+ "format": "regex"
179
+ },
180
+ "default": {}
181
+ },
182
+ "dependencies": {
183
+ "type": "object",
184
+ "additionalProperties": {
185
+ "anyOf": [
186
+ {
187
+ "$ref": "#"
188
+ },
189
+ {
190
+ "$ref": "#/definitions/stringArray"
191
+ }
192
+ ]
193
+ }
194
+ },
195
+ "propertyNames": {
196
+ "$ref": "#"
197
+ },
198
+ "const": true,
199
+ "enum": {
200
+ "type": "array",
201
+ "items": true,
202
+ "minItems": 1,
203
+ "uniqueItems": true
204
+ },
205
+ "type": {
206
+ "anyOf": [
207
+ {
208
+ "$ref": "#/definitions/simpleTypes"
209
+ },
210
+ {
211
+ "type": "array",
212
+ "items": {
213
+ "$ref": "#/definitions/simpleTypes"
214
+ },
215
+ "minItems": 1,
216
+ "uniqueItems": true
217
+ }
218
+ ]
219
+ },
220
+ "format": {
221
+ "type": "string"
222
+ },
223
+ "contentMediaType": {
224
+ "type": "string"
225
+ },
226
+ "contentEncoding": {
227
+ "type": "string"
228
+ },
229
+ "if": {
230
+ "$ref": "#"
231
+ },
232
+ "then": {
233
+ "$ref": "#"
234
+ },
235
+ "else": {
236
+ "$ref": "#"
237
+ },
238
+ "allOf": {
239
+ "$ref": "#/definitions/schemaArray"
240
+ },
241
+ "anyOf": {
242
+ "$ref": "#/definitions/schemaArray"
243
+ },
244
+ "oneOf": {
245
+ "$ref": "#/definitions/schemaArray"
246
+ },
247
+ "not": {
248
+ "$ref": "#"
249
+ }
250
+ },
251
+ "default": true
252
+ }],
253
+ ];
@@ -0,0 +1,329 @@
1
+ // Unevaluated vocabulary: the channel-consumer keyword class. Phase 1: runs
2
+ // after every other keyword in the same schema object has merged.
3
+
4
+ import { isObject } from "../json.js";
5
+ import { KeywordBehavior } from "../dialect.js";
6
+ import { LowerExpr, LowerStmt, lowerIR } from "../lowering.js";
7
+ import { childCursor } from "../cursor.js";
8
+ import { SELF } from "./core.js";
9
+ import {
10
+ properties,
11
+ patternProperties,
12
+ additionalProperties,
13
+ prefixItems,
14
+ items,
15
+ contains,
16
+ } from "./applicator.js";
17
+
18
+ /** 2020-12 unevaluated vocabulary URI. */
19
+ export const VOCAB_UNEVALUATED =
20
+ "https://json-schema.org/draft/2020-12/vocab/unevaluated";
21
+
22
+ /**
23
+ * EXEMPLAR (consumer class): reads visible dependency records (engine.ts
24
+ * visibility rule), applies the subschema to properties nobody evaluated,
25
+ * and produces like any other applicator.
26
+ */
27
+ export const unevaluatedProperties: KeywordBehavior = {
28
+ id: `${VOCAB_UNEVALUATED}#unevaluatedProperties`,
29
+ phase: 1,
30
+ analyze: () => ({
31
+ subschemas: SELF.subschemas,
32
+ consumes: [
33
+ properties.id,
34
+ patternProperties.id,
35
+ additionalProperties.id,
36
+ `${VOCAB_UNEVALUATED}#unevaluatedProperties`,
37
+ ],
38
+ produces: [`${VOCAB_UNEVALUATED}#unevaluatedProperties`],
39
+ evaluatesNames: { kind: "all" },
40
+ applications: [
41
+ { path: [], mode: "childSweep", conditional: false, asserts: true },
42
+ ],
43
+ }),
44
+ // Static-coverage path only (D9a): the planner classifies this schema
45
+ // object as interpreted when any coverage contributor is dynamic, so
46
+ // lower() is never called with a null coverage.
47
+ lower: (_value, lctx) => {
48
+ // Runtime-coverage path (phase B activates it): fold the unit's runtime
49
+ // channel into an evaluated-name set and sweep the names it does not
50
+ // cover, mirroring evaluate()'s seen-set skip. The object-gated
51
+ // collectedNames produce is the same one the static path emits — its
52
+ // attempted-apply accumulator is exactly the interpreter's `matched`.
53
+ if (lctx.runtimeCoverage()) {
54
+ const f = lctx.binding();
55
+ const b = lctx.binding();
56
+ lctx.emit(
57
+ lowerIR.when(lowerIR.typeIs(lctx.instance, "object"), [
58
+ { kind: "coverageFold", half: "names", binding: f },
59
+ {
60
+ kind: "forEachKey",
61
+ target: lctx.instance,
62
+ binding: b,
63
+ body: [
64
+ lowerIR.when(
65
+ lowerIR.not({
66
+ kind: "coverageCovers",
67
+ fold: f,
68
+ target: { kind: "binding", id: b },
69
+ }),
70
+ [
71
+ {
72
+ kind: "apply",
73
+ apply: {
74
+ path: [],
75
+ cursor: {
76
+ kind: "child",
77
+ of: { kind: "here" },
78
+ segment: { kind: "binding", id: b },
79
+ },
80
+ fold: "allMustPass",
81
+ },
82
+ },
83
+ ],
84
+ ),
85
+ ],
86
+ },
87
+ { kind: "produce", value: { kind: "collectedNames" } },
88
+ ]),
89
+ );
90
+ return;
91
+ }
92
+ const coverage = lctx.staticCoverage();
93
+ if (coverage === null) {
94
+ throw new Error(
95
+ "unevaluatedProperties lowering requires static coverage (planner bug)",
96
+ );
97
+ }
98
+ // The sweep is statically vacuous when sibling coverage is total, but the
99
+ // produce is not: evaluate() emits an (empty) names annotation for every
100
+ // object regardless. So the object-type guard always wraps a produce; the
101
+ // sweep is added only when some name can still be unevaluated.
102
+ const body: LowerStmt[] = [];
103
+ if (!coverage.coversAllNames) {
104
+ const b = lctx.binding();
105
+ const covered: LowerExpr[] = [
106
+ ...coverage.names.map((n): LowerExpr =>
107
+ lowerIR.cmp("===", { kind: "binding", id: b }, lowerIR.constant(n)),
108
+ ),
109
+ ...coverage.patterns.map((p): LowerExpr =>
110
+ lowerIR.regexTest(p, { kind: "binding", id: b }),
111
+ ),
112
+ ];
113
+ body.push({
114
+ kind: "forEachKey",
115
+ target: lctx.instance,
116
+ binding: b,
117
+ body: [
118
+ lowerIR.when(
119
+ covered.length === 0
120
+ ? lowerIR.constant(true)
121
+ : lowerIR.not(lowerIR.or(...covered)),
122
+ [
123
+ {
124
+ kind: "apply",
125
+ apply: {
126
+ path: [],
127
+ cursor: {
128
+ kind: "child",
129
+ of: { kind: "here" },
130
+ segment: { kind: "binding", id: b },
131
+ },
132
+ fold: "allMustPass",
133
+ },
134
+ },
135
+ ],
136
+ ),
137
+ ],
138
+ });
139
+ }
140
+ body.push({ kind: "produce", value: { kind: "collectedNames" } });
141
+ // Produce iff the instance is an object (nothing otherwise), matching
142
+ // evaluate()'s object-type guard before ctx.produce.
143
+ lctx.emit(lowerIR.when(lowerIR.typeIs(lctx.instance, "object"), body));
144
+ },
145
+ evaluate: (_value, cursor, ctx) => {
146
+ if (!isObject(cursor.value)) return true;
147
+ const seen = new Set<string>();
148
+ for (const p of ctx.visible([
149
+ properties.id,
150
+ patternProperties.id,
151
+ additionalProperties.id,
152
+ unevaluatedProperties.id,
153
+ ])) {
154
+ for (const name of p.data as string[]) seen.add(name);
155
+ }
156
+ let ok = true;
157
+ const matched: string[] = [];
158
+ for (const name of Object.keys(cursor.value)) {
159
+ if (seen.has(name)) continue;
160
+ matched.push(name);
161
+ if (
162
+ !ctx.apply(
163
+ ["unevaluatedProperties"],
164
+ childCursor(cursor, name, cursor.value[name]!),
165
+ )
166
+ )
167
+ ok = false;
168
+ }
169
+ // Dependency data only from an accepting keyword (Appendix D; see
170
+ // applicator.ts properties).
171
+ if (ok) ctx.produce(matched);
172
+ return ok;
173
+ },
174
+ };
175
+
176
+ /** Same consumer shape as {@link unevaluatedProperties}, over array indexes not covered by `prefixItems`/`items`/`contains`. */
177
+ export const unevaluatedItems: KeywordBehavior = {
178
+ id: `${VOCAB_UNEVALUATED}#unevaluatedItems`,
179
+ phase: 1,
180
+ analyze: () => ({
181
+ subschemas: SELF.subschemas,
182
+ consumes: [
183
+ prefixItems.id,
184
+ items.id,
185
+ contains.id,
186
+ `${VOCAB_UNEVALUATED}#unevaluatedItems`,
187
+ ],
188
+ produces: [`${VOCAB_UNEVALUATED}#unevaluatedItems`],
189
+ evaluatesIndexes: { kind: "all" },
190
+ applications: [
191
+ { path: [], mode: "childSweep", conditional: false, asserts: true },
192
+ ],
193
+ }),
194
+ // Static-coverage path only (D9a), same discipline as
195
+ // unevaluatedProperties: the planner classifies this schema object as
196
+ // interpreted whenever any index-coverage contributor is dynamic (e.g. a
197
+ // sibling `contains`, whose coverage is instance-dependent), so lower() is
198
+ // never called with a null/incomplete coverage.
199
+ lower: (_value, lctx) => {
200
+ // Runtime-coverage path (phase B activates it): fold the unit's runtime
201
+ // channel into a coveredPrefix/coveredIdx summary over this array's
202
+ // length and sweep the indexes it does not cover, mirroring evaluate()'s
203
+ // skip loop. The appliedTrue produce is the same one the static path
204
+ // emits (any attempted apply -> true, the interpreter's `applied` flag).
205
+ if (lctx.runtimeCoverage()) {
206
+ const f = lctx.binding();
207
+ const b = lctx.binding();
208
+ lctx.emit(
209
+ lowerIR.when(lowerIR.typeIs(lctx.instance, "array"), [
210
+ { kind: "coverageFold", half: "indexes", binding: f },
211
+ {
212
+ kind: "forEachIndex",
213
+ target: lctx.instance,
214
+ binding: b,
215
+ start: 0,
216
+ body: [
217
+ lowerIR.when(
218
+ lowerIR.not({
219
+ kind: "coverageCovers",
220
+ fold: f,
221
+ target: { kind: "binding", id: b },
222
+ }),
223
+ [
224
+ {
225
+ kind: "apply",
226
+ apply: {
227
+ path: [],
228
+ cursor: {
229
+ kind: "child",
230
+ of: { kind: "here" },
231
+ segment: { kind: "binding", id: b },
232
+ },
233
+ fold: "allMustPass",
234
+ },
235
+ },
236
+ ],
237
+ ),
238
+ ],
239
+ },
240
+ {
241
+ kind: "produce",
242
+ value: { kind: "collectedIndexes", render: "appliedTrue" },
243
+ },
244
+ ]),
245
+ );
246
+ return;
247
+ }
248
+ const coverage = lctx.staticCoverage();
249
+ if (coverage === null) {
250
+ throw new Error(
251
+ "unevaluatedItems lowering requires static coverage (planner bug)",
252
+ );
253
+ }
254
+ if (coverage.coversAllIndexes) return; // statically vacuous
255
+ const b = lctx.binding();
256
+ lctx.emit(
257
+ lowerIR.when(lowerIR.typeIs(lctx.instance, "array"), [
258
+ {
259
+ kind: "forEachIndex",
260
+ target: lctx.instance,
261
+ binding: b,
262
+ start: coverage.prefixCount,
263
+ body: [
264
+ {
265
+ kind: "apply",
266
+ apply: {
267
+ path: [],
268
+ cursor: {
269
+ kind: "child",
270
+ of: { kind: "here" },
271
+ segment: { kind: "binding", id: b },
272
+ },
273
+ fold: "allMustPass",
274
+ },
275
+ },
276
+ ],
277
+ },
278
+ ]),
279
+ );
280
+ // Annotation: true iff it applied to any unevaluated index.
281
+ lctx.emit({
282
+ kind: "produce",
283
+ value: { kind: "collectedIndexes", render: "appliedTrue" },
284
+ });
285
+ },
286
+ evaluate: (_value, cursor, ctx) => {
287
+ if (!Array.isArray(cursor.value)) return true;
288
+ const length = cursor.value.length;
289
+ let coveredPrefix = 0;
290
+ const coveredIdx = new Set<number>();
291
+ for (const p of ctx.visible([
292
+ prefixItems.id,
293
+ items.id,
294
+ contains.id,
295
+ unevaluatedItems.id,
296
+ ])) {
297
+ if (p.behaviorId === contains.id) {
298
+ if (p.data === true) coveredPrefix = length;
299
+ else for (const i of p.data as number[]) coveredIdx.add(i);
300
+ } else if (p.data === true) {
301
+ coveredPrefix = length;
302
+ } else if (p.behaviorId === prefixItems.id) {
303
+ coveredPrefix = Math.max(coveredPrefix, (p.data as number) + 1);
304
+ }
305
+ }
306
+ let ok = true;
307
+ let applied = false;
308
+ for (let i = coveredPrefix; i < length; i++) {
309
+ if (coveredIdx.has(i)) continue;
310
+ applied = true;
311
+ if (
312
+ !ctx.apply(
313
+ ["unevaluatedItems"],
314
+ childCursor(cursor, i, cursor.value[i]!),
315
+ )
316
+ ) {
317
+ ok = false;
318
+ }
319
+ }
320
+ if (applied && ok) ctx.produce(true);
321
+ return ok;
322
+ },
323
+ };
324
+
325
+ /** The 2020-12 unevaluated vocabulary's keyword behaviors, by name. */
326
+ export const unevaluatedVocabulary: Record<string, KeywordBehavior> = {
327
+ unevaluatedProperties,
328
+ unevaluatedItems,
329
+ };