@oh-my-pi/omptype 17.2.7 → 17.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/dist/js/compile.js +414 -219
- package/dist/js/errors.js +236 -41
- package/dist/js/from-json-schema.js +234 -0
- package/dist/js/index.js +1 -0
- package/dist/js/interp.js +514 -132
- package/dist/js/ir.js +972 -202
- package/dist/js/json-schema.js +73 -34
- package/dist/js/keywords.js +108 -1
- package/dist/js/type.js +2156 -172
- package/dist/js/typebox.js +41 -32
- package/dist/js/zod.js +2 -2
- package/dist/types/compile.d.ts +1 -1
- package/dist/types/errors.d.ts +19 -18
- package/dist/types/from-json-schema.d.ts +9 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/infer.d.ts +45 -6
- package/dist/types/interp.d.ts +9 -2
- package/dist/types/ir.d.ts +57 -6
- package/dist/types/json-schema.d.ts +10 -1
- package/dist/types/type.d.ts +324 -48
- package/dist/types/typebox.d.ts +78 -50
- package/package.json +5 -1
- package/src/compile.ts +598 -243
- package/src/errors.ts +247 -49
- package/src/from-json-schema.ts +231 -0
- package/src/index.ts +1 -0
- package/src/infer.ts +107 -32
- package/src/interp.ts +457 -118
- package/src/ir.ts +981 -212
- package/src/json-schema.ts +82 -34
- package/src/keywords.ts +111 -1
- package/src/type.ts +2760 -271
- package/src/typebox.ts +141 -98
- package/src/zod.ts +1 -1
package/dist/js/type.js
CHANGED
|
@@ -13,21 +13,122 @@
|
|
|
13
13
|
import { compile, compileAllows } from "./compile.js";
|
|
14
14
|
import { OmpErrors, OmpTypeError, TraversalError } from "./errors.js";
|
|
15
15
|
import { walk } from "./interp.js";
|
|
16
|
-
import { embed, expectedOf, hasMorph, IR_BRAND, keyOf, parseDef, } from "./ir.js";
|
|
16
|
+
import { distributeFilter, embed, expectedOf, hasMorph, IR_BRAND, isSimpleIR, keyOf, markThisOnlyResolver, parseDef, useAssignability, } from "./ir.js";
|
|
17
17
|
import { irToJsonSchema } from "./json-schema.js";
|
|
18
18
|
import { keywordIR, patternIR } from "./keywords.js";
|
|
19
|
+
// `Extract`/`Exclude` in the string DSL need assignability, which is defined here.
|
|
20
|
+
useAssignability(isSubtype);
|
|
19
21
|
/** Runtime constructor-like value used by ArkType-compatible `instanceof Type` checks. */
|
|
20
22
|
export const Type = Object.defineProperty(function Type() { }, Symbol.hasInstance, {
|
|
21
23
|
value: (value) => (typeof value === "function" || (typeof value === "object" && value !== null)) && IR_BRAND in value,
|
|
22
24
|
});
|
|
25
|
+
function descriptionOf(ir, seen = new Set()) {
|
|
26
|
+
if (ir.desc !== undefined)
|
|
27
|
+
return ir.desc;
|
|
28
|
+
if (seen.has(ir))
|
|
29
|
+
return ir.k === "alias" ? ir.name : expectedOf(ir);
|
|
30
|
+
seen.add(ir);
|
|
31
|
+
if (ir.k === "alias")
|
|
32
|
+
return descriptionOf(ir.resolve(), seen);
|
|
33
|
+
if (ir.k === "object") {
|
|
34
|
+
return `{ ${ir.props.map(prop => `${String(prop.key)}${prop.opt ? "?" : ""}: ${descriptionOf(prop.val, seen)}`).join(", ")} }`;
|
|
35
|
+
}
|
|
36
|
+
return expectedOf(ir);
|
|
37
|
+
}
|
|
38
|
+
function errorConfigOf(config) {
|
|
39
|
+
return {
|
|
40
|
+
...(config.description === undefined || config.expected !== undefined ? {} : { expected: config.description }),
|
|
41
|
+
...(config.expected === undefined ? {} : { expected: config.expected }),
|
|
42
|
+
...(config.actual === undefined ? {} : { actual: config.actual }),
|
|
43
|
+
...(config.problem === undefined ? {} : { problem: config.problem }),
|
|
44
|
+
...(config.message === undefined ? {} : { message: config.message }),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function configureNode(ir, config) {
|
|
48
|
+
return {
|
|
49
|
+
...ir,
|
|
50
|
+
cfg: { ...ir.cfg, ...errorConfigOf(config) },
|
|
51
|
+
...(config.description === undefined ? {} : { desc: config.description }),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function configureSelected(ir, config, selector) {
|
|
55
|
+
const domain = ir.k === "string" || ir.k === "number" || ir.k === "boolean" || ir.k === "bigint" || ir.k === "symbol"
|
|
56
|
+
? ir.k
|
|
57
|
+
: ir.k === "object" || ir.k === "array" || ir.k === "tuple" || ir.k === "instance" || ir.k === "anyobject"
|
|
58
|
+
? "object"
|
|
59
|
+
: undefined;
|
|
60
|
+
const kind = ir.k === "number" && ir.divisor !== undefined ? "divisor" : "domain";
|
|
61
|
+
if ((selector.kind === undefined || selector.kind === kind) &&
|
|
62
|
+
(selector.where === undefined || selector.where({ kind, domain }))) {
|
|
63
|
+
return configureNode(ir, config);
|
|
64
|
+
}
|
|
65
|
+
switch (ir.k) {
|
|
66
|
+
case "array":
|
|
67
|
+
return { ...ir, el: configureSelected(ir.el, config, selector) };
|
|
68
|
+
case "tuple":
|
|
69
|
+
return {
|
|
70
|
+
...ir,
|
|
71
|
+
prefix: ir.prefix.map(item => ({ ...item, val: configureSelected(item.val, config, selector) })),
|
|
72
|
+
...(ir.variadic === undefined ? {} : { variadic: configureSelected(ir.variadic, config, selector) }),
|
|
73
|
+
postfix: ir.postfix.map(item => configureSelected(item, config, selector)),
|
|
74
|
+
};
|
|
75
|
+
case "object":
|
|
76
|
+
return {
|
|
77
|
+
...ir,
|
|
78
|
+
props: ir.props.map(prop => ({ ...prop, val: configureSelected(prop.val, config, selector) })),
|
|
79
|
+
...(ir.index === undefined ? {} : { index: configureSelected(ir.index, config, selector) }),
|
|
80
|
+
symbolIndex: ir.symbolIndex === undefined ? undefined : configureSelected(ir.symbolIndex, config, selector),
|
|
81
|
+
patternIndexes: ir.patternIndexes?.map(index => ({
|
|
82
|
+
key: configureSelected(index.key, config, selector),
|
|
83
|
+
val: configureSelected(index.val, config, selector),
|
|
84
|
+
})),
|
|
85
|
+
};
|
|
86
|
+
case "union":
|
|
87
|
+
case "intersection":
|
|
88
|
+
return { ...ir, members: ir.members.map(member => configureSelected(member, config, selector)) };
|
|
89
|
+
case "refine":
|
|
90
|
+
return { ...ir, base: configureSelected(ir.base, config, selector) };
|
|
91
|
+
case "morph":
|
|
92
|
+
return {
|
|
93
|
+
...ir,
|
|
94
|
+
input: configureSelected(ir.input, config, selector),
|
|
95
|
+
...(ir.out === undefined ? {} : { out: configureSelected(ir.out, config, selector) }),
|
|
96
|
+
};
|
|
97
|
+
default:
|
|
98
|
+
return ir;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
23
101
|
class Ctx {
|
|
24
102
|
expectation;
|
|
103
|
+
errors;
|
|
104
|
+
path;
|
|
105
|
+
#data;
|
|
106
|
+
constructor(data, path = []) {
|
|
107
|
+
this.#data = data;
|
|
108
|
+
this.path = path.map(key => (typeof key === "symbol" ? String(key) : key));
|
|
109
|
+
}
|
|
110
|
+
error(input) {
|
|
111
|
+
const detail = typeof input === "string" ? { expected: input } : input;
|
|
112
|
+
const error = OmpErrors.single([...(detail.path ?? detail.relativePath ?? [])], detail.expected, this.#data, {
|
|
113
|
+
preserveActual: true,
|
|
114
|
+
...(Object.hasOwn(detail, "actual") ? { actual: String(detail.actual) } : {}),
|
|
115
|
+
});
|
|
116
|
+
if (this.errors)
|
|
117
|
+
this.errors.append(error);
|
|
118
|
+
else
|
|
119
|
+
this.errors = error;
|
|
120
|
+
return error;
|
|
121
|
+
}
|
|
25
122
|
mustBe(expectation) {
|
|
26
123
|
this.expectation = expectation;
|
|
27
124
|
return false;
|
|
28
125
|
}
|
|
29
|
-
reject(
|
|
30
|
-
|
|
126
|
+
reject(input) {
|
|
127
|
+
if (typeof input === "string") {
|
|
128
|
+
this.expectation = input;
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
this.error(input);
|
|
31
132
|
return false;
|
|
32
133
|
}
|
|
33
134
|
}
|
|
@@ -35,6 +136,7 @@ const kBase = Symbol("omptype.base");
|
|
|
35
136
|
const kSteps = Symbol("omptype.steps");
|
|
36
137
|
const EMPTY_STEPS = [];
|
|
37
138
|
const EMPTY_META = {};
|
|
139
|
+
const ARK_COMPAT_SCOPE = Object.freeze({ internal: Object.freeze({ name: "ark" }) });
|
|
38
140
|
/** Calls before the JIT compiles a schema (first two run the interpreter). */
|
|
39
141
|
const JIT_THRESHOLD = 3;
|
|
40
142
|
function metaOf(schema) {
|
|
@@ -42,54 +144,183 @@ function metaOf(schema) {
|
|
|
42
144
|
description: schema.description,
|
|
43
145
|
defaultValue: schema.defaultValue,
|
|
44
146
|
hasDefault: schema.hasDefault,
|
|
147
|
+
defaultOutput: schema.defaultOutput,
|
|
148
|
+
hasDefaultOutput: schema.hasDefaultOutput,
|
|
45
149
|
errorConfig: schema.errorConfig,
|
|
150
|
+
clone: schema.clone,
|
|
46
151
|
};
|
|
47
152
|
}
|
|
153
|
+
function inheritScope(source, target) {
|
|
154
|
+
if (source.resolver === undefined)
|
|
155
|
+
return target;
|
|
156
|
+
target.resolver = source.resolver;
|
|
157
|
+
Reflect.set(target, "$", source.$);
|
|
158
|
+
return target;
|
|
159
|
+
}
|
|
160
|
+
function invalidDefault(label, errors) {
|
|
161
|
+
const error = errors[0];
|
|
162
|
+
let heading = label;
|
|
163
|
+
for (let index = 0; index < error.path.length; index++) {
|
|
164
|
+
const segment = error.path[index];
|
|
165
|
+
if (typeof segment === "number") {
|
|
166
|
+
if (label === "Default" && index === 0)
|
|
167
|
+
heading = "Default value";
|
|
168
|
+
heading += ` at [${segment}]`;
|
|
169
|
+
}
|
|
170
|
+
else if (label === "Default" && index === 0) {
|
|
171
|
+
heading += ` ${String(segment)}`;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
heading += `.${String(segment)}`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
throw new OmpTypeError(`ParseError: ${heading} ${error.problem}`);
|
|
178
|
+
}
|
|
179
|
+
function rejectMutableStaticDefault(value) {
|
|
180
|
+
if (value !== null && typeof value === "object" && !(value instanceof Date)) {
|
|
181
|
+
throw new OmpTypeError("ParseError: A mutable default value must be specified as a factory");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function normalizeDefaults(ir, seen = new WeakSet()) {
|
|
185
|
+
if (seen.has(ir))
|
|
186
|
+
return;
|
|
187
|
+
seen.add(ir);
|
|
188
|
+
switch (ir.k) {
|
|
189
|
+
case "object":
|
|
190
|
+
for (const prop of ir.props) {
|
|
191
|
+
normalizeDefaults(prop.val, seen);
|
|
192
|
+
if (!prop.hasDefault || prop.defValidated)
|
|
193
|
+
continue;
|
|
194
|
+
let candidate;
|
|
195
|
+
let factory = false;
|
|
196
|
+
if (prop.defFactory && typeof prop.def === "function") {
|
|
197
|
+
candidate = prop.def();
|
|
198
|
+
factory = true;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
rejectMutableStaticDefault(prop.def);
|
|
202
|
+
candidate = prop.def;
|
|
203
|
+
}
|
|
204
|
+
const output = walk(prop.val, candidate);
|
|
205
|
+
if (output instanceof OmpErrors)
|
|
206
|
+
invalidDefault(`Default for ${String(prop.key)}`, output);
|
|
207
|
+
if (!factory)
|
|
208
|
+
prop.def = output;
|
|
209
|
+
prop.defValidated = true;
|
|
210
|
+
}
|
|
211
|
+
if (ir.index)
|
|
212
|
+
normalizeDefaults(ir.index, seen);
|
|
213
|
+
if (ir.symbolIndex)
|
|
214
|
+
normalizeDefaults(ir.symbolIndex, seen);
|
|
215
|
+
if (ir.patternIndexes) {
|
|
216
|
+
for (const index of ir.patternIndexes) {
|
|
217
|
+
normalizeDefaults(index.key, seen);
|
|
218
|
+
normalizeDefaults(index.val, seen);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return;
|
|
222
|
+
case "tuple":
|
|
223
|
+
for (let index = 0; index < ir.prefix.length; index++) {
|
|
224
|
+
const item = ir.prefix[index];
|
|
225
|
+
normalizeDefaults(item.val, seen);
|
|
226
|
+
if (!item.hasDefault || item.defValidated)
|
|
227
|
+
continue;
|
|
228
|
+
let candidate;
|
|
229
|
+
let factory = false;
|
|
230
|
+
if (item.defFactory && typeof item.def === "function") {
|
|
231
|
+
candidate = item.def();
|
|
232
|
+
factory = true;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
rejectMutableStaticDefault(item.def);
|
|
236
|
+
candidate = item.def;
|
|
237
|
+
}
|
|
238
|
+
const output = walk(item.val, candidate);
|
|
239
|
+
if (output instanceof OmpErrors)
|
|
240
|
+
invalidDefault(`Default for [${index}]`, output);
|
|
241
|
+
if (!factory)
|
|
242
|
+
item.def = output;
|
|
243
|
+
item.defValidated = true;
|
|
244
|
+
}
|
|
245
|
+
if (ir.variadic)
|
|
246
|
+
normalizeDefaults(ir.variadic, seen);
|
|
247
|
+
for (const item of ir.postfix)
|
|
248
|
+
normalizeDefaults(item, seen);
|
|
249
|
+
return;
|
|
250
|
+
case "array":
|
|
251
|
+
normalizeDefaults(ir.el, seen);
|
|
252
|
+
return;
|
|
253
|
+
case "union":
|
|
254
|
+
case "intersection":
|
|
255
|
+
for (const member of ir.members)
|
|
256
|
+
normalizeDefaults(member, seen);
|
|
257
|
+
return;
|
|
258
|
+
case "refine":
|
|
259
|
+
normalizeDefaults(ir.base, seen);
|
|
260
|
+
return;
|
|
261
|
+
case "morph":
|
|
262
|
+
normalizeDefaults(ir.input, seen);
|
|
263
|
+
if (ir.out)
|
|
264
|
+
normalizeDefaults(ir.out, seen);
|
|
265
|
+
return;
|
|
266
|
+
default:
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/** Emitted for `io: 'output'` when a bare pipe makes the output unknowable. */
|
|
271
|
+
const OPAQUE_OUTPUT_IR = { k: "unknown" };
|
|
48
272
|
const typeMethods = {
|
|
49
273
|
describe(description) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
...(config.actual === undefined ? {} : { actual: config.actual }),
|
|
57
|
-
...(config.problem === undefined ? {} : { problem: config.problem }),
|
|
58
|
-
...(config.message === undefined ? {} : { message: config.message }),
|
|
59
|
-
};
|
|
60
|
-
const meta = {
|
|
274
|
+
const ir = { ...this.ir, desc: description, cfg: { ...this.ir.cfg, expected: description } };
|
|
275
|
+
return makeType(ir, this[kSteps], { ...metaOf(this), description });
|
|
276
|
+
},
|
|
277
|
+
configure(config, selector = "self") {
|
|
278
|
+
const selected = selector === "self" ? configureNode(this.ir, config) : configureSelected(this.ir, config, selector);
|
|
279
|
+
return makeType(selected, this[kSteps], {
|
|
61
280
|
...metaOf(this),
|
|
62
|
-
errorConfig,
|
|
281
|
+
errorConfig: { ...this.errorConfig, ...errorConfigOf(config) },
|
|
63
282
|
...(config.description === undefined ? {} : { description: config.description }),
|
|
64
|
-
};
|
|
65
|
-
return config.description === undefined
|
|
66
|
-
? makeType(this.ir, this[kSteps], meta)
|
|
67
|
-
: makeType({ ...this.ir, desc: config.description }, this[kSteps], meta);
|
|
283
|
+
});
|
|
68
284
|
},
|
|
69
285
|
default(value) {
|
|
70
|
-
|
|
286
|
+
const factory = typeof value === "function";
|
|
287
|
+
if (!factory)
|
|
288
|
+
rejectMutableStaticDefault(value);
|
|
289
|
+
const candidate = factory ? value() : value;
|
|
290
|
+
const output = this.run(candidate);
|
|
291
|
+
if (output instanceof OmpErrors)
|
|
292
|
+
invalidDefault("Default", output);
|
|
293
|
+
return makeType(this.ir, this[kSteps], {
|
|
294
|
+
...metaOf(this),
|
|
295
|
+
defaultValue: value,
|
|
296
|
+
hasDefault: true,
|
|
297
|
+
...(factory ? {} : { defaultOutput: output, hasDefaultOutput: true }),
|
|
298
|
+
});
|
|
71
299
|
},
|
|
72
300
|
optional() {
|
|
73
301
|
return [this, "?"];
|
|
74
302
|
},
|
|
75
303
|
or(def) {
|
|
76
|
-
const other = parseDef(def);
|
|
304
|
+
const other = parseDef(def, this.resolver);
|
|
77
305
|
const a = embed(this);
|
|
78
306
|
const members = [...(a.k === "union" ? a.members : [a]), ...(other.k === "union" ? other.members : [other])];
|
|
79
|
-
return makeType({ k: "union", members }, [], {});
|
|
307
|
+
return inheritScope(this, makeType({ k: "union", members }, [], {}));
|
|
80
308
|
},
|
|
81
309
|
equals(def) {
|
|
82
|
-
return irEquals(embed(this), parseDef(def));
|
|
310
|
+
return irEquals(embed(this), parseDef(def, this.resolver));
|
|
83
311
|
},
|
|
84
312
|
ifEquals(def) {
|
|
85
|
-
return irEquals(embed(this), parseDef(def)) ? this : undefined;
|
|
313
|
+
return irEquals(embed(this), parseDef(def, this.resolver)) ? this : undefined;
|
|
314
|
+
},
|
|
315
|
+
ifExtends(def) {
|
|
316
|
+
return isSubtype(embed(this), parseDef(def, this.resolver)) ? this : undefined;
|
|
86
317
|
},
|
|
87
318
|
extends(def) {
|
|
88
|
-
return isSubtype(embed(this), parseDef(def));
|
|
319
|
+
return isSubtype(embed(this), parseDef(def, this.resolver));
|
|
89
320
|
},
|
|
90
321
|
overlaps(def) {
|
|
91
322
|
try {
|
|
92
|
-
intersect(embed(this), parseDef(def));
|
|
323
|
+
intersect(embed(this), parseDef(def, this.resolver));
|
|
93
324
|
return true;
|
|
94
325
|
}
|
|
95
326
|
catch (error) {
|
|
@@ -98,19 +329,22 @@ const typeMethods = {
|
|
|
98
329
|
throw error;
|
|
99
330
|
}
|
|
100
331
|
},
|
|
101
|
-
distribute(mapper) {
|
|
332
|
+
distribute(mapper, reducer) {
|
|
102
333
|
const branches = this.ir.k === "union" ? this.ir.members : [embed(this)];
|
|
103
|
-
const
|
|
334
|
+
const mapped = branches.map(branch => mapper(makeType(branch, [], {})));
|
|
335
|
+
if (reducer !== undefined)
|
|
336
|
+
return reducer(mapped);
|
|
337
|
+
const members = mapped.map(branch => embed(branch));
|
|
104
338
|
return makeType(members.length === 1 ? members[0] : { k: "union", members }, [], {});
|
|
105
339
|
},
|
|
106
340
|
select(kind) {
|
|
107
341
|
return selectNodes(this.ir, kind);
|
|
108
342
|
},
|
|
109
343
|
and(def) {
|
|
110
|
-
return makeType(intersect(embed(this), parseDef(def)), [], {});
|
|
344
|
+
return inheritScope(this, makeType(intersect(embed(this), parseDef(def, this.resolver)), [], {}));
|
|
111
345
|
},
|
|
112
346
|
array() {
|
|
113
|
-
return makeType({ k: "array", el: embed(this) }, [], {});
|
|
347
|
+
return inheritScope(this, makeType({ k: "array", el: embed(this) }, [], {}));
|
|
114
348
|
},
|
|
115
349
|
atLeastLength(bound) {
|
|
116
350
|
return makeType(withLengthBound(this.ir, "min", bound), this[kSteps], metaOf(this));
|
|
@@ -163,29 +397,26 @@ const typeMethods = {
|
|
|
163
397
|
return makeType(intersect(this.ir, patternIR(pattern)), this[kSteps], metaOf(this));
|
|
164
398
|
},
|
|
165
399
|
atOrAfter(bound) {
|
|
166
|
-
|
|
400
|
+
const timestamp = bound instanceof Date ? bound.valueOf() : bound;
|
|
401
|
+
return dateRefinement(this, timestamp, "at or after", value => value >= timestamp);
|
|
167
402
|
},
|
|
168
403
|
atOrBefore(bound) {
|
|
169
|
-
|
|
404
|
+
const timestamp = bound instanceof Date ? bound.valueOf() : bound;
|
|
405
|
+
return dateRefinement(this, timestamp, "at or before", value => value <= timestamp);
|
|
170
406
|
},
|
|
171
407
|
laterThan(bound) {
|
|
172
|
-
|
|
408
|
+
const timestamp = bound instanceof Date ? bound.valueOf() : bound;
|
|
409
|
+
return dateRefinement(this, timestamp, "later than", value => value > timestamp);
|
|
173
410
|
},
|
|
174
411
|
earlierThan(bound) {
|
|
175
|
-
|
|
412
|
+
const timestamp = bound instanceof Date ? bound.valueOf() : bound;
|
|
413
|
+
return dateRefinement(this, timestamp, "earlier than", value => value < timestamp);
|
|
176
414
|
},
|
|
177
|
-
pipe(
|
|
178
|
-
return
|
|
415
|
+
pipe(...pipes) {
|
|
416
|
+
return appendPipes(this, pipes, false);
|
|
179
417
|
},
|
|
180
418
|
to(def) {
|
|
181
|
-
|
|
182
|
-
return makeType(this.ir, [
|
|
183
|
-
...this[kSteps],
|
|
184
|
-
{
|
|
185
|
-
kind: "pipe",
|
|
186
|
-
fn: value => output(value),
|
|
187
|
-
},
|
|
188
|
-
], metaOf(this));
|
|
419
|
+
return appendPipes(this, [makeType(parseDef(def, this.resolver), [], {})], false, true);
|
|
189
420
|
},
|
|
190
421
|
filter(fn) {
|
|
191
422
|
return makeType(this.ir, [{ kind: "filter", fn }, ...this[kSteps]], metaOf(this));
|
|
@@ -194,72 +425,76 @@ const typeMethods = {
|
|
|
194
425
|
return makeType(this.ir, [...this[kSteps], { kind: "narrow", fn }], metaOf(this));
|
|
195
426
|
},
|
|
196
427
|
brand() {
|
|
197
|
-
return
|
|
428
|
+
return this;
|
|
198
429
|
},
|
|
199
430
|
as() {
|
|
200
|
-
return
|
|
431
|
+
return this;
|
|
432
|
+
},
|
|
433
|
+
readonly() {
|
|
434
|
+
return this;
|
|
201
435
|
},
|
|
202
436
|
keyof() {
|
|
203
437
|
return makeType(keyOf(this.ir), [], {});
|
|
204
438
|
},
|
|
205
|
-
get(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return makeType(
|
|
439
|
+
get(...path) {
|
|
440
|
+
if (path.length === 0)
|
|
441
|
+
return this;
|
|
442
|
+
let result = this.ir;
|
|
443
|
+
for (const key of path)
|
|
444
|
+
result = getPathIR(result, key);
|
|
445
|
+
return makeType(result, [], {});
|
|
212
446
|
},
|
|
213
447
|
pick(...keys) {
|
|
214
|
-
|
|
215
|
-
const selected = new Set(keys.map(String));
|
|
216
|
-
return makeType({ ...object, props: object.props.filter(prop => selected.has(prop.key)) }, [], {});
|
|
448
|
+
return makeType(selectObjectProps(this.ir, keys, true, "pick"), [], {});
|
|
217
449
|
},
|
|
218
450
|
omit(...keys) {
|
|
219
|
-
|
|
220
|
-
const omitted = new Set(keys.map(String));
|
|
221
|
-
return makeType({ ...object, props: object.props.filter(prop => !omitted.has(prop.key)) }, [], {});
|
|
451
|
+
return makeType(selectObjectProps(this.ir, keys, false, "omit"), [], {});
|
|
222
452
|
},
|
|
223
453
|
partial() {
|
|
224
|
-
|
|
225
|
-
return makeType({ ...object, props: object.props.map(prop => ({ ...prop, opt: true })) }, [], {});
|
|
454
|
+
return makeType(setObjectOptionality(this.ir, true, "partial"), [], {});
|
|
226
455
|
},
|
|
227
456
|
required() {
|
|
228
|
-
|
|
229
|
-
return makeType({ ...object, props: object.props.map(prop => ({ ...prop, opt: false })) }, [], {});
|
|
457
|
+
return makeType(setObjectOptionality(this.ir, false, "required"), [], {});
|
|
230
458
|
},
|
|
231
459
|
map(mapper) {
|
|
232
460
|
const object = requireObject(this.ir, "map");
|
|
233
461
|
const props = object.props.flatMap(prop => {
|
|
234
|
-
const
|
|
235
|
-
|
|
462
|
+
const original = propertyFromIR(prop);
|
|
463
|
+
const mapped = mapper(original);
|
|
464
|
+
return (Array.isArray(mapped) ? mapped : [mapped]).map(property => propertyToIR((property.kind === "required" || property.kind === "optional"
|
|
465
|
+
? property
|
|
466
|
+
: { ...property, kind: original.kind })));
|
|
236
467
|
});
|
|
237
468
|
return makeType({ ...object, props }, [], {});
|
|
238
469
|
},
|
|
239
470
|
merge(def) {
|
|
240
|
-
|
|
471
|
+
const merged = mergeObjectDefinition(this.ir, def, this.resolver);
|
|
472
|
+
return inheritScope(this, makeType(merged, [], {}));
|
|
241
473
|
},
|
|
242
474
|
extract(def) {
|
|
243
|
-
|
|
244
|
-
return makeType({
|
|
245
|
-
k: "refine",
|
|
246
|
-
base: embed(this),
|
|
247
|
-
pred: value => !(walk(other, value) instanceof OmpErrors),
|
|
248
|
-
expected: "a value included by the extracted type",
|
|
249
|
-
}, [], {});
|
|
475
|
+
return inheritScope(this, makeType(distributeFilter(this.ir, parseDef(def, this.resolver), true), [], {}));
|
|
250
476
|
},
|
|
251
477
|
exclude(def) {
|
|
252
|
-
|
|
253
|
-
return makeType({
|
|
254
|
-
k: "refine",
|
|
255
|
-
base: embed(this),
|
|
256
|
-
pred: value => walk(other, value) instanceof OmpErrors,
|
|
257
|
-
expected: "a value not excluded by the type",
|
|
258
|
-
}, [], {});
|
|
478
|
+
return inheritScope(this, makeType(distributeFilter(this.ir, parseDef(def, this.resolver), false), [], {}));
|
|
259
479
|
},
|
|
260
480
|
onUndeclaredKey(behavior) {
|
|
261
|
-
const
|
|
262
|
-
|
|
481
|
+
const extras = behavior === "ignore" ? "keep" : behavior;
|
|
482
|
+
const ir = withShallowExtras(this.ir, extras);
|
|
483
|
+
if (extras === "delete" && ir.k === "union") {
|
|
484
|
+
const objects = ir.members.filter((member) => member.k === "object");
|
|
485
|
+
for (let left = 0; left < objects.length; left++) {
|
|
486
|
+
for (let right = left + 1; right < objects.length; right++) {
|
|
487
|
+
const sharedRequired = objects[left].props.some(leftProp => !leftProp.opt &&
|
|
488
|
+
objects[right].props.some(rightProp => !rightProp.opt && rightProp.key === leftProp.key));
|
|
489
|
+
if (!sharedRequired) {
|
|
490
|
+
const leftExpression = expressionOf(objects[left]).replace(/ }$/, ", + (undeclared): delete }");
|
|
491
|
+
const rightExpression = expressionOf(objects[right]).replace(/ }$/, ", + (undeclared): delete }");
|
|
492
|
+
throw new OmpTypeError(`ParseError: An unordered union of a type including a morph and a type with overlapping input is indeterminate:\nLeft: ${leftExpression}\nRight: ${rightExpression}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return makeType(ir, this[kSteps], metaOf(this));
|
|
263
498
|
},
|
|
264
499
|
onDeepUndeclaredKey(behavior) {
|
|
265
500
|
return makeType(withDeepExtras(this.ir, behavior === "ignore" ? "keep" : behavior), this[kSteps], metaOf(this));
|
|
@@ -280,14 +515,14 @@ const typeMethods = {
|
|
|
280
515
|
return allows(data);
|
|
281
516
|
}
|
|
282
517
|
for (const step of steps) {
|
|
283
|
-
if (step.kind === "filter" && !step.fn(data, new Ctx()))
|
|
518
|
+
if (step.kind === "filter" && !step.fn(data, new Ctx(data)))
|
|
284
519
|
return false;
|
|
285
520
|
}
|
|
286
521
|
const out = this[kBase](data);
|
|
287
522
|
if (out instanceof OmpErrors)
|
|
288
523
|
return false;
|
|
289
524
|
for (const step of steps) {
|
|
290
|
-
if (step.kind === "narrow" && !step.fn(out, new Ctx()))
|
|
525
|
+
if (step.kind === "narrow" && !step.fn(out, new Ctx(out)))
|
|
291
526
|
return false;
|
|
292
527
|
}
|
|
293
528
|
return true;
|
|
@@ -305,24 +540,125 @@ const typeMethods = {
|
|
|
305
540
|
return out;
|
|
306
541
|
},
|
|
307
542
|
toJsonSchema(options) {
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
543
|
+
const ir = options?.io === "output" ? (this.opaqueOutput ? OPAQUE_OUTPUT_IR : (this.stepOut ?? this.ir)) : this.ir;
|
|
544
|
+
const description = options?.description ?? this.ir.desc;
|
|
545
|
+
if (description === undefined)
|
|
546
|
+
return irToJsonSchema(ir, options);
|
|
547
|
+
return irToJsonSchema(ir, { ...options, description });
|
|
312
548
|
},
|
|
313
549
|
};
|
|
550
|
+
Object.defineProperty(typeMethods, "expression", {
|
|
551
|
+
get() {
|
|
552
|
+
const input = expressionOf(this.ir);
|
|
553
|
+
if (!this.hasSteps)
|
|
554
|
+
return input;
|
|
555
|
+
if (this.opaqueOutput)
|
|
556
|
+
return `(In: ${input}) => Out<unknown>`;
|
|
557
|
+
return `(In: ${input}) => To<${expressionOf(this.stepOut ?? this.ir)}>`;
|
|
558
|
+
},
|
|
559
|
+
});
|
|
560
|
+
Object.defineProperty(typeMethods, "json", {
|
|
561
|
+
get() {
|
|
562
|
+
return arkJsonOf(this.ir);
|
|
563
|
+
},
|
|
564
|
+
});
|
|
314
565
|
Object.defineProperty(typeMethods, "props", {
|
|
315
566
|
get() {
|
|
316
567
|
const object = requireObject(this.ir, "props");
|
|
317
568
|
return object.props.map(prop => propertyFromIR(prop));
|
|
318
569
|
},
|
|
319
570
|
});
|
|
571
|
+
Object.defineProperty(typeMethods, "~standard", {
|
|
572
|
+
get() {
|
|
573
|
+
const jsonSchema = (io, options) => {
|
|
574
|
+
if (options.target !== "draft-2020-12" && options.target !== "draft-07") {
|
|
575
|
+
throw new OmpTypeError(`JSONSchema target '${options.target}' is not supported (must be "draft-2020-12" or "draft-07")`);
|
|
576
|
+
}
|
|
577
|
+
return this.toJsonSchema({ ...options.libraryOptions, target: options.target, io });
|
|
578
|
+
};
|
|
579
|
+
return {
|
|
580
|
+
version: 1,
|
|
581
|
+
vendor: "omptype",
|
|
582
|
+
validate: (value) => {
|
|
583
|
+
const out = this.run(value);
|
|
584
|
+
return out instanceof OmpErrors
|
|
585
|
+
? { issues: out }
|
|
586
|
+
: { value: out };
|
|
587
|
+
},
|
|
588
|
+
jsonSchema: {
|
|
589
|
+
input: options => jsonSchema("input", options),
|
|
590
|
+
output: options => jsonSchema("output", options),
|
|
591
|
+
},
|
|
592
|
+
};
|
|
593
|
+
},
|
|
594
|
+
});
|
|
595
|
+
Object.defineProperty(typeMethods, "in", {
|
|
596
|
+
get() {
|
|
597
|
+
return makeType(projectIO(this.ir, "in"), [], {});
|
|
598
|
+
},
|
|
599
|
+
});
|
|
600
|
+
Object.defineProperty(typeMethods, "out", {
|
|
601
|
+
get() {
|
|
602
|
+
if (this.opaqueOutput)
|
|
603
|
+
return makeType({ k: "unknown" }, [], {});
|
|
604
|
+
return makeType(projectIO(this.stepOut ?? this.ir, "out"), [], {});
|
|
605
|
+
},
|
|
606
|
+
});
|
|
607
|
+
const allowsMethod = typeMethods.allows;
|
|
608
|
+
const assertMethod = typeMethods.assert;
|
|
609
|
+
const fromMethod = typeMethods.from;
|
|
610
|
+
Object.defineProperties(typeMethods, {
|
|
611
|
+
description: {
|
|
612
|
+
get() {
|
|
613
|
+
return descriptionOf(this.ir);
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
allows: {
|
|
617
|
+
get() {
|
|
618
|
+
const allows = (data) => allowsMethod.call(this, data);
|
|
619
|
+
Object.defineProperty(this, "allows", { value: allows, writable: true });
|
|
620
|
+
return allows;
|
|
621
|
+
},
|
|
622
|
+
},
|
|
623
|
+
assert: {
|
|
624
|
+
get() {
|
|
625
|
+
const assert = assertMethod.bind(this);
|
|
626
|
+
Object.defineProperty(this, "assert", { value: assert });
|
|
627
|
+
return assert;
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
from: {
|
|
631
|
+
get() {
|
|
632
|
+
const from = fromMethod.bind(this);
|
|
633
|
+
Object.defineProperty(this, "from", { value: from });
|
|
634
|
+
return from;
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
pipe: {
|
|
638
|
+
get() {
|
|
639
|
+
const pipe = Object.assign((...pipes) => appendPipes(this, pipes, false), {
|
|
640
|
+
try: (...pipes) => appendPipes(this, pipes, true),
|
|
641
|
+
});
|
|
642
|
+
Object.defineProperty(this, "pipe", { value: pipe });
|
|
643
|
+
return pipe;
|
|
644
|
+
},
|
|
645
|
+
},
|
|
646
|
+
});
|
|
320
647
|
// Share the fluent surface without per-schema method allocations or copies.
|
|
321
648
|
// Function.prototype remains in the chain, except bind is intentionally hidden
|
|
322
649
|
// so generic tool wrappers recognize callable schemas rather than rebinding them.
|
|
323
650
|
Object.setPrototypeOf(typeMethods, Function.prototype);
|
|
324
651
|
Object.defineProperty(typeMethods, "bind", { value: undefined });
|
|
325
652
|
function makeType(ir, steps, meta) {
|
|
653
|
+
let morph = false;
|
|
654
|
+
if (!isSimpleIR(ir)) {
|
|
655
|
+
ir = normalizeIR(ir);
|
|
656
|
+
morph = hasMorph(ir);
|
|
657
|
+
if (morph) {
|
|
658
|
+
normalizeDefaults(ir);
|
|
659
|
+
assertDeterminateMorphUnions(ir);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
326
662
|
let calls = 0;
|
|
327
663
|
let impl = (data) => {
|
|
328
664
|
if (++calls >= JIT_THRESHOLD) {
|
|
@@ -331,21 +667,29 @@ function makeType(ir, steps, meta) {
|
|
|
331
667
|
}
|
|
332
668
|
return walk(ir, data);
|
|
333
669
|
};
|
|
334
|
-
const base =
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return result instanceof OmpErrors ? result.configure(meta.errorConfig ?? {}) : result;
|
|
339
|
-
};
|
|
340
|
-
const callable = steps.length === 0
|
|
670
|
+
const base = (data) => impl(data);
|
|
671
|
+
const errorConfig = meta.errorConfig ?? ir.cfg;
|
|
672
|
+
const filterInput = steps.some(step => step.kind === "filter") ? projectIO(ir, "in") : undefined;
|
|
673
|
+
const validate = steps.length === 0
|
|
341
674
|
? base
|
|
342
|
-
: (data) => {
|
|
675
|
+
: (data, contextPath = []) => {
|
|
676
|
+
if (filterInput !== undefined) {
|
|
677
|
+
const inputResult = walk(filterInput, data);
|
|
678
|
+
if (inputResult instanceof OmpErrors)
|
|
679
|
+
return inputResult;
|
|
680
|
+
}
|
|
343
681
|
for (const step of steps) {
|
|
344
682
|
if (step.kind !== "filter")
|
|
345
683
|
continue;
|
|
346
|
-
const ctx = new Ctx();
|
|
347
|
-
|
|
348
|
-
|
|
684
|
+
const ctx = new Ctx(data, contextPath);
|
|
685
|
+
const result = step.fn(data, ctx);
|
|
686
|
+
if (result instanceof OmpErrors)
|
|
687
|
+
return errorConfig === undefined ? result : result.configure(errorConfig);
|
|
688
|
+
if (ctx.errors)
|
|
689
|
+
return errorConfig === undefined ? ctx.errors : ctx.errors.configure(errorConfig);
|
|
690
|
+
if (!result) {
|
|
691
|
+
return OmpErrors.single([], ctx.expectation ??
|
|
692
|
+
(step.fn.name ? `valid according to ${step.fn.name}` : "valid (input predicate failed)"), data, errorConfig);
|
|
349
693
|
}
|
|
350
694
|
}
|
|
351
695
|
let out = base(data);
|
|
@@ -354,21 +698,87 @@ function makeType(ir, steps, meta) {
|
|
|
354
698
|
for (const step of steps) {
|
|
355
699
|
if (step.kind === "filter")
|
|
356
700
|
continue;
|
|
357
|
-
const ctx = new Ctx();
|
|
701
|
+
const ctx = new Ctx(out, contextPath);
|
|
358
702
|
if (step.kind === "narrow") {
|
|
359
|
-
|
|
360
|
-
|
|
703
|
+
const result = step.fn(out, ctx);
|
|
704
|
+
if (result instanceof OmpErrors) {
|
|
705
|
+
return errorConfig === undefined ? result : result.configure(errorConfig);
|
|
706
|
+
}
|
|
707
|
+
if (ctx.errors)
|
|
708
|
+
return errorConfig === undefined ? ctx.errors : ctx.errors.configure(errorConfig);
|
|
709
|
+
if (!result) {
|
|
710
|
+
return OmpErrors.single([], ctx.expectation ??
|
|
711
|
+
(step.fn.name ? `valid according to ${step.fn.name}` : "valid (narrow predicate failed)"), out, errorConfig);
|
|
361
712
|
}
|
|
362
713
|
}
|
|
363
714
|
else {
|
|
364
|
-
|
|
715
|
+
try {
|
|
716
|
+
out = step.fn(out, ctx);
|
|
717
|
+
}
|
|
718
|
+
catch (error) {
|
|
719
|
+
if (!step.try)
|
|
720
|
+
throw error;
|
|
721
|
+
const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
722
|
+
return OmpErrors.single([], `valid (morph threw ${detail})`, out, errorConfig);
|
|
723
|
+
}
|
|
365
724
|
if (out instanceof OmpErrors) {
|
|
366
|
-
return
|
|
725
|
+
return errorConfig === undefined ? out : out.configure(errorConfig);
|
|
367
726
|
}
|
|
368
727
|
}
|
|
369
728
|
}
|
|
370
729
|
return out;
|
|
371
730
|
};
|
|
731
|
+
const needsClone = morph || steps.some(step => step.kind === "pipe");
|
|
732
|
+
const clone = meta.clone;
|
|
733
|
+
let callable = validate;
|
|
734
|
+
if (needsClone && clone !== undefined) {
|
|
735
|
+
if (clone === false) {
|
|
736
|
+
callable = (data, path) => {
|
|
737
|
+
const out = validate(data, path);
|
|
738
|
+
if (out instanceof OmpErrors ||
|
|
739
|
+
out === data ||
|
|
740
|
+
typeof data !== "object" ||
|
|
741
|
+
data === null ||
|
|
742
|
+
typeof out !== "object" ||
|
|
743
|
+
out === null) {
|
|
744
|
+
return out;
|
|
745
|
+
}
|
|
746
|
+
if (Array.isArray(data) && Array.isArray(out)) {
|
|
747
|
+
data.splice(0, data.length, ...out);
|
|
748
|
+
}
|
|
749
|
+
else {
|
|
750
|
+
const target = data;
|
|
751
|
+
const source = out;
|
|
752
|
+
for (const key of Reflect.ownKeys(target)) {
|
|
753
|
+
if (!Object.hasOwn(source, key))
|
|
754
|
+
Reflect.deleteProperty(target, key);
|
|
755
|
+
}
|
|
756
|
+
for (const key of Reflect.ownKeys(source))
|
|
757
|
+
target[key] = source[key];
|
|
758
|
+
}
|
|
759
|
+
return data;
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
callable = (data, path) => validate(clone(data), path);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
// Root defaults materialize for absent input: `schema(undefined)` and the
|
|
767
|
+
// Standard Schema boundary (`~standard.validate(undefined)`) both yield the
|
|
768
|
+
// default instead of a base-IR rejection. Factories run per call; static
|
|
769
|
+
// defaults reuse the precomputed validated output (mutable statics are
|
|
770
|
+
// rejected at `.default()` time).
|
|
771
|
+
if (meta.hasDefault === true) {
|
|
772
|
+
const inner = callable;
|
|
773
|
+
const value = meta.defaultValue;
|
|
774
|
+
callable = (data, path) => {
|
|
775
|
+
if (data !== undefined)
|
|
776
|
+
return inner(data, path);
|
|
777
|
+
if (meta.hasDefaultOutput === true)
|
|
778
|
+
return meta.defaultOutput;
|
|
779
|
+
return inner(typeof value === "function" ? value() : value, path);
|
|
780
|
+
};
|
|
781
|
+
}
|
|
372
782
|
const self = callable;
|
|
373
783
|
self[IR_BRAND] = true;
|
|
374
784
|
self[kBase] = base;
|
|
@@ -377,17 +787,474 @@ function makeType(ir, steps, meta) {
|
|
|
377
787
|
self.hasSteps = steps.length > 0;
|
|
378
788
|
self.hasDefault = meta.hasDefault === true;
|
|
379
789
|
self.defaultValue = meta.defaultValue;
|
|
380
|
-
self.
|
|
381
|
-
self.
|
|
790
|
+
self.defaultOutput = meta.defaultOutput;
|
|
791
|
+
self.hasDefaultOutput = meta.hasDefaultOutput === true;
|
|
792
|
+
self.errorConfig = meta.errorConfig ?? ir.cfg;
|
|
793
|
+
self.clone = meta.clone;
|
|
382
794
|
self.run = callable;
|
|
795
|
+
self.$ = ARK_COMPAT_SCOPE;
|
|
796
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
797
|
+
const step = steps[i];
|
|
798
|
+
if (step.kind !== "pipe")
|
|
799
|
+
continue;
|
|
800
|
+
self.stepOut = step.out;
|
|
801
|
+
self.opaqueOutput = step.out === undefined;
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
383
804
|
Object.setPrototypeOf(self, typeMethods);
|
|
384
805
|
return self;
|
|
385
806
|
}
|
|
807
|
+
function appendPipes(source, pipes, catchErrors, forcePipeline = false) {
|
|
808
|
+
let schema = source;
|
|
809
|
+
for (const candidate of pipes) {
|
|
810
|
+
const isSchema = (typeof candidate === "function" || (typeof candidate === "object" && candidate !== null)) &&
|
|
811
|
+
IR_BRAND in candidate;
|
|
812
|
+
if (isSchema) {
|
|
813
|
+
const target = candidate;
|
|
814
|
+
if (!forcePipeline &&
|
|
815
|
+
schema[kSteps].length === 0 &&
|
|
816
|
+
!target.hasSteps &&
|
|
817
|
+
!hasMorph(schema.ir) &&
|
|
818
|
+
!hasMorph(target.ir)) {
|
|
819
|
+
schema = makeType(intersect(schema.ir, target.ir), [], metaOf(schema));
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
const out = target.opaqueOutput ? undefined : (target.stepOut ?? target.ir);
|
|
823
|
+
schema = makeType(schema.ir, [...schema[kSteps], { kind: "pipe", fn: value => target.run(value), out, try: catchErrors }], metaOf(schema));
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
if (typeof candidate !== "function")
|
|
827
|
+
throw new OmpTypeError("pipe operands must be functions or Types");
|
|
828
|
+
schema = makeType(schema.ir, [...schema[kSteps], { kind: "pipe", fn: candidate, try: catchErrors }], metaOf(schema));
|
|
829
|
+
}
|
|
830
|
+
return inheritScope(source, schema);
|
|
831
|
+
}
|
|
832
|
+
function projectIO(ir, io) {
|
|
833
|
+
switch (ir.k) {
|
|
834
|
+
case "morph":
|
|
835
|
+
return projectIO(io === "in" || ir.out === undefined ? ir.input : ir.out, io);
|
|
836
|
+
case "sub":
|
|
837
|
+
if (io === "out" && ir.schema.opaqueOutput)
|
|
838
|
+
return { k: "unknown" };
|
|
839
|
+
return projectIO(io === "out" ? (ir.schema.stepOut ?? ir.schema.ir) : ir.schema.ir, io);
|
|
840
|
+
case "array":
|
|
841
|
+
return { ...ir, el: projectIO(ir.el, io) };
|
|
842
|
+
case "tuple":
|
|
843
|
+
return {
|
|
844
|
+
...ir,
|
|
845
|
+
prefix: ir.prefix.map(item => ({
|
|
846
|
+
...item,
|
|
847
|
+
opt: io === "in" ? item.opt || item.hasDefault === true : item.opt && !item.hasDefault,
|
|
848
|
+
val: projectIO(item.val, io),
|
|
849
|
+
hasDefault: false,
|
|
850
|
+
def: undefined,
|
|
851
|
+
defFactory: false,
|
|
852
|
+
defValidated: false,
|
|
853
|
+
})),
|
|
854
|
+
variadic: ir.variadic === undefined ? undefined : projectIO(ir.variadic, io),
|
|
855
|
+
postfix: ir.postfix.map(item => projectIO(item, io)),
|
|
856
|
+
};
|
|
857
|
+
case "object":
|
|
858
|
+
return {
|
|
859
|
+
...ir,
|
|
860
|
+
props: ir.props.map(prop => ({
|
|
861
|
+
...prop,
|
|
862
|
+
opt: io === "in" ? prop.opt || prop.hasDefault === true : prop.opt && !prop.hasDefault,
|
|
863
|
+
val: projectIO(prop.val, io),
|
|
864
|
+
hasDefault: false,
|
|
865
|
+
def: undefined,
|
|
866
|
+
defFactory: false,
|
|
867
|
+
defValidated: false,
|
|
868
|
+
})),
|
|
869
|
+
index: ir.index === undefined ? undefined : projectIO(ir.index, io),
|
|
870
|
+
symbolIndex: ir.symbolIndex === undefined ? undefined : projectIO(ir.symbolIndex, io),
|
|
871
|
+
patternIndexes: ir.patternIndexes?.map(index => ({
|
|
872
|
+
key: projectIO(index.key, io),
|
|
873
|
+
val: projectIO(index.val, io),
|
|
874
|
+
})),
|
|
875
|
+
extras: ir.extras === "delete" ? (io === "in" ? "keep" : "reject") : ir.extras,
|
|
876
|
+
};
|
|
877
|
+
case "union":
|
|
878
|
+
case "intersection":
|
|
879
|
+
return { ...ir, members: ir.members.map(member => projectIO(member, io)) };
|
|
880
|
+
case "refine":
|
|
881
|
+
return { ...ir, base: projectIO(ir.base, io) };
|
|
882
|
+
case "alias":
|
|
883
|
+
return { ...ir, resolve: () => projectIO(ir.resolve(), io) };
|
|
884
|
+
default:
|
|
885
|
+
return ir;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
function morphIdentities(ir, identities = [], seen = new Set()) {
|
|
889
|
+
if (seen.has(ir))
|
|
890
|
+
return identities;
|
|
891
|
+
seen.add(ir);
|
|
892
|
+
switch (ir.k) {
|
|
893
|
+
case "morph":
|
|
894
|
+
identities.push(ir.out === undefined
|
|
895
|
+
? ir.fn
|
|
896
|
+
: `declared:${expressionOf(projectIO(ir.input, "in"))}=>${expressionOf(projectIO(ir.out, "out"))}`);
|
|
897
|
+
morphIdentities(ir.input, identities, seen);
|
|
898
|
+
if (ir.out !== undefined)
|
|
899
|
+
morphIdentities(ir.out, identities, seen);
|
|
900
|
+
break;
|
|
901
|
+
case "sub": {
|
|
902
|
+
const schema = ir.schema;
|
|
903
|
+
for (const step of schema[kSteps])
|
|
904
|
+
if (step.kind === "pipe")
|
|
905
|
+
identities.push(step.fn);
|
|
906
|
+
morphIdentities(schema.ir, identities, seen);
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
case "array":
|
|
910
|
+
morphIdentities(ir.el, identities, seen);
|
|
911
|
+
break;
|
|
912
|
+
case "tuple":
|
|
913
|
+
for (const item of ir.prefix)
|
|
914
|
+
morphIdentities(item.val, identities, seen);
|
|
915
|
+
if (ir.variadic !== undefined)
|
|
916
|
+
morphIdentities(ir.variadic, identities, seen);
|
|
917
|
+
for (const item of ir.postfix)
|
|
918
|
+
morphIdentities(item, identities, seen);
|
|
919
|
+
break;
|
|
920
|
+
case "object":
|
|
921
|
+
if (ir.extras === "delete")
|
|
922
|
+
identities.push(ir);
|
|
923
|
+
for (const prop of ir.props)
|
|
924
|
+
morphIdentities(prop.val, identities, seen);
|
|
925
|
+
if (ir.index !== undefined)
|
|
926
|
+
morphIdentities(ir.index, identities, seen);
|
|
927
|
+
if (ir.symbolIndex !== undefined)
|
|
928
|
+
morphIdentities(ir.symbolIndex, identities, seen);
|
|
929
|
+
for (const index of ir.patternIndexes ?? [])
|
|
930
|
+
morphIdentities(index.val, identities, seen);
|
|
931
|
+
break;
|
|
932
|
+
case "union":
|
|
933
|
+
case "intersection":
|
|
934
|
+
for (const member of ir.members)
|
|
935
|
+
morphIdentities(member, identities, seen);
|
|
936
|
+
break;
|
|
937
|
+
case "refine":
|
|
938
|
+
morphIdentities(ir.base, identities, seen);
|
|
939
|
+
break;
|
|
940
|
+
case "alias":
|
|
941
|
+
morphIdentities(ir.resolve(), identities, seen);
|
|
942
|
+
break;
|
|
943
|
+
}
|
|
944
|
+
return identities;
|
|
945
|
+
}
|
|
946
|
+
function assertDeterminateMorphUnions(ir, seen = new Set()) {
|
|
947
|
+
if (seen.has(ir))
|
|
948
|
+
return;
|
|
949
|
+
seen.add(ir);
|
|
950
|
+
if (ir.k === "union") {
|
|
951
|
+
for (let leftIndex = 0; leftIndex < ir.members.length; leftIndex++) {
|
|
952
|
+
const left = ir.members[leftIndex];
|
|
953
|
+
const leftMorphs = morphIdentities(left);
|
|
954
|
+
for (let rightIndex = leftIndex + 1; rightIndex < ir.members.length; rightIndex++) {
|
|
955
|
+
const right = ir.members[rightIndex];
|
|
956
|
+
const rightMorphs = morphIdentities(right);
|
|
957
|
+
if (leftMorphs.length === 0 && rightMorphs.length === 0)
|
|
958
|
+
continue;
|
|
959
|
+
if (leftMorphs.length === rightMorphs.length &&
|
|
960
|
+
leftMorphs.every((identity, index) => identity === rightMorphs[index])) {
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
// Unwrap one alias level eagerly: the disjointness probe relies on
|
|
964
|
+
// intersect() throwing, and deferred alias intersections resolve lazily.
|
|
965
|
+
let leftInput = projectIO(left, "in");
|
|
966
|
+
let rightInput = projectIO(right, "in");
|
|
967
|
+
if (leftInput.k === "alias")
|
|
968
|
+
leftInput = leftInput.resolve();
|
|
969
|
+
if (rightInput.k === "alias")
|
|
970
|
+
rightInput = rightInput.resolve();
|
|
971
|
+
if (leftInput.k === "object" && rightInput.k === "object") {
|
|
972
|
+
const leftKeys = new Set(leftInput.props.map(prop => prop.key));
|
|
973
|
+
const rightKeys = new Set(rightInput.props.map(prop => prop.key));
|
|
974
|
+
const leftRejectsRequiredRight = leftInput.extras === "reject" &&
|
|
975
|
+
rightInput.props.some(prop => !prop.opt && !prop.hasDefault && !leftKeys.has(prop.key));
|
|
976
|
+
const rightRejectsRequiredLeft = rightInput.extras === "reject" &&
|
|
977
|
+
leftInput.props.some(prop => !prop.opt && !prop.hasDefault && !rightKeys.has(prop.key));
|
|
978
|
+
if (leftRejectsRequiredRight || rightRejectsRequiredLeft)
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
try {
|
|
982
|
+
intersect(leftInput, rightInput);
|
|
983
|
+
}
|
|
984
|
+
catch (error) {
|
|
985
|
+
if (error instanceof OmpTypeError)
|
|
986
|
+
continue;
|
|
987
|
+
throw error;
|
|
988
|
+
}
|
|
989
|
+
throw new OmpTypeError("an unordered union with overlapping morph inputs is indeterminate");
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
switch (ir.k) {
|
|
994
|
+
case "alias":
|
|
995
|
+
assertDeterminateMorphUnions(ir.resolve(), seen);
|
|
996
|
+
break;
|
|
997
|
+
case "morph":
|
|
998
|
+
assertDeterminateMorphUnions(ir.input, seen);
|
|
999
|
+
if (ir.out !== undefined)
|
|
1000
|
+
assertDeterminateMorphUnions(ir.out, seen);
|
|
1001
|
+
break;
|
|
1002
|
+
case "sub":
|
|
1003
|
+
assertDeterminateMorphUnions(ir.schema.ir, seen);
|
|
1004
|
+
break;
|
|
1005
|
+
case "array":
|
|
1006
|
+
assertDeterminateMorphUnions(ir.el, seen);
|
|
1007
|
+
break;
|
|
1008
|
+
case "tuple":
|
|
1009
|
+
for (const item of ir.prefix)
|
|
1010
|
+
assertDeterminateMorphUnions(item.val, seen);
|
|
1011
|
+
if (ir.variadic !== undefined)
|
|
1012
|
+
assertDeterminateMorphUnions(ir.variadic, seen);
|
|
1013
|
+
for (const item of ir.postfix)
|
|
1014
|
+
assertDeterminateMorphUnions(item, seen);
|
|
1015
|
+
break;
|
|
1016
|
+
case "object":
|
|
1017
|
+
for (const prop of ir.props)
|
|
1018
|
+
assertDeterminateMorphUnions(prop.val, seen);
|
|
1019
|
+
if (ir.index !== undefined)
|
|
1020
|
+
assertDeterminateMorphUnions(ir.index, seen);
|
|
1021
|
+
if (ir.symbolIndex !== undefined)
|
|
1022
|
+
assertDeterminateMorphUnions(ir.symbolIndex, seen);
|
|
1023
|
+
for (const index of ir.patternIndexes ?? [])
|
|
1024
|
+
assertDeterminateMorphUnions(index.val, seen);
|
|
1025
|
+
break;
|
|
1026
|
+
case "union":
|
|
1027
|
+
case "intersection":
|
|
1028
|
+
for (const member of ir.members)
|
|
1029
|
+
assertDeterminateMorphUnions(member, seen);
|
|
1030
|
+
break;
|
|
1031
|
+
case "refine":
|
|
1032
|
+
assertDeterminateMorphUnions(ir.base, seen);
|
|
1033
|
+
break;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
function getPathIR(ir, requestedKey) {
|
|
1037
|
+
let key = requestedKey;
|
|
1038
|
+
if ((typeof key === "function" || (typeof key === "object" && key !== null)) && IR_BRAND in key) {
|
|
1039
|
+
const keySchema = key;
|
|
1040
|
+
if (keySchema.ir.k === "symbol")
|
|
1041
|
+
key = Symbol.for("omptype.index");
|
|
1042
|
+
else {
|
|
1043
|
+
const arrayIndex = type.arrayIndex;
|
|
1044
|
+
if (keySchema === arrayIndex)
|
|
1045
|
+
key = 0;
|
|
1046
|
+
else {
|
|
1047
|
+
throw new OmpTypeError(`${keySchema.expression} is not allowed as an array or object index; use a concrete property key`);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
if (typeof key !== "string" && typeof key !== "number" && typeof key !== "symbol") {
|
|
1052
|
+
throw new OmpTypeError(`get keys must be strings, numbers, or symbols`);
|
|
1053
|
+
}
|
|
1054
|
+
if (ir.k === "alias")
|
|
1055
|
+
return getPathIR(ir.resolve(), key);
|
|
1056
|
+
if (ir.k === "sub")
|
|
1057
|
+
return getPathIR(ir.schema.ir, key);
|
|
1058
|
+
if (ir.k === "union") {
|
|
1059
|
+
return unionOf(ir.members.map(member => getPathIR(member, key)));
|
|
1060
|
+
}
|
|
1061
|
+
if (ir.k === "array") {
|
|
1062
|
+
const index = typeof key === "number" ? key : typeof key === "string" && /^\d+$/.test(key) ? Number(key) : -1;
|
|
1063
|
+
if (!Number.isSafeInteger(index) || index < 0)
|
|
1064
|
+
throw new OmpTypeError(`key ${String(key)} is not declared`);
|
|
1065
|
+
return unionOf([ir.el, { k: "undefined" }]);
|
|
1066
|
+
}
|
|
1067
|
+
if (ir.k === "tuple") {
|
|
1068
|
+
const index = typeof key === "number" ? key : typeof key === "string" && /^\d+$/.test(key) ? Number(key) : -1;
|
|
1069
|
+
if (!Number.isSafeInteger(index) || index < 0)
|
|
1070
|
+
throw new OmpTypeError(`key ${String(key)} is not declared`);
|
|
1071
|
+
if (index < ir.prefix.length) {
|
|
1072
|
+
const item = ir.prefix[index];
|
|
1073
|
+
return item.opt ? unionOf([{ k: "undefined" }, item.val]) : item.val;
|
|
1074
|
+
}
|
|
1075
|
+
if (ir.variadic === undefined)
|
|
1076
|
+
throw new OmpTypeError(`key ${String(key)} is not declared`);
|
|
1077
|
+
return unionOf([{ k: "undefined" }, ir.variadic, ...ir.postfix]);
|
|
1078
|
+
}
|
|
1079
|
+
if (ir.k === "undefined")
|
|
1080
|
+
return ir;
|
|
1081
|
+
if (ir.k !== "object")
|
|
1082
|
+
throw new OmpTypeError("get requires an object schema");
|
|
1083
|
+
const matches = [];
|
|
1084
|
+
const prop = ir.props.find(candidate => candidate.key === String(key));
|
|
1085
|
+
if (prop !== undefined)
|
|
1086
|
+
matches.push(prop.val);
|
|
1087
|
+
if (typeof key === "string") {
|
|
1088
|
+
if (ir.index !== undefined)
|
|
1089
|
+
matches.push(ir.index);
|
|
1090
|
+
for (const index of ir.patternIndexes ?? []) {
|
|
1091
|
+
if (!(walk(index.key, key) instanceof OmpErrors))
|
|
1092
|
+
matches.push(index.val);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
else if (typeof key === "symbol" && ir.symbolIndex !== undefined) {
|
|
1096
|
+
matches.push(ir.symbolIndex);
|
|
1097
|
+
}
|
|
1098
|
+
if (matches.length === 0)
|
|
1099
|
+
throw new OmpTypeError(`key ${String(key)} is not declared`);
|
|
1100
|
+
const value = matches.reduce((left, right) => intersect(left, right));
|
|
1101
|
+
return prop !== undefined && !prop.opt ? value : unionOf([value, { k: "undefined" }]);
|
|
1102
|
+
}
|
|
1103
|
+
function unionOf(members) {
|
|
1104
|
+
const flattened = members.flatMap(member => (member.k === "union" ? member.members : [member]));
|
|
1105
|
+
return flattened.length === 1 ? flattened[0] : { k: "union", members: flattened };
|
|
1106
|
+
}
|
|
1107
|
+
function expressionOf(ir, ancestors = new Set()) {
|
|
1108
|
+
if (ancestors.has(ir))
|
|
1109
|
+
return ir.k === "alias" ? ir.name : ir.k;
|
|
1110
|
+
const nextAncestors = new Set(ancestors);
|
|
1111
|
+
nextAncestors.add(ir);
|
|
1112
|
+
const child = (node) => expressionOf(node, nextAncestors);
|
|
1113
|
+
switch (ir.k) {
|
|
1114
|
+
case "alias":
|
|
1115
|
+
return child(ir.resolve());
|
|
1116
|
+
case "sub":
|
|
1117
|
+
return child(ir.schema.ir);
|
|
1118
|
+
case "unknown":
|
|
1119
|
+
case "null":
|
|
1120
|
+
case "undefined":
|
|
1121
|
+
case "boolean":
|
|
1122
|
+
case "bigint":
|
|
1123
|
+
case "symbol":
|
|
1124
|
+
case "never":
|
|
1125
|
+
return ir.k;
|
|
1126
|
+
case "anyobject":
|
|
1127
|
+
return "object";
|
|
1128
|
+
case "string":
|
|
1129
|
+
return "string";
|
|
1130
|
+
case "number":
|
|
1131
|
+
return ir.divisor !== undefined ? `number % ${ir.divisor}` : ir.int ? "number % 1" : "number";
|
|
1132
|
+
case "lit":
|
|
1133
|
+
return typeof ir.v === "string" ? JSON.stringify(ir.v) : String(ir.v);
|
|
1134
|
+
case "array": {
|
|
1135
|
+
const element = child(ir.el);
|
|
1136
|
+
return `${ir.el.k === "union" || ir.el.k === "intersection" ? `(${element})` : element}[]`;
|
|
1137
|
+
}
|
|
1138
|
+
case "tuple": {
|
|
1139
|
+
const items = ir.prefix.map(item => {
|
|
1140
|
+
const value = child(item.val);
|
|
1141
|
+
if (item.hasDefault)
|
|
1142
|
+
return `${value} = ${child({ k: "lit", v: item.def })}`;
|
|
1143
|
+
return `${value}${item.opt ? "?" : ""}`;
|
|
1144
|
+
});
|
|
1145
|
+
if (ir.variadic !== undefined)
|
|
1146
|
+
items.push(`...${child(ir.variadic)}[]`);
|
|
1147
|
+
items.push(...ir.postfix.map(child));
|
|
1148
|
+
return `[${items.join(", ")}]`;
|
|
1149
|
+
}
|
|
1150
|
+
case "object": {
|
|
1151
|
+
const properties = ir.props.map(prop => `${String(prop.key)}${prop.opt ? "?" : ""}: ${child(prop.val)}`);
|
|
1152
|
+
if (ir.index !== undefined)
|
|
1153
|
+
properties.unshift(`[string]: ${child(ir.index)}`);
|
|
1154
|
+
if (ir.symbolIndex !== undefined)
|
|
1155
|
+
properties.unshift(`[symbol]: ${child(ir.symbolIndex)}`);
|
|
1156
|
+
return `{ ${properties.join(", ")} }`;
|
|
1157
|
+
}
|
|
1158
|
+
case "union":
|
|
1159
|
+
return [...new Set(ir.members.map(child))].join(" | ");
|
|
1160
|
+
case "intersection":
|
|
1161
|
+
return ir.members.map(child).join(" & ");
|
|
1162
|
+
case "refine":
|
|
1163
|
+
return child(ir.base);
|
|
1164
|
+
case "morph":
|
|
1165
|
+
return `(In: ${child(ir.input)}) => Out<${child(ir.out ?? { k: "unknown" })}>`;
|
|
1166
|
+
case "instance":
|
|
1167
|
+
return ir.ctor.name || "object";
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
function arkJsonOf(ir, ancestors = new Set()) {
|
|
1171
|
+
if (ancestors.has(ir))
|
|
1172
|
+
return ir.k === "alias" ? { alias: ir.name } : { cyclic: ir.k };
|
|
1173
|
+
const nextAncestors = new Set(ancestors);
|
|
1174
|
+
nextAncestors.add(ir);
|
|
1175
|
+
const child = (node) => arkJsonOf(node, nextAncestors);
|
|
1176
|
+
switch (ir.k) {
|
|
1177
|
+
case "alias":
|
|
1178
|
+
return child(ir.resolve());
|
|
1179
|
+
case "sub":
|
|
1180
|
+
return child(ir.schema.ir);
|
|
1181
|
+
case "lit":
|
|
1182
|
+
return {
|
|
1183
|
+
unit: ir.v === undefined ? "undefined" : typeof ir.v === "bigint" ? `${ir.v}n` : ir.v,
|
|
1184
|
+
};
|
|
1185
|
+
case "null":
|
|
1186
|
+
return { unit: null };
|
|
1187
|
+
case "undefined":
|
|
1188
|
+
return { unit: "undefined" };
|
|
1189
|
+
case "boolean":
|
|
1190
|
+
return [{ unit: false }, { unit: true }];
|
|
1191
|
+
case "union":
|
|
1192
|
+
case "intersection":
|
|
1193
|
+
return ir.members.map(child);
|
|
1194
|
+
case "array":
|
|
1195
|
+
return { proto: "Array", sequence: child(ir.el) };
|
|
1196
|
+
case "tuple":
|
|
1197
|
+
return {
|
|
1198
|
+
proto: "Array",
|
|
1199
|
+
sequence: {
|
|
1200
|
+
prefix: ir.prefix.map(item => child(item.val)),
|
|
1201
|
+
...(ir.variadic === undefined ? {} : { variadic: child(ir.variadic) }),
|
|
1202
|
+
...(ir.postfix.length === 0 ? {} : { postfix: ir.postfix.map(child) }),
|
|
1203
|
+
},
|
|
1204
|
+
};
|
|
1205
|
+
case "object":
|
|
1206
|
+
return {
|
|
1207
|
+
domain: "object",
|
|
1208
|
+
required: ir.props
|
|
1209
|
+
.filter(prop => !prop.opt && !prop.hasDefault)
|
|
1210
|
+
.map(prop => ({ key: prop.key, value: child(prop.val) })),
|
|
1211
|
+
optional: ir.props
|
|
1212
|
+
.filter(prop => prop.opt || prop.hasDefault)
|
|
1213
|
+
.map(prop => ({
|
|
1214
|
+
key: prop.key,
|
|
1215
|
+
value: child(prop.val),
|
|
1216
|
+
...(prop.hasDefault
|
|
1217
|
+
? {
|
|
1218
|
+
default: prop.defFactory && typeof prop.def === "function"
|
|
1219
|
+
? `$ark.${prop.def.name || "default"}`
|
|
1220
|
+
: prop.def,
|
|
1221
|
+
}
|
|
1222
|
+
: {}),
|
|
1223
|
+
})),
|
|
1224
|
+
};
|
|
1225
|
+
case "refine":
|
|
1226
|
+
return child(ir.base);
|
|
1227
|
+
case "morph":
|
|
1228
|
+
return { in: child(ir.input), ...(ir.out === undefined ? {} : { declaredOut: child(ir.out) }) };
|
|
1229
|
+
case "instance":
|
|
1230
|
+
return { proto: ir.ctor.name };
|
|
1231
|
+
case "anyobject":
|
|
1232
|
+
return { domain: "object" };
|
|
1233
|
+
default:
|
|
1234
|
+
return ir.k;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
386
1237
|
function requireObject(ir, operation) {
|
|
387
1238
|
if (ir.k !== "object")
|
|
388
1239
|
throw new OmpTypeError(`${operation} requires an object schema`);
|
|
389
1240
|
return ir;
|
|
390
1241
|
}
|
|
1242
|
+
function selectObjectProps(ir, keys, keepSelected, operation) {
|
|
1243
|
+
const object = requireObject(ir, operation);
|
|
1244
|
+
const selected = new Set(keys);
|
|
1245
|
+
for (const key of selected) {
|
|
1246
|
+
if (!object.props.some(prop => prop.key === key))
|
|
1247
|
+
throw new OmpTypeError(`key ${String(key)} does not exist`);
|
|
1248
|
+
}
|
|
1249
|
+
return { ...object, props: object.props.filter(prop => selected.has(prop.key) === keepSelected) };
|
|
1250
|
+
}
|
|
1251
|
+
function setObjectOptionality(ir, optional, operation) {
|
|
1252
|
+
const object = requireObject(ir, operation);
|
|
1253
|
+
return { ...object, props: object.props.map(prop => ({ ...prop, opt: optional })) };
|
|
1254
|
+
}
|
|
1255
|
+
function mergeObjectDefinition(ir, definition, resolve) {
|
|
1256
|
+
return mergeObjects(requireObject(ir, "merge"), requireObject(parseDef(definition, resolve), "merge"));
|
|
1257
|
+
}
|
|
391
1258
|
function propertyFromIR(prop) {
|
|
392
1259
|
return {
|
|
393
1260
|
kind: prop.opt ? "optional" : "required",
|
|
@@ -406,7 +1273,7 @@ function propertyToIR(property) {
|
|
|
406
1273
|
}
|
|
407
1274
|
const hasDefault = Object.hasOwn(property, "default");
|
|
408
1275
|
return {
|
|
409
|
-
key:
|
|
1276
|
+
key: property.key,
|
|
410
1277
|
opt: property.kind === "optional",
|
|
411
1278
|
val: embed(property.value),
|
|
412
1279
|
...(hasDefault
|
|
@@ -414,9 +1281,21 @@ function propertyToIR(property) {
|
|
|
414
1281
|
: {}),
|
|
415
1282
|
};
|
|
416
1283
|
}
|
|
417
|
-
function
|
|
418
|
-
if (
|
|
1284
|
+
function acceptsDateIR(ir) {
|
|
1285
|
+
if (ir.k === "instance")
|
|
1286
|
+
return ir.ctor === Date;
|
|
1287
|
+
if (ir.k === "refine")
|
|
1288
|
+
return acceptsDateIR(ir.base);
|
|
1289
|
+
if (ir.k === "union")
|
|
1290
|
+
return ir.members.every(acceptsDateIR);
|
|
1291
|
+
return false;
|
|
1292
|
+
}
|
|
1293
|
+
function dateRefinement(schema, timestamp, relation, predicate) {
|
|
1294
|
+
if (!Number.isFinite(timestamp))
|
|
419
1295
|
throw new OmpTypeError("date bound must be valid");
|
|
1296
|
+
if (!acceptsDateIR(schema.ir))
|
|
1297
|
+
throw new OmpTypeError("date bounds require a Date type");
|
|
1298
|
+
const bound = new Date(timestamp);
|
|
420
1299
|
return makeType({
|
|
421
1300
|
k: "refine",
|
|
422
1301
|
base: schema.ir,
|
|
@@ -456,6 +1335,12 @@ function selectNodes(root, kind) {
|
|
|
456
1335
|
visit(prop.val);
|
|
457
1336
|
if (node.index !== undefined)
|
|
458
1337
|
visit(node.index);
|
|
1338
|
+
if (node.symbolIndex !== undefined)
|
|
1339
|
+
visit(node.symbolIndex);
|
|
1340
|
+
for (const index of node.patternIndexes ?? []) {
|
|
1341
|
+
visit(index.key);
|
|
1342
|
+
visit(index.val);
|
|
1343
|
+
}
|
|
459
1344
|
break;
|
|
460
1345
|
case "union":
|
|
461
1346
|
case "intersection":
|
|
@@ -491,9 +1376,22 @@ function mergeObjects(left, right) {
|
|
|
491
1376
|
k: "object",
|
|
492
1377
|
props,
|
|
493
1378
|
index: right.index ?? left.index,
|
|
1379
|
+
symbolIndex: right.symbolIndex ?? left.symbolIndex,
|
|
1380
|
+
patternIndexes: left.patternIndexes === undefined && right.patternIndexes === undefined
|
|
1381
|
+
? undefined
|
|
1382
|
+
: [...(left.patternIndexes ?? []), ...(right.patternIndexes ?? [])],
|
|
494
1383
|
extras: right.extras === "keep" ? left.extras : right.extras,
|
|
495
1384
|
};
|
|
496
1385
|
}
|
|
1386
|
+
function withShallowExtras(ir, extras) {
|
|
1387
|
+
if (ir.k === "object")
|
|
1388
|
+
return { ...ir, extras };
|
|
1389
|
+
if (ir.k === "union")
|
|
1390
|
+
return { ...ir, members: ir.members.map(member => withShallowExtras(member, extras)) };
|
|
1391
|
+
if (ir.k === "alias")
|
|
1392
|
+
return withShallowExtras(ir.resolve(), extras);
|
|
1393
|
+
throw new OmpTypeError("onUndeclaredKey requires an object schema");
|
|
1394
|
+
}
|
|
497
1395
|
function withDeepExtras(ir, extras) {
|
|
498
1396
|
switch (ir.k) {
|
|
499
1397
|
case "object":
|
|
@@ -502,6 +1400,11 @@ function withDeepExtras(ir, extras) {
|
|
|
502
1400
|
extras,
|
|
503
1401
|
props: ir.props.map(prop => ({ ...prop, val: withDeepExtras(prop.val, extras) })),
|
|
504
1402
|
index: ir.index === undefined ? undefined : withDeepExtras(ir.index, extras),
|
|
1403
|
+
symbolIndex: ir.symbolIndex === undefined ? undefined : withDeepExtras(ir.symbolIndex, extras),
|
|
1404
|
+
patternIndexes: ir.patternIndexes?.map(index => ({
|
|
1405
|
+
key: withDeepExtras(index.key, extras),
|
|
1406
|
+
val: withDeepExtras(index.val, extras),
|
|
1407
|
+
})),
|
|
505
1408
|
};
|
|
506
1409
|
case "array":
|
|
507
1410
|
return { ...ir, el: withDeepExtras(ir.el, extras) };
|
|
@@ -527,12 +1430,81 @@ function withDeepExtras(ir, extras) {
|
|
|
527
1430
|
return ir;
|
|
528
1431
|
}
|
|
529
1432
|
}
|
|
1433
|
+
function intersectTupleWithArray(tuple, array) {
|
|
1434
|
+
if (array.min !== undefined || array.max !== undefined) {
|
|
1435
|
+
return { k: "intersection", members: [tuple, array] };
|
|
1436
|
+
}
|
|
1437
|
+
return {
|
|
1438
|
+
...tuple,
|
|
1439
|
+
prefix: tuple.prefix.map(item => ({ ...item, val: intersect(item.val, array.el) })),
|
|
1440
|
+
variadic: tuple.variadic === undefined ? undefined : intersect(tuple.variadic, array.el),
|
|
1441
|
+
postfix: tuple.postfix.map(item => intersect(item, array.el)),
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
function intersectTuples(left, right) {
|
|
1445
|
+
if (left.postfix.length !== 0 ||
|
|
1446
|
+
right.postfix.length !== 0 ||
|
|
1447
|
+
left.prefix.some(item => item.hasDefault) ||
|
|
1448
|
+
right.prefix.some(item => item.hasDefault)) {
|
|
1449
|
+
return { k: "intersection", members: [left, right] };
|
|
1450
|
+
}
|
|
1451
|
+
const leftRequired = left.prefix.filter(item => !item.opt).length;
|
|
1452
|
+
const rightRequired = right.prefix.filter(item => !item.opt).length;
|
|
1453
|
+
const minimum = Math.max(leftRequired, rightRequired);
|
|
1454
|
+
const leftMaximum = left.variadic === undefined ? left.prefix.length : Number.POSITIVE_INFINITY;
|
|
1455
|
+
const rightMaximum = right.variadic === undefined ? right.prefix.length : Number.POSITIVE_INFINITY;
|
|
1456
|
+
const maximum = Math.min(leftMaximum, rightMaximum);
|
|
1457
|
+
if (minimum > maximum)
|
|
1458
|
+
throw new OmpTypeError("tuple length intersection is unsatisfiable");
|
|
1459
|
+
const prefixLength = Number.isFinite(maximum) ? maximum : Math.max(left.prefix.length, right.prefix.length);
|
|
1460
|
+
const prefix = [];
|
|
1461
|
+
for (let index = 0; index < prefixLength; index++) {
|
|
1462
|
+
const leftItem = left.prefix[index];
|
|
1463
|
+
const rightItem = right.prefix[index];
|
|
1464
|
+
const leftNode = leftItem?.val ?? left.variadic;
|
|
1465
|
+
const rightNode = rightItem?.val ?? right.variadic;
|
|
1466
|
+
if (leftNode === undefined || rightNode === undefined)
|
|
1467
|
+
break;
|
|
1468
|
+
const required = (leftItem !== undefined && !leftItem.opt) || (rightItem !== undefined && !rightItem.opt);
|
|
1469
|
+
try {
|
|
1470
|
+
prefix.push({ val: intersect(leftNode, rightNode), opt: !required });
|
|
1471
|
+
}
|
|
1472
|
+
catch (error) {
|
|
1473
|
+
if (required || !(error instanceof OmpTypeError))
|
|
1474
|
+
throw error;
|
|
1475
|
+
break;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
const variadic = leftMaximum === Number.POSITIVE_INFINITY && rightMaximum === Number.POSITIVE_INFINITY
|
|
1479
|
+
? intersect(left.variadic, right.variadic)
|
|
1480
|
+
: undefined;
|
|
1481
|
+
return { k: "tuple", prefix, variadic, postfix: [] };
|
|
1482
|
+
}
|
|
1483
|
+
const kIntersections = Symbol("omptype.intersections");
|
|
530
1484
|
/** Intersect two IR nodes, rejecting statically disjoint domains. */
|
|
531
1485
|
function intersect(a, b) {
|
|
532
|
-
if (a.k === "alias")
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
1486
|
+
if (a.k === "alias" || b.k === "alias") {
|
|
1487
|
+
// Defer through a lazy alias so cyclic references terminate: revisiting
|
|
1488
|
+
// the same pair while it is being resolved returns the same node instead
|
|
1489
|
+
// of recursing forever.
|
|
1490
|
+
const target = a;
|
|
1491
|
+
target[kIntersections] ??= new WeakMap();
|
|
1492
|
+
const cache = target[kIntersections];
|
|
1493
|
+
const existing = cache.get(b);
|
|
1494
|
+
if (existing !== undefined)
|
|
1495
|
+
return existing;
|
|
1496
|
+
let resolved;
|
|
1497
|
+
const reference = {
|
|
1498
|
+
k: "alias",
|
|
1499
|
+
name: a.k === "alias" ? a.name : b.k === "alias" ? b.name : "intersection",
|
|
1500
|
+
resolve: () => (resolved ??= intersectResolved(a.k === "alias" ? a.resolve() : a, b.k === "alias" ? b.resolve() : b)),
|
|
1501
|
+
};
|
|
1502
|
+
cache.set(b, reference);
|
|
1503
|
+
return reference;
|
|
1504
|
+
}
|
|
1505
|
+
return intersectResolved(a, b);
|
|
1506
|
+
}
|
|
1507
|
+
function intersectResolved(a, b) {
|
|
536
1508
|
if (a.k === "never" || b.k === "never")
|
|
537
1509
|
throw new OmpTypeError("intersection with never is unsatisfiable");
|
|
538
1510
|
if (a.k === "unknown")
|
|
@@ -541,6 +1513,27 @@ function intersect(a, b) {
|
|
|
541
1513
|
return a;
|
|
542
1514
|
if (a === b)
|
|
543
1515
|
return a;
|
|
1516
|
+
if (a.k === "morph" && b.k === "morph") {
|
|
1517
|
+
if (a.fn !== b.fn || a.out !== b.out) {
|
|
1518
|
+
throw new OmpTypeError("intersection of distinct morphs is indeterminate");
|
|
1519
|
+
}
|
|
1520
|
+
return { ...a, input: intersect(a.input, b.input) };
|
|
1521
|
+
}
|
|
1522
|
+
if (a.k === "morph")
|
|
1523
|
+
return { ...a, input: intersect(a.input, b) };
|
|
1524
|
+
if (b.k === "morph")
|
|
1525
|
+
return { ...b, input: intersect(a, b.input) };
|
|
1526
|
+
if (a.k === "sub" && a.schema.hasSteps) {
|
|
1527
|
+
if (b.k === "sub" && b.schema.hasSteps) {
|
|
1528
|
+
if (a.schema === b.schema)
|
|
1529
|
+
return a;
|
|
1530
|
+
throw new OmpTypeError("intersection of distinct morphs is indeterminate");
|
|
1531
|
+
}
|
|
1532
|
+
const schema = a.schema;
|
|
1533
|
+
return embed(makeType(intersect(schema.ir, b), schema[kSteps], metaOf(schema)));
|
|
1534
|
+
}
|
|
1535
|
+
if (b.k === "sub" && b.schema.hasSteps)
|
|
1536
|
+
return intersect(b, a);
|
|
544
1537
|
if (a.k === "union" || b.k === "union") {
|
|
545
1538
|
const union = a.k === "union" ? a : b.k === "union" ? b : undefined;
|
|
546
1539
|
if (union === undefined)
|
|
@@ -576,7 +1569,24 @@ function intersect(a, b) {
|
|
|
576
1569
|
props.push(bp);
|
|
577
1570
|
else {
|
|
578
1571
|
const ap = props[index];
|
|
579
|
-
|
|
1572
|
+
const required = (!ap.opt && !ap.hasDefault) || (!bp.opt && !bp.hasDefault);
|
|
1573
|
+
if (ap.hasDefault && bp.hasDefault && !Object.is(ap.def, bp.def)) {
|
|
1574
|
+
throw new OmpTypeError(`ParseError: Invalid intersection of default values ${String(ap.def)} & ${String(bp.def)}`);
|
|
1575
|
+
}
|
|
1576
|
+
const defaulted = required ? undefined : ap.hasDefault ? ap : bp.hasDefault ? bp : undefined;
|
|
1577
|
+
props[index] = {
|
|
1578
|
+
key: ap.key,
|
|
1579
|
+
opt: ap.opt && bp.opt,
|
|
1580
|
+
val: intersect(ap.val, bp.val),
|
|
1581
|
+
...(defaulted
|
|
1582
|
+
? {
|
|
1583
|
+
def: defaulted.def,
|
|
1584
|
+
defFactory: defaulted.defFactory,
|
|
1585
|
+
hasDefault: true,
|
|
1586
|
+
defValidated: defaulted.defValidated,
|
|
1587
|
+
}
|
|
1588
|
+
: {}),
|
|
1589
|
+
};
|
|
580
1590
|
}
|
|
581
1591
|
}
|
|
582
1592
|
const extras = a.extras === "reject" || b.extras === "reject"
|
|
@@ -624,6 +1634,12 @@ function intersect(a, b) {
|
|
|
624
1634
|
}
|
|
625
1635
|
return { k: "array", el: intersect(a.el, b.el), min, max };
|
|
626
1636
|
}
|
|
1637
|
+
if (a.k === "tuple" && b.k === "tuple")
|
|
1638
|
+
return intersectTuples(a, b);
|
|
1639
|
+
if (a.k === "tuple" && b.k === "array")
|
|
1640
|
+
return intersectTupleWithArray(a, b);
|
|
1641
|
+
if (a.k === "array" && b.k === "tuple")
|
|
1642
|
+
return intersectTupleWithArray(b, a);
|
|
627
1643
|
if (a.k === "instance" && b.k === "instance") {
|
|
628
1644
|
if (a.ctor === b.ctor || a.ctor.prototype instanceof b.ctor)
|
|
629
1645
|
return a;
|
|
@@ -633,6 +1649,10 @@ function intersect(a, b) {
|
|
|
633
1649
|
}
|
|
634
1650
|
if (a.k === b.k && ["null", "undefined", "boolean", "bigint", "symbol", "anyobject"].includes(a.k))
|
|
635
1651
|
return a;
|
|
1652
|
+
if ((a.k === "object" && (b.k === "array" || b.k === "tuple")) ||
|
|
1653
|
+
(b.k === "object" && (a.k === "array" || a.k === "tuple"))) {
|
|
1654
|
+
return { k: "intersection", members: [a, b] };
|
|
1655
|
+
}
|
|
636
1656
|
const leftDomain = domainOf(a);
|
|
637
1657
|
const rightDomain = domainOf(b);
|
|
638
1658
|
if (leftDomain !== undefined && rightDomain !== undefined && leftDomain !== rightDomain) {
|
|
@@ -645,6 +1665,143 @@ function intersect(a, b) {
|
|
|
645
1665
|
const members = [...(a.k === "intersection" ? a.members : [a]), ...(b.k === "intersection" ? b.members : [b])];
|
|
646
1666
|
return { k: "intersection", members };
|
|
647
1667
|
}
|
|
1668
|
+
/** Reduce parsed unions/intersections to their observable semantic form. */
|
|
1669
|
+
function normalizeIR(ir) {
|
|
1670
|
+
switch (ir.k) {
|
|
1671
|
+
case "intersection": {
|
|
1672
|
+
const members = ir.members.map(normalizeIR);
|
|
1673
|
+
if (members.length === 0)
|
|
1674
|
+
return { k: "unknown" };
|
|
1675
|
+
return members.slice(1).reduce(intersect, members[0]);
|
|
1676
|
+
}
|
|
1677
|
+
case "union": {
|
|
1678
|
+
const members = [];
|
|
1679
|
+
let changed = false;
|
|
1680
|
+
for (let index = 0; index < ir.members.length; index++) {
|
|
1681
|
+
const original = ir.members[index];
|
|
1682
|
+
const member = normalizeIR(original);
|
|
1683
|
+
changed ||= member !== original;
|
|
1684
|
+
if (member.k === "union") {
|
|
1685
|
+
members.push(...member.members);
|
|
1686
|
+
changed = true;
|
|
1687
|
+
}
|
|
1688
|
+
else if (member.k === "never") {
|
|
1689
|
+
changed = true;
|
|
1690
|
+
}
|
|
1691
|
+
else if (member.k === "unknown") {
|
|
1692
|
+
return { k: "unknown" };
|
|
1693
|
+
}
|
|
1694
|
+
else {
|
|
1695
|
+
members.push(member);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
if (members.every(member => member.k === "lit" &&
|
|
1699
|
+
(member.v === null || (typeof member.v !== "object" && typeof member.v !== "function")))) {
|
|
1700
|
+
const pruned = [];
|
|
1701
|
+
for (const member of members) {
|
|
1702
|
+
if (member.k === "lit" && pruned.some(candidate => candidate.k === "lit" && candidate.v === member.v)) {
|
|
1703
|
+
changed = true;
|
|
1704
|
+
}
|
|
1705
|
+
else {
|
|
1706
|
+
pruned.push(member);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
if (pruned.length === 0)
|
|
1710
|
+
return { k: "never" };
|
|
1711
|
+
if (pruned.length === 1)
|
|
1712
|
+
return pruned[0];
|
|
1713
|
+
if (pruned.length === 2 && pruned.every(member => member.k === "lit" && typeof member.v === "boolean")) {
|
|
1714
|
+
return { k: "boolean" };
|
|
1715
|
+
}
|
|
1716
|
+
return changed ? { ...ir, members: pruned } : ir;
|
|
1717
|
+
}
|
|
1718
|
+
const pruned = members.filter((member, index) => !members.some((candidate, candidateIndex) => candidateIndex !== index &&
|
|
1719
|
+
!hasMorph(member) &&
|
|
1720
|
+
!hasMorph(candidate) &&
|
|
1721
|
+
isSubtype(member, candidate) &&
|
|
1722
|
+
(!isSubtype(candidate, member) || candidateIndex < index)));
|
|
1723
|
+
if (pruned.length === 0)
|
|
1724
|
+
return { k: "never" };
|
|
1725
|
+
if (pruned.length === 1)
|
|
1726
|
+
return pruned[0];
|
|
1727
|
+
if (pruned.length === 2 && pruned.every(member => member.k === "lit" && typeof member.v === "boolean")) {
|
|
1728
|
+
return { k: "boolean" };
|
|
1729
|
+
}
|
|
1730
|
+
if (!changed &&
|
|
1731
|
+
!pruned.some(member => member.k === "alias") &&
|
|
1732
|
+
pruned.length === ir.members.length &&
|
|
1733
|
+
pruned.every((member, index) => member === ir.members[index])) {
|
|
1734
|
+
return ir;
|
|
1735
|
+
}
|
|
1736
|
+
return { ...ir, members: pruned };
|
|
1737
|
+
}
|
|
1738
|
+
case "array": {
|
|
1739
|
+
const element = normalizeIR(ir.el);
|
|
1740
|
+
return element === ir.el && ir.el.k !== "alias" ? ir : { ...ir, el: element };
|
|
1741
|
+
}
|
|
1742
|
+
case "tuple": {
|
|
1743
|
+
const prefix = ir.prefix.map(item => {
|
|
1744
|
+
const value = normalizeIR(item.val);
|
|
1745
|
+
return value === item.val ? item : { ...item, val: value };
|
|
1746
|
+
});
|
|
1747
|
+
const variadic = ir.variadic === undefined ? undefined : normalizeIR(ir.variadic);
|
|
1748
|
+
const postfix = ir.postfix.map(normalizeIR);
|
|
1749
|
+
if (!ir.prefix.some(item => item.val.k === "alias") &&
|
|
1750
|
+
ir.variadic?.k !== "alias" &&
|
|
1751
|
+
!ir.postfix.some(item => item.k === "alias") &&
|
|
1752
|
+
prefix.every((item, index) => item === ir.prefix[index]) &&
|
|
1753
|
+
variadic === ir.variadic &&
|
|
1754
|
+
postfix.every((item, index) => item === ir.postfix[index])) {
|
|
1755
|
+
return ir;
|
|
1756
|
+
}
|
|
1757
|
+
return { ...ir, prefix, variadic, postfix };
|
|
1758
|
+
}
|
|
1759
|
+
case "object": {
|
|
1760
|
+
let props;
|
|
1761
|
+
for (let index = 0; index < ir.props.length; index++) {
|
|
1762
|
+
const prop = ir.props[index];
|
|
1763
|
+
const value = normalizeIR(prop.val);
|
|
1764
|
+
if (value === prop.val)
|
|
1765
|
+
continue;
|
|
1766
|
+
props ??= [...ir.props];
|
|
1767
|
+
props[index] = { ...prop, val: value };
|
|
1768
|
+
}
|
|
1769
|
+
const index = ir.index === undefined ? undefined : normalizeIR(ir.index);
|
|
1770
|
+
const symbolIndex = ir.symbolIndex === undefined ? undefined : normalizeIR(ir.symbolIndex);
|
|
1771
|
+
const patternIndexes = ir.patternIndexes?.map(pattern => {
|
|
1772
|
+
const key = normalizeIR(pattern.key);
|
|
1773
|
+
const val = normalizeIR(pattern.val);
|
|
1774
|
+
return key === pattern.key && val === pattern.val ? pattern : { key, val };
|
|
1775
|
+
});
|
|
1776
|
+
if (props === undefined &&
|
|
1777
|
+
!ir.props.some(prop => prop.val.k === "alias") &&
|
|
1778
|
+
ir.index?.k !== "alias" &&
|
|
1779
|
+
ir.symbolIndex?.k !== "alias" &&
|
|
1780
|
+
!ir.patternIndexes?.some(pattern => pattern.key.k === "alias" || pattern.val.k === "alias") &&
|
|
1781
|
+
index === ir.index &&
|
|
1782
|
+
symbolIndex === ir.symbolIndex &&
|
|
1783
|
+
patternIndexes?.every((pattern, patternIndex) => pattern === ir.patternIndexes?.[patternIndex]) !== false) {
|
|
1784
|
+
return ir;
|
|
1785
|
+
}
|
|
1786
|
+
return { ...ir, props: props ?? ir.props, index, symbolIndex, patternIndexes };
|
|
1787
|
+
}
|
|
1788
|
+
case "refine": {
|
|
1789
|
+
const base = normalizeIR(ir.base);
|
|
1790
|
+
return base === ir.base && ir.base.k !== "alias" ? ir : { ...ir, base };
|
|
1791
|
+
}
|
|
1792
|
+
case "morph": {
|
|
1793
|
+
const input = normalizeIR(ir.input);
|
|
1794
|
+
const out = ir.out === undefined ? undefined : normalizeIR(ir.out);
|
|
1795
|
+
return input === ir.input && out === ir.out && ir.input.k !== "alias" && ir.out?.k !== "alias"
|
|
1796
|
+
? ir
|
|
1797
|
+
: { ...ir, input, out };
|
|
1798
|
+
}
|
|
1799
|
+
case "alias":
|
|
1800
|
+
return ir;
|
|
1801
|
+
default:
|
|
1802
|
+
return ir;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
648
1805
|
function domainOf(ir) {
|
|
649
1806
|
switch (ir.k) {
|
|
650
1807
|
case "null":
|
|
@@ -829,16 +1986,175 @@ function withLengthBound(ir, side, bound) {
|
|
|
829
1986
|
throw new OmpTypeError(`cannot apply length bound to ${ir.k}`);
|
|
830
1987
|
}
|
|
831
1988
|
function withNumericBound(ir, side, bound, exclusive = false) {
|
|
1989
|
+
if (!Number.isFinite(bound))
|
|
1990
|
+
throw new OmpTypeError("numeric bound must be finite");
|
|
832
1991
|
if (ir.k === "number") {
|
|
833
1992
|
return side === "min" ? { ...ir, min: bound, xmin: exclusive } : { ...ir, max: bound, xmax: exclusive };
|
|
834
1993
|
}
|
|
1994
|
+
if (ir.k === "union") {
|
|
1995
|
+
return { ...ir, members: ir.members.map(member => withNumericBound(member, side, bound, exclusive)) };
|
|
1996
|
+
}
|
|
835
1997
|
throw new OmpTypeError(`cannot apply numeric bound to ${ir.k}`);
|
|
836
1998
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1999
|
+
const GENERIC_META = Symbol("omptype.generic");
|
|
2000
|
+
function validateGenericParameters(parameters) {
|
|
2001
|
+
const names = new Set();
|
|
2002
|
+
for (const parameter of parameters) {
|
|
2003
|
+
if (!/^[A-Za-z_$]\w*$/.test(parameter.name)) {
|
|
2004
|
+
throw new OmpTypeError(`invalid generic parameter "${parameter.name}"`);
|
|
2005
|
+
}
|
|
2006
|
+
if (names.has(parameter.name))
|
|
2007
|
+
throw new OmpTypeError(`duplicate generic parameter "${parameter.name}"`);
|
|
2008
|
+
names.add(parameter.name);
|
|
2009
|
+
}
|
|
2010
|
+
if (parameters.length === 0)
|
|
2011
|
+
throw new OmpTypeError("generic declarations require at least one parameter");
|
|
2012
|
+
}
|
|
2013
|
+
function parseGenericParameters(source) {
|
|
2014
|
+
const trimmed = source.trim();
|
|
2015
|
+
const body = trimmed.startsWith("<") && trimmed.endsWith(">") ? trimmed.slice(1, -1) : trimmed;
|
|
2016
|
+
const parts = [];
|
|
2017
|
+
let start = 0;
|
|
2018
|
+
let depth = 0;
|
|
2019
|
+
let quote = "";
|
|
2020
|
+
for (let index = 0; index < body.length; index++) {
|
|
2021
|
+
const char = body[index];
|
|
2022
|
+
if (quote !== "") {
|
|
2023
|
+
if (char === quote && body[index - 1] !== "\\")
|
|
2024
|
+
quote = "";
|
|
2025
|
+
continue;
|
|
2026
|
+
}
|
|
2027
|
+
if (char === "'" || char === '"' || char === "`")
|
|
2028
|
+
quote = char;
|
|
2029
|
+
else if (char === "<" || char === "(" || char === "[")
|
|
2030
|
+
depth++;
|
|
2031
|
+
else if (char === ">" || char === ")" || char === "]")
|
|
2032
|
+
depth = Math.max(0, depth - 1);
|
|
2033
|
+
else if (char === "," && depth === 0) {
|
|
2034
|
+
parts.push(body.slice(start, index).trim());
|
|
2035
|
+
start = index + 1;
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
parts.push(body.slice(start).trim());
|
|
2039
|
+
const parameters = parts.map(part => {
|
|
2040
|
+
const constrained = part.match(/^([A-Za-z_$]\w*)\s+extends\s+(.+)$/s);
|
|
2041
|
+
return constrained === null ? { name: part } : { name: constrained[1], constraintDef: constrained[2].trim() };
|
|
2042
|
+
});
|
|
2043
|
+
validateGenericParameters(parameters);
|
|
2044
|
+
return parameters;
|
|
2045
|
+
}
|
|
2046
|
+
function parseGenericDeclaration(source) {
|
|
2047
|
+
const match = source.trim().match(/^([A-Za-z_$]\w*)\s*(<.*>)$/s);
|
|
2048
|
+
if (match === null)
|
|
2049
|
+
return undefined;
|
|
2050
|
+
return { name: match[1], parameters: parseGenericParameters(match[2]) };
|
|
2051
|
+
}
|
|
2052
|
+
function isRuntimeGeneric(value) {
|
|
2053
|
+
return typeof value === "function" && GENERIC_META in value;
|
|
2054
|
+
}
|
|
2055
|
+
function genericResolver(parameters, arguments_, outer) {
|
|
2056
|
+
const byName = new Map();
|
|
2057
|
+
for (let index = 0; index < parameters.length; index++)
|
|
2058
|
+
byName.set(parameters[index].name, arguments_[index]);
|
|
2059
|
+
const resolve = ((name) => byName.get(name) ?? outer?.(name));
|
|
2060
|
+
resolve.hasGeneric = outer?.hasGeneric;
|
|
2061
|
+
resolve.generic = outer?.generic;
|
|
2062
|
+
return resolve;
|
|
2063
|
+
}
|
|
2064
|
+
function parseGenericArgument(definition, outer) {
|
|
2065
|
+
if (outer === undefined) {
|
|
2066
|
+
try {
|
|
2067
|
+
return parseDef(definition);
|
|
2068
|
+
}
|
|
2069
|
+
catch (error) {
|
|
2070
|
+
if (!(error instanceof OmpTypeError) || !error.message.includes('unknown keyword "this"'))
|
|
2071
|
+
throw error;
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
let root;
|
|
2075
|
+
const self = {
|
|
2076
|
+
k: "alias",
|
|
2077
|
+
name: "this",
|
|
2078
|
+
resolve: () => {
|
|
2079
|
+
if (root === undefined || root === self)
|
|
2080
|
+
throw new OmpTypeError('"this" cannot be used as a root definition');
|
|
2081
|
+
return root;
|
|
2082
|
+
},
|
|
2083
|
+
};
|
|
2084
|
+
const resolve = ((name) => (name === "this" ? self : outer?.(name)));
|
|
2085
|
+
if (outer !== undefined) {
|
|
2086
|
+
resolve.hasGeneric = outer.hasGeneric;
|
|
2087
|
+
resolve.generic = outer.generic;
|
|
2088
|
+
}
|
|
2089
|
+
else {
|
|
2090
|
+
// The retry only exists to serve "this"; parses of this-free member
|
|
2091
|
+
// strings inside the definition may still share the string cache.
|
|
2092
|
+
markThisOnlyResolver(resolve);
|
|
2093
|
+
}
|
|
2094
|
+
root = parseDef(definition, resolve);
|
|
2095
|
+
if (root === self)
|
|
2096
|
+
throw new OmpTypeError('"this" cannot be used as a root definition');
|
|
2097
|
+
return root;
|
|
2098
|
+
}
|
|
2099
|
+
function genericBodyIR(parameters, definition, arguments_, outer) {
|
|
2100
|
+
const resolve = genericResolver(parameters, arguments_, outer);
|
|
2101
|
+
const body = typeof definition === "function" && !(IR_BRAND in definition)
|
|
2102
|
+
? definition(Object.fromEntries(parameters.map((parameter, index) => [
|
|
2103
|
+
parameter.name,
|
|
2104
|
+
makeType(arguments_[index], EMPTY_STEPS, EMPTY_META),
|
|
2105
|
+
])))
|
|
2106
|
+
: definition;
|
|
2107
|
+
return parseDef(body, resolve);
|
|
2108
|
+
}
|
|
2109
|
+
function createRuntimeGeneric(parameters, definition, outer, validateBody = true) {
|
|
2110
|
+
const constraintResolve = ((name) => outer?.(name));
|
|
2111
|
+
constraintResolve.hasGeneric = outer?.hasGeneric;
|
|
2112
|
+
constraintResolve.generic = outer?.generic;
|
|
2113
|
+
validateGenericParameters(parameters);
|
|
2114
|
+
const placeholders = parameters.map(parameter => parameter.constraintDef === undefined
|
|
2115
|
+
? { k: "unknown" }
|
|
2116
|
+
: parseDef(parameter.constraintDef, constraintResolve));
|
|
2117
|
+
if (validateBody)
|
|
2118
|
+
genericBodyIR(parameters, definition, placeholders, outer);
|
|
2119
|
+
const meta = {
|
|
2120
|
+
parameters,
|
|
2121
|
+
instantiateIR(arguments_) {
|
|
2122
|
+
if (arguments_.length !== parameters.length) {
|
|
2123
|
+
throw new OmpTypeError(`generic expects ${parameters.length} arguments (received ${arguments_.length})`);
|
|
2124
|
+
}
|
|
2125
|
+
for (let index = 0; index < parameters.length; index++) {
|
|
2126
|
+
const parameter = parameters[index];
|
|
2127
|
+
if (parameter.constraintDef === undefined)
|
|
2128
|
+
continue;
|
|
2129
|
+
const constraint = parseDef(parameter.constraintDef, constraintResolve);
|
|
2130
|
+
if (!isSubtype(arguments_[index], constraint)) {
|
|
2131
|
+
throw new OmpTypeError(`${parameter.name} must be assignable to its constraint`);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
return genericBodyIR(parameters, definition, arguments_, outer);
|
|
2135
|
+
},
|
|
2136
|
+
};
|
|
2137
|
+
const generic = Object.assign((...arguments_) => makeType(meta.instantiateIR(arguments_.map(argument => parseGenericArgument(argument, outer))), EMPTY_STEPS, EMPTY_META), { [GENERIC_META]: meta });
|
|
2138
|
+
Object.defineProperty(generic, GENERIC_META, { value: meta });
|
|
2139
|
+
return generic;
|
|
2140
|
+
}
|
|
2141
|
+
export function type(first) {
|
|
2142
|
+
// biome-ignore lint/complexity/noArguments: Avoid allocating a rest array for the dominant single-definition call.
|
|
2143
|
+
const count = arguments.length;
|
|
2144
|
+
if (count === 2 && typeof first === "string" && first.trimStart().startsWith("<")) {
|
|
2145
|
+
// biome-ignore lint/complexity/noArguments: The generic path reads its second positional argument without a rest array.
|
|
2146
|
+
return createRuntimeGeneric(parseGenericParameters(first), arguments[1]);
|
|
2147
|
+
}
|
|
2148
|
+
let definition = first;
|
|
2149
|
+
if (count !== 1) {
|
|
2150
|
+
const expression = new Array(count);
|
|
2151
|
+
for (let index = 0; index < count; index++) {
|
|
2152
|
+
// biome-ignore lint/complexity/noArguments: Only multi-part expressions pay to materialize an argument array.
|
|
2153
|
+
expression[index] = arguments[index];
|
|
2154
|
+
}
|
|
2155
|
+
definition = expression;
|
|
2156
|
+
}
|
|
2157
|
+
return makeType(parseGenericArgument(definition), EMPTY_STEPS, EMPTY_META);
|
|
842
2158
|
}
|
|
843
2159
|
function keywordSchema(name) {
|
|
844
2160
|
const ir = keywordIR(name);
|
|
@@ -859,11 +2175,426 @@ function preformattedKeyword(name) {
|
|
|
859
2175
|
function caseResolver(value) {
|
|
860
2176
|
if (typeof value !== "function")
|
|
861
2177
|
throw new OmpTypeError("match case values must be functions");
|
|
862
|
-
return input => Reflect.apply(value, undefined, [input]);
|
|
2178
|
+
return (input, ...args) => Reflect.apply(value, undefined, [input, ...args]);
|
|
2179
|
+
}
|
|
2180
|
+
function unionIR(branches) {
|
|
2181
|
+
const members = branches.map(branch => branch.schema.ir);
|
|
2182
|
+
if (members.length === 0)
|
|
2183
|
+
return { k: "never" };
|
|
2184
|
+
if (members.length === 1)
|
|
2185
|
+
return members[0];
|
|
2186
|
+
return { k: "union", members };
|
|
2187
|
+
}
|
|
2188
|
+
function publicMatcher(state, fallback) {
|
|
2189
|
+
const fallbackResolver = typeof fallback === "function" ? caseResolver(fallback) : undefined;
|
|
2190
|
+
const casesIR = unionIR(state.branches);
|
|
2191
|
+
let casesSchema;
|
|
2192
|
+
if (state.key === undefined || state.branches.length === 0) {
|
|
2193
|
+
casesSchema =
|
|
2194
|
+
state.key === undefined
|
|
2195
|
+
? makeType(casesIR, EMPTY_STEPS, EMPTY_META)
|
|
2196
|
+
: state.parse({ [state.key]: "never" });
|
|
2197
|
+
}
|
|
2198
|
+
else {
|
|
2199
|
+
const first = state.branches[0].schema.ir;
|
|
2200
|
+
if (first.k !== "object")
|
|
2201
|
+
throw new OmpTypeError("match.at cases must define object schemas");
|
|
2202
|
+
const propertyKey = String(state.key);
|
|
2203
|
+
const values = [];
|
|
2204
|
+
for (const branch of state.branches) {
|
|
2205
|
+
if (branch.schema.ir.k !== "object")
|
|
2206
|
+
throw new OmpTypeError("match.at cases must define object schemas");
|
|
2207
|
+
const property = branch.schema.ir.props.find(candidate => candidate.key === propertyKey);
|
|
2208
|
+
if (property === undefined)
|
|
2209
|
+
throw new OmpTypeError(`match.at case is missing ${propertyKey}`);
|
|
2210
|
+
values.push(property.val);
|
|
2211
|
+
}
|
|
2212
|
+
const value = values.length === 1 ? values[0] : { k: "union", members: values };
|
|
2213
|
+
casesSchema = makeType({
|
|
2214
|
+
...first,
|
|
2215
|
+
props: first.props.map(property => (property.key === propertyKey ? { ...property, val: value } : property)),
|
|
2216
|
+
}, EMPTY_STEPS, EMPTY_META);
|
|
2217
|
+
}
|
|
2218
|
+
const execute = (input, args) => {
|
|
2219
|
+
let matchedInput = input;
|
|
2220
|
+
if (state.input !== undefined) {
|
|
2221
|
+
const validated = state.input.run(input);
|
|
2222
|
+
if (validated instanceof OmpErrors)
|
|
2223
|
+
return validated;
|
|
2224
|
+
matchedInput = validated;
|
|
2225
|
+
}
|
|
2226
|
+
for (const branch of state.branches) {
|
|
2227
|
+
const matched = branch.schema.run(matchedInput);
|
|
2228
|
+
if (!(matched instanceof OmpErrors))
|
|
2229
|
+
return branch.resolve(matched, ...args);
|
|
2230
|
+
}
|
|
2231
|
+
if (fallbackResolver !== undefined)
|
|
2232
|
+
return fallbackResolver(matchedInput, ...args);
|
|
2233
|
+
return casesSchema.run(matchedInput);
|
|
2234
|
+
};
|
|
2235
|
+
const schema = makeType({ k: "unknown" }, [{ kind: "pipe", fn: input => execute(input, []) }], EMPTY_META);
|
|
2236
|
+
const callable = ((value, ...args) => {
|
|
2237
|
+
const result = execute(value, args);
|
|
2238
|
+
if ((fallback === "assert" || fallback === "never") && result instanceof OmpErrors) {
|
|
2239
|
+
throw new TraversalError(result);
|
|
2240
|
+
}
|
|
2241
|
+
return result;
|
|
2242
|
+
});
|
|
2243
|
+
Object.assign(callable, schema);
|
|
2244
|
+
Object.setPrototypeOf(callable, typeMethods);
|
|
2245
|
+
return callable;
|
|
2246
|
+
}
|
|
2247
|
+
function addMatchCases(state, cases) {
|
|
2248
|
+
const branches = [...state.branches];
|
|
2249
|
+
let fallback;
|
|
2250
|
+
for (const rawDefinition of Reflect.ownKeys(cases)) {
|
|
2251
|
+
const value = Reflect.get(cases, rawDefinition);
|
|
2252
|
+
if (rawDefinition === "default") {
|
|
2253
|
+
if (value !== "assert" && value !== "never" && value !== "reject" && typeof value !== "function") {
|
|
2254
|
+
throw new OmpTypeError('match default must be "assert", "never", "reject" or a function');
|
|
2255
|
+
}
|
|
2256
|
+
fallback = value;
|
|
2257
|
+
continue;
|
|
2258
|
+
}
|
|
2259
|
+
const definition = typeof rawDefinition === "symbol" ? rawDefinition : String(rawDefinition);
|
|
2260
|
+
const caseDefinition = state.key === undefined ? definition : { [state.key]: definition };
|
|
2261
|
+
branches.push({
|
|
2262
|
+
definition,
|
|
2263
|
+
schema: state.parse(caseDefinition),
|
|
2264
|
+
resolve: caseResolver(value),
|
|
2265
|
+
});
|
|
2266
|
+
}
|
|
2267
|
+
const next = { ...state, branches };
|
|
2268
|
+
return fallback === undefined ? createMatchParser(next) : publicMatcher(next, fallback);
|
|
2269
|
+
}
|
|
2270
|
+
function createMatchParser(state) {
|
|
2271
|
+
const parser = ((cases) => addMatchCases(state, cases));
|
|
2272
|
+
parser.case = (definition, resolver) => {
|
|
2273
|
+
const caseDefinition = state.key === undefined ? definition : { [state.key]: definition };
|
|
2274
|
+
return createMatchParser({
|
|
2275
|
+
...state,
|
|
2276
|
+
branches: [
|
|
2277
|
+
...state.branches,
|
|
2278
|
+
{
|
|
2279
|
+
definition,
|
|
2280
|
+
schema: state.parse(caseDefinition),
|
|
2281
|
+
resolve: caseResolver(resolver),
|
|
2282
|
+
},
|
|
2283
|
+
],
|
|
2284
|
+
});
|
|
2285
|
+
};
|
|
2286
|
+
parser.match = cases => addMatchCases(state, cases);
|
|
2287
|
+
parser.default = fallback => publicMatcher(state, fallback);
|
|
2288
|
+
function at(key, cases) {
|
|
2289
|
+
if (state.key !== undefined)
|
|
2290
|
+
throw new OmpTypeError("match.at may only be specified once");
|
|
2291
|
+
const next = createMatchParser({ ...state, key });
|
|
2292
|
+
return cases === undefined ? next : next.match(cases);
|
|
2293
|
+
}
|
|
2294
|
+
parser.at = at;
|
|
2295
|
+
parser.strings = cases => {
|
|
2296
|
+
if (state.key === undefined)
|
|
2297
|
+
throw new OmpTypeError("match.strings requires match.at(key)");
|
|
2298
|
+
const definitions = {};
|
|
2299
|
+
for (const key of Reflect.ownKeys(cases)) {
|
|
2300
|
+
definitions[key === "default" ? key : JSON.stringify(String(key))] = Reflect.get(cases, key);
|
|
2301
|
+
}
|
|
2302
|
+
return addMatchCases(state, definitions);
|
|
2303
|
+
};
|
|
2304
|
+
parser.in = (...args) => {
|
|
2305
|
+
if (state.input !== undefined)
|
|
2306
|
+
throw new OmpTypeError("match.in may only be specified once");
|
|
2307
|
+
return createMatchParser({
|
|
2308
|
+
...state,
|
|
2309
|
+
...(args.length === 0 ? {} : { input: state.parse(args[0]) }),
|
|
2310
|
+
});
|
|
2311
|
+
};
|
|
2312
|
+
return parser;
|
|
2313
|
+
}
|
|
2314
|
+
/** Build a fluent first-match dispatcher from schema definitions. */
|
|
2315
|
+
const matchBuilder = createMatchParser({
|
|
2316
|
+
parse: definition => type.raw(definition),
|
|
2317
|
+
branches: [],
|
|
2318
|
+
});
|
|
2319
|
+
export { matchBuilder as match };
|
|
2320
|
+
function fnExpression(ir) {
|
|
2321
|
+
switch (ir.k) {
|
|
2322
|
+
case "unknown":
|
|
2323
|
+
case "null":
|
|
2324
|
+
case "undefined":
|
|
2325
|
+
case "boolean":
|
|
2326
|
+
case "bigint":
|
|
2327
|
+
case "symbol":
|
|
2328
|
+
case "never":
|
|
2329
|
+
case "string":
|
|
2330
|
+
case "number":
|
|
2331
|
+
return ir.k;
|
|
2332
|
+
case "anyobject":
|
|
2333
|
+
return "object";
|
|
2334
|
+
case "lit":
|
|
2335
|
+
return typeof ir.v === "string" ? JSON.stringify(ir.v) : String(ir.v);
|
|
2336
|
+
case "union":
|
|
2337
|
+
return ir.members.map(fnExpression).join(" | ");
|
|
2338
|
+
case "intersection":
|
|
2339
|
+
return ir.members.map(fnExpression).join(" & ");
|
|
2340
|
+
case "array": {
|
|
2341
|
+
const element = fnExpression(ir.el);
|
|
2342
|
+
return ir.el.k === "unknown" ? "Array" : `${element.includes(" | ") ? `(${element})` : element}[]`;
|
|
2343
|
+
}
|
|
2344
|
+
case "tuple": {
|
|
2345
|
+
const elements = ir.prefix.map(item => {
|
|
2346
|
+
const expression = fnExpression(item.val);
|
|
2347
|
+
if (item.hasDefault)
|
|
2348
|
+
return `${expression} = ${String(item.def)}`;
|
|
2349
|
+
return item.opt ? `${expression}?` : expression;
|
|
2350
|
+
});
|
|
2351
|
+
if (ir.variadic !== undefined)
|
|
2352
|
+
elements.push(`...${fnExpression({ k: "array", el: ir.variadic })}`);
|
|
2353
|
+
elements.push(...ir.postfix.map(fnExpression));
|
|
2354
|
+
return `[${elements.join(", ")}]`;
|
|
2355
|
+
}
|
|
2356
|
+
case "object":
|
|
2357
|
+
return `{ ${ir.props
|
|
2358
|
+
.map(property => `${String(property.key)}${property.opt ? "?" : ""}: ${fnExpression(property.val)}`)
|
|
2359
|
+
.join(", ")} }`;
|
|
2360
|
+
case "refine":
|
|
2361
|
+
return fnExpression(ir.base);
|
|
2362
|
+
case "morph":
|
|
2363
|
+
return `(In: ${fnExpression(ir.input)}) => To<${fnExpression(ir.out ?? { k: "unknown" })}>`;
|
|
2364
|
+
case "instance":
|
|
2365
|
+
return ir.ctor.name || "object";
|
|
2366
|
+
case "alias":
|
|
2367
|
+
return fnExpression(ir.resolve());
|
|
2368
|
+
case "sub": {
|
|
2369
|
+
const schema = ir.schema;
|
|
2370
|
+
if (!schema.hasSteps)
|
|
2371
|
+
return fnExpression(schema.ir);
|
|
2372
|
+
return `(In: ${fnExpression(schema.ir)}) => To<${fnExpression(schema.stepOut ?? { k: "unknown" })}>`;
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
function normalizeFnParameter(definition) {
|
|
2377
|
+
if (typeof definition !== "string")
|
|
2378
|
+
return definition;
|
|
2379
|
+
const optional = definition.match(/^(.*[^\s])\?$/);
|
|
2380
|
+
if (optional)
|
|
2381
|
+
return [optional[1], "?"];
|
|
2382
|
+
const defaulted = definition.match(/^(.*?)\s*=\s*(.+)$/);
|
|
2383
|
+
if (defaulted) {
|
|
2384
|
+
const source = defaulted[2];
|
|
2385
|
+
const value = source === "true"
|
|
2386
|
+
? true
|
|
2387
|
+
: source === "false"
|
|
2388
|
+
? false
|
|
2389
|
+
: source === "null"
|
|
2390
|
+
? null
|
|
2391
|
+
: Number.isNaN(Number(source))
|
|
2392
|
+
? source
|
|
2393
|
+
: Number(source);
|
|
2394
|
+
return [defaulted[1], "=", value];
|
|
2395
|
+
}
|
|
2396
|
+
return definition;
|
|
2397
|
+
}
|
|
2398
|
+
function makeFn(resolve) {
|
|
2399
|
+
function parser(...definitions) {
|
|
2400
|
+
const marker = definitions.indexOf(":");
|
|
2401
|
+
if (marker !== -1 && (marker !== definitions.length - 2 || definitions.lastIndexOf(":") !== marker)) {
|
|
2402
|
+
throw new OmpTypeError('":" must be followed by exactly one return type e.g:\nfn("string", ":", "number")(s => s.length)');
|
|
2403
|
+
}
|
|
2404
|
+
const spreadIndexes = [];
|
|
2405
|
+
for (let index = 0; index < definitions.length; index++) {
|
|
2406
|
+
if (definitions[index] === "...")
|
|
2407
|
+
spreadIndexes.push(index);
|
|
2408
|
+
}
|
|
2409
|
+
if (spreadIndexes.length > 1) {
|
|
2410
|
+
const secondSpread = definitions[spreadIndexes[1] + 1];
|
|
2411
|
+
if (Array.isArray(secondSpread) &&
|
|
2412
|
+
secondSpread.some(element => typeof element === "string" && (element.endsWith("?") || element.includes("=")))) {
|
|
2413
|
+
throw new OmpTypeError("An optional element may not follow a variadic element");
|
|
2414
|
+
}
|
|
2415
|
+
throw new OmpTypeError("A tuple may have at most one variadic element");
|
|
2416
|
+
}
|
|
2417
|
+
if (spreadIndexes.length === 1 && spreadIndexes[0] + 2 < (marker === -1 ? definitions.length : marker)) {
|
|
2418
|
+
const preceding = definitions.slice(0, spreadIndexes[0]);
|
|
2419
|
+
if (preceding.some(element => typeof element === "string" && (element.endsWith("?") || /\s=\s/.test(element)))) {
|
|
2420
|
+
throw new OmpTypeError("A postfix required element cannot follow an optional or defaultable element");
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
const parameterDefinitions = (marker === -1 ? definitions : definitions.slice(0, marker)).map(normalizeFnParameter);
|
|
2424
|
+
const params = makeType(parseDef(parameterDefinitions, resolve), [], {});
|
|
2425
|
+
const returns = marker === -1
|
|
2426
|
+
? makeType({ k: "unknown" }, [], {})
|
|
2427
|
+
: makeType(parseDef(definitions[marker + 1], resolve), [], {});
|
|
2428
|
+
const parameterExpression = fnExpression(params.ir);
|
|
2429
|
+
const returnsExpression = fnExpression(returns.ir);
|
|
2430
|
+
return (implementation) => {
|
|
2431
|
+
if (typeof implementation !== "function")
|
|
2432
|
+
throw new OmpTypeError("type.fn requires a function implementation");
|
|
2433
|
+
const raw = (...arguments_) => {
|
|
2434
|
+
const validatedArguments = params.assert(arguments_);
|
|
2435
|
+
const result = Reflect.apply(implementation, undefined, validatedArguments);
|
|
2436
|
+
return returns.assert(result);
|
|
2437
|
+
};
|
|
2438
|
+
const typed = raw.bind(undefined);
|
|
2439
|
+
Object.defineProperties(typed, {
|
|
2440
|
+
name: { value: `bound typed ${implementation.name}`, configurable: true },
|
|
2441
|
+
raw: { value: implementation, enumerable: true },
|
|
2442
|
+
params: { value: params, enumerable: true },
|
|
2443
|
+
returns: { value: returns, enumerable: true },
|
|
2444
|
+
expression: {
|
|
2445
|
+
value: `(${parameterExpression.slice(1, -1)}) => ${returnsExpression}`,
|
|
2446
|
+
enumerable: true,
|
|
2447
|
+
},
|
|
2448
|
+
});
|
|
2449
|
+
return typed;
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
return Object.assign(parser, { raw: parser });
|
|
2453
|
+
}
|
|
2454
|
+
/** Fix a schema's externally declared static type without changing its runtime validation. */
|
|
2455
|
+
// biome-ignore lint/complexity/noBannedTypes: empty default options object
|
|
2456
|
+
export function declare() {
|
|
2457
|
+
return {
|
|
2458
|
+
type: definition => type(definition),
|
|
2459
|
+
};
|
|
2460
|
+
}
|
|
2461
|
+
function isTypeValue(value) {
|
|
2462
|
+
return (typeof value === "function" || (typeof value === "object" && value !== null)) && IR_BRAND in value;
|
|
2463
|
+
}
|
|
2464
|
+
function resolveAlias(ir) {
|
|
2465
|
+
const seen = new Set();
|
|
2466
|
+
let current = ir;
|
|
2467
|
+
while (current.k === "alias" && !seen.has(current)) {
|
|
2468
|
+
seen.add(current);
|
|
2469
|
+
current = current.resolve();
|
|
2470
|
+
}
|
|
2471
|
+
return current;
|
|
2472
|
+
}
|
|
2473
|
+
function sameUnionMember(left, right) {
|
|
2474
|
+
const a = resolveAlias(left);
|
|
2475
|
+
const b = resolveAlias(right);
|
|
2476
|
+
if (a.k === "lit" && b.k === "lit")
|
|
2477
|
+
return Object.is(a.v, b.v);
|
|
2478
|
+
if (a.k !== b.k)
|
|
2479
|
+
return false;
|
|
2480
|
+
switch (a.k) {
|
|
2481
|
+
case "unknown":
|
|
2482
|
+
case "never":
|
|
2483
|
+
case "null":
|
|
2484
|
+
case "undefined":
|
|
2485
|
+
case "boolean":
|
|
2486
|
+
case "bigint":
|
|
2487
|
+
case "symbol":
|
|
2488
|
+
case "anyobject":
|
|
2489
|
+
return true;
|
|
2490
|
+
default:
|
|
2491
|
+
return false;
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
function buildOr(definitions, resolve) {
|
|
2495
|
+
const members = [];
|
|
2496
|
+
const add = (candidate) => {
|
|
2497
|
+
const resolved = resolveAlias(candidate);
|
|
2498
|
+
if (resolved.k === "unknown") {
|
|
2499
|
+
members.length = 0;
|
|
2500
|
+
members.push(resolved);
|
|
2501
|
+
return false;
|
|
2502
|
+
}
|
|
2503
|
+
if (resolved.k === "never")
|
|
2504
|
+
return true;
|
|
2505
|
+
if (resolved.k === "union") {
|
|
2506
|
+
for (const member of resolved.members) {
|
|
2507
|
+
if (!add(member))
|
|
2508
|
+
return false;
|
|
2509
|
+
}
|
|
2510
|
+
return true;
|
|
2511
|
+
}
|
|
2512
|
+
if (!members.some(member => sameUnionMember(member, candidate)))
|
|
2513
|
+
members.push(candidate);
|
|
2514
|
+
return true;
|
|
2515
|
+
};
|
|
2516
|
+
for (const definition of definitions) {
|
|
2517
|
+
if (!add(parseDef(definition, resolve)))
|
|
2518
|
+
break;
|
|
2519
|
+
}
|
|
2520
|
+
const ir = members.length === 0 ? { k: "never" } : members.length === 1 ? members[0] : { k: "union", members };
|
|
2521
|
+
return makeType(ir, [], {});
|
|
2522
|
+
}
|
|
2523
|
+
function buildAnd(definitions, resolve) {
|
|
2524
|
+
if (definitions.length === 0)
|
|
2525
|
+
return makeType({ k: "unknown" }, [], {});
|
|
2526
|
+
let ir = parseDef(definitions[0], resolve);
|
|
2527
|
+
for (let index = 1; index < definitions.length; index++) {
|
|
2528
|
+
ir = intersect(ir, parseDef(definitions[index], resolve));
|
|
2529
|
+
}
|
|
2530
|
+
return makeType(ir, [], {});
|
|
2531
|
+
}
|
|
2532
|
+
function requireNaryObject(ir) {
|
|
2533
|
+
const resolved = resolveAlias(ir);
|
|
2534
|
+
if (resolved.k !== "object")
|
|
2535
|
+
throw new OmpTypeError("merge requires an object schema");
|
|
2536
|
+
return resolved;
|
|
2537
|
+
}
|
|
2538
|
+
function buildMerge(definitions, resolve) {
|
|
2539
|
+
if (definitions.length === 0)
|
|
2540
|
+
return makeType({ k: "anyobject" }, [], {});
|
|
2541
|
+
let object = requireNaryObject(parseDef(definitions[0], resolve));
|
|
2542
|
+
for (let index = 1; index < definitions.length; index++) {
|
|
2543
|
+
object = mergeObjects(object, requireNaryObject(parseDef(definitions[index], resolve)));
|
|
2544
|
+
}
|
|
2545
|
+
return makeType(object, [], {});
|
|
2546
|
+
}
|
|
2547
|
+
function buildPipe(definitions, resolve) {
|
|
2548
|
+
if (definitions.length === 0)
|
|
2549
|
+
return makeType({ k: "unknown" }, [], {});
|
|
2550
|
+
const first = definitions[0];
|
|
2551
|
+
let schema = typeof first === "function" && !isTypeValue(first)
|
|
2552
|
+
? appendPipes(makeType({ k: "unknown" }, [], {}), [first], false, true)
|
|
2553
|
+
: isTypeValue(first)
|
|
2554
|
+
? first
|
|
2555
|
+
: makeType(parseDef(first, resolve), [], {});
|
|
2556
|
+
for (let index = 1; index < definitions.length; index++) {
|
|
2557
|
+
const definition = definitions[index];
|
|
2558
|
+
const pipe = typeof definition === "function" && !isTypeValue(definition)
|
|
2559
|
+
? definition
|
|
2560
|
+
: isTypeValue(definition)
|
|
2561
|
+
? definition
|
|
2562
|
+
: makeType(parseDef(definition, resolve), [], {});
|
|
2563
|
+
schema = appendPipes(schema, [pipe], false, true);
|
|
2564
|
+
}
|
|
2565
|
+
return schema;
|
|
2566
|
+
}
|
|
2567
|
+
function naryStatics(resolve) {
|
|
2568
|
+
return {
|
|
2569
|
+
or: (...definitions) => buildOr(definitions, resolve),
|
|
2570
|
+
and: (...definitions) => buildAnd(definitions, resolve),
|
|
2571
|
+
merge: (...definitions) => buildMerge(definitions, resolve),
|
|
2572
|
+
pipe: (...definitions) => buildPipe(definitions, resolve),
|
|
2573
|
+
};
|
|
863
2574
|
}
|
|
864
2575
|
(function (type) {
|
|
865
2576
|
/** Error aggregate returned by failed validations (`result instanceof type.errors`). */
|
|
866
2577
|
type.errors = OmpErrors;
|
|
2578
|
+
/** Build a union from zero or more definitions. */
|
|
2579
|
+
function or(...definitions) {
|
|
2580
|
+
return buildOr(definitions);
|
|
2581
|
+
}
|
|
2582
|
+
type.or = or;
|
|
2583
|
+
/** Build an intersection from zero or more definitions. */
|
|
2584
|
+
function and(...definitions) {
|
|
2585
|
+
return buildAnd(definitions);
|
|
2586
|
+
}
|
|
2587
|
+
type.and = and;
|
|
2588
|
+
/** Right-biased object merge over zero or more definitions. */
|
|
2589
|
+
function merge(...definitions) {
|
|
2590
|
+
return buildMerge(definitions);
|
|
2591
|
+
}
|
|
2592
|
+
type.merge = merge;
|
|
2593
|
+
/** Compose Types, definitions, and morph callbacks from left to right. */
|
|
2594
|
+
function pipe(...definitions) {
|
|
2595
|
+
return buildPipe(definitions);
|
|
2596
|
+
}
|
|
2597
|
+
type.pipe = pipe;
|
|
867
2598
|
const normalize = Object.assign(keywordSchema("string.normalize"), {
|
|
868
2599
|
preformatted: keywordSchema("string.normalize.NFC.preformatted"),
|
|
869
2600
|
NFC: preformattedKeyword("string.normalize.NFC"),
|
|
@@ -893,7 +2624,7 @@ function caseResolver(value) {
|
|
|
893
2624
|
v8: keywordSchema("string.uuid.v8"),
|
|
894
2625
|
});
|
|
895
2626
|
/** String validator and its refinement/morph keyword module. */
|
|
896
|
-
type.string = Object.
|
|
2627
|
+
type.string = Object.defineProperties(makeType({ k: "string" }, [], {}), Object.getOwnPropertyDescriptors({
|
|
897
2628
|
alpha: keywordSchema("string.alpha"),
|
|
898
2629
|
alphanumeric: keywordSchema("string.alphanumeric"),
|
|
899
2630
|
base64,
|
|
@@ -915,7 +2646,7 @@ function caseResolver(value) {
|
|
|
915
2646
|
upper: preformattedKeyword("string.upper"),
|
|
916
2647
|
url: parsedKeyword("string.url"),
|
|
917
2648
|
uuid,
|
|
918
|
-
});
|
|
2649
|
+
}));
|
|
919
2650
|
/** Runtime parser keyword family. */
|
|
920
2651
|
type.parse = {
|
|
921
2652
|
number: keywordSchema("parse.number"),
|
|
@@ -930,6 +2661,13 @@ function caseResolver(value) {
|
|
|
930
2661
|
type.number = Object.assign(makeType({ k: "number" }, [], {}), {
|
|
931
2662
|
integer: makeType({ k: "number", int: true }, [], {}),
|
|
932
2663
|
});
|
|
2664
|
+
/** Schema-valued key representing any non-negative integer array index. */
|
|
2665
|
+
type.arrayIndex = makeType({
|
|
2666
|
+
k: "refine",
|
|
2667
|
+
base: { k: "string" },
|
|
2668
|
+
pred: value => typeof value === "string" && /^(?:0|[1-9]\d*)$/.test(value),
|
|
2669
|
+
expected: "a non-negative integer string",
|
|
2670
|
+
}, [], {});
|
|
933
2671
|
/** Boolean validator. */
|
|
934
2672
|
type.boolean = makeType({ k: "boolean" }, [], {});
|
|
935
2673
|
/** Bigint validator. */
|
|
@@ -944,13 +2682,78 @@ function caseResolver(value) {
|
|
|
944
2682
|
type.any = type.unknown;
|
|
945
2683
|
/** Validator that rejects every value. */
|
|
946
2684
|
type.never = makeType({ k: "never" }, [], {});
|
|
2685
|
+
/** ArkType's built-in keyword namespace, including invokable utility generics. */
|
|
2686
|
+
type.keywords = {
|
|
2687
|
+
number: { integer: type.number.integer },
|
|
2688
|
+
Map: keywordSchema("Map"),
|
|
2689
|
+
Set: keywordSchema("Set"),
|
|
2690
|
+
RegExp: keywordSchema("RegExp"),
|
|
2691
|
+
File: keywordSchema("File"),
|
|
2692
|
+
Error: keywordSchema("Error"),
|
|
2693
|
+
// biome-ignore lint/complexity/noBannedTypes: built-in Function keyword
|
|
2694
|
+
Function: keywordSchema("Function"),
|
|
2695
|
+
Array: {
|
|
2696
|
+
liftFrom(definition) {
|
|
2697
|
+
const element = parseDef(definition);
|
|
2698
|
+
const array = { k: "array", el: element, desc: "an object" };
|
|
2699
|
+
return makeType({
|
|
2700
|
+
k: "morph",
|
|
2701
|
+
input: { k: "union", members: [element, array] },
|
|
2702
|
+
fn: value => (globalThis.Array.isArray(value) ? value : [value]),
|
|
2703
|
+
out: array,
|
|
2704
|
+
}, [], {});
|
|
2705
|
+
},
|
|
2706
|
+
},
|
|
2707
|
+
Record(key, value) {
|
|
2708
|
+
const keyIR = parseDef(key);
|
|
2709
|
+
if (keyIR.k !== "string" && keyIR.k !== "symbol") {
|
|
2710
|
+
throw new OmpTypeError("Record key must be assignable to string or symbol");
|
|
2711
|
+
}
|
|
2712
|
+
const valueIR = parseDef(value);
|
|
2713
|
+
const ir = keyIR.k === "symbol"
|
|
2714
|
+
? { k: "object", props: [], symbolIndex: valueIR, extras: "keep" }
|
|
2715
|
+
: { k: "object", props: [], index: valueIR, extras: "keep" };
|
|
2716
|
+
return makeType(ir, [], {});
|
|
2717
|
+
},
|
|
2718
|
+
Partial(definition) {
|
|
2719
|
+
return makeType(setObjectOptionality(parseDef(definition), true, "partial"), [], {});
|
|
2720
|
+
},
|
|
2721
|
+
Required(definition) {
|
|
2722
|
+
return makeType(setObjectOptionality(parseDef(definition), false, "required"), [], {});
|
|
2723
|
+
},
|
|
2724
|
+
Pick(definition, ...keys) {
|
|
2725
|
+
return makeType(selectObjectProps(parseDef(definition), keys, true, "pick"), [], {});
|
|
2726
|
+
},
|
|
2727
|
+
Omit(definition, ...keys) {
|
|
2728
|
+
return makeType(selectObjectProps(parseDef(definition), keys, false, "omit"), [], {});
|
|
2729
|
+
},
|
|
2730
|
+
Merge(left, right) {
|
|
2731
|
+
return makeType(mergeObjectDefinition(parseDef(left), right), [], {});
|
|
2732
|
+
},
|
|
2733
|
+
object: {
|
|
2734
|
+
json: Object.defineProperties(keywordSchema("object.json"), {
|
|
2735
|
+
stringify: {
|
|
2736
|
+
value: keywordSchema("object.json.stringify"),
|
|
2737
|
+
enumerable: true,
|
|
2738
|
+
},
|
|
2739
|
+
}),
|
|
2740
|
+
},
|
|
2741
|
+
unknown: { any: keywordSchema("unknown.any") },
|
|
2742
|
+
};
|
|
947
2743
|
/** Date instance validator. */
|
|
948
2744
|
// biome-ignore lint/suspicious/noShadowRestrictedNames: ArkType exposes this exact keyword.
|
|
949
2745
|
type.Date = makeType({ k: "instance", ctor: globalThis.Date, expected: "a Date" }, [], {});
|
|
950
2746
|
/** Validate instances of `ctor`. */
|
|
951
2747
|
function instanceOf(ctor) {
|
|
2748
|
+
if (typeof ctor !== "function" || ctor.prototype === undefined) {
|
|
2749
|
+
throw new OmpTypeError("instanceof operands must be constructors");
|
|
2750
|
+
}
|
|
952
2751
|
const name = Reflect.get(ctor, "name");
|
|
953
|
-
const expected =
|
|
2752
|
+
const expected = ctor.prototype === Error.prototype
|
|
2753
|
+
? "an Error"
|
|
2754
|
+
: typeof name === "string" && name.length > 0
|
|
2755
|
+
? `an instance of ${name}`
|
|
2756
|
+
: "an instance";
|
|
954
2757
|
return makeType({ k: "instance", ctor, expected }, [], {});
|
|
955
2758
|
}
|
|
956
2759
|
type.instanceOf = instanceOf;
|
|
@@ -966,59 +2769,57 @@ function caseResolver(value) {
|
|
|
966
2769
|
return makeType(ir, [], {});
|
|
967
2770
|
}
|
|
968
2771
|
type.enumerated = enumerated;
|
|
969
|
-
/**
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
for (const
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
}
|
|
980
|
-
return value => {
|
|
981
|
-
for (const branch of branches) {
|
|
982
|
-
if (branch.schema.allows(value))
|
|
983
|
-
return branch.resolve(value);
|
|
984
|
-
}
|
|
985
|
-
if (fallback !== undefined)
|
|
986
|
-
return fallback(value);
|
|
987
|
-
throw new OmpTypeError("match requires a matching case or default");
|
|
988
|
-
};
|
|
2772
|
+
/** Enumerate an enum-like object's forward values, excluding numeric reverse mappings. */
|
|
2773
|
+
// biome-ignore lint/suspicious/noShadowRestrictedNames: Object.prototype.valueOf method name API
|
|
2774
|
+
function valueOf(values) {
|
|
2775
|
+
const members = [];
|
|
2776
|
+
for (const key in values) {
|
|
2777
|
+
if (/^(?:0|[1-9]\d*)$/.test(key))
|
|
2778
|
+
continue;
|
|
2779
|
+
members.push({ k: "lit", v: values[key] });
|
|
2780
|
+
}
|
|
2781
|
+
const ir = members.length === 0 ? { k: "never" } : members.length === 1 ? members[0] : { k: "union", members };
|
|
2782
|
+
return makeType(ir, [], {});
|
|
989
2783
|
}
|
|
990
|
-
type.
|
|
2784
|
+
type.valueOf = valueOf;
|
|
2785
|
+
/** Fluent first-match dispatcher, also exported as standalone `match`. */
|
|
2786
|
+
type.match = matchBuilder;
|
|
991
2787
|
/** Preserve a definition's literal type while authoring reusable modules. */
|
|
992
2788
|
function define(definition) {
|
|
993
2789
|
return definition;
|
|
994
2790
|
}
|
|
995
2791
|
type.define = define;
|
|
2792
|
+
/** Build a function whose arguments and optional declared return are validated. */
|
|
2793
|
+
type.fn = makeFn();
|
|
2794
|
+
/** Fix an externally declared static type while retaining runtime validation. */
|
|
2795
|
+
// biome-ignore lint/complexity/noBannedTypes: empty default options object
|
|
2796
|
+
type.declare = () => ({
|
|
2797
|
+
type: definition => type(definition),
|
|
2798
|
+
});
|
|
996
2799
|
/** Build a lazy named scope from aliases and recursive definitions. */
|
|
997
2800
|
function scope(aliases, options) {
|
|
998
2801
|
return buildScope(aliases, options);
|
|
999
2802
|
}
|
|
1000
2803
|
type.scope = scope;
|
|
1001
2804
|
/** Compile a named schema module whose definitions may reference each other. */
|
|
1002
|
-
function module(definitions) {
|
|
1003
|
-
return scope(definitions).export();
|
|
2805
|
+
function module(definitions, options) {
|
|
2806
|
+
return scope(definitions, options).export();
|
|
1004
2807
|
}
|
|
1005
2808
|
type.module = module;
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
throw new OmpTypeError(`generic expects ${names.length} arguments (received ${arguments_.length})`);
|
|
2809
|
+
function generic(...arguments_) {
|
|
2810
|
+
if (arguments_.length === 2 && typeof arguments_[0] === "string" && arguments_[0].trimStart().startsWith("<")) {
|
|
2811
|
+
return createRuntimeGeneric(parseGenericParameters(arguments_[0]), arguments_[1]);
|
|
2812
|
+
}
|
|
2813
|
+
const parameters = arguments_.map(parameter => {
|
|
2814
|
+
if (typeof parameter === "string")
|
|
2815
|
+
return { name: parameter.trim() };
|
|
2816
|
+
if (Array.isArray(parameter) && typeof parameter[0] === "string") {
|
|
2817
|
+
return { name: parameter[0].trim(), constraintDef: parameter[1] };
|
|
1016
2818
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
};
|
|
2819
|
+
throw new OmpTypeError("generic parameters must be names or [name, constraint] pairs");
|
|
2820
|
+
});
|
|
2821
|
+
validateGenericParameters(parameters);
|
|
2822
|
+
return (definition) => createRuntimeGeneric(parameters, definition);
|
|
1022
2823
|
}
|
|
1023
2824
|
type.generic = generic;
|
|
1024
2825
|
/** Untyped builder for runtime-assembled definitions. */
|
|
@@ -1035,15 +2836,84 @@ Object.assign(type, {
|
|
|
1035
2836
|
true: makeType({ k: "lit", v: true }, [], {}),
|
|
1036
2837
|
false: makeType({ k: "lit", v: false }, [], {}),
|
|
1037
2838
|
});
|
|
2839
|
+
const MODULE_SCOPE = Symbol("omptype.moduleScope");
|
|
1038
2840
|
/** Build a scope whose aliases resolve lazily, including recursive cycles. */
|
|
1039
2841
|
export function scope(aliases, options) {
|
|
1040
2842
|
return buildScope(aliases, options);
|
|
1041
2843
|
}
|
|
1042
|
-
function
|
|
2844
|
+
(function (scope) {
|
|
2845
|
+
/** Preserve a scope definition's literal shape without constructing it. */
|
|
2846
|
+
function define(definitions) {
|
|
2847
|
+
return definitions;
|
|
2848
|
+
}
|
|
2849
|
+
scope.define = define;
|
|
2850
|
+
})(scope || (scope = {}));
|
|
2851
|
+
function isRuntimeModule(value) {
|
|
2852
|
+
return typeof value === "object" && value !== null && MODULE_SCOPE in value;
|
|
2853
|
+
}
|
|
2854
|
+
function buildScope(aliases, options) {
|
|
2855
|
+
const scopeMeta = options?.clone === undefined ? EMPTY_META : { clone: options.clone };
|
|
2856
|
+
const withScopeConfig = (ir) => options?.divisor === undefined ? ir : configureSelected(ir, options.divisor, { kind: "divisor" });
|
|
2857
|
+
const entries = new Map();
|
|
2858
|
+
for (const sourceName in aliases) {
|
|
2859
|
+
const isPrivate = sourceName.startsWith("#");
|
|
2860
|
+
const visibleName = isPrivate ? sourceName.slice(1) : sourceName;
|
|
2861
|
+
const declaration = parseGenericDeclaration(visibleName);
|
|
2862
|
+
const external = isRuntimeGeneric(aliases[sourceName]) ? aliases[sourceName] : undefined;
|
|
2863
|
+
const name = declaration?.name ?? visibleName;
|
|
2864
|
+
if (entries.has(name))
|
|
2865
|
+
throw new OmpTypeError(`alias "${name}" is declared as both public and private`);
|
|
2866
|
+
entries.set(name, {
|
|
2867
|
+
name,
|
|
2868
|
+
sourceName,
|
|
2869
|
+
private: isPrivate,
|
|
2870
|
+
genericParameters: declaration?.parameters ?? external?.[GENERIC_META].parameters,
|
|
2871
|
+
definition: aliases[sourceName],
|
|
2872
|
+
generic: external,
|
|
2873
|
+
materialized: false,
|
|
2874
|
+
});
|
|
2875
|
+
}
|
|
1043
2876
|
const references = new Map();
|
|
1044
2877
|
const targets = new Map();
|
|
1045
|
-
|
|
1046
|
-
|
|
2878
|
+
let scopeValue;
|
|
2879
|
+
const materialize = (entry) => {
|
|
2880
|
+
if (entry.materialized)
|
|
2881
|
+
return entry.definition;
|
|
2882
|
+
entry.materialized = true;
|
|
2883
|
+
if (entry.genericParameters === undefined &&
|
|
2884
|
+
typeof entry.definition === "function" &&
|
|
2885
|
+
!(IR_BRAND in entry.definition)) {
|
|
2886
|
+
entry.definition = Reflect.apply(entry.definition, undefined, []);
|
|
2887
|
+
}
|
|
2888
|
+
return entry.definition;
|
|
2889
|
+
};
|
|
2890
|
+
const moduleSchema = (module, parts) => {
|
|
2891
|
+
let current = module;
|
|
2892
|
+
for (const part of parts) {
|
|
2893
|
+
if (!isRuntimeModule(current))
|
|
2894
|
+
return undefined;
|
|
2895
|
+
const next = current[part];
|
|
2896
|
+
if (next === undefined)
|
|
2897
|
+
return undefined;
|
|
2898
|
+
current = next;
|
|
2899
|
+
}
|
|
2900
|
+
if (isRuntimeModule(current)) {
|
|
2901
|
+
const root = current.root;
|
|
2902
|
+
return root !== undefined && !isRuntimeModule(root) ? root : undefined;
|
|
2903
|
+
}
|
|
2904
|
+
return current;
|
|
2905
|
+
};
|
|
2906
|
+
const resolve = ((path) => {
|
|
2907
|
+
const [name, ...parts] = path.split(".");
|
|
2908
|
+
const entry = entries.get(name);
|
|
2909
|
+
if (entry === undefined || entry.genericParameters !== undefined)
|
|
2910
|
+
return undefined;
|
|
2911
|
+
const definition = materialize(entry);
|
|
2912
|
+
if (isRuntimeModule(definition)) {
|
|
2913
|
+
const schema = moduleSchema(definition, parts);
|
|
2914
|
+
return schema === undefined ? undefined : embed(schema);
|
|
2915
|
+
}
|
|
2916
|
+
if (parts.length !== 0)
|
|
1047
2917
|
return undefined;
|
|
1048
2918
|
const existing = references.get(name);
|
|
1049
2919
|
if (existing !== undefined)
|
|
@@ -1055,24 +2925,138 @@ function buildScope(aliases, _options) {
|
|
|
1055
2925
|
const target = targets.get(name);
|
|
1056
2926
|
if (target !== undefined)
|
|
1057
2927
|
return target;
|
|
1058
|
-
const parsed = parseDef(
|
|
2928
|
+
const parsed = parseDef(definition, resolve);
|
|
1059
2929
|
targets.set(name, parsed);
|
|
1060
2930
|
return parsed;
|
|
1061
2931
|
},
|
|
1062
2932
|
};
|
|
1063
2933
|
references.set(name, reference);
|
|
1064
2934
|
return reference;
|
|
2935
|
+
});
|
|
2936
|
+
const genericFor = (entry) => {
|
|
2937
|
+
if (entry.generic !== undefined)
|
|
2938
|
+
return entry.generic;
|
|
2939
|
+
const parameters = entry.genericParameters;
|
|
2940
|
+
if (parameters === undefined)
|
|
2941
|
+
throw new OmpTypeError(`alias "${entry.name}" is not generic`);
|
|
2942
|
+
entry.generic = createRuntimeGeneric(parameters, entry.definition, resolve, false);
|
|
2943
|
+
return entry.generic;
|
|
1065
2944
|
};
|
|
1066
|
-
const
|
|
1067
|
-
|
|
2945
|
+
const genericInstantiations = new Map();
|
|
2946
|
+
resolve.hasGeneric = name => entries.get(name)?.genericParameters !== undefined;
|
|
2947
|
+
resolve.generic = (name, arguments_) => {
|
|
2948
|
+
const entry = entries.get(name);
|
|
2949
|
+
if (entry === undefined || entry.genericParameters === undefined)
|
|
2950
|
+
return undefined;
|
|
2951
|
+
const key = `${name}<${arguments_.map(expectedOf).join(",")}>`;
|
|
2952
|
+
const existing = genericInstantiations.get(key);
|
|
2953
|
+
if (existing !== undefined)
|
|
2954
|
+
return existing;
|
|
2955
|
+
let target;
|
|
2956
|
+
const reference = {
|
|
2957
|
+
k: "alias",
|
|
2958
|
+
name: key,
|
|
2959
|
+
resolve: () => {
|
|
2960
|
+
target ??= genericFor(entry)[GENERIC_META].instantiateIR(arguments_);
|
|
2961
|
+
return target;
|
|
2962
|
+
},
|
|
2963
|
+
};
|
|
2964
|
+
genericInstantiations.set(key, reference);
|
|
2965
|
+
target = genericFor(entry)[GENERIC_META].instantiateIR(arguments_);
|
|
2966
|
+
return target;
|
|
2967
|
+
};
|
|
2968
|
+
const bind = (schema) => {
|
|
2969
|
+
Reflect.set(schema, "$", scopeValue);
|
|
2970
|
+
Reflect.set(schema, "resolver", resolve);
|
|
2971
|
+
return schema;
|
|
2972
|
+
};
|
|
2973
|
+
const parseScoped = (definition) => bind(makeType(withScopeConfig(parseDef(definition, resolve)), EMPTY_STEPS, scopeMeta));
|
|
2974
|
+
const scopedMatch = createMatchParser({
|
|
2975
|
+
parse: definition => parseScoped(definition),
|
|
2976
|
+
branches: [],
|
|
2977
|
+
});
|
|
2978
|
+
const scoped = Object.assign((definition) => parseScoped(definition), type, {
|
|
2979
|
+
fn: makeFn(resolve),
|
|
2980
|
+
match: scopedMatch,
|
|
2981
|
+
...naryStatics(resolve),
|
|
2982
|
+
});
|
|
2983
|
+
const targetFor = (name) => {
|
|
2984
|
+
const resolved = resolve(name);
|
|
2985
|
+
if (resolved === undefined)
|
|
2986
|
+
throw new OmpTypeError(`unknown alias "${name}"`);
|
|
2987
|
+
return resolved.k === "alias" ? resolved.resolve() : resolved;
|
|
2988
|
+
};
|
|
2989
|
+
const schemaFor = (name) => bind(makeType(withScopeConfig(targetFor(name)), EMPTY_STEPS, scopeMeta));
|
|
2990
|
+
const bindModule = (names) => {
|
|
2991
|
+
const module = {};
|
|
2992
|
+
Object.defineProperty(module, MODULE_SCOPE, { value: scopeValue });
|
|
2993
|
+
for (const name of names) {
|
|
2994
|
+
const entry = entries.get(name);
|
|
2995
|
+
if (entry === undefined)
|
|
2996
|
+
continue;
|
|
2997
|
+
if (entry.genericParameters !== undefined) {
|
|
2998
|
+
module[name] = genericFor(entry);
|
|
2999
|
+
continue;
|
|
3000
|
+
}
|
|
3001
|
+
const definition = materialize(entry);
|
|
3002
|
+
module[name] = isRuntimeModule(definition) ? definition : schemaFor(name);
|
|
3003
|
+
}
|
|
3004
|
+
return module;
|
|
3005
|
+
};
|
|
3006
|
+
scopeValue = {
|
|
1068
3007
|
type: scoped,
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
3008
|
+
match: scopedMatch,
|
|
3009
|
+
define(definition) {
|
|
3010
|
+
return definition;
|
|
3011
|
+
},
|
|
3012
|
+
resolve(name) {
|
|
3013
|
+
return schemaFor(name);
|
|
3014
|
+
},
|
|
3015
|
+
import(...names) {
|
|
3016
|
+
const selected = names.length === 0
|
|
3017
|
+
? [...entries.values()].filter(entry => !entry.private)
|
|
3018
|
+
: names.map(name => {
|
|
3019
|
+
const entry = entries.get(name);
|
|
3020
|
+
if (entry === undefined)
|
|
3021
|
+
throw new OmpTypeError(`unknown alias "${name}"`);
|
|
3022
|
+
return entry;
|
|
3023
|
+
});
|
|
3024
|
+
const imported = {};
|
|
3025
|
+
for (const entry of selected) {
|
|
3026
|
+
imported[`#${entry.sourceName.startsWith("#") ? entry.sourceName.slice(1) : entry.sourceName}`] =
|
|
3027
|
+
entry.genericParameters === undefined ? schemaFor(entry.name) : genericFor(entry);
|
|
3028
|
+
}
|
|
3029
|
+
return imported;
|
|
3030
|
+
},
|
|
3031
|
+
export(...names) {
|
|
3032
|
+
const selected = names.length === 0 ? [...entries.values()].filter(entry => !entry.private).map(entry => entry.name) : names;
|
|
3033
|
+
// Export is the eager boundary: malformed aliases and bad thunks fail here,
|
|
3034
|
+
// while recursive references inside valid definitions stay lazy.
|
|
3035
|
+
for (const entry of entries.values()) {
|
|
3036
|
+
if (entry.genericParameters !== undefined)
|
|
3037
|
+
genericFor(entry);
|
|
3038
|
+
else if (!isRuntimeModule(materialize(entry)))
|
|
3039
|
+
targetFor(entry.name);
|
|
3040
|
+
}
|
|
3041
|
+
return bindModule(selected);
|
|
3042
|
+
},
|
|
3043
|
+
get json() {
|
|
3044
|
+
const json = {};
|
|
3045
|
+
const add = (prefix, module) => {
|
|
3046
|
+
for (const name of Object.keys(module)) {
|
|
3047
|
+
const value = module[name];
|
|
3048
|
+
const path = prefix === "" ? name : `${prefix}.${name}`;
|
|
3049
|
+
if (isRuntimeModule(value))
|
|
3050
|
+
add(path, value);
|
|
3051
|
+
else
|
|
3052
|
+
json[path] = Reflect.get(value, "json");
|
|
3053
|
+
}
|
|
3054
|
+
};
|
|
3055
|
+
add("", bindModule([...entries.values()].filter(entry => !entry.private).map(entry => entry.name)));
|
|
3056
|
+
return json;
|
|
1074
3057
|
},
|
|
1075
3058
|
};
|
|
3059
|
+
return scopeValue;
|
|
1076
3060
|
}
|
|
1077
3061
|
/** `hasMorph` re-export for diagnostics/tooling. */
|
|
1078
3062
|
export { hasMorph };
|