diff --git a/library/CHANGELOG.md b/library/CHANGELOG.md index 099bc9812..92f1ce498 100644 --- a/library/CHANGELOG.md +++ b/library/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to the library will be documented in this file. ## vX.X.X (Month DD, YYYY) - Add `nanoid` action to validate Nano IDs (pull request #789) +- Add `undefinedable` and `undefinedableAsync` schema (issue #385) ## v0.39.0 (August 24, 2024) diff --git a/library/src/methods/getDefault/getDefault.ts b/library/src/methods/getDefault/getDefault.ts index c3a326dc4..7a21a8978 100644 --- a/library/src/methods/getDefault/getDefault.ts +++ b/library/src/methods/getDefault/getDefault.ts @@ -5,6 +5,8 @@ import type { NullishSchemaAsync, OptionalSchema, OptionalSchemaAsync, + UndefinedableSchema, + UndefinedableSchemaAsync, } from '../../schemas/index.ts'; import type { BaseIssue, @@ -41,6 +43,15 @@ export type InferDefault< | BaseSchema> | BaseSchemaAsync>, unknown + > + | UndefinedableSchema< + BaseSchema>, + unknown + > + | UndefinedableSchemaAsync< + | BaseSchema> + | BaseSchemaAsync>, + unknown >, > = TSchema extends | NullableSchema< @@ -70,6 +81,15 @@ export type InferDefault< | BaseSchemaAsync>, infer TDefault > + | UndefinedableSchema< + BaseSchema>, + infer TDefault + > + | UndefinedableSchemaAsync< + | BaseSchema> + | BaseSchemaAsync>, + infer TDefault + > ? [TDefault] extends [never] ? undefined : TDefault extends () => MaybePromise> diff --git a/library/src/methods/unwrap/unwrap.ts b/library/src/methods/unwrap/unwrap.ts index 84b860f4d..53bbb03cb 100644 --- a/library/src/methods/unwrap/unwrap.ts +++ b/library/src/methods/unwrap/unwrap.ts @@ -14,6 +14,8 @@ import type { NullishSchemaAsync, OptionalSchema, OptionalSchemaAsync, + UndefinedableSchema, + UndefinedableSchemaAsync, } from '../../schemas/index.ts'; import type { BaseIssue, @@ -75,6 +77,15 @@ export function unwrap< | BaseSchema> | BaseSchemaAsync>, unknown + > + | UndefinedableSchema< + BaseSchema>, + unknown + > + | UndefinedableSchemaAsync< + | BaseSchema> + | BaseSchemaAsync>, + unknown >, >(schema: TSchema): TSchema['wrapped'] { return schema.wrapped; diff --git a/library/src/schemas/index.ts b/library/src/schemas/index.ts index 320e668b8..9bb5c495c 100644 --- a/library/src/schemas/index.ts +++ b/library/src/schemas/index.ts @@ -38,6 +38,7 @@ export * from './symbol/index.ts'; export * from './tuple/index.ts'; export * from './tupleWithRest/index.ts'; export * from './undefined/index.ts'; +export * from './undefinedable/index.ts'; export * from './union/index.ts'; export * from './unknown/index.ts'; export * from './variant/index.ts'; diff --git a/library/src/schemas/looseObject/looseObject.test-d.ts b/library/src/schemas/looseObject/looseObject.test-d.ts index c18fd7ac4..05284b2eb 100644 --- a/library/src/schemas/looseObject/looseObject.test-d.ts +++ b/library/src/schemas/looseObject/looseObject.test-d.ts @@ -11,6 +11,7 @@ import { type StringIssue, type StringSchema, } from '../string/index.ts'; +import type { UndefinedableSchema } from '../undefinedable/index.ts'; import { looseObject, type LooseObjectSchema } from './looseObject.ts'; import type { LooseObjectIssue } from './types.ts'; @@ -46,6 +47,7 @@ describe('looseObject', () => { key3: NullishSchema, never>; key4: ObjectSchema<{ key: NumberSchema }, never>; key5: SchemaWithPipe<[StringSchema, ReadonlyAction]>; + key6: UndefinedableSchema, 'bar'>; }, undefined >; @@ -55,9 +57,10 @@ describe('looseObject', () => { { key1: string; key2?: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; key5: string; + key6: string | undefined; } & { [key: string]: unknown } >(); }); @@ -67,9 +70,10 @@ describe('looseObject', () => { { key1: string; key2: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; readonly key5: string; + key6: string; } & { [key: string]: unknown } >(); }); diff --git a/library/src/schemas/looseObject/looseObject.test.ts b/library/src/schemas/looseObject/looseObject.test.ts index 13dffdd2c..00588f5d3 100644 --- a/library/src/schemas/looseObject/looseObject.test.ts +++ b/library/src/schemas/looseObject/looseObject.test.ts @@ -143,7 +143,6 @@ describe('looseObject', () => { test('for nullish entry', () => { expectNoSchemaIssue(looseObject({ key: nullish(number()) }), [ {}, - // @ts-expect-error { key: undefined }, { key: null }, { key: 123 }, diff --git a/library/src/schemas/looseObject/looseObjectAsync.test-d.ts b/library/src/schemas/looseObject/looseObjectAsync.test-d.ts index 3f8e89efd..44b56bfa6 100644 --- a/library/src/schemas/looseObject/looseObjectAsync.test-d.ts +++ b/library/src/schemas/looseObject/looseObjectAsync.test-d.ts @@ -11,6 +11,7 @@ import { type StringIssue, type StringSchema, } from '../string/index.ts'; +import type { UndefinedableSchema } from '../undefinedable/index.ts'; import { looseObjectAsync, type LooseObjectSchemaAsync, @@ -51,6 +52,7 @@ describe('looseObjectAsync', () => { key3: NullishSchema, never>; key4: ObjectSchema<{ key: NumberSchema }, never>; key5: SchemaWithPipe<[StringSchema, ReadonlyAction]>; + key6: UndefinedableSchema, 'bar'>; }, undefined >; @@ -60,9 +62,10 @@ describe('looseObjectAsync', () => { { key1: string; key2?: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; key5: string; + key6: string | undefined; } & { [key: string]: unknown } >(); }); @@ -72,9 +75,10 @@ describe('looseObjectAsync', () => { { key1: string; key2: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; readonly key5: string; + key6: string; } & { [key: string]: unknown } >(); }); diff --git a/library/src/schemas/looseObject/looseObjectAsync.test.ts b/library/src/schemas/looseObject/looseObjectAsync.test.ts index c21648b12..31ab43ca2 100644 --- a/library/src/schemas/looseObject/looseObjectAsync.test.ts +++ b/library/src/schemas/looseObject/looseObjectAsync.test.ts @@ -165,13 +165,7 @@ describe('looseObjectAsync', () => { test('for nullish entry', async () => { await expectNoSchemaIssueAsync( looseObjectAsync({ key: nullish(number()) }), - [ - {}, - // @ts-expect-error - { key: undefined }, - { key: null }, - { key: 123 }, - ] + [{}, { key: undefined }, { key: null }, { key: 123 }] ); }); diff --git a/library/src/schemas/object/object.test-d.ts b/library/src/schemas/object/object.test-d.ts index cc60f792a..51e13c917 100644 --- a/library/src/schemas/object/object.test-d.ts +++ b/library/src/schemas/object/object.test-d.ts @@ -10,6 +10,7 @@ import { type StringIssue, type StringSchema, } from '../string/index.ts'; +import type { UndefinedableSchema } from '../undefinedable/index.ts'; import { object, type ObjectSchema } from './object.ts'; import type { ObjectIssue } from './types.ts'; @@ -45,6 +46,7 @@ describe('object', () => { key3: NullishSchema, never>; key4: ObjectSchema<{ key: NumberSchema }, never>; key5: SchemaWithPipe<[StringSchema, ReadonlyAction]>; + key6: UndefinedableSchema, 'bar'>; }, undefined >; @@ -53,9 +55,10 @@ describe('object', () => { expectTypeOf>().toEqualTypeOf<{ key1: string; key2?: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; key5: string; + key6: string | undefined; }>(); }); @@ -63,9 +66,10 @@ describe('object', () => { expectTypeOf>().toEqualTypeOf<{ key1: string; key2: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; readonly key5: string; + key6: string; }>(); }); diff --git a/library/src/schemas/object/object.test.ts b/library/src/schemas/object/object.test.ts index 7a224db2a..bba8b11e6 100644 --- a/library/src/schemas/object/object.test.ts +++ b/library/src/schemas/object/object.test.ts @@ -148,7 +148,6 @@ describe('object', () => { test('for nullish entry', () => { expectNoSchemaIssue(object({ key: nullish(number()) }), [ {}, - // @ts-expect-error { key: undefined }, { key: null }, { key: 123 }, diff --git a/library/src/schemas/object/objectAsync.test-d.ts b/library/src/schemas/object/objectAsync.test-d.ts index 429ef7c8e..ea891e200 100644 --- a/library/src/schemas/object/objectAsync.test-d.ts +++ b/library/src/schemas/object/objectAsync.test-d.ts @@ -10,6 +10,7 @@ import { type StringIssue, type StringSchema, } from '../string/index.ts'; +import type { UndefinedableSchema } from '../undefinedable/index.ts'; import { objectAsync, type ObjectSchemaAsync } from './objectAsync.ts'; import type { ObjectIssue } from './types.ts'; @@ -45,6 +46,7 @@ describe('objectAsync', () => { key3: NullishSchema, never>; key4: ObjectSchemaAsync<{ key: NumberSchema }, never>; key5: SchemaWithPipe<[StringSchema, ReadonlyAction]>; + key6: UndefinedableSchema, 'bar'>; }, undefined >; @@ -53,9 +55,10 @@ describe('objectAsync', () => { expectTypeOf>().toEqualTypeOf<{ key1: string; key2?: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; key5: string; + key6: string | undefined; }>(); }); @@ -63,9 +66,10 @@ describe('objectAsync', () => { expectTypeOf>().toEqualTypeOf<{ key1: string; key2: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; readonly key5: string; + key6: string; }>(); }); diff --git a/library/src/schemas/object/objectAsync.test.ts b/library/src/schemas/object/objectAsync.test.ts index 70978928e..d58038a25 100644 --- a/library/src/schemas/object/objectAsync.test.ts +++ b/library/src/schemas/object/objectAsync.test.ts @@ -160,7 +160,6 @@ describe('objectAsync', () => { test('for nullish entry', async () => { await expectNoSchemaIssueAsync(objectAsync({ key: nullish(number()) }), [ {}, - // @ts-expect-error { key: undefined }, { key: null }, { key: 123 }, diff --git a/library/src/schemas/objectWithRest/objectWithRest.test-d.ts b/library/src/schemas/objectWithRest/objectWithRest.test-d.ts index aa858fff3..6464c4ee8 100644 --- a/library/src/schemas/objectWithRest/objectWithRest.test-d.ts +++ b/library/src/schemas/objectWithRest/objectWithRest.test-d.ts @@ -16,6 +16,7 @@ import { type StringIssue, type StringSchema, } from '../string/index.ts'; +import type { UndefinedableSchema } from '../undefinedable/index.ts'; import { objectWithRest, type ObjectWithRestSchema } from './objectWithRest.ts'; import type { ObjectWithRestIssue } from './types.ts'; @@ -55,6 +56,7 @@ describe('objectWithRest', () => { key3: NullishSchema, never>; key4: ObjectSchema<{ key: NumberSchema }, never>; key5: SchemaWithPipe<[StringSchema, ReadonlyAction]>; + key6: UndefinedableSchema, 'bar'>; }, BooleanSchema, undefined @@ -65,9 +67,10 @@ describe('objectWithRest', () => { { key1: string; key2?: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; key5: string; + key6: string | undefined; } & { [key: string]: boolean } >(); }); @@ -77,9 +80,10 @@ describe('objectWithRest', () => { { key1: string; key2: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; readonly key5: string; + key6: string; } & { [key: string]: boolean } >(); }); diff --git a/library/src/schemas/objectWithRest/objectWithRestAsync.test-d.ts b/library/src/schemas/objectWithRest/objectWithRestAsync.test-d.ts index d564d8263..fd75024a5 100644 --- a/library/src/schemas/objectWithRest/objectWithRestAsync.test-d.ts +++ b/library/src/schemas/objectWithRest/objectWithRestAsync.test-d.ts @@ -16,6 +16,7 @@ import { type StringIssue, type StringSchema, } from '../string/index.ts'; +import type { UndefinedableSchema } from '../undefinedable/index.ts'; import { objectWithRestAsync, type ObjectWithRestSchemaAsync, @@ -58,6 +59,7 @@ describe('objectWithRestAsync', () => { key3: NullishSchema, never>; key4: ObjectSchema<{ key: NumberSchema }, never>; key5: SchemaWithPipe<[StringSchema, ReadonlyAction]>; + key6: UndefinedableSchema, 'bar'>; }, BooleanSchema, undefined @@ -68,9 +70,10 @@ describe('objectWithRestAsync', () => { { key1: string; key2?: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; key5: string; + key6: string | undefined; } & { [key: string]: boolean } >(); }); @@ -80,9 +83,10 @@ describe('objectWithRestAsync', () => { { key1: string; key2: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; readonly key5: string; + key6: string; } & { [key: string]: boolean } >(); }); diff --git a/library/src/schemas/record/types.ts b/library/src/schemas/record/types.ts index c21583110..b378e34fd 100644 --- a/library/src/schemas/record/types.ts +++ b/library/src/schemas/record/types.ts @@ -1,4 +1,4 @@ -import type { Brand } from '../../actions/index.ts'; +import type { Brand, ReadonlyAction } from '../../actions/index.ts'; import type { SchemaWithPipe, SchemaWithPipeAsync, @@ -10,8 +10,6 @@ import type { InferInput, InferOutput, MarkOptional, - PipeItem, - PipeItemAsync, Prettify, } from '../../types/index.ts'; @@ -78,26 +76,10 @@ type WithReadonly< | BaseSchemaAsync>, TObject extends WithQuestionMarks>, > = TValue extends - | SchemaWithPipe< - [ - BaseSchema>, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ...PipeItem>[], - ] - > - | SchemaWithPipeAsync< - [ - ( - | BaseSchema> - | BaseSchemaAsync> - ), - ...( - | PipeItem> // eslint-disable-line @typescript-eslint/no-explicit-any - | PipeItemAsync> // eslint-disable-line @typescript-eslint/no-explicit-any - )[], - ] - > - ? 'readonly' extends TValue['pipe'][number]['type'] + | SchemaWithPipe + | SchemaWithPipeAsync + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + ReadonlyAction extends TPipe[number] ? Readonly : TObject : TObject; diff --git a/library/src/schemas/strictObject/strictObject.test-d.ts b/library/src/schemas/strictObject/strictObject.test-d.ts index 72c9a531a..174b2abfb 100644 --- a/library/src/schemas/strictObject/strictObject.test-d.ts +++ b/library/src/schemas/strictObject/strictObject.test-d.ts @@ -11,6 +11,7 @@ import { type StringIssue, type StringSchema, } from '../string/index.ts'; +import type { UndefinedableSchema } from '../undefinedable/index.ts'; import { strictObject, type StrictObjectSchema } from './strictObject.ts'; import type { StrictObjectIssue } from './types.ts'; @@ -46,6 +47,7 @@ describe('strictObject', () => { key3: NullishSchema, never>; key4: ObjectSchema<{ key: NumberSchema }, never>; key5: SchemaWithPipe<[StringSchema, ReadonlyAction]>; + key6: UndefinedableSchema, 'bar'>; }, undefined >; @@ -54,9 +56,10 @@ describe('strictObject', () => { expectTypeOf>().toEqualTypeOf<{ key1: string; key2?: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; key5: string; + key6: string | undefined; }>(); }); @@ -64,9 +67,10 @@ describe('strictObject', () => { expectTypeOf>().toEqualTypeOf<{ key1: string; key2: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; readonly key5: string; + key6: string; }>(); }); diff --git a/library/src/schemas/strictObject/strictObject.test.ts b/library/src/schemas/strictObject/strictObject.test.ts index 138736119..d5c1ae91a 100644 --- a/library/src/schemas/strictObject/strictObject.test.ts +++ b/library/src/schemas/strictObject/strictObject.test.ts @@ -137,7 +137,6 @@ describe('strictObject', () => { test('for nullish entry', () => { expectNoSchemaIssue(strictObject({ key: nullish(number()) }), [ {}, - // @ts-expect-error { key: undefined }, { key: null }, { key: 123 }, diff --git a/library/src/schemas/strictObject/strictObjectAsync.test-d.ts b/library/src/schemas/strictObject/strictObjectAsync.test-d.ts index b1ef8c582..6a5ed5c8d 100644 --- a/library/src/schemas/strictObject/strictObjectAsync.test-d.ts +++ b/library/src/schemas/strictObject/strictObjectAsync.test-d.ts @@ -11,6 +11,7 @@ import { type StringIssue, type StringSchema, } from '../string/index.ts'; +import type { UndefinedableSchema } from '../undefinedable/index.ts'; import { strictObjectAsync, type StrictObjectSchemaAsync, @@ -51,6 +52,7 @@ describe('strictObjectAsync', () => { key3: NullishSchema, never>; key4: ObjectSchema<{ key: NumberSchema }, never>; key5: SchemaWithPipe<[StringSchema, ReadonlyAction]>; + key6: UndefinedableSchema, 'bar'>; }, undefined >; @@ -59,9 +61,10 @@ describe('strictObjectAsync', () => { expectTypeOf>().toEqualTypeOf<{ key1: string; key2?: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; key5: string; + key6: string | undefined; }>(); }); @@ -69,9 +72,10 @@ describe('strictObjectAsync', () => { expectTypeOf>().toEqualTypeOf<{ key1: string; key2: string; - key3?: string | null; + key3?: string | null | undefined; key4: { key: number }; readonly key5: string; + key6: string; }>(); }); diff --git a/library/src/schemas/strictObject/strictObjectAsync.test.ts b/library/src/schemas/strictObject/strictObjectAsync.test.ts index 4328ad92d..c548932c7 100644 --- a/library/src/schemas/strictObject/strictObjectAsync.test.ts +++ b/library/src/schemas/strictObject/strictObjectAsync.test.ts @@ -158,13 +158,7 @@ describe('strictObjectAsync', () => { test('for nullish entry', async () => { await expectNoSchemaIssueAsync( strictObjectAsync({ key: nullish(number()) }), - [ - {}, - // @ts-expect-error - { key: undefined }, - { key: null }, - { key: 123 }, - ] + [{}, { key: undefined }, { key: null }, { key: 123 }] ); }); }); diff --git a/library/src/schemas/undefinedable/index.ts b/library/src/schemas/undefinedable/index.ts new file mode 100644 index 000000000..5a26c14ef --- /dev/null +++ b/library/src/schemas/undefinedable/index.ts @@ -0,0 +1,3 @@ +export * from './undefinedable.ts'; +export * from './undefinedableAsync.ts'; +export * from './types.ts'; diff --git a/library/src/schemas/undefinedable/types.ts b/library/src/schemas/undefinedable/types.ts new file mode 100644 index 000000000..53692b0df --- /dev/null +++ b/library/src/schemas/undefinedable/types.ts @@ -0,0 +1,24 @@ +import type { + BaseIssue, + BaseSchema, + BaseSchemaAsync, + DefaultAsync, + DefaultValue, + InferOutput, + NonOptional, +} from '../../types/index.ts'; + +/** + * Infer undefinedable output type. + */ +export type InferUndefinedableOutput< + TWrapped extends + | BaseSchema> + | BaseSchemaAsync>, + TDefault extends DefaultAsync, +> = [TDefault] extends [never] + ? InferOutput | undefined + : // FIXME: For schemas that transform the input to `undefined`, this + // implementation may result in an incorrect output type + | NonOptional> + | Extract, undefined>; diff --git a/library/src/schemas/undefinedable/undefinedable.test-d.ts b/library/src/schemas/undefinedable/undefinedable.test-d.ts new file mode 100644 index 000000000..014c069a2 --- /dev/null +++ b/library/src/schemas/undefinedable/undefinedable.test-d.ts @@ -0,0 +1,78 @@ +import { describe, expectTypeOf, test } from 'vitest'; +import type { InferInput, InferIssue, InferOutput } from '../../types/index.ts'; +import { + string, + type StringIssue, + type StringSchema, +} from '../string/index.ts'; +import { undefinedable, type UndefinedableSchema } from './undefinedable.ts'; + +describe('undefinedable', () => { + describe('should return schema object', () => { + test('with never default', () => { + expectTypeOf(undefinedable(string())).toEqualTypeOf< + UndefinedableSchema, never> + >(); + }); + + test('with undefined default', () => { + expectTypeOf(undefinedable(string(), undefined)).toEqualTypeOf< + UndefinedableSchema, undefined> + >(); + }); + + test('with undefined getter default', () => { + expectTypeOf(undefinedable(string(), () => undefined)).toEqualTypeOf< + UndefinedableSchema, () => undefined> + >(); + }); + + test('with value default', () => { + expectTypeOf(undefinedable(string(), 'foo')).toEqualTypeOf< + UndefinedableSchema, 'foo'> + >(); + }); + + test('with value getter default', () => { + expectTypeOf(undefinedable(string(), () => 'foo')).toEqualTypeOf< + UndefinedableSchema, () => string> + >(); + }); + }); + + describe('should infer correct types', () => { + type Schema1 = UndefinedableSchema, never>; + type Schema2 = UndefinedableSchema, undefined>; + type Schema3 = UndefinedableSchema, 'foo'>; + type Schema4 = UndefinedableSchema< + StringSchema, + () => undefined + >; + type Schema5 = UndefinedableSchema, () => 'foo'>; + + test('of input', () => { + type Input = string | undefined; + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + }); + + test('of output', () => { + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + }); + + test('of issue', () => { + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + }); + }); +}); diff --git a/library/src/schemas/undefinedable/undefinedable.test.ts b/library/src/schemas/undefinedable/undefinedable.test.ts new file mode 100644 index 000000000..645e2ef17 --- /dev/null +++ b/library/src/schemas/undefinedable/undefinedable.test.ts @@ -0,0 +1,109 @@ +import { describe, expect, test } from 'vitest'; +import { expectNoSchemaIssue } from '../../vitest/index.ts'; +import { string, type StringSchema } from '../string/index.ts'; +import { undefinedable, type UndefinedableSchema } from './undefinedable.ts'; + +describe('undefinedable', () => { + describe('should return schema object', () => { + const baseSchema: Omit< + UndefinedableSchema, string>, + 'default' + > = { + kind: 'schema', + type: 'undefinedable', + reference: undefinedable, + expects: '(string | undefined)', + wrapped: { ...string(), _run: expect.any(Function) }, + async: false, + _run: expect.any(Function), + }; + + test('with never default', () => { + expect(undefinedable(string())).toStrictEqual(baseSchema); + }); + + test('with undefined default', () => { + expect(undefinedable(string(), undefined)).toStrictEqual({ + ...baseSchema, + default: undefined, + } satisfies UndefinedableSchema, undefined>); + }); + + test('with undefined getter default', () => { + const getter = () => undefined; + expect(undefinedable(string(), getter)).toStrictEqual({ + ...baseSchema, + default: getter, + } satisfies UndefinedableSchema, typeof getter>); + }); + + test('with value default', () => { + expect(undefinedable(string(), 'foo')).toStrictEqual({ + ...baseSchema, + default: 'foo', + } satisfies UndefinedableSchema, 'foo'>); + }); + + test('with value getter default', () => { + const getter = () => 'foo'; + expect(undefinedable(string(), getter)).toStrictEqual({ + ...baseSchema, + default: getter, + } satisfies UndefinedableSchema, typeof getter>); + }); + }); + + describe('should return dataset without issues', () => { + const schema = undefinedable(string()); + + test('for wrapper type', () => { + expectNoSchemaIssue(schema, ['', 'foo', '#$%']); + }); + + test('for undefined', () => { + expectNoSchemaIssue(schema, [undefined]); + }); + }); + + describe('should return dataset without default', () => { + const schema = undefinedable(string(), 'foo'); + + test('for wrapper type', () => { + expectNoSchemaIssue(schema, ['', 'bar', '#$%']); + }); + }); + + describe('should return dataset with default', () => { + const schema1 = undefinedable(string(), undefined); + const schema2 = undefinedable(string(), 'foo'); + const schema3 = undefinedable(string(), () => undefined); + const schema4 = undefinedable(string(), () => 'foo'); + + test('for undefined', () => { + expect( + schema1._run({ typed: false, value: undefined }, {}) + ).toStrictEqual({ + typed: true, + value: undefined, + }); + expect( + schema2._run({ typed: false, value: undefined }, {}) + ).toStrictEqual({ + typed: true, + value: 'foo', + }); + expect( + schema3._run({ typed: false, value: undefined }, {}) + ).toStrictEqual({ + typed: true, + value: undefined, + }); + expect( + schema4._run({ typed: false, value: undefined }, {}) + ).toStrictEqual({ + typed: true, + value: 'foo', + }); + }); + }); +}); diff --git a/library/src/schemas/undefinedable/undefinedable.ts b/library/src/schemas/undefinedable/undefinedable.ts new file mode 100644 index 000000000..129993177 --- /dev/null +++ b/library/src/schemas/undefinedable/undefinedable.ts @@ -0,0 +1,123 @@ +import { getDefault } from '../../methods/index.ts'; +import type { + BaseIssue, + BaseSchema, + Dataset, + Default, + InferInput, + InferIssue, +} from '../../types/index.ts'; +import type { InferUndefinedableOutput } from './types.ts'; + +/** + * Undefinedable schema type. + */ +export interface UndefinedableSchema< + TWrapped extends BaseSchema>, + TDefault extends Default, +> extends BaseSchema< + InferInput | undefined, + InferUndefinedableOutput, + InferIssue + > { + /** + * The schema type. + */ + readonly type: 'undefinedable'; + /** + * The schema reference. + */ + readonly reference: typeof undefinedable; + /** + * The expected property. + */ + readonly expects: `(${TWrapped['expects']} | undefined)`; + /** + * The wrapped schema. + */ + readonly wrapped: TWrapped; + /** + * The default value. + */ + readonly default: TDefault; +} + +/** + * Creates a undefinedable schema. + * + * @param wrapped The wrapped schema. + * + * @returns A undefinedable schema. + */ +export function undefinedable< + const TWrapped extends BaseSchema>, +>(wrapped: TWrapped): UndefinedableSchema; + +/** + * Creates a undefinedable schema. + * + * @param wrapped The wrapped schema. + * @param default_ The default value. + * + * @returns A undefinedable schema. + */ +export function undefinedable< + const TWrapped extends BaseSchema>, + const TDefault extends Default, +>( + wrapped: TWrapped, + default_: TDefault +): UndefinedableSchema; + +export function undefinedable( + wrapped: BaseSchema>, + ...args: unknown[] +): UndefinedableSchema< + BaseSchema>, + unknown +> { + // Create schema object + // @ts-expect-error + const schema: UndefinedableSchema< + BaseSchema>, + unknown + > = { + kind: 'schema', + type: 'undefinedable', + reference: undefinedable, + expects: `(${wrapped.expects} | undefined)`, + async: false, + wrapped, + _run(dataset, config) { + // If value is `undefined`, override it with default or return dataset + if (dataset.value === undefined) { + // If default is specified, override value of dataset + if ('default' in this) { + dataset.value = getDefault( + this, + dataset as Dataset, + config + ); + } + + // If value is still `undefined`, return dataset + if (dataset.value === undefined) { + dataset.typed = true; + return dataset; + } + } + + // Otherwise, return dataset of wrapped schema + return this.wrapped._run(dataset, config); + }, + }; + + // Add default if specified + if (0 in args) { + // @ts-expect-error + schema.default = args[0]; + } + + // Return schema object + return schema; +} diff --git a/library/src/schemas/undefinedable/undefinedableAsync.test-d.ts b/library/src/schemas/undefinedable/undefinedableAsync.test-d.ts new file mode 100644 index 000000000..e19393789 --- /dev/null +++ b/library/src/schemas/undefinedable/undefinedableAsync.test-d.ts @@ -0,0 +1,117 @@ +import { describe, expectTypeOf, test } from 'vitest'; +import type { InferInput, InferIssue, InferOutput } from '../../types/index.ts'; +import { + string, + type StringIssue, + type StringSchema, +} from '../string/index.ts'; +import { + undefinedableAsync, + type UndefinedableSchemaAsync, +} from './undefinedableAsync.ts'; + +describe('undefinedableAsync', () => { + describe('should return schema object', () => { + test('with never default', () => { + expectTypeOf(undefinedableAsync(string())).toEqualTypeOf< + UndefinedableSchemaAsync, never> + >(); + }); + + test('with undefined default', () => { + expectTypeOf(undefinedableAsync(string(), undefined)).toEqualTypeOf< + UndefinedableSchemaAsync, undefined> + >(); + }); + + test('with undefined getter default', () => { + expectTypeOf(undefinedableAsync(string(), () => undefined)).toEqualTypeOf< + UndefinedableSchemaAsync, () => undefined> + >(); + }); + + test('with async undefined getter default', () => { + expectTypeOf( + undefinedableAsync(string(), async () => undefined) + ).toEqualTypeOf< + UndefinedableSchemaAsync< + StringSchema, + () => Promise + > + >(); + }); + + test('with value default', () => { + expectTypeOf(undefinedableAsync(string(), 'foo')).toEqualTypeOf< + UndefinedableSchemaAsync, 'foo'> + >(); + }); + + test('with value getter default', () => { + expectTypeOf(undefinedableAsync(string(), () => 'foo')).toEqualTypeOf< + UndefinedableSchemaAsync, () => string> + >(); + }); + + test('with async value getter default', () => { + expectTypeOf( + undefinedableAsync(string(), async () => 'foo') + ).toEqualTypeOf< + UndefinedableSchemaAsync, () => Promise> + >(); + }); + }); + + describe('should infer correct types', () => { + type Schema1 = UndefinedableSchemaAsync, never>; + type Schema2 = UndefinedableSchemaAsync, undefined>; + type Schema3 = UndefinedableSchemaAsync, 'foo'>; + type Schema4 = UndefinedableSchemaAsync< + StringSchema, + () => undefined + >; + type Schema5 = UndefinedableSchemaAsync< + StringSchema, + () => 'foo' + >; + type Schema6 = UndefinedableSchemaAsync< + StringSchema, + () => Promise + >; + type Schema7 = UndefinedableSchemaAsync< + StringSchema, + () => Promise<'foo'> + >; + + test('of input', () => { + type Input = string | undefined; + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + }); + + test('of output', () => { + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + }); + + test('of issue', () => { + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + }); + }); +}); diff --git a/library/src/schemas/undefinedable/undefinedableAsync.test.ts b/library/src/schemas/undefinedable/undefinedableAsync.test.ts new file mode 100644 index 000000000..61daa0afc --- /dev/null +++ b/library/src/schemas/undefinedable/undefinedableAsync.test.ts @@ -0,0 +1,154 @@ +import { describe, expect, test } from 'vitest'; +import { expectNoSchemaIssueAsync } from '../../vitest/index.ts'; +import { string, type StringSchema } from '../string/index.ts'; +import { + undefinedableAsync, + type UndefinedableSchemaAsync, +} from './undefinedableAsync.ts'; + +describe('undefinedableAsync', () => { + describe('should return schema object', () => { + const baseSchema: Omit< + UndefinedableSchemaAsync, string>, + 'default' + > = { + kind: 'schema', + type: 'undefinedable', + reference: undefinedableAsync, + expects: '(string | undefined)', + wrapped: { ...string(), _run: expect.any(Function) }, + async: true, + _run: expect.any(Function), + }; + + test('with never default', () => { + expect(undefinedableAsync(string())).toStrictEqual(baseSchema); + }); + + test('with undefined default', () => { + expect(undefinedableAsync(string(), undefined)).toStrictEqual({ + ...baseSchema, + default: undefined, + } satisfies UndefinedableSchemaAsync, undefined>); + }); + + test('with undefined getter default', () => { + const getter = () => undefined; + expect(undefinedableAsync(string(), getter)).toStrictEqual({ + ...baseSchema, + default: getter, + } satisfies UndefinedableSchemaAsync< + StringSchema, + typeof getter + >); + }); + + test('with async undefined getter default', () => { + const getter = async () => undefined; + expect(undefinedableAsync(string(), getter)).toStrictEqual({ + ...baseSchema, + default: getter, + } satisfies UndefinedableSchemaAsync< + StringSchema, + typeof getter + >); + }); + + test('with value default', () => { + expect(undefinedableAsync(string(), 'foo')).toStrictEqual({ + ...baseSchema, + default: 'foo', + } satisfies UndefinedableSchemaAsync, 'foo'>); + }); + + test('with value getter default', () => { + const getter = () => 'foo'; + expect(undefinedableAsync(string(), getter)).toStrictEqual({ + ...baseSchema, + default: getter, + } satisfies UndefinedableSchemaAsync< + StringSchema, + typeof getter + >); + }); + + test('with async value getter default', () => { + const getter = async () => 'foo'; + expect(undefinedableAsync(string(), getter)).toStrictEqual({ + ...baseSchema, + default: getter, + } satisfies UndefinedableSchemaAsync< + StringSchema, + typeof getter + >); + }); + }); + + describe('should return dataset without issues', () => { + const schema = undefinedableAsync(string()); + + test('for wrapper type', async () => { + await expectNoSchemaIssueAsync(schema, ['', 'foo', '#$%']); + }); + + test('for undefined', async () => { + await expectNoSchemaIssueAsync(schema, [undefined]); + }); + }); + + describe('should return dataset without default', () => { + const schema = undefinedableAsync(string(), 'foo'); + + test('for wrapper type', async () => { + await expectNoSchemaIssueAsync(schema, ['', 'bar', '#$%']); + }); + }); + + describe('should return dataset with default', () => { + const schema1 = undefinedableAsync(string(), undefined); + const schema2 = undefinedableAsync(string(), 'foo'); + const schema3 = undefinedableAsync(string(), () => undefined); + const schema4 = undefinedableAsync(string(), () => 'foo'); + const schema5 = undefinedableAsync(string(), async () => undefined); + const schema6 = undefinedableAsync(string(), async () => 'foo'); + + test('for undefined', async () => { + expect( + await schema1._run({ typed: false, value: undefined }, {}) + ).toStrictEqual({ + typed: true, + value: undefined, + }); + expect( + await schema2._run({ typed: false, value: undefined }, {}) + ).toStrictEqual({ + typed: true, + value: 'foo', + }); + expect( + await schema3._run({ typed: false, value: undefined }, {}) + ).toStrictEqual({ + typed: true, + value: undefined, + }); + expect( + await schema4._run({ typed: false, value: undefined }, {}) + ).toStrictEqual({ + typed: true, + value: 'foo', + }); + expect( + await schema5._run({ typed: false, value: undefined }, {}) + ).toStrictEqual({ + typed: true, + value: undefined, + }); + expect( + await schema6._run({ typed: false, value: undefined }, {}) + ).toStrictEqual({ + typed: true, + value: 'foo', + }); + }); + }); +}); diff --git a/library/src/schemas/undefinedable/undefinedableAsync.ts b/library/src/schemas/undefinedable/undefinedableAsync.ts new file mode 100644 index 000000000..63d716886 --- /dev/null +++ b/library/src/schemas/undefinedable/undefinedableAsync.ts @@ -0,0 +1,134 @@ +import { getDefault } from '../../methods/index.ts'; +import type { + BaseIssue, + BaseSchema, + BaseSchemaAsync, + Dataset, + DefaultAsync, + InferInput, + InferIssue, +} from '../../types/index.ts'; +import type { InferUndefinedableOutput } from './types.ts'; + +/** + * Undefinedable schema async type. + */ +export interface UndefinedableSchemaAsync< + TWrapped extends + | BaseSchema> + | BaseSchemaAsync>, + TDefault extends DefaultAsync, +> extends BaseSchemaAsync< + InferInput | undefined, + InferUndefinedableOutput, + InferIssue + > { + /** + * The schema type. + */ + readonly type: 'undefinedable'; + /** + * The schema reference. + */ + readonly reference: typeof undefinedableAsync; + /** + * The expected property. + */ + readonly expects: `(${TWrapped['expects']} | undefined)`; + /** + * The wrapped schema. + */ + readonly wrapped: TWrapped; + /** + * The default value. + */ + readonly default: TDefault; +} + +/** + * Creates an undefinedable schema. + * + * @param wrapped The wrapped schema. + * + * @returns An undefinedable schema. + */ +export function undefinedableAsync< + const TWrapped extends + | BaseSchema> + | BaseSchemaAsync>, +>(wrapped: TWrapped): UndefinedableSchemaAsync; + +/** + * Creates an undefinedable schema. + * + * @param wrapped The wrapped schema. + * @param default_ The default value. + * + * @returns An undefinedable schema. + */ +export function undefinedableAsync< + const TWrapped extends + | BaseSchema> + | BaseSchemaAsync>, + const TDefault extends DefaultAsync, +>( + wrapped: TWrapped, + default_: TDefault +): UndefinedableSchemaAsync; + +export function undefinedableAsync( + wrapped: + | BaseSchema> + | BaseSchemaAsync>, + ...args: unknown[] +): UndefinedableSchemaAsync< + | BaseSchema> + | BaseSchemaAsync>, + unknown +> { + // Create schema object + // @ts-expect-error + const schema: UndefinedableSchemaAsync< + | BaseSchema> + | BaseSchemaAsync>, + unknown + > = { + kind: 'schema', + type: 'undefinedable', + reference: undefinedableAsync, + expects: `(${wrapped.expects} | undefined)`, + async: true, + wrapped, + async _run(dataset, config) { + // If value is `undefined`, override it with default or return dataset + if (dataset.value === undefined) { + // If default is specified, override value of dataset + if ('default' in this) { + dataset.value = await getDefault( + this, + dataset as Dataset, + config + ); + } + + // If value is still `undefined`, return dataset + if (dataset.value === undefined) { + dataset.typed = true; + return dataset; + } + } + + // Otherwise, return dataset of wrapped schema + return this.wrapped._run(dataset, config); + }, + }; + + // Add default if specified + if (0 in args) { + // @ts-expect-error + schema.default = args[0]; + } + + // Return schema object + return schema; +} diff --git a/library/src/types/issue.ts b/library/src/types/issue.ts index 11e439ddb..98707f043 100644 --- a/library/src/types/issue.ts +++ b/library/src/types/issue.ts @@ -56,6 +56,8 @@ import type { TupleWithRestIssue, TupleWithRestSchema, TupleWithRestSchemaAsync, + UndefinedableSchema, + UndefinedableSchemaAsync, UnionIssue, UnionSchema, UnionSchemaAsync, @@ -571,6 +573,33 @@ export type IssueDotPath< undefined > > + | UndefinedableSchema< + infer TWrapped, + Default< + BaseSchema< + unknown, + unknown, + BaseIssue + >, + undefined + > + > + | UndefinedableSchemaAsync< + infer TWrapped, + DefaultAsync< + | BaseSchema< + unknown, + unknown, + BaseIssue + > + | BaseSchemaAsync< + unknown, + unknown, + BaseIssue + >, + undefined + > + > ? IssueDotPath : // Otherwise never; diff --git a/library/src/types/object.ts b/library/src/types/object.ts index 6bccc17ab..17c66d123 100644 --- a/library/src/types/object.ts +++ b/library/src/types/object.ts @@ -117,14 +117,10 @@ type ExactOptionalInput< | BaseSchema> | BaseSchemaAsync>, > = TSchema extends - | NullishSchema - | NullishSchemaAsync - ? ExactOptionalInput | null - : TSchema extends - | OptionalSchema - | OptionalSchemaAsync - ? ExactOptionalInput - : InferInput; + | OptionalSchema + | OptionalSchemaAsync + ? ExactOptionalInput + : InferInput; /** * Exact optional output type. @@ -134,18 +130,12 @@ type ExactOptionalOutput< | BaseSchema> | BaseSchemaAsync>, > = TSchema extends - | NullishSchema - | NullishSchemaAsync + | OptionalSchema + | OptionalSchemaAsync ? HasDefault extends true ? InferOutput - : ExactOptionalOutput | null - : TSchema extends - | OptionalSchema - | OptionalSchemaAsync - ? HasDefault extends true - ? InferOutput - : ExactOptionalOutput - : InferOutput; + : ExactOptionalOutput + : InferOutput; /** * Infer entries input type. diff --git a/website/src/routes/api/(actions)/brand/index.mdx b/website/src/routes/api/(actions)/brand/index.mdx index 6f3b15ca9..e6619bd7d 100644 --- a/website/src/routes/api/(actions)/brand/index.mdx +++ b/website/src/routes/api/(actions)/brand/index.mdx @@ -102,6 +102,7 @@ The following APIs can be combined with `brand`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(actions)/check/index.mdx b/website/src/routes/api/(actions)/check/index.mdx index c7d621058..22e910ba2 100644 --- a/website/src/routes/api/(actions)/check/index.mdx +++ b/website/src/routes/api/(actions)/check/index.mdx @@ -104,6 +104,7 @@ The following APIs can be combined with `check`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(actions)/description/index.mdx b/website/src/routes/api/(actions)/description/index.mdx index 93488a1da..84226179f 100644 --- a/website/src/routes/api/(actions)/description/index.mdx +++ b/website/src/routes/api/(actions)/description/index.mdx @@ -100,6 +100,7 @@ The following APIs can be combined with `description`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(actions)/rawCheck/index.mdx b/website/src/routes/api/(actions)/rawCheck/index.mdx index c08bd59a8..c05691af5 100644 --- a/website/src/routes/api/(actions)/rawCheck/index.mdx +++ b/website/src/routes/api/(actions)/rawCheck/index.mdx @@ -127,6 +127,7 @@ The following APIs can be combined with `rawCheck`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(actions)/rawTransform/index.mdx b/website/src/routes/api/(actions)/rawTransform/index.mdx index cd7ff7ae4..8716748ba 100644 --- a/website/src/routes/api/(actions)/rawTransform/index.mdx +++ b/website/src/routes/api/(actions)/rawTransform/index.mdx @@ -145,6 +145,7 @@ The following APIs can be combined with `rawTransform`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(actions)/readonly/index.mdx b/website/src/routes/api/(actions)/readonly/index.mdx index 6883f8b3b..e3d88b970 100644 --- a/website/src/routes/api/(actions)/readonly/index.mdx +++ b/website/src/routes/api/(actions)/readonly/index.mdx @@ -97,6 +97,7 @@ The following APIs can be combined with `readonly`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(actions)/transform/index.mdx b/website/src/routes/api/(actions)/transform/index.mdx index bb880b5d3..76f7870d0 100644 --- a/website/src/routes/api/(actions)/transform/index.mdx +++ b/website/src/routes/api/(actions)/transform/index.mdx @@ -113,6 +113,7 @@ The following APIs can be combined with `transform`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/arrayAsync/index.mdx b/website/src/routes/api/(async)/arrayAsync/index.mdx index dd32259c3..7411220bb 100644 --- a/website/src/routes/api/(async)/arrayAsync/index.mdx +++ b/website/src/routes/api/(async)/arrayAsync/index.mdx @@ -106,6 +106,7 @@ The following APIs can be combined with `arrayAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/awaitAsync/index.mdx b/website/src/routes/api/(async)/awaitAsync/index.mdx index 99fb3a7f8..20c28bdc8 100644 --- a/website/src/routes/api/(async)/awaitAsync/index.mdx +++ b/website/src/routes/api/(async)/awaitAsync/index.mdx @@ -94,6 +94,7 @@ The following APIs can be combined with `awaitAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/checkAsync/index.mdx b/website/src/routes/api/(async)/checkAsync/index.mdx index c20227322..6f2ec87e0 100644 --- a/website/src/routes/api/(async)/checkAsync/index.mdx +++ b/website/src/routes/api/(async)/checkAsync/index.mdx @@ -107,6 +107,7 @@ The following APIs can be combined with `checkAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/customAsync/index.mdx b/website/src/routes/api/(async)/customAsync/index.mdx index fef6cf184..5d0acfb14 100644 --- a/website/src/routes/api/(async)/customAsync/index.mdx +++ b/website/src/routes/api/(async)/customAsync/index.mdx @@ -94,6 +94,7 @@ The following APIs can be combined with `customAsync`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(async)/fallbackAsync/index.mdx b/website/src/routes/api/(async)/fallbackAsync/index.mdx index ec88d4a6f..d8602b874 100644 --- a/website/src/routes/api/(async)/fallbackAsync/index.mdx +++ b/website/src/routes/api/(async)/fallbackAsync/index.mdx @@ -108,6 +108,7 @@ The following APIs can be combined with `fallbackAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/getDefaultsAsync/index.mdx b/website/src/routes/api/(async)/getDefaultsAsync/index.mdx index 70bf4c35f..fef72a777 100644 --- a/website/src/routes/api/(async)/getDefaultsAsync/index.mdx +++ b/website/src/routes/api/(async)/getDefaultsAsync/index.mdx @@ -111,6 +111,7 @@ The following APIs can be combined with `getDefaultsAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/getFallbacksAsync/index.mdx b/website/src/routes/api/(async)/getFallbacksAsync/index.mdx index 2d60fb9ee..5221da09e 100644 --- a/website/src/routes/api/(async)/getFallbacksAsync/index.mdx +++ b/website/src/routes/api/(async)/getFallbacksAsync/index.mdx @@ -109,6 +109,7 @@ The following APIs can be combined with `getFallbacksAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/intersectAsync/index.mdx b/website/src/routes/api/(async)/intersectAsync/index.mdx index 5e941ae92..60a86e02b 100644 --- a/website/src/routes/api/(async)/intersectAsync/index.mdx +++ b/website/src/routes/api/(async)/intersectAsync/index.mdx @@ -121,6 +121,7 @@ The following APIs can be combined with `intersectAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/lazyAsync/index.mdx b/website/src/routes/api/(async)/lazyAsync/index.mdx index 0769706dc..b1e791efc 100644 --- a/website/src/routes/api/(async)/lazyAsync/index.mdx +++ b/website/src/routes/api/(async)/lazyAsync/index.mdx @@ -151,9 +151,10 @@ The following APIs can be combined with `lazyAsync`. 'string', 'symbol', 'tuple', + 'tupleWithRest', 'undefined', + 'undefinedable', 'union', - 'unionWithRest', 'unknown', 'variant', 'void', diff --git a/website/src/routes/api/(async)/looseObjectAsync/index.mdx b/website/src/routes/api/(async)/looseObjectAsync/index.mdx index e1192b7e9..7b48577ea 100644 --- a/website/src/routes/api/(async)/looseObjectAsync/index.mdx +++ b/website/src/routes/api/(async)/looseObjectAsync/index.mdx @@ -113,6 +113,7 @@ The following APIs can be combined with `looseObjectAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/looseTupleAsync/index.mdx b/website/src/routes/api/(async)/looseTupleAsync/index.mdx index a979b1756..edf16df29 100644 --- a/website/src/routes/api/(async)/looseTupleAsync/index.mdx +++ b/website/src/routes/api/(async)/looseTupleAsync/index.mdx @@ -107,6 +107,7 @@ The following APIs can be combined with `looseTupleAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/mapAsync/index.mdx b/website/src/routes/api/(async)/mapAsync/index.mdx index 2b37d5fa3..3674a25f6 100644 --- a/website/src/routes/api/(async)/mapAsync/index.mdx +++ b/website/src/routes/api/(async)/mapAsync/index.mdx @@ -105,6 +105,7 @@ The following APIs can be combined with `mapAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/nonNullableAsync/index.mdx b/website/src/routes/api/(async)/nonNullableAsync/index.mdx index 7208d86b9..6d98168a3 100644 --- a/website/src/routes/api/(async)/nonNullableAsync/index.mdx +++ b/website/src/routes/api/(async)/nonNullableAsync/index.mdx @@ -110,6 +110,7 @@ The following APIs can be combined with `nonNullableAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/nonNullishAsync/index.mdx b/website/src/routes/api/(async)/nonNullishAsync/index.mdx index 66fc514c6..1ed36b2d6 100644 --- a/website/src/routes/api/(async)/nonNullishAsync/index.mdx +++ b/website/src/routes/api/(async)/nonNullishAsync/index.mdx @@ -106,6 +106,7 @@ The following APIs can be combined with `nonNullishAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/nonOptionalAsync/index.mdx b/website/src/routes/api/(async)/nonOptionalAsync/index.mdx index c42fb1a44..7bf3ef3be 100644 --- a/website/src/routes/api/(async)/nonOptionalAsync/index.mdx +++ b/website/src/routes/api/(async)/nonOptionalAsync/index.mdx @@ -116,6 +116,7 @@ The following APIs can be combined with `nonOptionalAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/objectAsync/index.mdx b/website/src/routes/api/(async)/objectAsync/index.mdx index 7579b7c4f..c0da376d0 100644 --- a/website/src/routes/api/(async)/objectAsync/index.mdx +++ b/website/src/routes/api/(async)/objectAsync/index.mdx @@ -113,6 +113,7 @@ The following APIs can be combined with `objectAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/objectWithRestAsync/index.mdx b/website/src/routes/api/(async)/objectWithRestAsync/index.mdx index a73652afe..d07c1b371 100644 --- a/website/src/routes/api/(async)/objectWithRestAsync/index.mdx +++ b/website/src/routes/api/(async)/objectWithRestAsync/index.mdx @@ -127,6 +127,7 @@ The following APIs can be combined with `objectWithRestAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/parseAsync/index.mdx b/website/src/routes/api/(async)/parseAsync/index.mdx index bf798f77e..d432742b0 100644 --- a/website/src/routes/api/(async)/parseAsync/index.mdx +++ b/website/src/routes/api/(async)/parseAsync/index.mdx @@ -108,6 +108,7 @@ The following APIs can be combined with `parseAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/parserAsync/index.mdx b/website/src/routes/api/(async)/parserAsync/index.mdx index a738a07f6..723de006b 100644 --- a/website/src/routes/api/(async)/parserAsync/index.mdx +++ b/website/src/routes/api/(async)/parserAsync/index.mdx @@ -102,6 +102,7 @@ The following APIs can be combined with `parserAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/safeParseAsync/index.mdx b/website/src/routes/api/(async)/safeParseAsync/index.mdx index 754be47e0..87e444d72 100644 --- a/website/src/routes/api/(async)/safeParseAsync/index.mdx +++ b/website/src/routes/api/(async)/safeParseAsync/index.mdx @@ -1,10 +1,165 @@ --- title: safeParseAsync +description: Parses an unknown input based on a schema. source: /methods/safeParse/safeParseAsync.ts contributors: - fabian-hiller + - EltonLobo07 --- +import { ApiList, Property } from '~/components'; +import { properties } from './properties'; + # safeParseAsync -> The content of this page is not yet ready. Until then, please use the [source code](https://github.com/fabian-hiller/valibot/blob/main/library/src/methods/safeParse/safeParseAsync.ts) or take a look at [issue #287](https://github.com/fabian-hiller/valibot/issues/287) to help us extend the API reference. +Parses an unknown input based on a schema. + +```ts +const result = v.safeParseAsync(schema, input, config); +``` + +## Generics + +- `TSchema` + +## Parameters + +- `schema` +- `input` +- `config` + +## Returns + +- `result` + +## Example + +The following example shows how `safeParseAsync` can be used. + +```ts +import { isEmailPresent } from '~/api'; + +const StoredEmailSchema = v.pipeAsync( + v.string(), + v.email(), + v.checkAsync(isEmailPresent, 'The email is not in the database.') +); +const result = await v.safeParseAsync(StoredEmailSchema, 'jane@example.com'); + +if (result.success) { + const storedEmail = result.output; +} else { + console.error(result.issues); +} +``` + +## Related + +The following APIs can be combined with `safeParseAsync`. + +### Schemas + + + +### Methods + + + +### Utils + + + +### Async + + diff --git a/website/src/routes/api/(async)/safeParseAsync/properties.ts b/website/src/routes/api/(async)/safeParseAsync/properties.ts new file mode 100644 index 000000000..516deb2b1 --- /dev/null +++ b/website/src/routes/api/(async)/safeParseAsync/properties.ts @@ -0,0 +1,96 @@ +import type { PropertyProps } from '~/components'; + +export const properties: Record = { + TSchema: { + modifier: 'extends', + type: { + type: 'union', + options: [ + { + type: 'custom', + name: 'BaseSchema', + href: '../BaseSchema/', + generics: [ + 'unknown', + 'unknown', + { + type: 'custom', + name: 'BaseIssue', + href: '../BaseIssue/', + generics: ['unknown'], + }, + ], + }, + { + type: 'custom', + name: 'BaseSchemaAsync', + href: '../BaseSchemaAsync/', + generics: [ + 'unknown', + 'unknown', + { + type: 'custom', + name: 'BaseIssue', + href: '../BaseIssue/', + generics: ['unknown'], + }, + ], + }, + ], + }, + }, + schema: { + type: { + type: 'custom', + name: 'TSchema', + }, + }, + input: { + type: 'unknown', + }, + config: { + type: { + type: 'union', + options: [ + { + type: 'custom', + name: 'Config', + href: '../Config/', + generics: [ + { + type: 'custom', + name: 'InferIssue', + href: '../InferIssue/', + generics: [ + { + type: 'custom', + name: 'TSchema', + }, + ], + }, + ], + }, + 'undefined', + ], + }, + }, + result: { + type: { + type: 'custom', + name: 'Promise', + generics: [ + { + type: 'custom', + name: 'SafeParseResult', + href: '../SafeParseResult/', + generics: [ + { + type: 'custom', + name: 'TSchema', + }, + ], + }, + ], + }, + }, +}; diff --git a/website/src/routes/api/(async)/setAsync/index.mdx b/website/src/routes/api/(async)/setAsync/index.mdx index e848da89d..bd54032bb 100644 --- a/website/src/routes/api/(async)/setAsync/index.mdx +++ b/website/src/routes/api/(async)/setAsync/index.mdx @@ -103,6 +103,7 @@ The following APIs can be combined with `setAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/strictObjectAsync/index.mdx b/website/src/routes/api/(async)/strictObjectAsync/index.mdx index 02c5b08f7..0584b0c0f 100644 --- a/website/src/routes/api/(async)/strictObjectAsync/index.mdx +++ b/website/src/routes/api/(async)/strictObjectAsync/index.mdx @@ -113,6 +113,7 @@ The following APIs can be combined with `strictObjectAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/strictTupleAsync/index.mdx b/website/src/routes/api/(async)/strictTupleAsync/index.mdx index 566aee2b7..01715affd 100644 --- a/website/src/routes/api/(async)/strictTupleAsync/index.mdx +++ b/website/src/routes/api/(async)/strictTupleAsync/index.mdx @@ -107,6 +107,7 @@ The following APIs can be combined with `strictTupleAsync`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/tupleAsync/index.mdx b/website/src/routes/api/(async)/tupleAsync/index.mdx index 1d0230276..e8bff9e0a 100644 --- a/website/src/routes/api/(async)/tupleAsync/index.mdx +++ b/website/src/routes/api/(async)/tupleAsync/index.mdx @@ -107,6 +107,7 @@ The following APIs can be combined with `tupleAsync`. 'symbol', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/tupleWithRestAsync/index.mdx b/website/src/routes/api/(async)/tupleWithRestAsync/index.mdx index 1c644a6a7..743534ac7 100644 --- a/website/src/routes/api/(async)/tupleWithRestAsync/index.mdx +++ b/website/src/routes/api/(async)/tupleWithRestAsync/index.mdx @@ -114,6 +114,7 @@ The following APIs can be combined with `tupleWithRestAsync`. 'symbol', 'tuple', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(async)/undefinedableAsync/index.mdx b/website/src/routes/api/(async)/undefinedableAsync/index.mdx new file mode 100644 index 000000000..2152a8594 --- /dev/null +++ b/website/src/routes/api/(async)/undefinedableAsync/index.mdx @@ -0,0 +1,10 @@ +--- +title: undefinedableAsync +source: /schemas/undefinedable/undefinedableAsync.ts +contributors: + - fabian-hiller +--- + +# undefinedableAsync + +> The content of this page is not yet ready. Until then, please use the [source code](https://github.com/fabian-hiller/valibot/blob/main/library/src/schemas/undefinedable/undefinedableAsync.ts) or take a look at [issue #287](https://github.com/fabian-hiller/valibot/issues/287) to help us extend the API reference. diff --git a/website/src/routes/api/(async)/unionAsync/index.mdx b/website/src/routes/api/(async)/unionAsync/index.mdx index 2e30ee54b..e9c89be1b 100644 --- a/website/src/routes/api/(async)/unionAsync/index.mdx +++ b/website/src/routes/api/(async)/unionAsync/index.mdx @@ -1,10 +1,258 @@ --- title: unionAsync +description: Creates an union schema. source: /schemas/union/unionAsync.ts contributors: - fabian-hiller + - EltonLobo07 --- +import { Link } from '@builder.io/qwik-city'; +import { ApiList, Property } from '~/components'; +import { properties } from './properties'; + # unionAsync -> The content of this page is not yet ready. Until then, please use the [source code](https://github.com/fabian-hiller/valibot/blob/main/library/src/schemas/union/unionAsync.ts) or take a look at [issue #287](https://github.com/fabian-hiller/valibot/issues/287) to help us extend the API reference. +Creates an union schema. + +> I recommend that you read the unions guide before using this schema function. + +```ts +const Schema = v.unionAsync(options, message); +``` + +## Generics + +- `TOptions` +- `TMessage` + +## Parameters + +- `options` +- `message` + +### Explanation + +With `unionAsync` you can validate if the input matches one of the given `options`. If the input does not match a schema and cannot be clearly assigned to one of the options, you can use `message` to customize the error message. + +If a bad input can be uniquely assigned to one of the schemas based on the data type, the result of that schema is returned. Otherwise, a general issue is returned that contains the issues of each schema as subissues. This is a special case within the library, as the issues of `unionAsync` can contradict each other. + +## Returns + +- `Schema` + +## Examples + +The following examples show how `unionAsync` can be used. + +### User schema + +Schema to validate a user's email or username. + +```ts +import { isEmailPresent, isUsernamePresent } from '~/api'; + +const UserSchema = v.unionAsync([ + v.pipeAsync( + v.string(), + v.email(), + v.checkAsync(isEmailPresent, 'The email is not in the database.') + ), + v.pipeAsync( + v.string(), + v.nonEmpty(), + v.checkAsync(isUsernamePresent, 'The username is not in the database.') + ), +]); +``` + +## Related + +The following APIs can be combined with `unionAsync`. + +### Schemas + + + +### Methods + + + +### Actions + + + +### Utils + + + +### Async + + diff --git a/website/src/routes/api/(async)/unionAsync/properties.ts b/website/src/routes/api/(async)/unionAsync/properties.ts new file mode 100644 index 000000000..7deef5cbf --- /dev/null +++ b/website/src/routes/api/(async)/unionAsync/properties.ts @@ -0,0 +1,76 @@ +import type { PropertyProps } from '~/components'; + +export const properties: Record = { + TOptions: { + modifier: 'extends', + type: { + type: 'custom', + name: 'UnionOptionsAsync', + href: '../UnionOptionsAsync/', + }, + }, + TMessage: { + modifier: 'extends', + type: { + type: 'union', + options: [ + { + type: 'custom', + name: 'ErrorMessage', + href: '../ErrorMessage/', + generics: [ + { + type: 'custom', + name: 'UnionIssue', + href: '../UnionIssue/', + generics: [ + { + type: 'custom', + name: 'InferIssue', + href: '../InferIssue/', + generics: [ + { + type: 'custom', + name: 'TOptions', + indexes: ['number'], + }, + ], + }, + ], + }, + ], + }, + 'undefined', + ], + }, + }, + options: { + type: { + type: 'custom', + name: 'TOptions', + }, + }, + message: { + type: { + type: 'custom', + name: 'TMessage', + }, + }, + Schema: { + type: { + type: 'custom', + name: 'UnionSchemaAsync', + href: '../UnionSchemaAsync/', + generics: [ + { + type: 'custom', + name: 'TOptions', + }, + { + type: 'custom', + name: 'TMessage', + }, + ], + }, + }, +}; diff --git a/website/src/routes/api/(methods)/config/index.mdx b/website/src/routes/api/(methods)/config/index.mdx index ae0d156b4..76de8a95a 100644 --- a/website/src/routes/api/(methods)/config/index.mdx +++ b/website/src/routes/api/(methods)/config/index.mdx @@ -114,6 +114,7 @@ The following APIs can be combined with `config`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(methods)/fallback/index.mdx b/website/src/routes/api/(methods)/fallback/index.mdx index 1c55f39f1..aaa4ee8e8 100644 --- a/website/src/routes/api/(methods)/fallback/index.mdx +++ b/website/src/routes/api/(methods)/fallback/index.mdx @@ -113,6 +113,7 @@ The following APIs can be combined with `fallback`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(methods)/getDefault/index.mdx b/website/src/routes/api/(methods)/getDefault/index.mdx index 7f19715fb..6898fd5eb 100644 --- a/website/src/routes/api/(methods)/getDefault/index.mdx +++ b/website/src/routes/api/(methods)/getDefault/index.mdx @@ -92,6 +92,7 @@ The following APIs can be combined with `getDefault`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(methods)/getDefaults/index.mdx b/website/src/routes/api/(methods)/getDefaults/index.mdx index a0b33a377..60fc3495c 100644 --- a/website/src/routes/api/(methods)/getDefaults/index.mdx +++ b/website/src/routes/api/(methods)/getDefaults/index.mdx @@ -106,6 +106,7 @@ The following APIs can be combined with `getDefaults`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(methods)/getFallback/index.mdx b/website/src/routes/api/(methods)/getFallback/index.mdx index b2e869f86..8c7715662 100644 --- a/website/src/routes/api/(methods)/getFallback/index.mdx +++ b/website/src/routes/api/(methods)/getFallback/index.mdx @@ -92,6 +92,7 @@ The following APIs can be combined with `getFallback`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(methods)/getFallbacks/index.mdx b/website/src/routes/api/(methods)/getFallbacks/index.mdx index 65a632370..b2c2af28b 100644 --- a/website/src/routes/api/(methods)/getFallbacks/index.mdx +++ b/website/src/routes/api/(methods)/getFallbacks/index.mdx @@ -106,6 +106,7 @@ The following APIs can be combined with `getFallbacks`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(methods)/is/index.mdx b/website/src/routes/api/(methods)/is/index.mdx index 506aab3da..0de780b6e 100644 --- a/website/src/routes/api/(methods)/is/index.mdx +++ b/website/src/routes/api/(methods)/is/index.mdx @@ -93,6 +93,7 @@ The following APIs can be combined with `is`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(methods)/keyof/index.mdx b/website/src/routes/api/(methods)/keyof/index.mdx index 019c18fb1..ca25d9ca5 100644 --- a/website/src/routes/api/(methods)/keyof/index.mdx +++ b/website/src/routes/api/(methods)/keyof/index.mdx @@ -72,6 +72,7 @@ The following APIs can be combined with `keyof`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(methods)/omit/index.mdx b/website/src/routes/api/(methods)/omit/index.mdx index 262cbd840..a50ef4e1d 100644 --- a/website/src/routes/api/(methods)/omit/index.mdx +++ b/website/src/routes/api/(methods)/omit/index.mdx @@ -87,6 +87,7 @@ The following APIs can be combined with `omit`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(methods)/parse/index.mdx b/website/src/routes/api/(methods)/parse/index.mdx index 5c54b915a..0223eaee4 100644 --- a/website/src/routes/api/(methods)/parse/index.mdx +++ b/website/src/routes/api/(methods)/parse/index.mdx @@ -99,6 +99,7 @@ The following APIs can be combined with `parse`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(methods)/parser/index.mdx b/website/src/routes/api/(methods)/parser/index.mdx index 90a1c16b7..34ec74749 100644 --- a/website/src/routes/api/(methods)/parser/index.mdx +++ b/website/src/routes/api/(methods)/parser/index.mdx @@ -95,6 +95,7 @@ The following APIs can be combined with `parser`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(methods)/partial/index.mdx b/website/src/routes/api/(methods)/partial/index.mdx index a601810c5..9dc785bc2 100644 --- a/website/src/routes/api/(methods)/partial/index.mdx +++ b/website/src/routes/api/(methods)/partial/index.mdx @@ -100,6 +100,7 @@ The following APIs can be combined with `partial`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(methods)/pick/index.mdx b/website/src/routes/api/(methods)/pick/index.mdx index 535d5262e..acaaefed4 100644 --- a/website/src/routes/api/(methods)/pick/index.mdx +++ b/website/src/routes/api/(methods)/pick/index.mdx @@ -87,6 +87,7 @@ The following APIs can be combined with `pick`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(methods)/pipe/index.mdx b/website/src/routes/api/(methods)/pipe/index.mdx index 6f4ba987d..5816816f8 100644 --- a/website/src/routes/api/(methods)/pipe/index.mdx +++ b/website/src/routes/api/(methods)/pipe/index.mdx @@ -114,6 +114,7 @@ The following APIs can be combined with `pipe`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(methods)/required/index.mdx b/website/src/routes/api/(methods)/required/index.mdx index cfb263f3f..e825c78d7 100644 --- a/website/src/routes/api/(methods)/required/index.mdx +++ b/website/src/routes/api/(methods)/required/index.mdx @@ -102,6 +102,7 @@ The following APIs can be combined with `required`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(methods)/safeParse/index.mdx b/website/src/routes/api/(methods)/safeParse/index.mdx index e5f8b24b4..e65403c93 100644 --- a/website/src/routes/api/(methods)/safeParse/index.mdx +++ b/website/src/routes/api/(methods)/safeParse/index.mdx @@ -94,6 +94,7 @@ The following APIs can be combined with `safeParse`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(methods)/safeParser/index.mdx b/website/src/routes/api/(methods)/safeParser/index.mdx index 0fb3d55a5..0aa7ff9f7 100644 --- a/website/src/routes/api/(methods)/safeParser/index.mdx +++ b/website/src/routes/api/(methods)/safeParser/index.mdx @@ -95,6 +95,7 @@ The following APIs can be combined with `safeParser`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(methods)/unwrap/index.mdx b/website/src/routes/api/(methods)/unwrap/index.mdx index eb310e5d9..81ce85495 100644 --- a/website/src/routes/api/(methods)/unwrap/index.mdx +++ b/website/src/routes/api/(methods)/unwrap/index.mdx @@ -56,6 +56,7 @@ The following APIs can be combined with `unwrap`. 'nullable', 'nullish', 'optional', + 'undefinedable', ]} /> diff --git a/website/src/routes/api/(schemas)/any/index.mdx b/website/src/routes/api/(schemas)/any/index.mdx index 5b0675bd9..94a56fded 100644 --- a/website/src/routes/api/(schemas)/any/index.mdx +++ b/website/src/routes/api/(schemas)/any/index.mdx @@ -53,6 +53,7 @@ The following APIs can be combined with `any`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/array/index.mdx b/website/src/routes/api/(schemas)/array/index.mdx index 811fdab94..0286fe7f1 100644 --- a/website/src/routes/api/(schemas)/array/index.mdx +++ b/website/src/routes/api/(schemas)/array/index.mdx @@ -130,6 +130,7 @@ The following APIs can be combined with `array`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/bigint/index.mdx b/website/src/routes/api/(schemas)/bigint/index.mdx index 9357de56c..aa6d82fc7 100644 --- a/website/src/routes/api/(schemas)/bigint/index.mdx +++ b/website/src/routes/api/(schemas)/bigint/index.mdx @@ -81,6 +81,7 @@ The following APIs can be combined with `bigint`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/blob/index.mdx b/website/src/routes/api/(schemas)/blob/index.mdx index 7f6259ea5..2a8edb927 100644 --- a/website/src/routes/api/(schemas)/blob/index.mdx +++ b/website/src/routes/api/(schemas)/blob/index.mdx @@ -79,6 +79,7 @@ The following APIs can be combined with `blob`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/boolean/index.mdx b/website/src/routes/api/(schemas)/boolean/index.mdx index 5399d5ebf..794b369f1 100644 --- a/website/src/routes/api/(schemas)/boolean/index.mdx +++ b/website/src/routes/api/(schemas)/boolean/index.mdx @@ -77,6 +77,7 @@ The following APIs can be combined with `boolean`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/custom/index.mdx b/website/src/routes/api/(schemas)/custom/index.mdx index e0ace0339..8c866a639 100644 --- a/website/src/routes/api/(schemas)/custom/index.mdx +++ b/website/src/routes/api/(schemas)/custom/index.mdx @@ -82,6 +82,7 @@ The following APIs can be combined with `custom`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/date/index.mdx b/website/src/routes/api/(schemas)/date/index.mdx index 6159ff584..0ada973d7 100644 --- a/website/src/routes/api/(schemas)/date/index.mdx +++ b/website/src/routes/api/(schemas)/date/index.mdx @@ -86,6 +86,7 @@ The following APIs can be combined with `date`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/enum/index.mdx b/website/src/routes/api/(schemas)/enum/index.mdx index e1bf91c4e..524940333 100644 --- a/website/src/routes/api/(schemas)/enum/index.mdx +++ b/website/src/routes/api/(schemas)/enum/index.mdx @@ -80,6 +80,7 @@ The following APIs can be combined with `enum`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/file/index.mdx b/website/src/routes/api/(schemas)/file/index.mdx index 5b85a0784..7238da78c 100644 --- a/website/src/routes/api/(schemas)/file/index.mdx +++ b/website/src/routes/api/(schemas)/file/index.mdx @@ -79,6 +79,7 @@ The following APIs can be combined with `file`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/function/index.mdx b/website/src/routes/api/(schemas)/function/index.mdx index 8bfc168ea..4cc5de325 100644 --- a/website/src/routes/api/(schemas)/function/index.mdx +++ b/website/src/routes/api/(schemas)/function/index.mdx @@ -61,6 +61,7 @@ The following APIs can be combined with `function`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/instance/index.mdx b/website/src/routes/api/(schemas)/instance/index.mdx index ba21047c6..01f01b696 100644 --- a/website/src/routes/api/(schemas)/instance/index.mdx +++ b/website/src/routes/api/(schemas)/instance/index.mdx @@ -87,6 +87,7 @@ The following APIs can be combined with `instance`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/intersect/index.mdx b/website/src/routes/api/(schemas)/intersect/index.mdx index 0fde37503..93fa38f33 100644 --- a/website/src/routes/api/(schemas)/intersect/index.mdx +++ b/website/src/routes/api/(schemas)/intersect/index.mdx @@ -102,6 +102,7 @@ The following APIs can be combined with `intersect`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/lazy/index.mdx b/website/src/routes/api/(schemas)/lazy/index.mdx index 64cad44df..f9d7f09a2 100644 --- a/website/src/routes/api/(schemas)/lazy/index.mdx +++ b/website/src/routes/api/(schemas)/lazy/index.mdx @@ -139,6 +139,7 @@ The following APIs can be combined with `lazy`. 'undefined', 'union', 'unionWithRest', + 'undefinedable', 'unknown', 'variant', 'void', diff --git a/website/src/routes/api/(schemas)/literal/index.mdx b/website/src/routes/api/(schemas)/literal/index.mdx index 4dee4c7a8..d50c40e48 100644 --- a/website/src/routes/api/(schemas)/literal/index.mdx +++ b/website/src/routes/api/(schemas)/literal/index.mdx @@ -91,6 +91,7 @@ The following APIs can be combined with `literal`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/looseObject/index.mdx b/website/src/routes/api/(schemas)/looseObject/index.mdx index 6ed3710c1..251a4826d 100644 --- a/website/src/routes/api/(schemas)/looseObject/index.mdx +++ b/website/src/routes/api/(schemas)/looseObject/index.mdx @@ -142,6 +142,7 @@ The following APIs can be combined with `looseObject`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/looseTuple/index.mdx b/website/src/routes/api/(schemas)/looseTuple/index.mdx index b9b58e598..ad51bbfef 100644 --- a/website/src/routes/api/(schemas)/looseTuple/index.mdx +++ b/website/src/routes/api/(schemas)/looseTuple/index.mdx @@ -97,6 +97,7 @@ The following APIs can be combined with `looseTuple`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/map/index.mdx b/website/src/routes/api/(schemas)/map/index.mdx index 6cfd58032..42cb25df2 100644 --- a/website/src/routes/api/(schemas)/map/index.mdx +++ b/website/src/routes/api/(schemas)/map/index.mdx @@ -104,6 +104,7 @@ The following APIs can be combined with `map`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/nan/index.mdx b/website/src/routes/api/(schemas)/nan/index.mdx index 740d4206d..14468d037 100644 --- a/website/src/routes/api/(schemas)/nan/index.mdx +++ b/website/src/routes/api/(schemas)/nan/index.mdx @@ -61,6 +61,7 @@ The following APIs can be combined with `nan`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/never/index.mdx b/website/src/routes/api/(schemas)/never/index.mdx index 2976649af..890eb4847 100644 --- a/website/src/routes/api/(schemas)/never/index.mdx +++ b/website/src/routes/api/(schemas)/never/index.mdx @@ -61,6 +61,7 @@ The following APIs can be combined with `never`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/nonNullable/index.mdx b/website/src/routes/api/(schemas)/nonNullable/index.mdx index f98674a12..2cc635b46 100644 --- a/website/src/routes/api/(schemas)/nonNullable/index.mdx +++ b/website/src/routes/api/(schemas)/nonNullable/index.mdx @@ -106,6 +106,7 @@ The following APIs can be combined with `nonNullable`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/nonNullish/index.mdx b/website/src/routes/api/(schemas)/nonNullish/index.mdx index 69b3ad962..3fed2fdd0 100644 --- a/website/src/routes/api/(schemas)/nonNullish/index.mdx +++ b/website/src/routes/api/(schemas)/nonNullish/index.mdx @@ -106,6 +106,7 @@ The following APIs can be combined with `nonNullish`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/nonOptional/index.mdx b/website/src/routes/api/(schemas)/nonOptional/index.mdx index abfd03d33..e737e7ca2 100644 --- a/website/src/routes/api/(schemas)/nonOptional/index.mdx +++ b/website/src/routes/api/(schemas)/nonOptional/index.mdx @@ -106,6 +106,7 @@ The following APIs can be combined with `nonOptional`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/null/index.mdx b/website/src/routes/api/(schemas)/null/index.mdx index bc3d24c80..d854ecf5c 100644 --- a/website/src/routes/api/(schemas)/null/index.mdx +++ b/website/src/routes/api/(schemas)/null/index.mdx @@ -61,6 +61,7 @@ The following APIs can be combined with `null`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/nullable/index.mdx b/website/src/routes/api/(schemas)/nullable/index.mdx index ef02cfbda..8de6bf1ee 100644 --- a/website/src/routes/api/(schemas)/nullable/index.mdx +++ b/website/src/routes/api/(schemas)/nullable/index.mdx @@ -127,6 +127,7 @@ The following APIs can be combined with `nullable`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/nullish/index.mdx b/website/src/routes/api/(schemas)/nullish/index.mdx index 134ca9106..f632517a2 100644 --- a/website/src/routes/api/(schemas)/nullish/index.mdx +++ b/website/src/routes/api/(schemas)/nullish/index.mdx @@ -127,6 +127,7 @@ The following APIs can be combined with `nullish`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/number/index.mdx b/website/src/routes/api/(schemas)/number/index.mdx index 0d96833ff..7a0213562 100644 --- a/website/src/routes/api/(schemas)/number/index.mdx +++ b/website/src/routes/api/(schemas)/number/index.mdx @@ -89,6 +89,7 @@ The following APIs can be combined with `number`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/object/index.mdx b/website/src/routes/api/(schemas)/object/index.mdx index 6f95a3b81..5646c74c9 100644 --- a/website/src/routes/api/(schemas)/object/index.mdx +++ b/website/src/routes/api/(schemas)/object/index.mdx @@ -142,6 +142,7 @@ The following APIs can be combined with `object`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/objectWithRest/index.mdx b/website/src/routes/api/(schemas)/objectWithRest/index.mdx index 22c4f5d95..0e2ea6aec 100644 --- a/website/src/routes/api/(schemas)/objectWithRest/index.mdx +++ b/website/src/routes/api/(schemas)/objectWithRest/index.mdx @@ -160,6 +160,7 @@ The following APIs can be combined with `objectWithRest`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/optional/index.mdx b/website/src/routes/api/(schemas)/optional/index.mdx index 926dd814d..af32a7ec9 100644 --- a/website/src/routes/api/(schemas)/optional/index.mdx +++ b/website/src/routes/api/(schemas)/optional/index.mdx @@ -127,6 +127,7 @@ The following APIs can be combined with `optional`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/picklist/index.mdx b/website/src/routes/api/(schemas)/picklist/index.mdx index d160a4a58..ae54fb89a 100644 --- a/website/src/routes/api/(schemas)/picklist/index.mdx +++ b/website/src/routes/api/(schemas)/picklist/index.mdx @@ -96,6 +96,7 @@ The following APIs can be combined with `picklist`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/promise/index.mdx b/website/src/routes/api/(schemas)/promise/index.mdx index ba4d9a338..323604369 100644 --- a/website/src/routes/api/(schemas)/promise/index.mdx +++ b/website/src/routes/api/(schemas)/promise/index.mdx @@ -77,6 +77,7 @@ The following APIs can be combined with `promise`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/record/index.mdx b/website/src/routes/api/(schemas)/record/index.mdx index 5d75055be..e083cccbc 100644 --- a/website/src/routes/api/(schemas)/record/index.mdx +++ b/website/src/routes/api/(schemas)/record/index.mdx @@ -138,6 +138,7 @@ The following APIs can be combined with `record`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/set/index.mdx b/website/src/routes/api/(schemas)/set/index.mdx index 527751a55..181f0af02 100644 --- a/website/src/routes/api/(schemas)/set/index.mdx +++ b/website/src/routes/api/(schemas)/set/index.mdx @@ -102,6 +102,7 @@ The following APIs can be combined with `set`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/strictObject/index.mdx b/website/src/routes/api/(schemas)/strictObject/index.mdx index 2ee6fb76f..2aea51755 100644 --- a/website/src/routes/api/(schemas)/strictObject/index.mdx +++ b/website/src/routes/api/(schemas)/strictObject/index.mdx @@ -142,6 +142,7 @@ The following APIs can be combined with `strictObject`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/strictTuple/index.mdx b/website/src/routes/api/(schemas)/strictTuple/index.mdx index 9d119ae87..3cd612891 100644 --- a/website/src/routes/api/(schemas)/strictTuple/index.mdx +++ b/website/src/routes/api/(schemas)/strictTuple/index.mdx @@ -97,6 +97,7 @@ The following APIs can be combined with `strictTuple`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/string/index.mdx b/website/src/routes/api/(schemas)/string/index.mdx index eb2f410f8..51be9e044 100644 --- a/website/src/routes/api/(schemas)/string/index.mdx +++ b/website/src/routes/api/(schemas)/string/index.mdx @@ -106,6 +106,7 @@ The following APIs can be combined with `string`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/symbol/index.mdx b/website/src/routes/api/(schemas)/symbol/index.mdx index eaf7d3f07..22922812e 100644 --- a/website/src/routes/api/(schemas)/symbol/index.mdx +++ b/website/src/routes/api/(schemas)/symbol/index.mdx @@ -74,6 +74,7 @@ The following APIs can be combined with `symbol`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/tuple/index.mdx b/website/src/routes/api/(schemas)/tuple/index.mdx index 0388c1b01..9a19a73bd 100644 --- a/website/src/routes/api/(schemas)/tuple/index.mdx +++ b/website/src/routes/api/(schemas)/tuple/index.mdx @@ -97,6 +97,7 @@ The following APIs can be combined with `tuple`. 'symbol', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/tupleWithRest/index.mdx b/website/src/routes/api/(schemas)/tupleWithRest/index.mdx index bfc242852..bb42510c8 100644 --- a/website/src/routes/api/(schemas)/tupleWithRest/index.mdx +++ b/website/src/routes/api/(schemas)/tupleWithRest/index.mdx @@ -100,6 +100,7 @@ The following APIs can be combined with `tupleWithRest`. 'symbol', 'tuple', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/undefined/index.mdx b/website/src/routes/api/(schemas)/undefined/index.mdx index 9b09babac..1d1491829 100644 --- a/website/src/routes/api/(schemas)/undefined/index.mdx +++ b/website/src/routes/api/(schemas)/undefined/index.mdx @@ -61,6 +61,7 @@ The following APIs can be combined with `undefined`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/undefinedable/index.mdx b/website/src/routes/api/(schemas)/undefinedable/index.mdx new file mode 100644 index 000000000..6e74212f0 --- /dev/null +++ b/website/src/routes/api/(schemas)/undefinedable/index.mdx @@ -0,0 +1,179 @@ +--- +title: undefinedable +description: Creates an undefinedable schema. +source: /schemas/undefinedable/undefinedable.ts +contributors: + - fabian-hiller +--- + +import { Link } from '@builder.io/qwik-city'; +import { ApiList, Property } from '~/components'; +import { properties } from './properties'; + +# undefinedable + +Creates an undefinedable schema. + +```ts +const Schema = v.undefinedable(wrapped, default_); +``` + +## Generics + +- `TWrapped` +- `TDefault` + +## Parameters + +- `wrapped` +- `default_` {/* prettier-ignore */} + +### Explanation + +With `undefinedable` the validation of your schema will pass `undefined` inputs, and if you specify a `default_` input value, the schema will use it if the input is `undefined`. For this reason, the output type may differ from the input type of the schema. + +> `undefinedable` behaves exactly the same as `optional` at runtime. The only difference is the input and output type when used for object entries. While `optional` adds a question mark to the key, `undefinedable` does not. + +> Note that `undefinedable` does not accept `null` as an input. If you want to accept `null` inputs, use `nullable`, and if you want to accept `null` and `undefined` inputs, use `nullish` instead. Also, if you want to set a default output value for any invalid input, you should use `fallback` instead. + +## Returns + +- `Schema` + +## Examples + +The following examples show how `undefinedable` can be used. + +### Undefinedable string schema + +Schema that accepts `string` and `undefined`. + +```ts +const UndefinedableStringSchema = v.undefinedable( + v.string(), + "I'm the default!" +); +``` + +### Undefinedable date schema + +Schema that accepts [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) and `undefined`. + +> By using a function as the `default_` parameter, the schema will return a new [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) instance each time the input is `undefined`. + +```ts +const UndefinedableDateSchema = v.undefinedable(v.date(), () => new Date()); +``` + +### Undefinedable entry schema + +Object schema with an undefinedable entry. + +```ts +const UndefinedableEntrySchema = v.object({ + key: v.undefinedable(v.string()), +}); +``` + +### Unwrap undefinedable schema + +Use `unwrap` to undo the effect of `undefinedable`. + +```ts +const UndefinedableNumberSchema = v.undefinedable(v.number()); +const NumberSchema = v.unwrap(UndefinedableNumberSchema); +``` + +## Related + +The following APIs can be combined with `undefinedable`. + +### Schemas + + + +### Methods + + + +### Actions + + + +### Utils + + diff --git a/website/src/routes/api/(schemas)/undefinedable/properties.ts b/website/src/routes/api/(schemas)/undefinedable/properties.ts new file mode 100644 index 000000000..18d48a10b --- /dev/null +++ b/website/src/routes/api/(schemas)/undefinedable/properties.ts @@ -0,0 +1,71 @@ +import type { PropertyProps } from '~/components'; + +export const properties: Record = { + TWrapped: { + modifier: 'extends', + type: { + type: 'custom', + name: 'BaseSchema', + href: '../BaseSchema/', + generics: [ + 'unknown', + 'unknown', + { + type: 'custom', + name: 'BaseIssue', + href: '../BaseIssue/', + generics: ['unknown'], + }, + ], + }, + }, + TDefault: { + modifier: 'extends', + type: { + type: 'union', + options: [ + { + type: 'custom', + name: 'Default', + href: '../Default/', + generics: [ + { + type: 'custom', + name: 'TWrapped', + }, + ], + }, + 'never', + ], + }, + }, + wrapped: { + type: { + type: 'custom', + name: 'TWrapped', + }, + }, + default_: { + type: { + type: 'custom', + name: 'TDefault', + }, + }, + Schema: { + type: { + type: 'custom', + name: 'UndefinedableSchema', + href: '../UndefinedableSchema/', + generics: [ + { + type: 'custom', + name: 'TWrapped', + }, + { + type: 'custom', + name: 'TDefault', + }, + ], + }, + }, +}; diff --git a/website/src/routes/api/(schemas)/union/index.mdx b/website/src/routes/api/(schemas)/union/index.mdx index 51d8941c0..9964e00e1 100644 --- a/website/src/routes/api/(schemas)/union/index.mdx +++ b/website/src/routes/api/(schemas)/union/index.mdx @@ -118,6 +118,7 @@ The following APIs can be combined with `union`. 'tuple', 'tupleWithRest', 'undefined', + 'undefinedable', 'union', 'unknown', 'variant', diff --git a/website/src/routes/api/(schemas)/unknown/index.mdx b/website/src/routes/api/(schemas)/unknown/index.mdx index c4bc7e4ea..689994b72 100644 --- a/website/src/routes/api/(schemas)/unknown/index.mdx +++ b/website/src/routes/api/(schemas)/unknown/index.mdx @@ -53,6 +53,7 @@ The following APIs can be combined with `unknwon`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(schemas)/void/index.mdx b/website/src/routes/api/(schemas)/void/index.mdx index 89e3efdc8..7e309e385 100644 --- a/website/src/routes/api/(schemas)/void/index.mdx +++ b/website/src/routes/api/(schemas)/void/index.mdx @@ -61,6 +61,7 @@ The following APIs can be combined with `void`. 'strictTuple', 'tuple', 'tupleWithRest', + 'undefinedable', 'union', ]} /> diff --git a/website/src/routes/api/(types)/UndefinedableSchema/index.mdx b/website/src/routes/api/(types)/UndefinedableSchema/index.mdx new file mode 100644 index 000000000..cc88ef48b --- /dev/null +++ b/website/src/routes/api/(types)/UndefinedableSchema/index.mdx @@ -0,0 +1,28 @@ +--- +title: UndefinedableSchema +description: Undefinedable schema type. +contributors: + - fabian-hiller + - sqmasep +--- + +import { Property } from '~/components'; +import { properties } from './properties'; + +# UndefinedableSchema + +Undefinedable schema type. + +## Generics + +- `TWrapped` +- `TDefault` + +## Definition + +- `UndefinedableSchema` + - `type` + - `reference` + - `expects` + - `wrapped` + - `default` diff --git a/website/src/routes/api/(types)/UndefinedableSchema/properties.ts b/website/src/routes/api/(types)/UndefinedableSchema/properties.ts new file mode 100644 index 000000000..e41e994aa --- /dev/null +++ b/website/src/routes/api/(types)/UndefinedableSchema/properties.ts @@ -0,0 +1,141 @@ +import type { PropertyProps } from '~/components'; + +export const properties: Record = { + TWrapped: { + modifier: 'extends', + type: { + type: 'custom', + name: 'BaseSchema', + href: '../BaseSchema/', + generics: [ + 'unknown', + 'unknown', + { + type: 'custom', + name: 'BaseIssue', + href: '../BaseIssue/', + generics: ['unknown'], + }, + ], + }, + }, + TDefault: { + modifier: 'extends', + type: { + type: 'custom', + name: 'Default', + href: '../Default/', + generics: [ + { + type: 'custom', + name: 'TWrapped', + }, + 'undefined', + ], + }, + }, + BaseSchema: { + modifier: 'extends', + type: { + type: 'custom', + name: 'BaseSchema', + href: '../BaseSchema/', + generics: [ + { + type: 'union', + options: [ + { + type: 'custom', + name: 'InferInput', + href: '../InferInput/', + generics: [ + { + type: 'custom', + name: 'TWrapped', + }, + ], + }, + 'undefined', + ], + }, + { + type: 'custom', + name: 'InferUndefinedableOutput', + href: '../InferUndefinedableOutput/', + generics: [ + { + type: 'custom', + name: 'TWrapped', + }, + { + type: 'custom', + name: 'TDefault', + }, + ], + }, + { + type: 'custom', + name: 'InferIssue', + href: '../InferIssue/', + generics: [ + { + type: 'custom', + name: 'TWrapped', + }, + ], + }, + ], + }, + }, + type: { + type: { + type: 'string', + value: 'undefinedable', + }, + }, + reference: { + type: { + type: 'custom', + modifier: 'typeof', + name: 'undefinedable', + href: '../undefinedable/', + }, + }, + expects: { + type: { + type: 'template', + parts: [ + { + type: 'string', + value: '(', + }, + { + type: 'custom', + name: 'TWrapped', + indexes: [ + { + type: 'string', + value: 'expects', + }, + ], + }, + { + type: 'string', + value: ' | undefined)', + }, + ], + }, + }, + wrapped: { + type: { + type: 'custom', + name: 'TWrapped', + }, + }, + default: { + type: { + type: 'custom', + name: 'TDefault', + }, + }, +}; diff --git a/website/src/routes/api/(types)/UndefinedableSchemaAsync/index.mdx b/website/src/routes/api/(types)/UndefinedableSchemaAsync/index.mdx new file mode 100644 index 000000000..3de24c86c --- /dev/null +++ b/website/src/routes/api/(types)/UndefinedableSchemaAsync/index.mdx @@ -0,0 +1,27 @@ +--- +title: UndefinedableSchemaAsync +description: Undefinedable schema async type. +contributors: + - fabian-hiller +--- + +import { Property } from '~/components'; +import { properties } from './properties'; + +# UndefinedableSchemaAsync + +Undefinedable schema async type. + +## Generics + +- `TWrapped` +- `TDefault` + +## Definition + +- `UndefinedableSchemaAsync` + - `type` + - `reference` + - `expects` + - `wrapped` + - `default` diff --git a/website/src/routes/api/(types)/UndefinedableSchemaAsync/properties.ts b/website/src/routes/api/(types)/UndefinedableSchemaAsync/properties.ts new file mode 100644 index 000000000..aab595c9e --- /dev/null +++ b/website/src/routes/api/(types)/UndefinedableSchemaAsync/properties.ts @@ -0,0 +1,160 @@ +import type { PropertyProps } from '~/components'; + +export const properties: Record = { + TWrapped: { + modifier: 'extends', + type: { + type: 'union', + options: [ + { + type: 'custom', + name: 'BaseSchema', + href: '../BaseSchema/', + generics: [ + 'unknown', + 'unknown', + { + type: 'custom', + name: 'BaseIssue', + href: '../BaseIssue/', + generics: ['unknown'], + }, + ], + }, + { + type: 'custom', + name: 'BaseSchemaAsync', + href: '../BaseSchemaAsync/', + generics: [ + 'unknown', + 'unknown', + { + type: 'custom', + name: 'BaseIssue', + href: '../BaseIssue/', + generics: ['unknown'], + }, + ], + }, + ], + }, + }, + TDefault: { + modifier: 'extends', + type: { + type: 'custom', + name: 'DefaultAsync', + href: '../DefaultAsync/', + generics: [ + { + type: 'custom', + name: 'TWrapped', + }, + 'undefined', + ], + }, + }, + BaseSchemaAsync: { + type: { + type: 'custom', + name: 'BaseSchemaAsync', + href: '../BaseSchemaAsync/', + generics: [ + { + type: 'union', + options: [ + { + type: 'custom', + name: 'InferInput', + href: '../InferInput/', + generics: [ + { + type: 'custom', + name: 'TWrapped', + }, + ], + }, + 'undefined', + ], + }, + { + type: 'custom', + name: 'InferUndefinedableOutput', + href: '../InferUndefinedableOutput/', + generics: [ + { + type: 'custom', + name: 'TWrapped', + }, + { + type: 'custom', + name: 'TDefault', + }, + ], + }, + { + type: 'custom', + name: 'InferIssue', + href: '../InferIssue/', + generics: [ + { + type: 'custom', + name: 'TWrapped', + }, + ], + }, + ], + }, + }, + type: { + type: { + type: 'string', + value: 'undefinedable', + }, + }, + reference: { + type: { + type: 'custom', + modifier: 'typeof', + name: 'undefinedableAsync', + href: '../undefinedableAsync/', + }, + }, + expects: { + type: { + type: 'template', + parts: [ + { + type: 'string', + value: '(', + }, + { + type: 'custom', + name: 'TWrapped', + indexes: [ + { + type: 'string', + value: 'expects', + }, + ], + }, + { + type: 'string', + value: ' | undefined)', + }, + ], + }, + }, + wrapped: { + type: { + type: 'custom', + name: 'TWrapped', + }, + }, + default: { + type: { + type: 'custom', + name: 'TDefault', + }, + }, +}; diff --git a/website/src/routes/api/(utils)/entriesFromList/index.mdx b/website/src/routes/api/(utils)/entriesFromList/index.mdx index c9b318034..8af47e0f8 100644 --- a/website/src/routes/api/(utils)/entriesFromList/index.mdx +++ b/website/src/routes/api/(utils)/entriesFromList/index.mdx @@ -87,6 +87,7 @@ The following APIs can be combined with `entriesFromList`. 'symbol', 'tuple', 'undefined', + 'undefinedable', 'union', 'unionWithRest', 'unknown', diff --git a/website/src/routes/api/menu.md b/website/src/routes/api/menu.md index 4f0b792ad..de2bde510 100644 --- a/website/src/routes/api/menu.md +++ b/website/src/routes/api/menu.md @@ -42,6 +42,7 @@ - [tuple](/api/tuple/) - [tupleWithRest](/api/tupleWithRest/) - [undefined](/api/undefined/) +- [undefinedable](/api/undefinedable/) - [union](/api/union/) - [unknown](/api/unknown/) - [variant](/api/variant/) @@ -217,6 +218,7 @@ - [transformAsync](/api/transformAsync/) - [tupleAsync](/api/tupleAsync/) - [tupleWithRestAsync](/api/tupleWithRestAsync/) +- [undefinedableAsync](/api/undefinedableAsync/) - [unionAsync](/api/unionAsync/) - [variantAsync](/api/variantAsync/) @@ -561,6 +563,8 @@ - [TypedDataset](/api/TypedDataset/) - [UlidAction](/api/UlidAction/) - [UlidIssue](/api/UlidIssue/) +- [UndefinedableSchema](/api/UndefinedableSchema/) +- [UndefinedableSchemaAsync](/api/UndefinedableSchemaAsync/) - [UndefinedIssue](/api/UndefinedIssue/) - [UndefinedSchema](/api/UndefinedSchema/) - [UnionOptions](/api/UnionOptions/) diff --git a/website/src/routes/guides/(main-concepts)/infer-types/index.mdx b/website/src/routes/guides/(main-concepts)/infer-types/index.mdx index 1a078146e..b1d56a3c1 100644 --- a/website/src/routes/guides/(main-concepts)/infer-types/index.mdx +++ b/website/src/routes/guides/(main-concepts)/infer-types/index.mdx @@ -33,7 +33,7 @@ type LoginInput = v.InferInput; // { email: string; password ## Infer output types -The output type differs from the input type only if you use `optional`, `nullable` or `nullish` with a default value or `brand`, `readonly` or `transform` to transform the input or data type of a schema after validation. The output type corresponds to the output of `parse` and `safeParse`. To infer it, you use the utility type `InferOutput`. +The output type differs from the input type only if you use `optional`, `nullable`, `nullish` or `undefinedable` with a default value or `brand`, `readonly` or `transform` to transform the input or data type of a schema after validation. The output type corresponds to the output of `parse` and `safeParse`. To infer it, you use the utility type `InferOutput`. ```ts import * as v from 'valibot'; diff --git a/website/src/routes/guides/(main-concepts)/schemas/index.mdx b/website/src/routes/guides/(main-concepts)/schemas/index.mdx index 976130d48..ad5bfe295 100644 --- a/website/src/routes/guides/(main-concepts)/schemas/index.mdx +++ b/website/src/routes/guides/(main-concepts)/schemas/index.mdx @@ -121,6 +121,7 @@ Beyond primitive and complex values, I also provide schema functions for more sp 'nullish', 'optional', 'picklist', + 'undefinedable', 'union', 'unknown', 'variant', @@ -147,6 +148,7 @@ const NullableSchema = v.nullable(v.string()); // string | null const NullishSchema = v.nullish(v.string()); // string | null | undefined const OptionalSchema = v.optional(v.string()); // string | undefined const PicklistSchema = v.picklist(['a', 'b']); // 'a' | 'b' +const UndefinedableSchema = v.undefinedable(v.string()); // string | undefined const UnionSchema = v.union([v.string(), v.number()]); // string | number const UnknownSchema = v.unknown(); // unknown const VariantSchema = v.variant('type', [ diff --git a/website/src/routes/guides/(schemas)/optionals/index.mdx b/website/src/routes/guides/(schemas)/optionals/index.mdx index 159714463..520e0407e 100644 --- a/website/src/routes/guides/(schemas)/optionals/index.mdx +++ b/website/src/routes/guides/(schemas)/optionals/index.mdx @@ -11,11 +11,11 @@ import { Link } from '@builder.io/qwik-city'; # Optionals -It often happens that `undefined` or `null` should also be accepted instead of the value. To make the API more readable for this and to reduce boilerplate, I offer a shortcut for this functionality with `optional`, `nullable` and `nullish`. +It often happens that `undefined` or `null` should also be accepted instead of the value. To make the API more readable for this and to reduce boilerplate, I offer a shortcut for this functionality with `optional`, `nullable`, `nullish` and `undefinedable`. ## How it works -To accept `undefined` or `null` besides your actual value, you just have to wrap the schema in `optional`, `nullable` and `nullish`. +To accept `undefined` or `null` besides your actual value, you just have to wrap the schema in `optional`, `nullable`, and `undefinedable`. ```ts import * as v from 'valibot'; @@ -23,6 +23,7 @@ import * as v from 'valibot'; const OptionalStringSchema = v.optional(v.string()); // string | undefined const NullableStringSchema = v.nullable(v.string()); // string | null const NullishStringSchema = v.nullish(v.string()); // string | null | undefined +const UndefinedableStringSchema = v.undefinedable(v.string()); // string | undefined ``` ### Use in objects @@ -37,7 +38,7 @@ const OptionalKeySchema = v.object({ key: v.optional(v.string()) }); // { key?: ## Default values -The special thing about `optional`, `nullable` and `nullish` is that the schema functions accept a default value as the second argument. Depending on the schema function, this default value is always used if the input is `undefined` or `null`. +The special thing about `optional`, `nullable`, `nullish` and `undefinedable` is that the schema functions accept a default value as the second argument. Depending on the schema function, this default value is always used if the input is `undefined` or `null`. ```ts import * as v from 'valibot';