@oh-my-pi/omptype 17.2.6 → 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 +40 -2
- package/README.md +83 -14
- package/dist/js/ark.js +16 -0
- package/dist/js/compile.js +942 -0
- package/dist/js/errors.js +394 -0
- package/dist/js/from-json-schema.js +234 -0
- package/dist/js/index.js +13 -0
- package/dist/js/infer.js +2 -0
- package/dist/js/interp.js +858 -0
- package/dist/js/ir.js +1700 -0
- package/dist/js/json-schema.js +312 -0
- package/dist/js/keywords.js +340 -0
- package/dist/js/type.js +3062 -0
- package/dist/js/typebox.js +355 -0
- package/dist/js/zod.js +269 -0
- package/dist/types/ark.d.ts +3 -11
- package/dist/types/compile.d.ts +2 -0
- package/dist/types/errors.d.ts +53 -16
- package/dist/types/from-json-schema.d.ts +9 -0
- package/dist/types/index.d.ts +4 -4
- package/dist/types/infer.d.ts +147 -31
- package/dist/types/interp.d.ts +14 -5
- package/dist/types/ir.d.ts +129 -9
- package/dist/types/json-schema.d.ts +15 -1
- package/dist/types/keywords.d.ts +7 -0
- package/dist/types/type.d.ts +525 -60
- package/dist/types/typebox.d.ts +78 -50
- package/dist/types/zod.d.ts +1 -1
- package/package.json +24 -8
- package/src/ark.ts +4 -14
- package/src/compile.ts +887 -154
- package/src/errors.ts +347 -37
- package/src/from-json-schema.ts +231 -0
- package/src/index.ts +4 -4
- package/src/infer.ts +363 -81
- package/src/interp.ts +585 -102
- package/src/ir.ts +1444 -128
- package/src/json-schema.ts +162 -19
- package/src/keywords.ts +380 -0
- package/src/type.ts +3842 -198
- package/src/typebox.ts +142 -99
- package/src/zod.ts +41 -28
package/src/typebox.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OmpTypeError } from "./errors";
|
|
2
|
-
import type { Def } from "./ir";
|
|
3
|
-
import { type NarrowContext, type Type as OmpType, type } from "./type";
|
|
2
|
+
import type { Def, EmbeddableSchema } from "./ir";
|
|
3
|
+
import { type NarrowContext, type Type as OmpType, type ToJsonSchemaOptions, type } from "./type";
|
|
4
4
|
|
|
5
5
|
export interface Meta {
|
|
6
6
|
title?: string;
|
|
@@ -32,7 +32,7 @@ export interface ArrayOpts extends Meta {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export interface ObjectOpts extends Meta {
|
|
35
|
-
additionalProperties?: boolean |
|
|
35
|
+
additionalProperties?: boolean | AnySchema;
|
|
36
36
|
}
|
|
37
37
|
const OPTIONAL_INNER = Symbol("omptype.typebox.optionalInner");
|
|
38
38
|
const OBJECT_INFO = Symbol("omptype.typebox.objectInfo");
|
|
@@ -52,53 +52,84 @@ interface LegacyTypeBoxCompat<T> {
|
|
|
52
52
|
safeParse(input: unknown): TypeBoxSafeParseResult<T>;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
export
|
|
66
|
-
|
|
67
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Erased schema surface accepted anywhere this facade takes a schema, native
|
|
57
|
+
* omptype schemas included (those carry no legacy compat members).
|
|
58
|
+
*
|
|
59
|
+
* Members use method syntax so parameter positions stay bivariant: the typed
|
|
60
|
+
* {@link TTyped} form is invariant in its static type (its `in`/`out`
|
|
61
|
+
* validators are), so a concrete `TString` is not assignable to
|
|
62
|
+
* `TTyped<unknown>`. Erasing here is what keeps `TString`, `TObject<…>` and
|
|
63
|
+
* friends assignable to a plain schema annotation.
|
|
64
|
+
*/
|
|
65
|
+
export interface AnySchema extends EmbeddableSchema {
|
|
66
|
+
(data: unknown): unknown;
|
|
67
|
+
readonly infer: unknown;
|
|
68
|
+
readonly hasSteps: boolean;
|
|
69
|
+
toJsonSchema(options?: ToJsonSchemaOptions): Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Every schema this facade returns: the TypeBox `TSchema` analog. Erased like
|
|
74
|
+
* {@link AnySchema}, plus the legacy compat members builder results carry.
|
|
75
|
+
*/
|
|
76
|
+
export interface TSchema extends AnySchema {
|
|
77
|
+
/** TypeBox compatibility validator used by legacy extension loaders. */
|
|
78
|
+
__validator(data: unknown): unknown;
|
|
79
|
+
/** Zod-style compatibility parser used by legacy extensions. */
|
|
80
|
+
safeParse(input: unknown): TypeBoxSafeParseResult<unknown>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Schema carrying a statically known type; every `TXxx` alias resolves here. */
|
|
84
|
+
export type TTyped<T> = OmpType<T> & LegacyTypeBoxCompat<T>;
|
|
85
|
+
export type Static<T extends AnySchema> = T["infer"];
|
|
86
|
+
export type TAny = TTyped<unknown>;
|
|
87
|
+
export type TUnknown = TTyped<unknown>;
|
|
88
|
+
export type TNever = TTyped<never>;
|
|
89
|
+
export type TNull = TTyped<null>;
|
|
90
|
+
export type TString = TTyped<string>;
|
|
91
|
+
export type TNumber = TTyped<number>;
|
|
92
|
+
export type TInteger = TTyped<number>;
|
|
93
|
+
export type TBoolean = TTyped<boolean>;
|
|
94
|
+
export type TLiteral<V extends string | number | boolean | null> = TTyped<V>;
|
|
95
|
+
export type TArray<E extends AnySchema> = TTyped<Static<E>[]>;
|
|
96
|
+
export type TTuple<E extends readonly AnySchema[] = readonly AnySchema[]> = TTyped<{
|
|
68
97
|
-readonly [K in keyof E]: Static<E[K]>;
|
|
69
98
|
}>;
|
|
70
|
-
export type TOptional<E extends
|
|
71
|
-
export type TUnion<E extends readonly
|
|
72
|
-
export type TIntersect<E extends readonly
|
|
99
|
+
export type TOptional<E extends AnySchema> = TTyped<Static<E> | undefined> & { readonly [OPTIONAL_INNER]: E };
|
|
100
|
+
export type TUnion<E extends readonly AnySchema[] = readonly AnySchema[]> = TTyped<Static<E[number]>>;
|
|
101
|
+
export type TIntersect<E extends readonly AnySchema[] = readonly AnySchema[]> = TTyped<
|
|
73
102
|
UnionToIntersection<Static<E[number]>>
|
|
74
103
|
>;
|
|
75
|
-
export type TEnum<E extends readonly (string | number)[] = readonly (string | number)[]> =
|
|
76
|
-
export type TRecord<K extends
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
export type
|
|
80
|
-
|
|
81
|
-
type
|
|
82
|
-
|
|
104
|
+
export type TEnum<E extends readonly (string | number)[] = readonly (string | number)[]> = TTyped<E[number]>;
|
|
105
|
+
export type TRecord<K extends AnySchema, V extends AnySchema> = TTyped<
|
|
106
|
+
Record<Extract<Static<K>, PropertyKey>, Static<V>>
|
|
107
|
+
>;
|
|
108
|
+
export type TNullable<E extends AnySchema> = TTyped<Static<E> | null>;
|
|
109
|
+
export type TReadonly<E extends AnySchema> = TTyped<Readonly<Static<E>>>;
|
|
110
|
+
export type TUnsafe<T = unknown> = TTyped<T>;
|
|
111
|
+
|
|
112
|
+
type OptionalKeys<P extends Record<string, AnySchema>> = {
|
|
113
|
+
[K in keyof P]-?: P[K] extends { readonly [OPTIONAL_INNER]: AnySchema } ? K : never;
|
|
83
114
|
}[keyof P];
|
|
84
|
-
type RequiredKeys<P extends Record<string,
|
|
85
|
-
type ObjectStatic<P extends Record<string,
|
|
115
|
+
type RequiredKeys<P extends Record<string, AnySchema>> = Exclude<keyof P, OptionalKeys<P>>;
|
|
116
|
+
type ObjectStatic<P extends Record<string, AnySchema>> = {
|
|
86
117
|
[K in RequiredKeys<P>]: Static<P[K]>;
|
|
87
118
|
} & {
|
|
88
119
|
[K in OptionalKeys<P>]?: Exclude<Static<P[K]>, undefined>;
|
|
89
120
|
};
|
|
90
|
-
export type TObject<P extends Record<string,
|
|
91
|
-
type RequiredProps<P extends Record<string,
|
|
121
|
+
export type TObject<P extends Record<string, AnySchema> = Record<string, AnySchema>> = TTyped<ObjectStatic<P>>;
|
|
122
|
+
type RequiredProps<P extends Record<string, AnySchema>> = {
|
|
92
123
|
[K in keyof P]: P[K] extends TOptional<infer E> ? E : P[K];
|
|
93
124
|
};
|
|
94
125
|
|
|
95
126
|
interface RuntimeType<T> extends OmpType<T> {
|
|
96
|
-
[OPTIONAL_INNER]?:
|
|
127
|
+
[OPTIONAL_INNER]?: AnySchema;
|
|
97
128
|
[OBJECT_INFO]?: ObjectInfo;
|
|
98
129
|
describe(description: string): RuntimeType<T>;
|
|
99
130
|
default(value: T | (() => T)): RuntimeType<T>;
|
|
100
|
-
or<
|
|
101
|
-
and<
|
|
131
|
+
or<schema extends AnySchema>(schema: schema): RuntimeType<T | Static<schema>>;
|
|
132
|
+
and<schema extends AnySchema>(schema: schema): RuntimeType<T & Static<schema>>;
|
|
102
133
|
array(): RuntimeType<T[]>;
|
|
103
134
|
atLeastLength(bound: number): RuntimeType<T>;
|
|
104
135
|
atMostLength(bound: number): RuntimeType<T>;
|
|
@@ -109,7 +140,18 @@ interface RuntimeType<T> extends OmpType<T> {
|
|
|
109
140
|
}
|
|
110
141
|
type CompatRuntime<T> = RuntimeType<T> & LegacyTypeBoxCompat<T>;
|
|
111
142
|
|
|
112
|
-
type ObjectInfo = {
|
|
143
|
+
type ObjectInfo = {
|
|
144
|
+
props: Record<string, AnySchema>;
|
|
145
|
+
additionalProperties?: boolean | AnySchema;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
function asRuntime<T>(schema: AnySchema): RuntimeType<T> {
|
|
149
|
+
return schema as unknown as RuntimeType<T>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function asSchema<T>(schema: AnySchema): TTyped<T> {
|
|
153
|
+
return schema as unknown as TTyped<T>;
|
|
154
|
+
}
|
|
113
155
|
|
|
114
156
|
function validationFailure(message: string): TypeBoxValidationFailure {
|
|
115
157
|
return { message };
|
|
@@ -149,7 +191,7 @@ function applyMeta<T>(schema: RuntimeType<T>, opts?: Meta): CompatRuntime<T> {
|
|
|
149
191
|
}
|
|
150
192
|
|
|
151
193
|
function withJsonSchemaKeywords<T>(schema: CompatRuntime<T>, keywords: Record<string, unknown>): CompatRuntime<T> {
|
|
152
|
-
const emitBase = schema.toJsonSchema;
|
|
194
|
+
const emitBase = schema.toJsonSchema.bind(schema);
|
|
153
195
|
schema.toJsonSchema = options => ({ ...emitBase(options), ...keywords });
|
|
154
196
|
return schema;
|
|
155
197
|
}
|
|
@@ -161,9 +203,7 @@ function checkFiniteOption(name: string, value: number | undefined): void {
|
|
|
161
203
|
function tString(opts?: StringOpts): TString {
|
|
162
204
|
checkFiniteOption("minLength", opts?.minLength);
|
|
163
205
|
checkFiniteOption("maxLength", opts?.maxLength);
|
|
164
|
-
let schema = type.raw(
|
|
165
|
-
opts?.format === "url" || opts?.format === "uri" ? "string.url" : "string",
|
|
166
|
-
) as RuntimeType<string>;
|
|
206
|
+
let schema = asRuntime<string>(type.raw(opts?.format === "url" || opts?.format === "uri" ? "string.url" : "string"));
|
|
167
207
|
if (opts?.minLength !== undefined) schema = schema.atLeastLength(opts.minLength);
|
|
168
208
|
if (opts?.maxLength !== undefined) schema = schema.atMostLength(opts.maxLength);
|
|
169
209
|
if (opts?.pattern !== undefined) {
|
|
@@ -229,7 +269,7 @@ function tNumber(opts?: NumberOpts, integer = false): TNumber {
|
|
|
229
269
|
const keyword = integer ? "number.integer" : "number";
|
|
230
270
|
const lowerDsl = lower ? `${lower.value} ${lower.exclusive ? "<" : "<="} ` : "";
|
|
231
271
|
const upperDsl = upper ? ` ${upper.exclusive ? "<" : "<="} ${upper.value}` : "";
|
|
232
|
-
let schema = type.raw(`${lowerDsl}${keyword}${upperDsl}`)
|
|
272
|
+
let schema = asRuntime<number>(type.raw(`${lowerDsl}${keyword}${upperDsl}`));
|
|
233
273
|
if (opts?.multipleOf !== undefined) {
|
|
234
274
|
const divisor = opts.multipleOf;
|
|
235
275
|
schema = schema.narrow((value, ctx) => {
|
|
@@ -244,33 +284,32 @@ function tNumber(opts?: NumberOpts, integer = false): TNumber {
|
|
|
244
284
|
}
|
|
245
285
|
|
|
246
286
|
function tLiteral<const V extends string | number | boolean | null>(value: V, opts?: Meta): TLiteral<V> {
|
|
247
|
-
return applyMeta(type.enumerated(value)
|
|
287
|
+
return applyMeta(asRuntime<V>(type.enumerated(value)), opts);
|
|
248
288
|
}
|
|
249
289
|
|
|
250
290
|
function tNever(opts?: Meta): TNever {
|
|
251
291
|
return applyMeta(
|
|
252
|
-
(type.raw("unknown")
|
|
292
|
+
asRuntime<unknown>(type.raw("unknown")).narrow((_value, ctx): _value is never => ctx.mustBe("never")),
|
|
253
293
|
opts,
|
|
254
294
|
);
|
|
255
295
|
}
|
|
256
296
|
|
|
257
|
-
function tUnion<const E extends readonly
|
|
258
|
-
if (schemas.length === 0) return tNever(opts)
|
|
259
|
-
let result = schemas[0]
|
|
297
|
+
function tUnion<const E extends readonly AnySchema[]>(schemas: E, opts?: Meta): TUnion<E> {
|
|
298
|
+
if (schemas.length === 0) return asSchema<Static<E[number]>>(tNever(opts));
|
|
299
|
+
let result = asRuntime<unknown>(schemas[0]);
|
|
260
300
|
for (let i = 1; i < schemas.length; i++) result = result.or(schemas[i]);
|
|
261
|
-
return applyMeta(result, opts)
|
|
301
|
+
return asSchema<Static<E[number]>>(applyMeta(result, opts));
|
|
262
302
|
}
|
|
263
303
|
|
|
264
|
-
function tIntersect<const E extends readonly
|
|
304
|
+
function tIntersect<const E extends readonly AnySchema[]>(
|
|
265
305
|
schemas: E,
|
|
266
306
|
opts?: Meta,
|
|
267
|
-
):
|
|
268
|
-
if (schemas.length === 0)
|
|
269
|
-
return applyMeta(type.raw("unknown")
|
|
270
|
-
|
|
271
|
-
>;
|
|
307
|
+
): TTyped<UnionToIntersection<Static<E[number]>>> {
|
|
308
|
+
if (schemas.length === 0) {
|
|
309
|
+
return applyMeta(asRuntime<UnionToIntersection<Static<E[number]>>>(type.raw("unknown")), opts);
|
|
310
|
+
}
|
|
272
311
|
const validateAll = (): RuntimeType<UnionToIntersection<Static<E[number]>>> => {
|
|
273
|
-
const base = type.raw("unknown")
|
|
312
|
+
const base = asRuntime<unknown>(type.raw("unknown"));
|
|
274
313
|
return base.narrow((value, ctx): value is UnionToIntersection<Static<E[number]>> => {
|
|
275
314
|
for (const schema of schemas) {
|
|
276
315
|
if (schema(value) instanceof type.errors) return ctx.mustBe("a value satisfying every intersection member");
|
|
@@ -279,14 +318,14 @@ function tIntersect<const E extends readonly TSchema[]>(
|
|
|
279
318
|
});
|
|
280
319
|
};
|
|
281
320
|
if (schemas.some(schema => schema.hasSteps)) return applyMeta(validateAll(), opts);
|
|
282
|
-
let result = schemas[0]
|
|
321
|
+
let result = asRuntime<unknown>(schemas[0]);
|
|
283
322
|
try {
|
|
284
323
|
for (let i = 1; i < schemas.length; i++) result = result.and(schemas[i]);
|
|
285
324
|
} catch (error) {
|
|
286
325
|
if (error instanceof OmpTypeError) return applyMeta(validateAll(), opts);
|
|
287
326
|
throw error;
|
|
288
327
|
}
|
|
289
|
-
return
|
|
328
|
+
return asSchema<UnionToIntersection<Static<E[number]>>>(applyMeta(result, opts));
|
|
290
329
|
}
|
|
291
330
|
type UnionToIntersection<U> = (U extends unknown ? (value: U) => void : never) extends (value: infer I) => void
|
|
292
331
|
? I
|
|
@@ -306,16 +345,17 @@ function enumValues(values: Record<string, string | number> | readonly (string |
|
|
|
306
345
|
function tEnum<const E extends Record<string, string | number> | readonly (string | number)[]>(
|
|
307
346
|
values: E,
|
|
308
347
|
opts?: Meta,
|
|
309
|
-
):
|
|
310
|
-
return applyMeta(
|
|
311
|
-
E extends readonly (infer V)[] ? V : E[keyof E]
|
|
312
|
-
|
|
348
|
+
): TTyped<E extends readonly (infer V)[] ? V : E[keyof E]> {
|
|
349
|
+
return applyMeta(
|
|
350
|
+
asRuntime<E extends readonly (infer V)[] ? V : E[keyof E]>(type.enumerated(...enumValues(values))),
|
|
351
|
+
opts,
|
|
352
|
+
);
|
|
313
353
|
}
|
|
314
354
|
|
|
315
|
-
function tArray<E extends
|
|
355
|
+
function tArray<E extends AnySchema>(item: E, opts?: ArrayOpts): TArray<E> {
|
|
316
356
|
checkFiniteOption("minItems", opts?.minItems);
|
|
317
357
|
checkFiniteOption("maxItems", opts?.maxItems);
|
|
318
|
-
let schema =
|
|
358
|
+
let schema = asRuntime<Static<E>>(item).array();
|
|
319
359
|
if (opts?.minItems !== undefined) schema = schema.atLeastLength(opts.minItems);
|
|
320
360
|
if (opts?.maxItems !== undefined) schema = schema.atMostLength(opts.maxItems);
|
|
321
361
|
if (opts?.uniqueItems) {
|
|
@@ -342,8 +382,8 @@ function jsonEqual(left: unknown, right: unknown): boolean {
|
|
|
342
382
|
}
|
|
343
383
|
}
|
|
344
384
|
|
|
345
|
-
function tTuple<const E extends readonly
|
|
346
|
-
const schema = (type.raw("unknown")
|
|
385
|
+
function tTuple<const E extends readonly AnySchema[]>(items: E, opts?: Meta): TTuple<E> {
|
|
386
|
+
const schema = asRuntime<unknown>(type.raw("unknown")).narrow(
|
|
347
387
|
(value, ctx): value is { -readonly [K in keyof E]: Static<E[K]> } => {
|
|
348
388
|
if (!Array.isArray(value) || value.length !== items.length)
|
|
349
389
|
return ctx.mustBe(`a tuple of length ${items.length}`);
|
|
@@ -355,25 +395,29 @@ function tTuple<const E extends readonly TSchema[]>(items: E, opts?: Meta): TTup
|
|
|
355
395
|
return applyMeta(schema, opts);
|
|
356
396
|
}
|
|
357
397
|
|
|
358
|
-
function tObject<const P extends Record<string,
|
|
398
|
+
function tObject<const P extends Record<string, AnySchema>>(properties: P, opts?: ObjectOpts): TObject<P> {
|
|
359
399
|
const def: Record<string, Def> = {};
|
|
360
|
-
const props: Record<string,
|
|
400
|
+
const props: Record<string, AnySchema> = {};
|
|
361
401
|
for (const key in properties) {
|
|
362
402
|
const schema = properties[key];
|
|
363
|
-
const inner =
|
|
364
|
-
|
|
403
|
+
const inner = asRuntime<unknown>(schema)[OPTIONAL_INNER];
|
|
404
|
+
// A defaulted `Type.Optional(...)` maps to a plain defaulted key:
|
|
405
|
+
// omptype (like ArkType) rejects `key?` with a default, and a default
|
|
406
|
+
// already makes the key omittable on input.
|
|
407
|
+
const optionalKey = inner !== undefined && !asRuntime<unknown>(inner).hasDefault;
|
|
408
|
+
def[optionalKey ? `${key}?` : key] = inner ?? schema;
|
|
365
409
|
props[key] = schema;
|
|
366
410
|
}
|
|
367
411
|
if (opts?.additionalProperties === false) def["+"] = "reject";
|
|
368
412
|
else if (opts?.additionalProperties && opts.additionalProperties !== true)
|
|
369
|
-
def["[string]"] = opts.additionalProperties
|
|
370
|
-
const schema = applyMeta(type.raw(def)
|
|
413
|
+
def["[string]"] = opts.additionalProperties;
|
|
414
|
+
const schema = applyMeta(asRuntime<ObjectStatic<P>>(type.raw(def)), opts);
|
|
371
415
|
schema[OBJECT_INFO] = { props, additionalProperties: opts?.additionalProperties };
|
|
372
416
|
return schema;
|
|
373
417
|
}
|
|
374
418
|
|
|
375
|
-
function tRecord<K extends
|
|
376
|
-
const base = (type.raw({ "[string]": value })
|
|
419
|
+
function tRecord<K extends AnySchema, V extends AnySchema>(key: K, value: V, opts?: Meta): TRecord<K, V> {
|
|
420
|
+
const base = asRuntime<Record<string, Static<V>>>(type.raw({ "[string]": value })).narrow((record, ctx) => {
|
|
377
421
|
for (const name in record)
|
|
378
422
|
if (key(name) instanceof type.errors) return ctx.mustBe("an object with valid record keys");
|
|
379
423
|
return true;
|
|
@@ -381,72 +425,70 @@ function tRecord<K extends TSchema, V extends TSchema>(key: K, value: V, opts?:
|
|
|
381
425
|
return applyMeta(base, opts) as TRecord<K, V>;
|
|
382
426
|
}
|
|
383
427
|
|
|
384
|
-
function tOptional<E extends
|
|
428
|
+
function tOptional<E extends AnySchema>(schema: E, opts?: Meta): TOptional<E> {
|
|
385
429
|
const marker = applyMeta(
|
|
386
|
-
|
|
430
|
+
asRuntime<Static<E>>(schema).or(asRuntime<undefined>(type.raw("undefined"))),
|
|
387
431
|
opts,
|
|
388
432
|
) as RuntimeType<Static<E> | undefined>;
|
|
389
433
|
marker[OPTIONAL_INNER] = schema;
|
|
390
434
|
return marker as unknown as TOptional<E>;
|
|
391
435
|
}
|
|
392
436
|
|
|
393
|
-
function tNullable<E extends
|
|
394
|
-
return applyMeta(
|
|
437
|
+
function tNullable<E extends AnySchema>(schema: E, opts?: Meta): TTyped<Static<E> | null> {
|
|
438
|
+
return applyMeta(asRuntime<Static<E>>(schema).or(asRuntime<null>(type.raw("null"))), opts);
|
|
395
439
|
}
|
|
396
440
|
|
|
397
|
-
function requireObject(schema:
|
|
398
|
-
const info =
|
|
441
|
+
function requireObject(schema: AnySchema, operation: string): ObjectInfo {
|
|
442
|
+
const info = asRuntime<unknown>(schema)[OBJECT_INFO];
|
|
399
443
|
if (!info) throw new OmpTypeError(`Type.${operation} requires a schema created by Type.Object`);
|
|
400
444
|
return info;
|
|
401
445
|
}
|
|
402
446
|
|
|
403
|
-
function tPartial<P extends Record<string,
|
|
447
|
+
function tPartial<P extends Record<string, AnySchema>>(schema: TObject<P>): TTyped<Partial<ObjectStatic<P>>> {
|
|
404
448
|
const info = requireObject(schema, "Partial");
|
|
405
|
-
const props: Record<string,
|
|
449
|
+
const props: Record<string, AnySchema> = {};
|
|
406
450
|
for (const key in info.props)
|
|
407
|
-
props[key] = (info.props[key]
|
|
408
|
-
|
|
409
|
-
: tOptional(info.props[key]);
|
|
410
|
-
return tObject(props, { additionalProperties: info.additionalProperties }) as TSchema<Partial<ObjectStatic<P>>>;
|
|
451
|
+
props[key] = asRuntime<unknown>(info.props[key])[OPTIONAL_INNER] ? info.props[key] : tOptional(info.props[key]);
|
|
452
|
+
return tObject(props, { additionalProperties: info.additionalProperties }) as TTyped<Partial<ObjectStatic<P>>>;
|
|
411
453
|
}
|
|
412
454
|
|
|
413
|
-
function tRequired<P extends Record<string,
|
|
455
|
+
function tRequired<P extends Record<string, AnySchema>>(schema: TObject<P>): TObject<RequiredProps<P>> {
|
|
414
456
|
const info = requireObject(schema, "Required");
|
|
415
|
-
const props: Record<string,
|
|
457
|
+
const props: Record<string, AnySchema> = {};
|
|
416
458
|
for (const key in info.props) {
|
|
417
|
-
props[key] = (info.props[key]
|
|
459
|
+
props[key] = asRuntime<unknown>(info.props[key])[OPTIONAL_INNER] ?? info.props[key];
|
|
418
460
|
}
|
|
419
461
|
return tObject(props, { additionalProperties: info.additionalProperties }) as TObject<RequiredProps<P>>;
|
|
420
462
|
}
|
|
421
463
|
|
|
422
|
-
function tPick<P extends Record<string,
|
|
464
|
+
function tPick<P extends Record<string, AnySchema>, const K extends readonly (keyof P)[]>(
|
|
423
465
|
schema: TObject<P>,
|
|
424
466
|
keys: K,
|
|
425
467
|
): TObject<Pick<P, K[number]>> {
|
|
426
468
|
const info = requireObject(schema, "Pick");
|
|
427
|
-
const props: Record<string,
|
|
469
|
+
const props: Record<string, AnySchema> = {};
|
|
428
470
|
for (const key of keys) if (typeof key === "string" && info.props[key]) props[key] = info.props[key];
|
|
429
471
|
return tObject(props, { additionalProperties: info.additionalProperties }) as TObject<Pick<P, K[number]>>;
|
|
430
472
|
}
|
|
431
473
|
|
|
432
|
-
function tOmit<P extends Record<string,
|
|
474
|
+
function tOmit<P extends Record<string, AnySchema>, const K extends readonly (keyof P)[]>(
|
|
433
475
|
schema: TObject<P>,
|
|
434
476
|
keys: K,
|
|
435
477
|
): TObject<Omit<P, K[number]>> {
|
|
436
478
|
const info = requireObject(schema, "Omit");
|
|
437
479
|
const omitted = new Set<PropertyKey>(keys);
|
|
438
|
-
const props: Record<string,
|
|
480
|
+
const props: Record<string, AnySchema> = {};
|
|
439
481
|
for (const key in info.props) if (!omitted.has(key)) props[key] = info.props[key];
|
|
440
482
|
return tObject(props, { additionalProperties: info.additionalProperties }) as TObject<Omit<P, K[number]>>;
|
|
441
483
|
}
|
|
442
484
|
|
|
443
|
-
function tComposite<const E extends readonly TObject<Record<string,
|
|
485
|
+
function tComposite<const E extends readonly TObject<Record<string, AnySchema>>[]>(
|
|
444
486
|
schemas: E,
|
|
445
487
|
opts?: ObjectOpts,
|
|
446
|
-
):
|
|
447
|
-
const props: Record<string,
|
|
488
|
+
): TTyped<UnionToIntersection<Static<E[number]>>> {
|
|
489
|
+
const props: Record<string, AnySchema> = {};
|
|
448
490
|
for (const schema of schemas) Object.assign(props, requireObject(schema, "Composite").props);
|
|
449
|
-
return
|
|
491
|
+
return asSchema<UnionToIntersection<Static<E[number]>>>(tObject(props, opts));
|
|
450
492
|
}
|
|
451
493
|
|
|
452
494
|
function tUnsafe<T = unknown>(_jsonSchema: Record<string, unknown> = {}): TUnsafe<T> {
|
|
@@ -459,10 +501,10 @@ export const Type = {
|
|
|
459
501
|
String: tString,
|
|
460
502
|
Number: (opts?: NumberOpts) => tNumber(opts),
|
|
461
503
|
Integer: (opts?: NumberOpts) => tNumber(opts, true),
|
|
462
|
-
Boolean: (opts?: Meta) => applyMeta(type.raw("boolean")
|
|
463
|
-
Null: (opts?: Meta) => applyMeta(type.raw("null")
|
|
464
|
-
Any: (opts?: Meta) => applyMeta(type.raw("unknown")
|
|
465
|
-
Unknown: (opts?: Meta) => applyMeta(type.raw("unknown")
|
|
504
|
+
Boolean: (opts?: Meta) => applyMeta(asRuntime<boolean>(type.raw("boolean")), opts),
|
|
505
|
+
Null: (opts?: Meta) => applyMeta(asRuntime<null>(type.raw("null")), opts),
|
|
506
|
+
Any: (opts?: Meta) => applyMeta(asRuntime<unknown>(type.raw("unknown")), opts),
|
|
507
|
+
Unknown: (opts?: Meta) => applyMeta(asRuntime<unknown>(type.raw("unknown")), opts),
|
|
466
508
|
Never: tNever,
|
|
467
509
|
Literal: tLiteral,
|
|
468
510
|
Union: tUnion,
|
|
@@ -474,7 +516,8 @@ export const Type = {
|
|
|
474
516
|
Record: tRecord,
|
|
475
517
|
Optional: tOptional,
|
|
476
518
|
Nullable: tNullable,
|
|
477
|
-
Readonly: <E extends
|
|
519
|
+
Readonly: <E extends AnySchema>(schema: E): TReadonly<E> =>
|
|
520
|
+
asSchema<Readonly<Static<E>>>(withLegacyCompat(asRuntime<Readonly<Static<E>>>(schema))),
|
|
478
521
|
Partial: tPartial,
|
|
479
522
|
Required: tRequired,
|
|
480
523
|
Pick: tPick,
|
package/src/zod.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { OmpTypeError } from "./errors";
|
|
1
|
+
import { type OmpErrors, OmpTypeError } from "./errors";
|
|
2
2
|
import { type EmbeddableSchema, embed, type IR, IR_BRAND, type PropIR } from "./ir";
|
|
3
|
-
import { type
|
|
3
|
+
import { type NarrowContext, type Type, type } from "./type";
|
|
4
4
|
|
|
5
5
|
interface OptionalSchemaMarker {
|
|
6
6
|
readonly _optional: true;
|
|
@@ -11,6 +11,15 @@ interface RefineOptions {
|
|
|
11
11
|
error?: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
interface Decoratable<out Out> extends EmbeddableSchema {
|
|
15
|
+
(value: unknown): Out | OmpErrors;
|
|
16
|
+
narrow(predicate: (value: Out, context: NarrowContext) => unknown): Decoratable<Out>;
|
|
17
|
+
pipe<Next>(transform: (value: Out, context: NarrowContext) => Next): Decoratable<Exclude<Next, OmpErrors>>;
|
|
18
|
+
or(def: unknown): Decoratable<unknown>;
|
|
19
|
+
describe(description: string): Decoratable<Out>;
|
|
20
|
+
default(value: Out | (() => Out)): Decoratable<Out>;
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
export interface ZodLikeIssue {
|
|
15
24
|
path: PropertyKey[];
|
|
16
25
|
message: string;
|
|
@@ -21,7 +30,7 @@ export type ZodLikeSafeParseResult<Out> =
|
|
|
21
30
|
| { success: false; error: { message: string; issues: ZodLikeIssue[] } };
|
|
22
31
|
|
|
23
32
|
/** A callable omptype schema carrying the Zod-v4-style fluent surface. */
|
|
24
|
-
export interface ZodLikeSchema<out Out> extends Type<Out> {
|
|
33
|
+
export interface ZodLikeSchema<out Out> extends Type<Out, unknown> {
|
|
25
34
|
readonly _output: Out;
|
|
26
35
|
/** @internal Used while composing object property IR. */
|
|
27
36
|
readonly isOptional: boolean;
|
|
@@ -47,7 +56,7 @@ export interface ZodLikeSchema<out Out> extends Type<Out> {
|
|
|
47
56
|
partial(): Out extends object ? ZodLikeSchema<Partial<Out>> : ZodLikeSchema<Out>;
|
|
48
57
|
}
|
|
49
58
|
|
|
50
|
-
function schemaFromIR<Out>(ir: IR):
|
|
59
|
+
function schemaFromIR<Out>(ir: IR): Decoratable<Out> {
|
|
51
60
|
const embedded: EmbeddableSchema = {
|
|
52
61
|
[IR_BRAND]: true,
|
|
53
62
|
ir,
|
|
@@ -55,23 +64,19 @@ function schemaFromIR<Out>(ir: IR): FluentType<Out> {
|
|
|
55
64
|
hasDefault: false,
|
|
56
65
|
run: value => value,
|
|
57
66
|
};
|
|
58
|
-
return type.raw(embedded) as
|
|
67
|
+
return type.raw(embedded) as unknown as Decoratable<Out>;
|
|
59
68
|
}
|
|
60
69
|
|
|
61
|
-
function
|
|
62
|
-
let next =
|
|
63
|
-
|
|
70
|
+
function restrictBase<Out>(source: Decoratable<Out>, ir: IR): Decoratable<Out> {
|
|
71
|
+
let next = source.hasSteps
|
|
72
|
+
? schemaFromIR<Out>({ k: "morph", input: ir, fn: value => source(value) })
|
|
73
|
+
: schemaFromIR<Out>(ir);
|
|
74
|
+
if (source.ir.desc !== undefined) next = next.describe(source.ir.desc);
|
|
64
75
|
if (source.hasDefault) next = next.default(source.defaultValue as Out | (() => Out));
|
|
65
76
|
return next;
|
|
66
77
|
}
|
|
67
78
|
|
|
68
|
-
function
|
|
69
|
-
let next = schemaFromIR<Out>(ir);
|
|
70
|
-
if (source.hasSteps) next = next.pipe(value => source(value)) as FluentType<Out>;
|
|
71
|
-
return withMeta(source, next);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function lengthBound(kind: "min" | "max", schema: FluentType<unknown>, bound: number): void {
|
|
79
|
+
function lengthBound(kind: "min" | "max", schema: Decoratable<unknown>, bound: number): void {
|
|
75
80
|
if (schema.ir.k !== "string" && schema.ir.k !== "array") return;
|
|
76
81
|
if (!Number.isSafeInteger(bound) || bound < 0) {
|
|
77
82
|
throw new OmpTypeError(`${kind} length must be a nonnegative safe integer`);
|
|
@@ -98,8 +103,8 @@ function isStringKeyIR(ir: IR): boolean {
|
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
|
|
101
|
-
function decorate<Out>(schema:
|
|
102
|
-
const next = (inner:
|
|
106
|
+
function decorate<Out>(schema: Decoratable<Out>, optional = false): ZodLikeSchema<Out> {
|
|
107
|
+
const next = (inner: Decoratable<Out>, nextOptional = optional): ZodLikeSchema<Out> => decorate(inner, nextOptional);
|
|
103
108
|
const withObjectExtras = (extras: "keep" | "reject" | "delete"): ZodLikeSchema<Out> => {
|
|
104
109
|
if (schema.ir.k !== "object") throw new OmpTypeError("object mode requires an object schema");
|
|
105
110
|
return next(restrictBase(schema, { ...schema.ir, extras }));
|
|
@@ -181,21 +186,21 @@ function decorate<Out>(schema: FluentType<Out>, optional = false): ZodLikeSchema
|
|
|
181
186
|
return next(restrictBase(schema, { ...schema.ir, url: true }));
|
|
182
187
|
},
|
|
183
188
|
optional(): ZodLikeSchema<Out | undefined> & OptionalSchemaMarker {
|
|
184
|
-
const widened = schema.or(type.raw("undefined")) as
|
|
189
|
+
const widened = schema.or(type.raw("undefined")) as Decoratable<Out | undefined>;
|
|
185
190
|
return decorate(widened, true) as ZodLikeSchema<Out | undefined> & OptionalSchemaMarker;
|
|
186
191
|
},
|
|
187
192
|
nullable(): ZodLikeSchema<Out | null> {
|
|
188
|
-
return decorate(schema.or(type.raw("null")) as
|
|
193
|
+
return decorate(schema.or(type.raw("null")) as Decoratable<Out | null>, optional);
|
|
189
194
|
},
|
|
190
195
|
default(
|
|
191
196
|
value: Exclude<Out, undefined> | (() => Exclude<Out, undefined>),
|
|
192
197
|
): ZodLikeSchema<Exclude<Out, undefined>> {
|
|
193
198
|
type DefaultOut = Exclude<Out, undefined>;
|
|
194
|
-
const widened = schema.or(type.raw("undefined")) as
|
|
199
|
+
const widened = schema.or(type.raw("undefined")) as Decoratable<Out | undefined>;
|
|
195
200
|
const piped = widened.pipe(output => {
|
|
196
201
|
if (output !== undefined) return output as DefaultOut;
|
|
197
202
|
return typeof value === "function" ? (value as () => DefaultOut)() : value;
|
|
198
|
-
}) as
|
|
203
|
+
}) as Decoratable<DefaultOut>;
|
|
199
204
|
return decorate(piped.default(value as DefaultOut | (() => DefaultOut)));
|
|
200
205
|
},
|
|
201
206
|
describe(description: string): ZodLikeSchema<Out> {
|
|
@@ -221,7 +226,7 @@ function decorate<Out>(schema: FluentType<Out>, optional = false): ZodLikeSchema
|
|
|
221
226
|
}
|
|
222
227
|
return typeof fallback === "function" ? (fallback as () => Out)() : fallback;
|
|
223
228
|
});
|
|
224
|
-
return decorate(caught as
|
|
229
|
+
return decorate(caught as Decoratable<Out>, optional);
|
|
225
230
|
},
|
|
226
231
|
strict(): ZodLikeSchema<Out> {
|
|
227
232
|
return withObjectExtras("reject");
|
|
@@ -242,6 +247,10 @@ function decorate<Out>(schema: FluentType<Out>, optional = false): ZodLikeSchema
|
|
|
242
247
|
}) as unknown as ZodLikeSchema<Out>;
|
|
243
248
|
}
|
|
244
249
|
|
|
250
|
+
function decorateUnknown(schema: Decoratable<unknown>): ZodLikeSchema<unknown> {
|
|
251
|
+
return decorate(schema);
|
|
252
|
+
}
|
|
253
|
+
|
|
245
254
|
export type infer<T> = T extends { readonly _output: infer Out } ? Out : never;
|
|
246
255
|
|
|
247
256
|
type SchemaOutput<Schema> = Schema extends { readonly _output: infer Out } ? Out : never;
|
|
@@ -254,7 +263,7 @@ type ObjectOutput<S extends Shape> = {
|
|
|
254
263
|
type Simplify<T> = { [K in keyof T]: T[K] };
|
|
255
264
|
type UnionOutput<Schemas extends readonly ZodLikeSchema<unknown>[]> = SchemaOutput<Schemas[number]>;
|
|
256
265
|
|
|
257
|
-
function objectSchema<const S extends Shape>(shape: S): ZodLikeSchema<
|
|
266
|
+
function objectSchema<const S extends Shape>(shape: S): ZodLikeSchema<ObjectOutput<S>> {
|
|
258
267
|
const props: PropIR[] = [];
|
|
259
268
|
for (const key in shape) {
|
|
260
269
|
const member = shape[key];
|
|
@@ -266,18 +275,21 @@ function objectSchema<const S extends Shape>(shape: S): ZodLikeSchema<Simplify<O
|
|
|
266
275
|
}
|
|
267
276
|
props.push(prop);
|
|
268
277
|
}
|
|
269
|
-
return
|
|
278
|
+
return decorateUnknown(schemaFromIR<unknown>({ k: "object", props, extras: "delete" })) as unknown as ZodLikeSchema<
|
|
279
|
+
ObjectOutput<S>
|
|
280
|
+
>;
|
|
270
281
|
}
|
|
271
282
|
|
|
272
283
|
export const string = (): ZodLikeSchema<string> => decorate(schemaFromIR(type.string.ir));
|
|
273
284
|
export const number = (): ZodLikeSchema<number> => decorate(schemaFromIR(type.number.ir));
|
|
274
285
|
export const boolean = (): ZodLikeSchema<boolean> => decorate(schemaFromIR(type.boolean.ir));
|
|
275
|
-
export const literal = <const Value>(value: Value): ZodLikeSchema<Value> =>
|
|
286
|
+
export const literal = <const Value>(value: Value): ZodLikeSchema<Value> =>
|
|
287
|
+
decorate(schemaFromIR<Value>(type.enumerated(value).ir));
|
|
276
288
|
const enumSchema = <const Values extends readonly [string, ...string[]]>(
|
|
277
289
|
values: Values,
|
|
278
290
|
): ZodLikeSchema<Values[number]> => {
|
|
279
291
|
if (values.length === 0) throw new OmpTypeError("enum requires at least one value");
|
|
280
|
-
return decorate(type.enumerated(...values));
|
|
292
|
+
return decorate(schemaFromIR<Values[number]>(type.enumerated(...values).ir));
|
|
281
293
|
};
|
|
282
294
|
|
|
283
295
|
export { enumSchema as enum };
|
|
@@ -312,8 +324,9 @@ export const record = <Key extends string, Value>(
|
|
|
312
324
|
};
|
|
313
325
|
export const unknown = (): ZodLikeSchema<unknown> => decorate(schemaFromIR(type.unknown.ir));
|
|
314
326
|
export const any = (): ZodLikeSchema<unknown> => decorate(schemaFromIR(type.unknown.ir));
|
|
315
|
-
const nullSchema = (): ZodLikeSchema<null> => decorate(type.raw("null") as
|
|
316
|
-
const undefinedSchema = (): ZodLikeSchema<undefined> =>
|
|
327
|
+
const nullSchema = (): ZodLikeSchema<null> => decorate(type.raw("null") as unknown as Decoratable<null>);
|
|
328
|
+
const undefinedSchema = (): ZodLikeSchema<undefined> =>
|
|
329
|
+
decorate(type.raw("undefined") as unknown as Decoratable<undefined>);
|
|
317
330
|
|
|
318
331
|
export { nullSchema as null, undefinedSchema as undefined };
|
|
319
332
|
|