@buckeyestudio/omptype 18.4.13

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/src/typebox.ts ADDED
@@ -0,0 +1,545 @@
1
+ import { OmpTypeError } from "./errors";
2
+ import type { Def, EmbeddableSchema } from "./ir";
3
+ import { type NarrowContext, type Type as OmpType, type ToJsonSchemaOptions, type } from "./type";
4
+
5
+ export interface Meta {
6
+ title?: string;
7
+ description?: string;
8
+ default?: unknown;
9
+ examples?: unknown[];
10
+ [key: string]: unknown;
11
+ }
12
+
13
+ export interface StringOpts extends Meta {
14
+ minLength?: number;
15
+ maxLength?: number;
16
+ pattern?: string;
17
+ format?: string;
18
+ }
19
+
20
+ export interface NumberOpts extends Meta {
21
+ minimum?: number;
22
+ maximum?: number;
23
+ exclusiveMinimum?: number;
24
+ exclusiveMaximum?: number;
25
+ multipleOf?: number;
26
+ }
27
+
28
+ export interface ArrayOpts extends Meta {
29
+ minItems?: number;
30
+ maxItems?: number;
31
+ uniqueItems?: boolean;
32
+ }
33
+
34
+ export interface ObjectOpts extends Meta {
35
+ additionalProperties?: boolean | AnySchema;
36
+ }
37
+ const OPTIONAL_INNER = Symbol("omptype.typebox.optionalInner");
38
+ const OBJECT_INFO = Symbol("omptype.typebox.objectInfo");
39
+
40
+ export interface TypeBoxValidationFailure {
41
+ message: string;
42
+ }
43
+
44
+ export type TypeBoxSafeParseResult<T> =
45
+ | { success: true; data: T }
46
+ | { success: false; error: TypeBoxValidationFailure };
47
+
48
+ interface LegacyTypeBoxCompat<T> {
49
+ /** TypeBox compatibility validator used by legacy extension loaders. */
50
+ __validator(data: unknown): T | TypeBoxValidationFailure;
51
+ /** Zod-style compatibility parser used by legacy extensions. */
52
+ safeParse(input: unknown): TypeBoxSafeParseResult<T>;
53
+ }
54
+
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<{
97
+ -readonly [K in keyof E]: Static<E[K]>;
98
+ }>;
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<
102
+ UnionToIntersection<Static<E[number]>>
103
+ >;
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;
114
+ }[keyof P];
115
+ type RequiredKeys<P extends Record<string, AnySchema>> = Exclude<keyof P, OptionalKeys<P>>;
116
+ type ObjectStatic<P extends Record<string, AnySchema>> = {
117
+ [K in RequiredKeys<P>]: Static<P[K]>;
118
+ } & {
119
+ [K in OptionalKeys<P>]?: Exclude<Static<P[K]>, undefined>;
120
+ };
121
+ export type TObject<P extends Record<string, AnySchema> = Record<string, AnySchema>> = TTyped<ObjectStatic<P>>;
122
+ type RequiredProps<P extends Record<string, AnySchema>> = {
123
+ [K in keyof P]: P[K] extends TOptional<infer E> ? E : P[K];
124
+ };
125
+
126
+ interface RuntimeType<T> extends OmpType<T> {
127
+ [OPTIONAL_INNER]?: AnySchema;
128
+ [OBJECT_INFO]?: ObjectInfo;
129
+ describe(description: string): RuntimeType<T>;
130
+ default(value: T | (() => T)): RuntimeType<T>;
131
+ or<schema extends AnySchema>(schema: schema): RuntimeType<T | Static<schema>>;
132
+ and<schema extends AnySchema>(schema: schema): RuntimeType<T & Static<schema>>;
133
+ array(): RuntimeType<T[]>;
134
+ atLeastLength(bound: number): RuntimeType<T>;
135
+ atMostLength(bound: number): RuntimeType<T>;
136
+ atLeast(bound: number): RuntimeType<T>;
137
+ atMost(bound: number): RuntimeType<T>;
138
+ narrow<N extends T>(predicate: (value: T, ctx: NarrowContext) => value is N): RuntimeType<N>;
139
+ narrow(predicate: (value: T, ctx: NarrowContext) => boolean): RuntimeType<T>;
140
+ }
141
+ type CompatRuntime<T> = RuntimeType<T> & LegacyTypeBoxCompat<T>;
142
+
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
+ }
155
+
156
+ function validationFailure(message: string): TypeBoxValidationFailure {
157
+ return { message };
158
+ }
159
+
160
+ function withLegacyCompat<T>(schema: OmpType<T>): CompatRuntime<T> {
161
+ const compatSchema = schema as unknown as CompatRuntime<T>;
162
+ if (!Object.hasOwn(compatSchema, "__validator")) {
163
+ Object.defineProperty(compatSchema, "__validator", {
164
+ value: (data: unknown): T | TypeBoxValidationFailure => {
165
+ const result = schema(data);
166
+ return result instanceof type.errors ? validationFailure(result.summary) : result;
167
+ },
168
+ configurable: true,
169
+ });
170
+ }
171
+ if (!Object.hasOwn(compatSchema, "safeParse")) {
172
+ Object.defineProperty(compatSchema, "safeParse", {
173
+ value: (input: unknown): TypeBoxSafeParseResult<T> => {
174
+ const result = schema(input);
175
+ return result instanceof type.errors
176
+ ? { success: false, error: validationFailure(result.summary) }
177
+ : { success: true, data: result };
178
+ },
179
+ configurable: true,
180
+ });
181
+ }
182
+ return compatSchema;
183
+ }
184
+
185
+ function applyMeta<T>(schema: RuntimeType<T>, opts?: Meta): CompatRuntime<T> {
186
+ let result = schema;
187
+ const description = opts?.description ?? opts?.title;
188
+ if (description !== undefined) result = result.describe(description);
189
+ if (opts && Object.hasOwn(opts, "default")) result = result.default(opts.default as T);
190
+ return withLegacyCompat(result);
191
+ }
192
+
193
+ function withJsonSchemaKeywords<T>(schema: CompatRuntime<T>, keywords: Record<string, unknown>): CompatRuntime<T> {
194
+ const emitBase = schema.toJsonSchema.bind(schema);
195
+ schema.toJsonSchema = options => ({ ...emitBase(options), ...keywords });
196
+ return schema;
197
+ }
198
+
199
+ function checkFiniteOption(name: string, value: number | undefined): void {
200
+ if (value !== undefined && !Number.isFinite(value)) throw new OmpTypeError(`${name} must be finite`);
201
+ }
202
+
203
+ function tString(opts?: StringOpts): TString {
204
+ checkFiniteOption("minLength", opts?.minLength);
205
+ checkFiniteOption("maxLength", opts?.maxLength);
206
+ let schema = asRuntime<string>(type.raw(opts?.format === "url" || opts?.format === "uri" ? "string.url" : "string"));
207
+ if (opts?.minLength !== undefined) schema = schema.atLeastLength(opts.minLength);
208
+ if (opts?.maxLength !== undefined) schema = schema.atMostLength(opts.maxLength);
209
+ if (opts?.pattern !== undefined) {
210
+ let regex: RegExp;
211
+ try {
212
+ regex = new RegExp(opts.pattern);
213
+ } catch {
214
+ throw new OmpTypeError(`invalid regular expression pattern ${JSON.stringify(opts.pattern)}`);
215
+ }
216
+ schema = schema.narrow((value, ctx) => regex.test(value) || ctx.mustBe(`a string matching ${opts.pattern}`));
217
+ }
218
+ if (opts?.format !== undefined && opts.format !== "url" && opts.format !== "uri") {
219
+ const format = opts.format;
220
+ const valid = formatPredicate(format);
221
+ schema = schema.narrow((value, ctx) => valid(value) || ctx.mustBe(`a string in ${format} format`));
222
+ }
223
+ const result = applyMeta(schema, opts);
224
+ const keywords: Record<string, unknown> = {};
225
+ if (opts?.pattern !== undefined) keywords.pattern = opts.pattern;
226
+ if (opts?.format !== undefined) keywords.format = opts.format === "url" ? "uri" : opts.format;
227
+ return opts?.pattern !== undefined || opts?.format !== undefined ? withJsonSchemaKeywords(result, keywords) : result;
228
+ }
229
+
230
+ function formatPredicate(format: string): (value: string) => boolean {
231
+ switch (format) {
232
+ case "url":
233
+ case "uri":
234
+ return value => {
235
+ try {
236
+ new URL(value);
237
+ return true;
238
+ } catch {
239
+ return false;
240
+ }
241
+ };
242
+ case "email":
243
+ return value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
244
+ case "uuid":
245
+ return value => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
246
+ case "date-time":
247
+ return value =>
248
+ /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(value) &&
249
+ !Number.isNaN(Date.parse(value));
250
+ case "date":
251
+ return value => /^\d{4}-\d\d-\d\d$/.test(value) && !Number.isNaN(Date.parse(`${value}T00:00:00Z`));
252
+ default:
253
+ return () => true;
254
+ }
255
+ }
256
+
257
+ function tNumber(opts?: NumberOpts, integer = false): TNumber {
258
+ for (const key of ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"] as const) {
259
+ checkFiniteOption(key, opts?.[key]);
260
+ }
261
+ if (opts?.multipleOf !== undefined && opts.multipleOf <= 0)
262
+ throw new OmpTypeError("multipleOf must be greater than zero");
263
+ let lower: { value: number; exclusive: boolean } | undefined;
264
+ if (opts?.minimum !== undefined) lower = { value: opts.minimum, exclusive: false };
265
+ if (opts?.exclusiveMinimum !== undefined && (!lower || opts.exclusiveMinimum >= lower.value)) {
266
+ lower = { value: opts.exclusiveMinimum, exclusive: true };
267
+ }
268
+ let upper: { value: number; exclusive: boolean } | undefined;
269
+ if (opts?.maximum !== undefined) upper = { value: opts.maximum, exclusive: false };
270
+ if (opts?.exclusiveMaximum !== undefined && (!upper || opts.exclusiveMaximum <= upper.value)) {
271
+ upper = { value: opts.exclusiveMaximum, exclusive: true };
272
+ }
273
+ const keyword = integer ? "number.integer" : "number";
274
+ // The `LO <= TYPE <= HI` range spelling requires both bounds; a min-only
275
+ // bound must use the postfix `TYPE >= LO` form (see parseBounded in ir.ts).
276
+ let src: string;
277
+ if (lower && upper) {
278
+ src = `${lower.value} ${lower.exclusive ? "<" : "<="} ${keyword} ${upper.exclusive ? "<" : "<="} ${upper.value}`;
279
+ } else if (lower) {
280
+ src = `${keyword} ${lower.exclusive ? ">" : ">="} ${lower.value}`;
281
+ } else if (upper) {
282
+ src = `${keyword} ${upper.exclusive ? "<" : "<="} ${upper.value}`;
283
+ } else {
284
+ src = keyword;
285
+ }
286
+ let schema = asRuntime<number>(type.raw(src));
287
+ if (opts?.multipleOf !== undefined) {
288
+ const divisor = opts.multipleOf;
289
+ schema = schema.narrow((value, ctx) => {
290
+ const quotient = value / divisor;
291
+ return (
292
+ Math.abs(quotient - Math.round(quotient)) <= Number.EPSILON * Math.max(1, Math.abs(quotient)) ||
293
+ ctx.mustBe(`a multiple of ${divisor}`)
294
+ );
295
+ });
296
+ }
297
+ const result = applyMeta(schema, opts);
298
+ return opts?.multipleOf !== undefined ? withJsonSchemaKeywords(result, { multipleOf: opts.multipleOf }) : result;
299
+ }
300
+
301
+ function tLiteral<const V extends string | number | boolean | null>(value: V, opts?: Meta): TLiteral<V> {
302
+ return applyMeta(asRuntime<V>(type.enumerated(value)), opts);
303
+ }
304
+
305
+ function tNever(opts?: Meta): TNever {
306
+ return applyMeta(
307
+ asRuntime<unknown>(type.raw("unknown")).narrow((_value, ctx): _value is never => ctx.mustBe("never")),
308
+ opts,
309
+ );
310
+ }
311
+
312
+ function tUnion<const E extends readonly AnySchema[]>(schemas: E, opts?: Meta): TUnion<E> {
313
+ if (schemas.length === 0) return asSchema<Static<E[number]>>(tNever(opts));
314
+ let result = asRuntime<unknown>(schemas[0]);
315
+ for (let i = 1; i < schemas.length; i++) result = result.or(schemas[i]);
316
+ return asSchema<Static<E[number]>>(applyMeta(result, opts));
317
+ }
318
+
319
+ function tIntersect<const E extends readonly AnySchema[]>(
320
+ schemas: E,
321
+ opts?: Meta,
322
+ ): TTyped<UnionToIntersection<Static<E[number]>>> {
323
+ if (schemas.length === 0) {
324
+ return applyMeta(asRuntime<UnionToIntersection<Static<E[number]>>>(type.raw("unknown")), opts);
325
+ }
326
+ const validateAll = (): RuntimeType<UnionToIntersection<Static<E[number]>>> => {
327
+ const base = asRuntime<unknown>(type.raw("unknown"));
328
+ return base.narrow((value, ctx): value is UnionToIntersection<Static<E[number]>> => {
329
+ for (const schema of schemas) {
330
+ if (schema(value) instanceof type.errors) return ctx.mustBe("a value satisfying every intersection member");
331
+ }
332
+ return true;
333
+ });
334
+ };
335
+ if (schemas.some(schema => schema.hasSteps)) return applyMeta(validateAll(), opts);
336
+ let result = asRuntime<unknown>(schemas[0]);
337
+ try {
338
+ for (let i = 1; i < schemas.length; i++) result = result.and(schemas[i]);
339
+ } catch (error) {
340
+ if (error instanceof OmpTypeError) return applyMeta(validateAll(), opts);
341
+ throw error;
342
+ }
343
+ return asSchema<UnionToIntersection<Static<E[number]>>>(applyMeta(result, opts));
344
+ }
345
+ type UnionToIntersection<U> = (U extends unknown ? (value: U) => void : never) extends (value: infer I) => void
346
+ ? I
347
+ : never;
348
+
349
+ function enumValues(values: Record<string, string | number> | readonly (string | number)[]): (string | number)[] {
350
+ if (Array.isArray(values)) return [...values];
351
+ const result: (string | number)[] = [];
352
+ const record = values as Record<string, string | number>;
353
+ for (const key in record) {
354
+ const value = record[key];
355
+ if (!(/^\d+$/.test(key) && typeof value === "string") && !result.includes(value)) result.push(value);
356
+ }
357
+ return result;
358
+ }
359
+
360
+ function tEnum<const E extends Record<string, string | number> | readonly (string | number)[]>(
361
+ values: E,
362
+ opts?: Meta,
363
+ ): TTyped<E extends readonly (infer V)[] ? V : E[keyof E]> {
364
+ return applyMeta(
365
+ asRuntime<E extends readonly (infer V)[] ? V : E[keyof E]>(type.enumerated(...enumValues(values))),
366
+ opts,
367
+ );
368
+ }
369
+
370
+ function tArray<E extends AnySchema>(item: E, opts?: ArrayOpts): TArray<E> {
371
+ checkFiniteOption("minItems", opts?.minItems);
372
+ checkFiniteOption("maxItems", opts?.maxItems);
373
+ let schema = asRuntime<Static<E>>(item).array();
374
+ if (opts?.minItems !== undefined) schema = schema.atLeastLength(opts.minItems);
375
+ if (opts?.maxItems !== undefined) schema = schema.atMostLength(opts.maxItems);
376
+ if (opts?.uniqueItems) {
377
+ schema = schema.narrow((values, ctx) => {
378
+ for (let i = 0; i < values.length; i++) {
379
+ for (let j = i + 1; j < values.length; j++) {
380
+ if (jsonEqual(values[i], values[j])) return ctx.mustBe("an array with unique items");
381
+ }
382
+ }
383
+ return true;
384
+ });
385
+ }
386
+ const result = applyMeta(schema, opts);
387
+ return opts?.uniqueItems ? withJsonSchemaKeywords(result, { uniqueItems: true }) : result;
388
+ }
389
+
390
+ function jsonEqual(left: unknown, right: unknown): boolean {
391
+ if (Object.is(left, right)) return true;
392
+ if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) return false;
393
+ try {
394
+ return JSON.stringify(left) === JSON.stringify(right);
395
+ } catch {
396
+ return false;
397
+ }
398
+ }
399
+
400
+ function tTuple<const E extends readonly AnySchema[]>(items: E, opts?: Meta): TTuple<E> {
401
+ const schema = asRuntime<unknown>(type.raw("unknown")).narrow(
402
+ (value, ctx): value is { -readonly [K in keyof E]: Static<E[K]> } => {
403
+ if (!Array.isArray(value) || value.length !== items.length)
404
+ return ctx.mustBe(`a tuple of length ${items.length}`);
405
+ for (let i = 0; i < items.length; i++)
406
+ if (items[i](value[i]) instanceof type.errors) return ctx.mustBe(`a valid item at index ${i}`);
407
+ return true;
408
+ },
409
+ );
410
+ return applyMeta(schema, opts);
411
+ }
412
+
413
+ function tObject<const P extends Record<string, AnySchema>>(properties: P, opts?: ObjectOpts): TObject<P> {
414
+ const def: Record<string, Def> = {};
415
+ const props: Record<string, AnySchema> = {};
416
+ for (const key in properties) {
417
+ const schema = properties[key];
418
+ const inner = asRuntime<unknown>(schema)[OPTIONAL_INNER];
419
+ // A defaulted `Type.Optional(...)` maps to a plain defaulted key:
420
+ // omptype (like ArkType) rejects `key?` with a default, and a default
421
+ // already makes the key omittable on input.
422
+ const optionalKey = inner !== undefined && !asRuntime<unknown>(inner).hasDefault;
423
+ def[optionalKey ? `${key}?` : key] = inner ?? schema;
424
+ props[key] = schema;
425
+ }
426
+ if (opts?.additionalProperties === false) def["+"] = "reject";
427
+ else if (opts?.additionalProperties && opts.additionalProperties !== true)
428
+ def["[string]"] = opts.additionalProperties;
429
+ const schema = applyMeta(asRuntime<ObjectStatic<P>>(type.raw(def)), opts);
430
+ schema[OBJECT_INFO] = { props, additionalProperties: opts?.additionalProperties };
431
+ return schema;
432
+ }
433
+
434
+ function tRecord<K extends AnySchema, V extends AnySchema>(key: K, value: V, opts?: Meta): TRecord<K, V> {
435
+ const base = asRuntime<Record<string, Static<V>>>(type.raw({ "[string]": value })).narrow((record, ctx) => {
436
+ for (const name in record)
437
+ if (key(name) instanceof type.errors) return ctx.mustBe("an object with valid record keys");
438
+ return true;
439
+ });
440
+ return applyMeta(base, opts) as TRecord<K, V>;
441
+ }
442
+
443
+ function tOptional<E extends AnySchema>(schema: E, opts?: Meta): TOptional<E> {
444
+ const marker = applyMeta(
445
+ asRuntime<Static<E>>(schema).or(asRuntime<undefined>(type.raw("undefined"))),
446
+ opts,
447
+ ) as RuntimeType<Static<E> | undefined>;
448
+ marker[OPTIONAL_INNER] = schema;
449
+ return marker as unknown as TOptional<E>;
450
+ }
451
+
452
+ function tNullable<E extends AnySchema>(schema: E, opts?: Meta): TTyped<Static<E> | null> {
453
+ return applyMeta(asRuntime<Static<E>>(schema).or(asRuntime<null>(type.raw("null"))), opts);
454
+ }
455
+
456
+ function requireObject(schema: AnySchema, operation: string): ObjectInfo {
457
+ const info = asRuntime<unknown>(schema)[OBJECT_INFO];
458
+ if (!info) throw new OmpTypeError(`Type.${operation} requires a schema created by Type.Object`);
459
+ return info;
460
+ }
461
+
462
+ function tPartial<P extends Record<string, AnySchema>>(schema: TObject<P>): TTyped<Partial<ObjectStatic<P>>> {
463
+ const info = requireObject(schema, "Partial");
464
+ const props: Record<string, AnySchema> = {};
465
+ for (const key in info.props)
466
+ props[key] = asRuntime<unknown>(info.props[key])[OPTIONAL_INNER] ? info.props[key] : tOptional(info.props[key]);
467
+ return tObject(props, { additionalProperties: info.additionalProperties }) as TTyped<Partial<ObjectStatic<P>>>;
468
+ }
469
+
470
+ function tRequired<P extends Record<string, AnySchema>>(schema: TObject<P>): TObject<RequiredProps<P>> {
471
+ const info = requireObject(schema, "Required");
472
+ const props: Record<string, AnySchema> = {};
473
+ for (const key in info.props) {
474
+ props[key] = asRuntime<unknown>(info.props[key])[OPTIONAL_INNER] ?? info.props[key];
475
+ }
476
+ return tObject(props, { additionalProperties: info.additionalProperties }) as TObject<RequiredProps<P>>;
477
+ }
478
+
479
+ function tPick<P extends Record<string, AnySchema>, const K extends readonly (keyof P)[]>(
480
+ schema: TObject<P>,
481
+ keys: K,
482
+ ): TObject<Pick<P, K[number]>> {
483
+ const info = requireObject(schema, "Pick");
484
+ const props: Record<string, AnySchema> = {};
485
+ for (const key of keys) if (typeof key === "string" && info.props[key]) props[key] = info.props[key];
486
+ return tObject(props, { additionalProperties: info.additionalProperties }) as TObject<Pick<P, K[number]>>;
487
+ }
488
+
489
+ function tOmit<P extends Record<string, AnySchema>, const K extends readonly (keyof P)[]>(
490
+ schema: TObject<P>,
491
+ keys: K,
492
+ ): TObject<Omit<P, K[number]>> {
493
+ const info = requireObject(schema, "Omit");
494
+ const omitted = new Set<PropertyKey>(keys);
495
+ const props: Record<string, AnySchema> = {};
496
+ for (const key in info.props) if (!omitted.has(key)) props[key] = info.props[key];
497
+ return tObject(props, { additionalProperties: info.additionalProperties }) as TObject<Omit<P, K[number]>>;
498
+ }
499
+
500
+ function tComposite<const E extends readonly TObject<Record<string, AnySchema>>[]>(
501
+ schemas: E,
502
+ opts?: ObjectOpts,
503
+ ): TTyped<UnionToIntersection<Static<E[number]>>> {
504
+ const props: Record<string, AnySchema> = {};
505
+ for (const schema of schemas) Object.assign(props, requireObject(schema, "Composite").props);
506
+ return asSchema<UnionToIntersection<Static<E[number]>>>(tObject(props, opts));
507
+ }
508
+
509
+ function tUnsafe<T = unknown>(_jsonSchema: Record<string, unknown> = {}): TUnsafe<T> {
510
+ // Raw JSON Schema is accepted for source compatibility but is not retained or validated:
511
+ // omptype cannot honestly implement that contract without importing a second validator.
512
+ return withLegacyCompat(type.unknown as OmpType<T>);
513
+ }
514
+
515
+ export const Type = {
516
+ String: tString,
517
+ Number: (opts?: NumberOpts) => tNumber(opts),
518
+ Integer: (opts?: NumberOpts) => tNumber(opts, true),
519
+ Boolean: (opts?: Meta) => applyMeta(asRuntime<boolean>(type.raw("boolean")), opts),
520
+ Null: (opts?: Meta) => applyMeta(asRuntime<null>(type.raw("null")), opts),
521
+ Any: (opts?: Meta) => applyMeta(asRuntime<unknown>(type.raw("unknown")), opts),
522
+ Unknown: (opts?: Meta) => applyMeta(asRuntime<unknown>(type.raw("unknown")), opts),
523
+ Never: tNever,
524
+ Literal: tLiteral,
525
+ Union: tUnion,
526
+ Intersect: tIntersect,
527
+ Enum: tEnum,
528
+ Array: tArray,
529
+ Tuple: tTuple,
530
+ Object: tObject,
531
+ Record: tRecord,
532
+ Optional: tOptional,
533
+ Nullable: tNullable,
534
+ Readonly: <E extends AnySchema>(schema: E): TReadonly<E> =>
535
+ asSchema<Readonly<Static<E>>>(withLegacyCompat(asRuntime<Readonly<Static<E>>>(schema))),
536
+ Partial: tPartial,
537
+ Required: tRequired,
538
+ Pick: tPick,
539
+ Omit: tOmit,
540
+ Composite: tComposite,
541
+ Unsafe: tUnsafe,
542
+ } as const;
543
+
544
+ export type TypeBuilder = typeof Type;
545
+ export default { Type };