@oh-my-pi/omptype 17.2.7 → 17.2.9
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 +29 -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 +56 -34
- 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 +153 -100
- package/src/zod.ts +1 -1
package/dist/js/typebox.js
CHANGED
|
@@ -2,6 +2,12 @@ import { OmpTypeError } from "./errors.js";
|
|
|
2
2
|
import { type } from "./type.js";
|
|
3
3
|
const OPTIONAL_INNER = Symbol("omptype.typebox.optionalInner");
|
|
4
4
|
const OBJECT_INFO = Symbol("omptype.typebox.objectInfo");
|
|
5
|
+
function asRuntime(schema) {
|
|
6
|
+
return schema;
|
|
7
|
+
}
|
|
8
|
+
function asSchema(schema) {
|
|
9
|
+
return schema;
|
|
10
|
+
}
|
|
5
11
|
function validationFailure(message) {
|
|
6
12
|
return { message };
|
|
7
13
|
}
|
|
@@ -50,7 +56,7 @@ function checkFiniteOption(name, value) {
|
|
|
50
56
|
function tString(opts) {
|
|
51
57
|
checkFiniteOption("minLength", opts?.minLength);
|
|
52
58
|
checkFiniteOption("maxLength", opts?.maxLength);
|
|
53
|
-
let schema = type.raw(opts?.format === "url" || opts?.format === "uri" ? "string.url" : "string");
|
|
59
|
+
let schema = asRuntime(type.raw(opts?.format === "url" || opts?.format === "uri" ? "string.url" : "string"));
|
|
54
60
|
if (opts?.minLength !== undefined)
|
|
55
61
|
schema = schema.atLeastLength(opts.minLength);
|
|
56
62
|
if (opts?.maxLength !== undefined)
|
|
@@ -117,9 +123,22 @@ function tNumber(opts, integer = false) {
|
|
|
117
123
|
upper = { value: opts.exclusiveMaximum, exclusive: true };
|
|
118
124
|
}
|
|
119
125
|
const keyword = integer ? "number.integer" : "number";
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
let
|
|
126
|
+
// The `LO <= TYPE <= HI` range spelling requires both bounds; a min-only
|
|
127
|
+
// bound must use the postfix `TYPE >= LO` form (see parseBounded in ir.ts).
|
|
128
|
+
let src;
|
|
129
|
+
if (lower && upper) {
|
|
130
|
+
src = `${lower.value} ${lower.exclusive ? "<" : "<="} ${keyword} ${upper.exclusive ? "<" : "<="} ${upper.value}`;
|
|
131
|
+
}
|
|
132
|
+
else if (lower) {
|
|
133
|
+
src = `${keyword} ${lower.exclusive ? ">" : ">="} ${lower.value}`;
|
|
134
|
+
}
|
|
135
|
+
else if (upper) {
|
|
136
|
+
src = `${keyword} ${upper.exclusive ? "<" : "<="} ${upper.value}`;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
src = keyword;
|
|
140
|
+
}
|
|
141
|
+
let schema = asRuntime(type.raw(src));
|
|
123
142
|
if (opts?.multipleOf !== undefined) {
|
|
124
143
|
const divisor = opts.multipleOf;
|
|
125
144
|
schema = schema.narrow((value, ctx) => {
|
|
@@ -131,24 +150,25 @@ function tNumber(opts, integer = false) {
|
|
|
131
150
|
return applyMeta(schema, opts);
|
|
132
151
|
}
|
|
133
152
|
function tLiteral(value, opts) {
|
|
134
|
-
return applyMeta(type.enumerated(value), opts);
|
|
153
|
+
return applyMeta(asRuntime(type.enumerated(value)), opts);
|
|
135
154
|
}
|
|
136
155
|
function tNever(opts) {
|
|
137
|
-
return applyMeta(type.raw("unknown").narrow((_value, ctx) => ctx.mustBe("never")), opts);
|
|
156
|
+
return applyMeta(asRuntime(type.raw("unknown")).narrow((_value, ctx) => ctx.mustBe("never")), opts);
|
|
138
157
|
}
|
|
139
158
|
function tUnion(schemas, opts) {
|
|
140
159
|
if (schemas.length === 0)
|
|
141
|
-
return tNever(opts);
|
|
142
|
-
let result = schemas[0];
|
|
160
|
+
return asSchema(tNever(opts));
|
|
161
|
+
let result = asRuntime(schemas[0]);
|
|
143
162
|
for (let i = 1; i < schemas.length; i++)
|
|
144
163
|
result = result.or(schemas[i]);
|
|
145
|
-
return applyMeta(result, opts);
|
|
164
|
+
return asSchema(applyMeta(result, opts));
|
|
146
165
|
}
|
|
147
166
|
function tIntersect(schemas, opts) {
|
|
148
|
-
if (schemas.length === 0)
|
|
149
|
-
return applyMeta(type.raw("unknown"), opts);
|
|
167
|
+
if (schemas.length === 0) {
|
|
168
|
+
return applyMeta(asRuntime(type.raw("unknown")), opts);
|
|
169
|
+
}
|
|
150
170
|
const validateAll = () => {
|
|
151
|
-
const base = type.raw("unknown");
|
|
171
|
+
const base = asRuntime(type.raw("unknown"));
|
|
152
172
|
return base.narrow((value, ctx) => {
|
|
153
173
|
for (const schema of schemas) {
|
|
154
174
|
if (schema(value) instanceof type.errors)
|
|
@@ -159,7 +179,7 @@ function tIntersect(schemas, opts) {
|
|
|
159
179
|
};
|
|
160
180
|
if (schemas.some(schema => schema.hasSteps))
|
|
161
181
|
return applyMeta(validateAll(), opts);
|
|
162
|
-
let result = schemas[0];
|
|
182
|
+
let result = asRuntime(schemas[0]);
|
|
163
183
|
try {
|
|
164
184
|
for (let i = 1; i < schemas.length; i++)
|
|
165
185
|
result = result.and(schemas[i]);
|
|
@@ -169,7 +189,7 @@ function tIntersect(schemas, opts) {
|
|
|
169
189
|
return applyMeta(validateAll(), opts);
|
|
170
190
|
throw error;
|
|
171
191
|
}
|
|
172
|
-
return applyMeta(result, opts);
|
|
192
|
+
return asSchema(applyMeta(result, opts));
|
|
173
193
|
}
|
|
174
194
|
function enumValues(values) {
|
|
175
195
|
if (Array.isArray(values))
|
|
@@ -184,12 +204,12 @@ function enumValues(values) {
|
|
|
184
204
|
return result;
|
|
185
205
|
}
|
|
186
206
|
function tEnum(values, opts) {
|
|
187
|
-
return applyMeta(type.enumerated(...enumValues(values)), opts);
|
|
207
|
+
return applyMeta(asRuntime(type.enumerated(...enumValues(values))), opts);
|
|
188
208
|
}
|
|
189
209
|
function tArray(item, opts) {
|
|
190
210
|
checkFiniteOption("minItems", opts?.minItems);
|
|
191
211
|
checkFiniteOption("maxItems", opts?.maxItems);
|
|
192
|
-
let schema = item.array();
|
|
212
|
+
let schema = asRuntime(item).array();
|
|
193
213
|
if (opts?.minItems !== undefined)
|
|
194
214
|
schema = schema.atLeastLength(opts.minItems);
|
|
195
215
|
if (opts?.maxItems !== undefined)
|
|
@@ -221,7 +241,7 @@ function jsonEqual(left, right) {
|
|
|
221
241
|
}
|
|
222
242
|
}
|
|
223
243
|
function tTuple(items, opts) {
|
|
224
|
-
const schema = type.raw("unknown").narrow((value, ctx) => {
|
|
244
|
+
const schema = asRuntime(type.raw("unknown")).narrow((value, ctx) => {
|
|
225
245
|
if (!Array.isArray(value) || value.length !== items.length)
|
|
226
246
|
return ctx.mustBe(`a tuple of length ${items.length}`);
|
|
227
247
|
for (let i = 0; i < items.length; i++)
|
|
@@ -236,20 +256,24 @@ function tObject(properties, opts) {
|
|
|
236
256
|
const props = {};
|
|
237
257
|
for (const key in properties) {
|
|
238
258
|
const schema = properties[key];
|
|
239
|
-
const inner = schema[OPTIONAL_INNER];
|
|
240
|
-
|
|
259
|
+
const inner = asRuntime(schema)[OPTIONAL_INNER];
|
|
260
|
+
// A defaulted `Type.Optional(...)` maps to a plain defaulted key:
|
|
261
|
+
// omptype (like ArkType) rejects `key?` with a default, and a default
|
|
262
|
+
// already makes the key omittable on input.
|
|
263
|
+
const optionalKey = inner !== undefined && !asRuntime(inner).hasDefault;
|
|
264
|
+
def[optionalKey ? `${key}?` : key] = inner ?? schema;
|
|
241
265
|
props[key] = schema;
|
|
242
266
|
}
|
|
243
267
|
if (opts?.additionalProperties === false)
|
|
244
268
|
def["+"] = "reject";
|
|
245
269
|
else if (opts?.additionalProperties && opts.additionalProperties !== true)
|
|
246
270
|
def["[string]"] = opts.additionalProperties;
|
|
247
|
-
const schema = applyMeta(type.raw(def), opts);
|
|
271
|
+
const schema = applyMeta(asRuntime(type.raw(def)), opts);
|
|
248
272
|
schema[OBJECT_INFO] = { props, additionalProperties: opts?.additionalProperties };
|
|
249
273
|
return schema;
|
|
250
274
|
}
|
|
251
275
|
function tRecord(key, value, opts) {
|
|
252
|
-
const base = type.raw({ "[string]": value }).narrow((record, ctx) => {
|
|
276
|
+
const base = asRuntime(type.raw({ "[string]": value })).narrow((record, ctx) => {
|
|
253
277
|
for (const name in record)
|
|
254
278
|
if (key(name) instanceof type.errors)
|
|
255
279
|
return ctx.mustBe("an object with valid record keys");
|
|
@@ -258,15 +282,15 @@ function tRecord(key, value, opts) {
|
|
|
258
282
|
return applyMeta(base, opts);
|
|
259
283
|
}
|
|
260
284
|
function tOptional(schema, opts) {
|
|
261
|
-
const marker = applyMeta(schema.or(type.raw("undefined")), opts);
|
|
285
|
+
const marker = applyMeta(asRuntime(schema).or(asRuntime(type.raw("undefined"))), opts);
|
|
262
286
|
marker[OPTIONAL_INNER] = schema;
|
|
263
287
|
return marker;
|
|
264
288
|
}
|
|
265
289
|
function tNullable(schema, opts) {
|
|
266
|
-
return applyMeta(schema.or(type.raw("null")), opts);
|
|
290
|
+
return applyMeta(asRuntime(schema).or(asRuntime(type.raw("null"))), opts);
|
|
267
291
|
}
|
|
268
292
|
function requireObject(schema, operation) {
|
|
269
|
-
const info = schema[OBJECT_INFO];
|
|
293
|
+
const info = asRuntime(schema)[OBJECT_INFO];
|
|
270
294
|
if (!info)
|
|
271
295
|
throw new OmpTypeError(`Type.${operation} requires a schema created by Type.Object`);
|
|
272
296
|
return info;
|
|
@@ -275,16 +299,14 @@ function tPartial(schema) {
|
|
|
275
299
|
const info = requireObject(schema, "Partial");
|
|
276
300
|
const props = {};
|
|
277
301
|
for (const key in info.props)
|
|
278
|
-
props[key] = info.props[key][OPTIONAL_INNER]
|
|
279
|
-
? info.props[key]
|
|
280
|
-
: tOptional(info.props[key]);
|
|
302
|
+
props[key] = asRuntime(info.props[key])[OPTIONAL_INNER] ? info.props[key] : tOptional(info.props[key]);
|
|
281
303
|
return tObject(props, { additionalProperties: info.additionalProperties });
|
|
282
304
|
}
|
|
283
305
|
function tRequired(schema) {
|
|
284
306
|
const info = requireObject(schema, "Required");
|
|
285
307
|
const props = {};
|
|
286
308
|
for (const key in info.props) {
|
|
287
|
-
props[key] = info.props[key][OPTIONAL_INNER] ?? info.props[key];
|
|
309
|
+
props[key] = asRuntime(info.props[key])[OPTIONAL_INNER] ?? info.props[key];
|
|
288
310
|
}
|
|
289
311
|
return tObject(props, { additionalProperties: info.additionalProperties });
|
|
290
312
|
}
|
|
@@ -309,7 +331,7 @@ function tComposite(schemas, opts) {
|
|
|
309
331
|
const props = {};
|
|
310
332
|
for (const schema of schemas)
|
|
311
333
|
Object.assign(props, requireObject(schema, "Composite").props);
|
|
312
|
-
return tObject(props, opts);
|
|
334
|
+
return asSchema(tObject(props, opts));
|
|
313
335
|
}
|
|
314
336
|
function tUnsafe(_jsonSchema = {}) {
|
|
315
337
|
// Raw JSON Schema is accepted for source compatibility but is not retained or validated:
|
|
@@ -320,10 +342,10 @@ export const Type = {
|
|
|
320
342
|
String: tString,
|
|
321
343
|
Number: (opts) => tNumber(opts),
|
|
322
344
|
Integer: (opts) => tNumber(opts, true),
|
|
323
|
-
Boolean: (opts) => applyMeta(type.raw("boolean"), opts),
|
|
324
|
-
Null: (opts) => applyMeta(type.raw("null"), opts),
|
|
325
|
-
Any: (opts) => applyMeta(type.raw("unknown"), opts),
|
|
326
|
-
Unknown: (opts) => applyMeta(type.raw("unknown"), opts),
|
|
345
|
+
Boolean: (opts) => applyMeta(asRuntime(type.raw("boolean")), opts),
|
|
346
|
+
Null: (opts) => applyMeta(asRuntime(type.raw("null")), opts),
|
|
347
|
+
Any: (opts) => applyMeta(asRuntime(type.raw("unknown")), opts),
|
|
348
|
+
Unknown: (opts) => applyMeta(asRuntime(type.raw("unknown")), opts),
|
|
327
349
|
Never: tNever,
|
|
328
350
|
Literal: tLiteral,
|
|
329
351
|
Union: tUnion,
|
|
@@ -335,7 +357,7 @@ export const Type = {
|
|
|
335
357
|
Record: tRecord,
|
|
336
358
|
Optional: tOptional,
|
|
337
359
|
Nullable: tNullable,
|
|
338
|
-
Readonly: (schema) => withLegacyCompat(schema),
|
|
360
|
+
Readonly: (schema) => asSchema(withLegacyCompat(asRuntime(schema))),
|
|
339
361
|
Partial: tPartial,
|
|
340
362
|
Required: tRequired,
|
|
341
363
|
Pick: tPick,
|
package/dist/js/zod.js
CHANGED
|
@@ -15,8 +15,8 @@ function restrictBase(source, ir) {
|
|
|
15
15
|
let next = source.hasSteps
|
|
16
16
|
? schemaFromIR({ k: "morph", input: ir, fn: value => source(value) })
|
|
17
17
|
: schemaFromIR(ir);
|
|
18
|
-
if (source.
|
|
19
|
-
next = next.describe(source.
|
|
18
|
+
if (source.ir.desc !== undefined)
|
|
19
|
+
next = next.describe(source.ir.desc);
|
|
20
20
|
if (source.hasDefault)
|
|
21
21
|
next = next.default(source.defaultValue);
|
|
22
22
|
return next;
|
package/dist/types/compile.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ import { type IR } from "./ir.js";
|
|
|
2
2
|
/** Compile `ir` into a specialized validator. */
|
|
3
3
|
export declare function compile(ir: IR): (value: unknown) => unknown;
|
|
4
4
|
/** Compile `ir` into an allocation-free boolean validator. */
|
|
5
|
-
export declare function compileAllows(ir: IR): (value: unknown) =>
|
|
5
|
+
export declare function compileAllows(ir: IR): (value: unknown) => value is unknown;
|
|
6
6
|
/** Generated source for inspection/debugging. */
|
|
7
7
|
export declare function compileToSource(ir: IR): string;
|
package/dist/types/errors.d.ts
CHANGED
|
@@ -11,17 +11,22 @@
|
|
|
11
11
|
export interface ErrorContext {
|
|
12
12
|
readonly code: string;
|
|
13
13
|
readonly path: readonly PropertyKey[];
|
|
14
|
+
readonly propString: string;
|
|
14
15
|
readonly data: unknown;
|
|
15
16
|
readonly expected: string;
|
|
16
17
|
readonly actual: string;
|
|
17
18
|
readonly problem: string;
|
|
19
|
+
readonly description: string;
|
|
20
|
+
readonly rule?: unknown;
|
|
18
21
|
}
|
|
19
22
|
/** Per-schema overrides for validation error text. */
|
|
20
23
|
export interface ErrorConfig {
|
|
21
24
|
readonly expected?: string | ((context: ErrorContext) => string);
|
|
22
|
-
readonly actual?: string | ((
|
|
25
|
+
readonly actual?: string | ((data: unknown) => string);
|
|
23
26
|
readonly problem?: string | ((context: ErrorContext) => string);
|
|
24
27
|
readonly message?: string | ((context: ErrorContext) => string);
|
|
28
|
+
/** Internal: custom predicate expectations display the offending value rather than its domain. */
|
|
29
|
+
readonly preserveActual?: boolean;
|
|
25
30
|
}
|
|
26
31
|
/** A single validation failure at one path. */
|
|
27
32
|
export declare class OmpError {
|
|
@@ -35,6 +40,10 @@ export declare class OmpError {
|
|
|
35
40
|
path: PropertyKey[], expected: string,
|
|
36
41
|
/** The value that failed validation. */
|
|
37
42
|
data: unknown, config?: ErrorConfig);
|
|
43
|
+
/** Prefix this failure when a nested schema delegates validation. */
|
|
44
|
+
prefix(key: PropertyKey): this;
|
|
45
|
+
/** Apply schema-local formatting to this failure. */
|
|
46
|
+
configure(config: ErrorConfig): this;
|
|
38
47
|
/** Stable category for programmatic error handling. */
|
|
39
48
|
get code(): string;
|
|
40
49
|
/** Human-readable expectation, including a configured override. */
|
|
@@ -43,44 +52,36 @@ export declare class OmpError {
|
|
|
43
52
|
get actual(): string;
|
|
44
53
|
/** Path-less problem statement: `must be <expected> (was <actual>)`. */
|
|
45
54
|
get problem(): string;
|
|
46
|
-
/** Full message including the path prefix. */
|
|
47
55
|
get message(): string;
|
|
48
56
|
toString(): string;
|
|
49
57
|
}
|
|
50
58
|
/** Sentinel for a required key that was absent (distinguishes from `undefined`). */
|
|
51
59
|
export declare const MISSING: unique symbol;
|
|
52
60
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* Validators fast-fail, so allocating an `Array` subclass and a separate entry
|
|
56
|
-
* on every rejection only penalizes callers that inspect errors by identity.
|
|
57
|
-
* Indexing, iteration, and `map` materialize the entry on demand.
|
|
61
|
+
* Validation failure result. The common single-error case remains lazy;
|
|
62
|
+
* traversal only materializes an entry array when a second error is appended.
|
|
58
63
|
*/
|
|
59
64
|
type StoredPath = PropertyKey[] | PropertyKey | undefined;
|
|
60
65
|
export declare class OmpErrors implements Iterable<OmpError> {
|
|
61
66
|
#private;
|
|
62
|
-
/** Number of failures; omptype validators fast-fail on the first error. */
|
|
63
|
-
readonly length = 1;
|
|
64
67
|
constructor(path: StoredPath, expected: string, data: unknown, config?: ErrorConfig);
|
|
65
|
-
|
|
68
|
+
get length(): number;
|
|
66
69
|
get 0(): OmpError;
|
|
67
70
|
static single(path: PropertyKey[], expected: string, data: unknown, config?: ErrorConfig): OmpErrors;
|
|
68
|
-
/**
|
|
71
|
+
/** Append all failures from `other`, preserving traversal order. */
|
|
72
|
+
append(other: OmpErrors): this;
|
|
73
|
+
/** Prefix every failure path with `key` when nesting sub-schemas. */
|
|
69
74
|
prefix(key: PropertyKey): this;
|
|
70
|
-
/** Apply schema-local message formatting without rebuilding
|
|
75
|
+
/** Apply schema-local message formatting without rebuilding failures. */
|
|
71
76
|
configure(config: ErrorConfig): this;
|
|
72
|
-
/** Index the failure by its dotted property path (`""` for the root). */
|
|
73
77
|
get byPath(): Readonly<Record<string, OmpError>>;
|
|
74
|
-
/** Transform the failure entry into a plain array. */
|
|
75
78
|
map<result>(fn: (error: OmpError, index: number, errors: OmpErrors) => result): result[];
|
|
76
|
-
/** Select the failure entry into a plain array. */
|
|
77
79
|
filter(fn: (error: OmpError, index: number, errors: OmpErrors) => unknown): OmpError[];
|
|
78
|
-
/** Iterate over the single failure entry. */
|
|
79
80
|
[Symbol.iterator](): IterableIterator<OmpError>;
|
|
80
|
-
/**
|
|
81
|
+
/** @internal Render multiple branch failures as alternatives rather than independent failures. */
|
|
82
|
+
asAlternatives(): this;
|
|
81
83
|
get summary(): string;
|
|
82
84
|
toString(): string;
|
|
83
|
-
/** Throw a `TraversalError` carrying this result. */
|
|
84
85
|
throw(): never;
|
|
85
86
|
}
|
|
86
87
|
/** Error thrown by `Type.assert` on invalid input. */
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type BaseType } from "./type.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build a callable omptype schema from a JSON Schema document.
|
|
4
|
+
*
|
|
5
|
+
* # Errors
|
|
6
|
+
* Throws {@link OmpTypeError} on malformed nodes, unresolvable `$ref`s, or
|
|
7
|
+
* types omptype cannot represent.
|
|
8
|
+
*/
|
|
9
|
+
export declare function fromJsonSchema(schema: unknown): BaseType;
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/infer.d.ts
CHANGED
|
@@ -10,25 +10,47 @@ type Trim<s extends string> = TrimLeft<TrimRight<s>>;
|
|
|
10
10
|
* `never` is intentionally absent: the parser rejects it and the fallback in
|
|
11
11
|
* `InferMember` treats a missing entry as "not a primitive".
|
|
12
12
|
*/
|
|
13
|
+
type ArkAny = ReturnType<typeof JSON.parse>;
|
|
13
14
|
interface PrimitiveMap {
|
|
14
15
|
string: string;
|
|
15
16
|
"string.url": string;
|
|
16
17
|
number: number;
|
|
17
18
|
"number.integer": number;
|
|
19
|
+
"number.epoch": number;
|
|
20
|
+
"number.safe": number;
|
|
21
|
+
"number.NaN": number;
|
|
22
|
+
"number.Infinity": number;
|
|
23
|
+
"number.NegativeInfinity": number;
|
|
18
24
|
boolean: boolean;
|
|
19
25
|
null: null;
|
|
20
26
|
undefined: undefined;
|
|
21
27
|
unknown: unknown;
|
|
28
|
+
"unknown.any": ArkAny;
|
|
22
29
|
any: unknown;
|
|
23
30
|
object: object;
|
|
24
31
|
bigint: bigint;
|
|
25
32
|
symbol: symbol;
|
|
33
|
+
Key: PropertyKey;
|
|
26
34
|
Date: Date;
|
|
35
|
+
Array: unknown[];
|
|
36
|
+
Function: Function;
|
|
37
|
+
RegExp: RegExp;
|
|
38
|
+
File: File;
|
|
39
|
+
Error: Error;
|
|
40
|
+
Set: Set<unknown>;
|
|
41
|
+
Map: Map<unknown, unknown>;
|
|
42
|
+
WeakSet: WeakSet<WeakKey>;
|
|
43
|
+
WeakMap: WeakMap<WeakKey, unknown>;
|
|
44
|
+
Promise: Promise<unknown>;
|
|
45
|
+
FormData: FormData;
|
|
46
|
+
"object.json": unknown;
|
|
27
47
|
true: true;
|
|
28
48
|
false: false;
|
|
29
49
|
}
|
|
30
50
|
type Merge<left, right> = left extends object ? right extends object ? Omit<left, keyof right> & right : never : never;
|
|
31
|
-
type InferUtility<s extends string> = s extends `Record<${string},${infer value}>` ? Record<string, InferString<value>> : s extends `Array<${infer element}>` ? InferString<element>[] : s extends `Partial<${infer value}>` ? Partial<InferString<value>> : s extends `Required<${infer value}>` ? Required<InferString<value>> : s extends `Pick<${infer value},${infer keys}>` ? Pick<InferString<value>, Extract<InferString<keys>, keyof InferString<value>>> : s extends `Omit<${infer value},${infer keys}>` ? Omit<InferString<value>, Extract<InferString<keys>, keyof InferString<value>>> : s extends `Merge<${infer left},${infer right}>` ? Merge<InferString<left>, InferString<right>> : never;
|
|
51
|
+
type InferUtility<s extends string> = s extends `Record<${string},${infer value}>` ? Record<string, InferString<value>> : s extends `Array<${infer element}>` | `Array.liftFrom<${infer element}>` ? InferString<element>[] : s extends `Partial<${infer value}>` ? Partial<InferString<value>> : s extends `Required<${infer value}>` ? Required<InferString<value>> : s extends `Pick<${infer value},${infer keys}>` ? Pick<InferString<value>, Extract<InferString<keys>, keyof InferString<value>>> : s extends `Omit<${infer value},${infer keys}>` ? Omit<InferString<value>, Extract<InferString<keys>, keyof InferString<value>>> : s extends `Merge<${infer left},${infer right}>` ? Merge<InferString<left>, InferString<right>> : never;
|
|
52
|
+
/** Output types of morph (`.parse`) keywords; `never` when `s` is not one. */
|
|
53
|
+
type InferParse<s extends string> = s extends "string.numeric.parse" | "string.integer.parse" | "parse.number" | "parse.integer" ? number : s extends "string.date.parse" | "string.date.iso.parse" | "string.date.epoch.parse" | "parse.date" ? Date : s extends "string.url.parse" | "parse.url" ? URL : s extends "string.json.parse" | "parse.json" ? unknown : s extends "object.json.stringify" ? string : s extends "FormData.parse" ? Record<string, Bun.FormDataEntryValue | Bun.FormDataEntryValue[]> : s extends "parse.boolean" ? boolean : s extends "parse.bigint" ? bigint : never;
|
|
32
54
|
/**
|
|
33
55
|
* Member inference as a flat false-branch chain: TypeScript tail-evaluates
|
|
34
56
|
* chained conditionals in the false position, so this stays at constant
|
|
@@ -36,17 +58,21 @@ type InferUtility<s extends string> = s extends `Record<${string},${infer value}
|
|
|
36
58
|
* fallback inside a true branch and accumulated depth per step.
|
|
37
59
|
*/
|
|
38
60
|
type InferMember<member extends string> = Trim<member> extends infer s extends string ? InferMemberTrimmed<s> : unknown;
|
|
39
|
-
type InferMemberTrimmed<s extends string> = s extends `(${infer inner})` ? InferString<inner> : s extends `${infer element}[]` ? InferMember<element>[] : s extends `'${infer literal}'` | `"${infer literal}"` ? literal : s extends `d'${string}'` | `d"${string}"` ? Date : s extends `\`${string}\`` ? string : s extends `/${string}/${string}` | `/${string}/` ? string : s extends `${infer literal extends number}` ? literal : s extends keyof PrimitiveMap ? PrimitiveMap[s] : InferUtility<s> extends infer utility ? [utility] extends [never] ? InferMemberFallback<s> : utility : unknown
|
|
61
|
+
type InferMemberTrimmed<s extends string> = s extends `(${infer inner})` ? InferString<inner> : s extends `${infer element}[]` ? InferMember<element>[] : s extends `'${infer literal}'` | `"${infer literal}"` ? literal : s extends `d'${string}'` | `d"${string}"` ? Date : s extends `\`${string}\`` ? string : s extends `/${string}/${string}` | `/${string}/` ? string : s extends `${infer literal extends number}` ? literal : s extends keyof PrimitiveMap ? PrimitiveMap[s] : [InferParse<s>] extends [never] ? InferUtility<s> extends infer utility ? [utility] extends [never] ? InferMemberFallback<s> : utility : unknown : InferParse<s>;
|
|
40
62
|
type InferMemberFallback<s extends string> = s extends `string.${string}` ? string : s extends `${string}Date${string}` ? Date : s extends `${string}string${string}` ? string : s extends `${string}number${string}` ? number : unknown;
|
|
41
63
|
/** Split unions without distributing over the accumulated members. */
|
|
42
64
|
type InferUnion<s extends string, result = never> = s extends `${infer head}|${infer tail}` ? InferUnion<tail, result | InferMember<head>> : result | InferMember<s>;
|
|
65
|
+
/** Input side of one union member: morph keywords accept their source type. */
|
|
66
|
+
type InferMemberIn<member extends string> = Trim<member> extends infer s extends string ? s extends `${string}.parse` | `parse.${string}` ? string : InferMemberTrimmed<s> : unknown;
|
|
67
|
+
/** Split unions on the input side without distributing over accumulated members. */
|
|
68
|
+
type InferUnionIn<s extends string, result = never> = s extends `${infer head}|${infer tail}` ? InferUnionIn<tail, result | InferMemberIn<head>> : result | InferMemberIn<s>;
|
|
43
69
|
type HasInlineDefault<s extends string> = s extends `${string}=${string}` ? s extends `${string}<${string}` | `${string}>${string}` ? false : true : false;
|
|
44
70
|
type WithoutInlineDefault<s extends string> = HasInlineDefault<s> extends true ? (s extends `${infer base}=${string}` ? Trim<base> : s) : s;
|
|
45
|
-
type InferStringOutput<s extends string> =
|
|
71
|
+
type InferStringOutput<s extends string> = InferUnion<s>;
|
|
46
72
|
/** String-DSL output inference. */
|
|
47
73
|
export type InferString<s extends string> = WithoutInlineDefault<Trim<s>> extends infer trimmed extends string ? trimmed extends `${infer base}?` ? InferString<base> : trimmed extends `(${infer inner})[]` ? InferUnion<inner>[] : InferStringOutput<trimmed> : unknown;
|
|
48
74
|
/** String-DSL input inference, preserving the source side of morph keywords. */
|
|
49
|
-
export type InferStringIn<s extends string> = WithoutInlineDefault<Trim<s>> extends infer trimmed extends string ? trimmed extends `${
|
|
75
|
+
export type InferStringIn<s extends string> = WithoutInlineDefault<Trim<s>> extends infer trimmed extends string ? trimmed extends `${infer base}?` ? InferStringIn<base> : trimmed extends `(${infer inner})[]` ? InferUnionIn<inner>[] : InferUnionIn<trimmed> : unknown;
|
|
50
76
|
type HasDefault<def> = def extends string ? HasInlineDefault<def> : def extends readonly [unknown, "=", unknown] ? true : def extends {
|
|
51
77
|
readonly hasDefault: true;
|
|
52
78
|
} ? true : false;
|
|
@@ -92,15 +118,28 @@ type InferObject<def extends object> = "[string]" extends keyof def ? [Definitio
|
|
|
92
118
|
type InferObjectIn<def extends object> = "[string]" extends keyof def ? [DefinitionKeys<def>] extends [never] ? Record<string, DefBox<def["[string]"]>["in"]> : Simplify<InputRequired<def> & InputOptional<def> & InputSpread<def>> & InputIndex<def> : Simplify<InputRequired<def> & InputOptional<def> & InputSpread<def>>;
|
|
93
119
|
/** Object-literal inference used by fluent composition overloads. */
|
|
94
120
|
export type InferObjectDef<def extends object> = InferObject<def>;
|
|
95
|
-
type InferLiteralDef<def> = def extends
|
|
121
|
+
type InferLiteralDef<def> = def extends {
|
|
122
|
+
readonly infer: infer output;
|
|
123
|
+
} ? output : def extends string ? InferString<def> : def extends object ? InferObjectLiteral<def> : unknown;
|
|
124
|
+
type InferLiteralDefIn<def> = def extends {
|
|
125
|
+
readonly inferIn: infer input;
|
|
126
|
+
} ? input : def extends string ? InferStringIn<def> : def extends object ? InferObjectLiteralIn<def> : unknown;
|
|
96
127
|
type LiteralRequired<def extends object> = {
|
|
97
128
|
-readonly [key in DefinitionKeys<def> as IsOptionalProp<key, def[key]> extends true ? HasDefault<def[key]> extends true ? PropName<key> : never : PropName<key>]-?: InferLiteralDef<UnwrapProperty<def[key]>>;
|
|
98
129
|
};
|
|
99
130
|
type LiteralOptional<def extends object> = {
|
|
100
131
|
-readonly [key in DefinitionKeys<def> as IsOptionalProp<key, def[key]> extends true ? HasDefault<def[key]> extends true ? never : PropName<key> : never]?: InferLiteralDef<UnwrapProperty<def[key]>>;
|
|
101
132
|
};
|
|
102
|
-
|
|
133
|
+
type LiteralRequiredIn<def extends object> = {
|
|
134
|
+
-readonly [key in DefinitionKeys<def> as IsOptionalProp<key, def[key]> extends true ? never : HasDefault<def[key]> extends true ? never : PropName<key>]-?: InferLiteralDefIn<UnwrapProperty<def[key]>>;
|
|
135
|
+
};
|
|
136
|
+
type LiteralOptionalIn<def extends object> = {
|
|
137
|
+
-readonly [key in DefinitionKeys<def> as IsOptionalProp<key, def[key]> extends true ? PropName<key> : HasDefault<def[key]> extends true ? PropName<key> : never]?: InferLiteralDefIn<UnwrapProperty<def[key]>>;
|
|
138
|
+
};
|
|
139
|
+
/** Object-literal inference that unwraps embedded schema values one level deep. */
|
|
103
140
|
export type InferObjectLiteral<def extends object> = Simplify<LiteralRequired<def> & LiteralOptional<def>>;
|
|
141
|
+
/** Input-side object-literal inference (embedded schemas contribute `inferIn`). */
|
|
142
|
+
export type InferObjectLiteralIn<def extends object> = Simplify<LiteralRequiredIn<def> & LiteralOptionalIn<def>>;
|
|
104
143
|
type InstanceOf<ctor> = ctor extends abstract new (...args: never[]) => infer instance ? instance : never;
|
|
105
144
|
type SpreadOutput<def> = InferDef<def> extends readonly (infer element)[] ? element[] : never[];
|
|
106
145
|
type SpreadInput<def> = InferDefIn<def> extends readonly (infer element)[] ? element[] : never[];
|
package/dist/types/interp.d.ts
CHANGED
|
@@ -10,8 +10,15 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { OmpErrors } from "./errors.js";
|
|
12
12
|
import { type IR } from "./ir.js";
|
|
13
|
-
/**
|
|
14
|
-
export declare function
|
|
13
|
+
/** Return an independent runtime value for a prevalidated static default. */
|
|
14
|
+
export declare function materializeDefault(payload: unknown): unknown;
|
|
15
|
+
/**
|
|
16
|
+
* Validate `value` against `ir`; returns output value or `OmpErrors`.
|
|
17
|
+
* `path` seeds the traversal location so nested step callbacks observe
|
|
18
|
+
* absolute ctx.path values when a compiled parent delegates a subtree;
|
|
19
|
+
* resulting error paths are then already absolute.
|
|
20
|
+
*/
|
|
21
|
+
export declare function walk(ir: IR, value: unknown, path?: PropertyKey[]): unknown;
|
|
15
22
|
/** True when a union failure can be replaced with a more specific nested error. */
|
|
16
23
|
export declare function canRefineUnionFailure(member: IR): boolean;
|
|
17
24
|
/**
|
package/dist/types/ir.d.ts
CHANGED
|
@@ -9,12 +9,26 @@
|
|
|
9
9
|
* consumed by the interpreter (`interp.ts`), the JIT compiler (`compile.ts`),
|
|
10
10
|
* and the JSON Schema emitter (`json-schema.ts`).
|
|
11
11
|
*/
|
|
12
|
-
import { OmpErrors } from "./errors.js";
|
|
12
|
+
import { type ErrorConfig, OmpErrors } from "./errors.js";
|
|
13
13
|
/** Brand carried by `Type` instances so the parser can embed them in defs. */
|
|
14
14
|
export declare const IR_BRAND: unique symbol;
|
|
15
15
|
declare const kMorph: unique symbol;
|
|
16
|
+
declare const kMorphOwner: unique symbol;
|
|
17
|
+
declare const kAlias: unique symbol;
|
|
18
|
+
declare const kAliasOwner: unique symbol;
|
|
19
|
+
declare const kSimple: unique symbol;
|
|
20
|
+
declare const kSimpleOwner: unique symbol;
|
|
16
21
|
interface IRAnalysis {
|
|
17
22
|
[kMorph]?: boolean;
|
|
23
|
+
[kMorphOwner]?: object;
|
|
24
|
+
[kAlias]?: boolean;
|
|
25
|
+
[kAliasOwner]?: object;
|
|
26
|
+
[kSimple]?: boolean;
|
|
27
|
+
[kSimpleOwner]?: object;
|
|
28
|
+
/** Node-local metadata used for shallow error formatting. */
|
|
29
|
+
cfg?: ErrorConfig;
|
|
30
|
+
/** True when `desc` was derived from the node itself rather than authored via `.describe()`. */
|
|
31
|
+
descAuto?: boolean;
|
|
18
32
|
}
|
|
19
33
|
/**
|
|
20
34
|
* The parser-facing surface of an embedded `Type` instance.
|
|
@@ -26,13 +40,20 @@ export interface EmbeddableSchema {
|
|
|
26
40
|
ir: IR;
|
|
27
41
|
/** True when the schema carries `.pipe()`/`.narrow()` steps. */
|
|
28
42
|
hasSteps: boolean;
|
|
43
|
+
/** Output IR of the last `.to(target)` step, when statically known. */
|
|
44
|
+
stepOut?: IR;
|
|
45
|
+
/** True when the last pipe step is bare — output shape statically unknown. */
|
|
46
|
+
opaqueOutput?: boolean;
|
|
29
47
|
/** `.default()` payload; a function is a factory invoked per fill. */
|
|
30
48
|
defaultValue?: unknown;
|
|
31
49
|
hasDefault: boolean;
|
|
50
|
+
/** Precomputed output for a non-factory default after validation and morphs. */
|
|
51
|
+
defaultOutput?: unknown;
|
|
52
|
+
hasDefaultOutput?: boolean;
|
|
32
53
|
/** `.describe()` annotation, emitted into JSON Schema. */
|
|
33
54
|
description?: string;
|
|
34
55
|
/** Full validate+morph pipeline (identical to calling the schema). */
|
|
35
|
-
run(value: unknown): unknown;
|
|
56
|
+
run(value: unknown, path?: readonly PropertyKey[]): unknown;
|
|
36
57
|
}
|
|
37
58
|
/** Policy for undeclared object keys. */
|
|
38
59
|
export type Extras = "keep" | "reject" | "delete";
|
|
@@ -52,6 +73,8 @@ export interface TupleItemIR {
|
|
|
52
73
|
def?: unknown;
|
|
53
74
|
defFactory?: boolean;
|
|
54
75
|
hasDefault?: boolean;
|
|
76
|
+
/** True once the default has been validated and static morph output precomputed. */
|
|
77
|
+
defValidated?: boolean;
|
|
55
78
|
}
|
|
56
79
|
/** Fixed, optional, variadic, and postfix tuple sequence. */
|
|
57
80
|
export interface TupleIR {
|
|
@@ -124,12 +147,17 @@ export type IR = IRAnalysis & ({
|
|
|
124
147
|
k: "object";
|
|
125
148
|
props: PropIR[];
|
|
126
149
|
index?: IR;
|
|
150
|
+
symbolIndex?: IR;
|
|
151
|
+
patternIndexes?: {
|
|
152
|
+
key: IR;
|
|
153
|
+
val: IR;
|
|
154
|
+
}[];
|
|
127
155
|
extras: Extras;
|
|
128
156
|
desc?: string;
|
|
129
157
|
} | {
|
|
130
158
|
k: "refine";
|
|
131
159
|
base: IR;
|
|
132
|
-
pred: (value: unknown) => boolean;
|
|
160
|
+
pred: (value: unknown) => boolean | OmpErrors;
|
|
133
161
|
expected: string;
|
|
134
162
|
json?: Record<string, unknown>;
|
|
135
163
|
desc?: string;
|
|
@@ -157,28 +185,51 @@ export type IR = IRAnalysis & ({
|
|
|
157
185
|
desc?: string;
|
|
158
186
|
});
|
|
159
187
|
export interface PropIR {
|
|
160
|
-
key:
|
|
188
|
+
key: PropertyKey;
|
|
161
189
|
opt: boolean;
|
|
162
190
|
val: IR;
|
|
163
191
|
/** Default payload (value, or factory when `defFactory`); missing key is filled. */
|
|
164
192
|
def?: unknown;
|
|
165
193
|
defFactory?: boolean;
|
|
166
194
|
hasDefault?: boolean;
|
|
195
|
+
/** True once the default has been validated and static morph output precomputed. */
|
|
196
|
+
defValidated?: boolean;
|
|
167
197
|
}
|
|
168
198
|
/** Definition input accepted by `type()` and object property values. */
|
|
169
199
|
export type Def = string | RegExp | Date | EmbeddableSchema | readonly unknown[] | {
|
|
170
200
|
readonly [k: string]: unknown;
|
|
171
201
|
};
|
|
172
|
-
/** Resolve
|
|
173
|
-
export
|
|
202
|
+
/** Resolve named scope aliases and, when present, scoped generic invocations. */
|
|
203
|
+
export interface AliasResolver {
|
|
204
|
+
(name: string): IR | undefined;
|
|
205
|
+
hasGeneric?(name: string): boolean;
|
|
206
|
+
generic?(name: string, arguments_: readonly IR[]): IR | undefined;
|
|
207
|
+
}
|
|
208
|
+
/** Declare that `resolve` only intercepts `this` (see THIS_ONLY_RESOLVERS). */
|
|
209
|
+
export declare function markThisOnlyResolver(resolve: AliasResolver): void;
|
|
210
|
+
/** Install the assignability comparator used by `Extract`/`Exclude`. */
|
|
211
|
+
export declare function useAssignability(compare: (source: IR, target: IR) => boolean): void;
|
|
212
|
+
/**
|
|
213
|
+
* Distribute `base` over its union members, keeping those assignable to
|
|
214
|
+
* `target` (`keepAssignable`) or those that are not (`Exclude`).
|
|
215
|
+
*/
|
|
216
|
+
export declare function distributeFilter(base: IR, target: IR, keepAssignable: boolean): IR;
|
|
174
217
|
/** Embed a schema value: inline pure structure, keep `sub` nodes for stepped schemas. */
|
|
175
218
|
export declare function embed(schema: EmbeddableSchema): IR;
|
|
176
219
|
/** Build the runtime schema for an object's or tuple's keys. */
|
|
177
220
|
export declare function keyOf(node: IR): IR;
|
|
178
221
|
/** Parse a definition, optionally resolving names from an enclosing scope. */
|
|
179
222
|
export declare function parseDef(def: unknown, resolve?: AliasResolver): IR;
|
|
223
|
+
/** Whether `ir` needs no construction-time normalization or morph analysis. */
|
|
224
|
+
export declare function isSimpleIR(ir: IR): boolean;
|
|
180
225
|
/** True when validating `ir` can produce an output different from its input. */
|
|
181
226
|
export declare function hasMorph(ir: IR): boolean;
|
|
227
|
+
/**
|
|
228
|
+
* True when a traversal of `ir` can revisit nodes through recursive aliases,
|
|
229
|
+
* requiring cycle guards in the interpreter. Embedded sub-schemas run their
|
|
230
|
+
* own guarded traversal and are intentionally not inspected.
|
|
231
|
+
*/
|
|
232
|
+
export declare function hasAlias(ir: IR): boolean;
|
|
182
233
|
/** Human-readable expectation for error messages, e.g. `"a string"`. */
|
|
183
234
|
export declare function expectedOf(ir: IR): string;
|
|
184
235
|
export {};
|
|
@@ -2,7 +2,16 @@ import type { IR } from "./ir.js";
|
|
|
2
2
|
export interface JsonSchemaOptions {
|
|
3
3
|
description?: string;
|
|
4
4
|
target?: string;
|
|
5
|
-
dialect?: string;
|
|
5
|
+
dialect?: string | null;
|
|
6
|
+
/**
|
|
7
|
+
* Which side of morphs and defaults to describe:
|
|
8
|
+
* - `'input'` — accepted payloads: morphs emit their input shape, defaulted
|
|
9
|
+
* properties are optional (with `default` annotations).
|
|
10
|
+
* - `'output'` — produced values: morphs emit their output shape, defaulted
|
|
11
|
+
* properties are required (always present after validation).
|
|
12
|
+
* Unset keeps the hybrid legacy behavior (morph output, defaults optional).
|
|
13
|
+
*/
|
|
14
|
+
io?: "input" | "output";
|
|
6
15
|
fallback?: (context: {
|
|
7
16
|
base: Record<string, unknown>;
|
|
8
17
|
}) => unknown;
|