diff --git a/src/struct.ts b/src/struct.ts index b482fd3b..b598bd77 100644 --- a/src/struct.ts +++ b/src/struct.ts @@ -11,6 +11,7 @@ export class Struct { readonly TYPE!: T type: string schema: S + extend?: any coercer: (value: unknown, context: Context) => unknown validator: (value: unknown, context: Context) => Iterable refiner: (value: T, context: Context) => Iterable @@ -22,6 +23,7 @@ export class Struct { constructor(props: { type: string schema: S + extend?: any coercer?: Coercer validator?: Validator refiner?: Refiner @@ -31,12 +33,14 @@ export class Struct { type, schema, validator, + extend, refiner, coercer = (value: unknown) => value, entries = function* () {}, } = props this.type = type + this.extend = extend this.schema = schema this.entries = entries this.coercer = coercer diff --git a/src/structs/coercions.ts b/src/structs/coercions.ts index 6dca4ece..f578c299 100644 --- a/src/structs/coercions.ts +++ b/src/structs/coercions.ts @@ -42,7 +42,7 @@ export function defaulted( strict?: boolean } = {} ): Struct { - return coerce(struct, unknown(), (x) => { + const result = coerce(struct, unknown(), (x) => { const f = typeof fallback === 'function' ? fallback() : fallback if (x === undefined) { @@ -67,6 +67,10 @@ export function defaulted( return x }) + + result.extend = ["def", fallback]; + + return result; } /** diff --git a/src/structs/types.ts b/src/structs/types.ts index 8bcc0ef3..0682c59e 100644 --- a/src/structs/types.ts +++ b/src/structs/types.ts @@ -142,12 +142,16 @@ export function func(): Struct { export function instance( Class: T -): Struct, null> { - return define('instance', (value) => { - return ( - value instanceof Class || - `Expected a \`${Class.name}\` instance, but received: ${print(value)}` - ) +): Struct, T> { + return new Struct({ + type: 'instance', + schema: Class, + validator: (value) => { + return ( + value instanceof Class || + `Expected a \`${Class.name}\` instance, but received: ${print(value)}` + ) + } }) } @@ -170,10 +174,10 @@ export function integer(): Struct { export function intersection( Structs: [A, ...B] -): Struct & UnionToIntersection[number]>, null> { +): Struct & UnionToIntersection[number]>, [A, ...B]> { return new Struct({ type: 'intersection', - schema: null, + schema: Structs, *entries(value, ctx) { for (const S of Structs) { yield* S.entries(value, ctx) @@ -225,11 +229,11 @@ export function map(): Struct, null> export function map( Key: Struct, Value: Struct -): Struct, null> +): Struct, [Struct, Struct]> export function map(Key?: Struct, Value?: Struct): any { return new Struct({ type: 'map', - schema: null, + schema: [Key, Value], *entries(value) { if (Key && Value && value instanceof Map) { for (const [k, v] of value.entries()) { @@ -265,6 +269,7 @@ export function never(): Struct { export function nullable(struct: Struct): Struct { return new Struct({ ...struct, + extend: ["null"], validator: (value, ctx) => value === null || struct.validator(value, ctx), refiner: (value, ctx) => value === null || struct.refiner(value, ctx), }) @@ -332,6 +337,7 @@ export function object(schema?: S): any { export function optional(struct: Struct): Struct { return new Struct({ ...struct, + extend: ["?"], validator: (value, ctx) => value === undefined || struct.validator(value, ctx), refiner: (value, ctx) => value === undefined || struct.refiner(value, ctx), @@ -348,10 +354,10 @@ export function optional(struct: Struct): Struct { export function record( Key: Struct, Value: Struct -): Struct, null> { +): Struct, [Struct, Struct]> { return new Struct({ type: 'record', - schema: null, + schema: [Key, Value], *entries(value) { if (isObject(value)) { for (const k in value) { @@ -388,11 +394,11 @@ export function regexp(): Struct { */ export function set(): Struct, null> -export function set(Element: Struct): Struct, null> +export function set(Element: Struct): Struct, Struct> export function set(Element?: Struct): any { return new Struct({ type: 'set', - schema: null, + schema: Element, *entries(value) { if (Element && value instanceof Set) { for (const v of value) { @@ -432,12 +438,12 @@ export function string(): Struct { export function tuple( Structs: [A, ...B] -): Struct<[Infer, ...InferStructTuple], null> { +): Struct<[Infer, ...InferStructTuple], [A, ...B]> { const Never = never() return new Struct({ type: 'tuple', - schema: null, + schema: Structs, *entries(value) { if (Array.isArray(value)) { const length = Math.max(Structs.length, value.length) @@ -494,11 +500,11 @@ export function type( export function union( Structs: [A, ...B] -): Struct | InferStructTuple[number], null> { +): Struct | InferStructTuple[number], [A, ...B]> { const description = Structs.map((s) => s.type).join(' | ') return new Struct({ type: 'union', - schema: null, + schema: Structs, coercer(value) { for (const S of Structs) { const [error, coerced] = S.validate(value, { coerce: true }) @@ -543,4 +549,4 @@ export function union( export function unknown(): Struct { return define('unknown', () => true) -} +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 92df9755..27df5c7d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -340,7 +340,7 @@ export type If = B extends true ? Then : Else export type StructSchema = [T] extends [string | undefined | null] ? [T] extends [IsMatch] - ? null + ? any : [T] extends [IsUnion] ? EnumSchema : T @@ -354,6 +354,10 @@ export type StructSchema = [T] extends [string | undefined | null] ? [T] extends [IsExactMatch] ? null : T + : T extends Map + ? [Struct, Struct] + : T extends Set + ? Struct : T extends | bigint | symbol @@ -363,21 +367,19 @@ export type StructSchema = [T] extends [string | undefined | null] | Date | Error | RegExp - | Map | WeakMap - | Set | WeakSet | Promise ? null : T extends Array ? T extends IsTuple - ? null + ? any : Struct : T extends object ? T extends IsRecord - ? null + ? [any, any] : { [K in keyof T]: Describe } - : null + : any /** * A schema for tuple structs.