@oh-my-pi/omptype 18.16.0 → 18.18.0

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.
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Compiles simple anchored `pattern`s into char-code checks for generated
3
+ * validators: `^[0-9a-f]{8}-…$`, `^\d{4}-\d{2}-\d{2}$`, `^[A-Za-z0-9._-]+$`.
4
+ *
5
+ * Supported: ASCII literals and escaped punctuation, `\d`, `\w`, non-negated
6
+ * ASCII classes, and quantifiers, with at most one variable-length segment.
7
+ * Fixed segments then pin every position (from the start before the variable
8
+ * segment, from the end after it), so the check is exact without
9
+ * backtracking. ASCII-only classes reject every UTF-16 surrogate, so counting
10
+ * code units agrees with the `u` flag's code points. Anything else returns
11
+ * `undefined` and stays a `RegExp`.
12
+ */
13
+ const DIGIT = [[48, 57]];
14
+ const WORD = [
15
+ [48, 57],
16
+ [65, 90],
17
+ [95, 95],
18
+ [97, 122],
19
+ ];
20
+ function isPunctuation(code) {
21
+ return code < 128 && !(code >= 48 && code <= 57) && !(code >= 65 && code <= 90) && !(code >= 97 && code <= 122);
22
+ }
23
+ /** Ranges of an escape (`\d`, `\w`, `\.`), or `undefined` when unsupported. */
24
+ function escapeRanges(code) {
25
+ if (code === undefined)
26
+ return undefined;
27
+ if (code === 100)
28
+ return DIGIT;
29
+ if (code === 119)
30
+ return WORD;
31
+ return isPunctuation(code) ? [[code, code]] : undefined;
32
+ }
33
+ function parseClass(body, start) {
34
+ const ranges = [];
35
+ let index = start;
36
+ if (body.charCodeAt(index) === 94)
37
+ return undefined; // negated
38
+ const single = () => {
39
+ const code = body.charCodeAt(index);
40
+ if (code === 92) {
41
+ const escaped = body.charCodeAt(index + 1);
42
+ if (!isPunctuation(escaped))
43
+ return undefined;
44
+ index += 2;
45
+ return escaped;
46
+ }
47
+ if (Number.isNaN(code) || code >= 128 || code === 91 || code === 93)
48
+ return undefined;
49
+ index++;
50
+ return code;
51
+ };
52
+ while (index < body.length && body.charCodeAt(index) !== 93) {
53
+ if (body.charCodeAt(index) === 92) {
54
+ const named = body.charCodeAt(index + 1);
55
+ if (named === 100 || named === 119) {
56
+ ranges.push(...escapeRanges(named));
57
+ index += 2;
58
+ continue;
59
+ }
60
+ }
61
+ const low = single();
62
+ if (low === undefined)
63
+ return undefined;
64
+ if (body.charCodeAt(index) === 45 && index + 1 < body.length && body.charCodeAt(index + 1) !== 93) {
65
+ index++;
66
+ const high = single();
67
+ if (high === undefined || high < low)
68
+ return undefined;
69
+ ranges.push([low, high]);
70
+ }
71
+ else {
72
+ ranges.push([low, low]);
73
+ }
74
+ }
75
+ if (index >= body.length || ranges.length === 0)
76
+ return undefined;
77
+ return { ranges, end: index + 1 };
78
+ }
79
+ function parseQuantifier(body, start) {
80
+ const code = body.charCodeAt(start);
81
+ let result;
82
+ if (code === 43)
83
+ result = { min: 1, max: Infinity, end: start + 1 };
84
+ else if (code === 42)
85
+ result = { min: 0, max: Infinity, end: start + 1 };
86
+ else if (code === 63)
87
+ result = { min: 0, max: 1, end: start + 1 };
88
+ else if (code === 123) {
89
+ const match = /^\{(\d+)(,(\d*))?\}/.exec(body.slice(start));
90
+ if (match === null)
91
+ return undefined;
92
+ const min = Number(match[1]);
93
+ const max = match[2] === undefined ? min : match[3] === "" ? Infinity : Number(match[3]);
94
+ if (max < min)
95
+ return undefined;
96
+ result = { min, max, end: start + match[0].length };
97
+ }
98
+ else {
99
+ return { min: 1, max: 1, end: start };
100
+ }
101
+ // Lazy and possessive-looking suffixes change nothing here but are rare; keep them as regexes.
102
+ return body.charCodeAt(result.end) === 63 || body.charCodeAt(result.end) === 43 ? undefined : result;
103
+ }
104
+ function parse(source) {
105
+ if (source.length < 2 || source.charCodeAt(0) !== 94 || source.charCodeAt(source.length - 1) !== 36) {
106
+ return undefined;
107
+ }
108
+ const body = source.slice(1, -1);
109
+ // `\$` at the end is a literal dollar followed by no anchor.
110
+ let slashes = 0;
111
+ for (let index = body.length - 1; index >= 0 && body.charCodeAt(index) === 92; index--)
112
+ slashes++;
113
+ if (slashes % 2 === 1)
114
+ return undefined;
115
+ const segments = [];
116
+ let index = 0;
117
+ while (index < body.length) {
118
+ const code = body.charCodeAt(index);
119
+ let ranges;
120
+ if (code === 91) {
121
+ const parsed = parseClass(body, index + 1);
122
+ if (parsed === undefined)
123
+ return undefined;
124
+ ranges = parsed.ranges;
125
+ index = parsed.end;
126
+ }
127
+ else if (code === 92) {
128
+ ranges = escapeRanges(body.charCodeAt(index + 1));
129
+ index += 2;
130
+ }
131
+ else if (code < 128 && !"()|.*+?{}^$]".includes(body[index])) {
132
+ ranges = [[code, code]];
133
+ index++;
134
+ }
135
+ if (ranges === undefined)
136
+ return undefined;
137
+ const quantifier = parseQuantifier(body, index);
138
+ if (quantifier === undefined)
139
+ return undefined;
140
+ index = quantifier.end;
141
+ segments.push({ ranges, min: quantifier.min, max: quantifier.max });
142
+ }
143
+ let variable = 0;
144
+ for (const segment of segments)
145
+ if (segment.min !== segment.max)
146
+ variable++;
147
+ return variable <= 1 ? segments : undefined;
148
+ }
149
+ function rangeTest(ranges) {
150
+ const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
151
+ const merged = [];
152
+ for (const [low, high] of sorted) {
153
+ const last = merged[merged.length - 1];
154
+ if (last !== undefined && low <= last[1] + 1)
155
+ last[1] = Math.max(last[1], high);
156
+ else
157
+ merged.push([low, high]);
158
+ }
159
+ return merged.map(([low, high]) => (low === high ? `c===${low}` : `(c>=${low}&&c<=${high})`)).join("||");
160
+ }
161
+ /** Statements checking positions `[from, to)` (`to` may be an expression) against `ranges`. */
162
+ function positions(ranges, from, to, count) {
163
+ const test = rangeTest(ranges);
164
+ if (count !== undefined && count <= 4) {
165
+ let out = "";
166
+ for (let offset = 0; offset < count; offset++) {
167
+ out += `c=s.charCodeAt(${from}+${offset});if(!(${test}))return false;`;
168
+ }
169
+ return out;
170
+ }
171
+ return `for(let i=${from};i<${to};i++){c=s.charCodeAt(i);if(!(${test}))return false;}`;
172
+ }
173
+ /**
174
+ * Source of `function name(s)` testing a string against `source` with the
175
+ * `u` flag's semantics, or `undefined` when the pattern is not simple.
176
+ */
177
+ export function compileSimplePattern(source, name) {
178
+ const segments = parse(source);
179
+ if (segments === undefined)
180
+ return undefined;
181
+ let fixed = 0;
182
+ let variable;
183
+ for (const segment of segments) {
184
+ if (segment.min === segment.max)
185
+ fixed += segment.min;
186
+ else
187
+ variable = segment;
188
+ }
189
+ let body = "const n=s.length;let c;";
190
+ if (variable === undefined)
191
+ body += `if(n!==${fixed})return false;`;
192
+ else {
193
+ body += `if(n<${fixed + variable.min})return false;`;
194
+ if (variable.max !== Infinity)
195
+ body += `if(n>${fixed + variable.max})return false;`;
196
+ }
197
+ // Positions before the variable segment count from 0, after it from the end.
198
+ let position = 0;
199
+ let fromEnd = false;
200
+ let suffix = 0;
201
+ if (variable !== undefined) {
202
+ for (let index = segments.length - 1; segments[index] !== variable; index--)
203
+ suffix += segments[index].min;
204
+ }
205
+ for (const segment of segments) {
206
+ if (segment === variable) {
207
+ body += positions(segment.ranges, String(position), `n-${suffix}`, undefined);
208
+ fromEnd = true;
209
+ continue;
210
+ }
211
+ if (segment.min === 0)
212
+ continue;
213
+ const start = fromEnd ? `n-${suffix}` : String(position);
214
+ body += positions(segment.ranges, start, `${start}+${segment.min}`, segment.min);
215
+ if (fromEnd)
216
+ suffix -= segment.min;
217
+ else
218
+ position += segment.min;
219
+ }
220
+ return `function ${name}(s){${body}return true}`;
221
+ }
@@ -0,0 +1,448 @@
1
+ /**
2
+ * Planner: lowers JSON Schema documents into a draft-independent {@link Plan}
3
+ * graph that both validator backends consume.
4
+ *
5
+ * Planning applies everything that does not depend on the instance: dialect
6
+ * differences (draft 7 `$ref` overriding its siblings, array-form `items`
7
+ * with `additionalItems`, `dependencies`), vocabularies, `$ref` resolution,
8
+ * and `$dynamicRef` resolution. The dynamic scope only matters through the
9
+ * outermost resource declaring each `$dynamicAnchor` name, so plans are
10
+ * memoized per (schema, scope) and recursion stays finite.
11
+ */
12
+ import { OmpTypeError } from "../errors.js";
13
+ import { VOCAB_APPLICATOR, VOCAB_UNEVALUATED, VOCAB_VALIDATION, } from "./resolve.js";
14
+ import { compilePattern, isInheritedName } from "./runtime.js";
15
+ export const T_NULL = 1;
16
+ export const T_BOOLEAN = 2;
17
+ export const T_INTEGER = 4;
18
+ /** Any finite number, integers included. */
19
+ export const T_NUMBER = 8;
20
+ export const T_STRING = 16;
21
+ export const T_ARRAY = 32;
22
+ export const T_OBJECT = 64;
23
+ export const T_ANY = 127;
24
+ /** Every number type: `type: "number"`. */
25
+ export const T_NUMERIC = T_NUMBER | T_INTEGER;
26
+ /** The single JSON kind a passing value of `types` has, as a mask; 0 when several. */
27
+ export function kindOf(types) {
28
+ if (types === 0)
29
+ return 0;
30
+ if ((types & T_NUMERIC) === types)
31
+ return types;
32
+ return (types & (types - 1)) === 0 ? types : 0;
33
+ }
34
+ const TYPE_FLAGS = {
35
+ null: T_NULL,
36
+ boolean: T_BOOLEAN,
37
+ integer: T_INTEGER,
38
+ number: T_NUMERIC,
39
+ string: T_STRING,
40
+ array: T_ARRAY,
41
+ object: T_OBJECT,
42
+ };
43
+ /** The `true` schema. */
44
+ export const TRUE_PLAN = { types: T_ANY, empty: true, flags: 0 };
45
+ /** The `false` schema. */
46
+ export const FALSE_PLAN = { types: 0, empty: false, flags: 0 };
47
+ export const F_CONST = 1;
48
+ export const F_ENUM = 2;
49
+ /** Numeric keywords. */
50
+ export const F_NUMBER = 4;
51
+ /** String keywords. */
52
+ export const F_STRING = 8;
53
+ /** Array keywords. */
54
+ export const F_ARRAY = 16;
55
+ /** `required` and `properties`. */
56
+ export const F_PROPERTIES = 32;
57
+ /** Keywords that visit every key: `patternProperties`, `additionalProperties`, `propertyNames`, property counts. */
58
+ export const F_KEYS = 64;
59
+ /** `dependentRequired`, `dependentSchemas`. */
60
+ export const F_DEPENDENT = 128;
61
+ export const F_REFS = 256;
62
+ /** `allOf`, `anyOf`, `oneOf`, `not`, `if`. */
63
+ export const F_APPLICATORS = 512;
64
+ /** `unevaluatedProperties`, `unevaluatedItems`. */
65
+ export const F_UNEVALUATED = 1024;
66
+ /** Keyword group of each constraining plan field; neutral fields map to 0. */
67
+ const FIELD_FLAGS = {
68
+ hasConst: F_CONST,
69
+ enumValues: F_ENUM,
70
+ multipleOf: F_NUMBER,
71
+ minimum: F_NUMBER,
72
+ maximum: F_NUMBER,
73
+ exclusiveMinimum: F_NUMBER,
74
+ exclusiveMaximum: F_NUMBER,
75
+ minLength: F_STRING,
76
+ maxLength: F_STRING,
77
+ pattern: F_STRING,
78
+ format: F_STRING,
79
+ minItems: F_ARRAY,
80
+ maxItems: F_ARRAY,
81
+ uniqueItems: F_ARRAY,
82
+ prefixItems: F_ARRAY,
83
+ items: F_ARRAY,
84
+ contains: F_ARRAY,
85
+ minContains: F_ARRAY,
86
+ maxContains: F_ARRAY,
87
+ required: F_PROPERTIES,
88
+ properties: F_PROPERTIES,
89
+ patternProperties: F_KEYS,
90
+ additionalProperties: F_KEYS,
91
+ propertyNames: F_KEYS,
92
+ minProperties: F_KEYS,
93
+ maxProperties: F_KEYS,
94
+ dependentRequired: F_DEPENDENT,
95
+ dependentSchemas: F_DEPENDENT,
96
+ refs: F_REFS,
97
+ allOf: F_APPLICATORS,
98
+ anyOf: F_APPLICATORS,
99
+ oneOf: F_APPLICATORS,
100
+ not: F_APPLICATORS,
101
+ if: F_APPLICATORS,
102
+ ifThen: F_APPLICATORS,
103
+ ifElse: F_APPLICATORS,
104
+ unevaluatedProperties: F_UNEVALUATED,
105
+ unevaluatedItems: F_UNEVALUATED,
106
+ };
107
+ const EMPTY_SCOPE = { key: "", anchors: new Map() };
108
+ function enter(scope, resource) {
109
+ if (resource.dynamicAnchors.size === 0)
110
+ return scope;
111
+ let anchors;
112
+ for (const name of resource.dynamicAnchors.keys()) {
113
+ if (scope.anchors.has(name))
114
+ continue;
115
+ anchors ??= new Map(scope.anchors);
116
+ anchors.set(name, resource);
117
+ }
118
+ if (anchors === undefined)
119
+ return scope;
120
+ const key = [...anchors]
121
+ .map(([name, owner]) => `${name}\u0001${owner.uri}`)
122
+ .sort()
123
+ .join("\u0002");
124
+ return { key, anchors };
125
+ }
126
+ function isNode(value) {
127
+ return typeof value === "object" && value !== null && !Array.isArray(value);
128
+ }
129
+ function stringList(value) {
130
+ if (!Array.isArray(value))
131
+ return undefined;
132
+ for (const item of value)
133
+ if (typeof item !== "string")
134
+ return undefined;
135
+ return value;
136
+ }
137
+ /** Lowers one registry's documents into plans. */
138
+ export class Planner {
139
+ #registry;
140
+ #formats;
141
+ /** Plans in the empty dynamic scope (almost all of them), by schema. */
142
+ #memo = new Map();
143
+ /** Plans under `$dynamicAnchor` scopes, by scope key then schema. */
144
+ #scoped = new Map();
145
+ /** Whether any `properties` subschema declares a `default`. */
146
+ sawDefaults = false;
147
+ constructor(registry, formats) {
148
+ this.#registry = registry;
149
+ this.#formats = formats;
150
+ }
151
+ /** Plan the registry's root document. */
152
+ root(schema) {
153
+ const target = this.#registry.addRoot(schema);
154
+ return this.#build(target.schema, target.location, enter(EMPTY_SCOPE, target.location.resource));
155
+ }
156
+ #build(schema, location, scope) {
157
+ if (schema === true)
158
+ return TRUE_PLAN;
159
+ if (schema === false)
160
+ return FALSE_PLAN;
161
+ if (!isNode(schema))
162
+ throw new OmpTypeError("JSON Schema nodes must be booleans or objects");
163
+ let memo = this.#memo;
164
+ if (scope.key !== "") {
165
+ let scoped = this.#scoped.get(scope.key);
166
+ if (scoped === undefined) {
167
+ scoped = new Map();
168
+ this.#scoped.set(scope.key, scoped);
169
+ }
170
+ memo = scoped;
171
+ }
172
+ const known = memo.get(schema);
173
+ if (known !== undefined)
174
+ return known;
175
+ const plan = { types: T_ANY, empty: false, flags: 0 };
176
+ memo.set(schema, plan);
177
+ this.#fill(plan, schema, location, scope);
178
+ let flags = 0;
179
+ for (const field in plan)
180
+ flags |= FIELD_FLAGS[field] ?? 0;
181
+ if (flags === 0) {
182
+ // Type-only schemas (`{ "type": "string", "description": … }`) have no subschemas, so
183
+ // nothing captured `plan` while filling it; every one of them shares a plan per mask.
184
+ const shared = typePlan(plan.types);
185
+ memo.set(schema, shared);
186
+ return shared;
187
+ }
188
+ plan.flags = flags;
189
+ return plan;
190
+ }
191
+ /** Plan a subschema reached structurally from `parent`. */
192
+ #child(schema, parent, scope) {
193
+ if (!isNode(schema))
194
+ return this.#build(schema, parent, scope);
195
+ const location = this.#registry.location(schema) ?? parent;
196
+ return this.#build(schema, location, location.resource === parent.resource ? scope : enter(scope, location.resource));
197
+ }
198
+ #ref(ref, location, scope) {
199
+ const target = this.#registry.resolve(ref, location.base);
200
+ return this.#build(target.schema, target.location, enter(scope, target.location.resource));
201
+ }
202
+ /**
203
+ * `$dynamicRef` resolves like `$ref` unless its initial target declares the
204
+ * fragment as a `$dynamicAnchor`; then the outermost resource in scope
205
+ * declaring that anchor wins.
206
+ */
207
+ #dynamicRef(ref, location, scope) {
208
+ const target = this.#registry.resolve(ref, location.base);
209
+ const hash = ref.indexOf("#");
210
+ const name = hash < 0 ? "" : ref.slice(hash + 1);
211
+ if (name === "" || name.charCodeAt(0) === 47 || !isNode(target.schema) || target.schema.$dynamicAnchor !== name) {
212
+ return this.#build(target.schema, target.location, enter(scope, target.location.resource));
213
+ }
214
+ const outermost = scope.anchors.get(name);
215
+ const dynamic = outermost?.dynamicAnchors.get(name);
216
+ if (outermost === undefined || dynamic === undefined) {
217
+ return this.#build(target.schema, target.location, enter(scope, target.location.resource));
218
+ }
219
+ const dynamicLocation = this.#registry.location(dynamic) ?? { base: outermost.uri, resource: outermost };
220
+ return this.#build(dynamic, dynamicLocation, enter(scope, outermost));
221
+ }
222
+ /** Lower the keywords `node` declares; one pass over its own keys. */
223
+ #fill(plan, node, location, scope) {
224
+ const { draft, vocab } = location.resource;
225
+ if (typeof node.$ref === "string") {
226
+ plan.refs = [this.#ref(node.$ref, location, scope)];
227
+ // Draft 7: `$ref` overrides every sibling keyword.
228
+ if (draft === "7")
229
+ return;
230
+ }
231
+ const validation = (vocab & VOCAB_VALIDATION) !== 0;
232
+ const applicator = (vocab & VOCAB_APPLICATOR) !== 0;
233
+ const unevaluated = draft !== "7" && (vocab & VOCAB_UNEVALUATED) !== 0;
234
+ for (const key in node) {
235
+ const value = node[key];
236
+ switch (key) {
237
+ case "$dynamicRef":
238
+ if (draft === "2020-12" && typeof value === "string") {
239
+ (plan.refs ??= []).push(this.#dynamicRef(value, location, scope));
240
+ }
241
+ break;
242
+ case "type":
243
+ if (validation)
244
+ plan.types = typeFlags(value);
245
+ break;
246
+ case "const":
247
+ if (validation) {
248
+ plan.hasConst = true;
249
+ plan.constValue = value;
250
+ }
251
+ break;
252
+ case "enum":
253
+ if (validation && Array.isArray(value))
254
+ plan.enumValues = value;
255
+ break;
256
+ case "multipleOf":
257
+ if (validation && typeof value === "number" && value > 0)
258
+ plan.multipleOf = value;
259
+ break;
260
+ case "minimum":
261
+ case "maximum":
262
+ case "exclusiveMinimum":
263
+ case "exclusiveMaximum":
264
+ if (validation && typeof value === "number" && Number.isFinite(value))
265
+ plan[key] = value;
266
+ break;
267
+ case "minLength":
268
+ case "maxLength":
269
+ case "minItems":
270
+ case "maxItems":
271
+ case "minProperties":
272
+ case "maxProperties":
273
+ if (validation && typeof value === "number" && value >= 0)
274
+ plan[key] = value;
275
+ break;
276
+ case "minContains":
277
+ case "maxContains":
278
+ if (validation && draft !== "7" && typeof value === "number" && value >= 0)
279
+ plan[key] = value;
280
+ break;
281
+ case "pattern":
282
+ if (validation && typeof value === "string")
283
+ plan.pattern = pattern(value);
284
+ break;
285
+ case "uniqueItems":
286
+ if (validation && value === true)
287
+ plan.uniqueItems = true;
288
+ break;
289
+ case "required": {
290
+ const required = validation ? stringList(value) : undefined;
291
+ if (required !== undefined && required.length > 0)
292
+ plan.required = required;
293
+ break;
294
+ }
295
+ case "dependentRequired":
296
+ case "dependencies":
297
+ if (!isNode(value))
298
+ break;
299
+ for (const property in value) {
300
+ const dependency = value[property];
301
+ if (Array.isArray(dependency)) {
302
+ const required = validation ? stringList(dependency) : undefined;
303
+ if (required !== undefined && required.length > 0) {
304
+ (plan.dependentRequired ??= []).push({ key: property, required });
305
+ }
306
+ }
307
+ else if (applicator &&
308
+ key === "dependencies" &&
309
+ (isNode(dependency) || typeof dependency === "boolean")) {
310
+ (plan.dependentSchemas ??= []).push({
311
+ key: property,
312
+ plan: this.#child(dependency, location, scope),
313
+ inherited: isInheritedName(property),
314
+ });
315
+ }
316
+ }
317
+ break;
318
+ case "dependentSchemas":
319
+ if (!applicator || !isNode(value))
320
+ break;
321
+ for (const property in value) {
322
+ (plan.dependentSchemas ??= []).push({
323
+ key: property,
324
+ plan: this.#child(value[property], location, scope),
325
+ inherited: isInheritedName(property),
326
+ });
327
+ }
328
+ break;
329
+ case "format": {
330
+ const check = typeof value === "string" ? this.#formats?.[value] : undefined;
331
+ if (check !== undefined) {
332
+ plan.format = check;
333
+ plan.formatName = value;
334
+ }
335
+ break;
336
+ }
337
+ case "allOf":
338
+ case "anyOf":
339
+ case "oneOf":
340
+ if (applicator && Array.isArray(value) && value.length > 0) {
341
+ plan[key] = value.map(branch => this.#child(branch, location, scope));
342
+ }
343
+ break;
344
+ case "not":
345
+ if (applicator)
346
+ plan.not = this.#child(value, location, scope);
347
+ break;
348
+ case "if":
349
+ if (!applicator)
350
+ break;
351
+ plan.if = this.#child(value, location, scope);
352
+ if (node.then !== undefined)
353
+ plan.ifThen = this.#child(node.then, location, scope);
354
+ if (node.else !== undefined)
355
+ plan.ifElse = this.#child(node.else, location, scope);
356
+ break;
357
+ case "properties":
358
+ if (applicator && isNode(value))
359
+ this.#properties(plan, value, location, scope);
360
+ break;
361
+ case "patternProperties":
362
+ if (!applicator || !isNode(value))
363
+ break;
364
+ for (const source in value) {
365
+ (plan.patternProperties ??= []).push({
366
+ pattern: pattern(source),
367
+ plan: this.#child(value[source], location, scope),
368
+ });
369
+ }
370
+ break;
371
+ case "additionalProperties":
372
+ case "propertyNames":
373
+ case "contains":
374
+ if (applicator)
375
+ plan[key] = this.#child(value, location, scope);
376
+ break;
377
+ case "items":
378
+ if (!applicator)
379
+ break;
380
+ if (Array.isArray(value)) {
381
+ // Draft 7 tuples (and 2019-09): `additionalItems` covers the rest.
382
+ plan.prefixItems = value.map(item => this.#child(item, location, scope));
383
+ if (node.additionalItems !== undefined) {
384
+ plan.items = this.#child(node.additionalItems, location, scope);
385
+ }
386
+ }
387
+ else {
388
+ plan.items = this.#child(value, location, scope);
389
+ }
390
+ break;
391
+ case "prefixItems":
392
+ if (applicator && draft === "2020-12" && Array.isArray(value) && !Array.isArray(node.items)) {
393
+ plan.prefixItems = value.map(item => this.#child(item, location, scope));
394
+ }
395
+ break;
396
+ case "unevaluatedProperties":
397
+ case "unevaluatedItems":
398
+ if (unevaluated)
399
+ plan[key] = this.#child(value, location, scope);
400
+ break;
401
+ }
402
+ }
403
+ }
404
+ #properties(plan, properties, location, scope) {
405
+ const entries = [];
406
+ for (const key in properties) {
407
+ const value = properties[key];
408
+ entries.push({ key, plan: this.#child(value, location, scope), inherited: isInheritedName(key) });
409
+ if (isNode(value) && "default" in value) {
410
+ (plan.defaults ??= []).push({ key, value: value.default });
411
+ this.sawDefaults = true;
412
+ }
413
+ }
414
+ if (entries.length > 0)
415
+ plan.properties = entries;
416
+ }
417
+ }
418
+ const TYPE_PLANS = new Map([
419
+ [T_ANY, TRUE_PLAN],
420
+ [0, FALSE_PLAN],
421
+ ]);
422
+ /** The shared plan of a schema constraining nothing but its type. */
423
+ function typePlan(types) {
424
+ let plan = TYPE_PLANS.get(types);
425
+ if (plan === undefined) {
426
+ plan = { types, empty: false, flags: 0 };
427
+ TYPE_PLANS.set(types, plan);
428
+ }
429
+ return plan;
430
+ }
431
+ function typeFlags(value) {
432
+ if (typeof value === "string")
433
+ return TYPE_FLAGS[value] ?? T_ANY;
434
+ if (!Array.isArray(value))
435
+ return T_ANY;
436
+ let types = 0;
437
+ for (const name of value)
438
+ types |= typeof name === "string" ? (TYPE_FLAGS[name] ?? 0) : 0;
439
+ return types;
440
+ }
441
+ function pattern(source) {
442
+ try {
443
+ return compilePattern(source);
444
+ }
445
+ catch {
446
+ throw new OmpTypeError(`invalid pattern: ${source}`);
447
+ }
448
+ }