diff --git a/.changeset/stupid-chicken-itch.md b/.changeset/stupid-chicken-itch.md new file mode 100644 index 00000000000..9151839bb06 --- /dev/null +++ b/.changeset/stupid-chicken-itch.md @@ -0,0 +1,5 @@ +--- +'@builder.io/qwik-city': minor +--- + +feat: add `valibot$` validator and fix types of `zod$` implementation diff --git a/packages/docs/src/routes/api/qwik-city/api.json b/packages/docs/src/routes/api/qwik-city/api.json index 4e3e7dd3d0a..ab78ed2768e 100644 --- a/packages/docs/src/routes/api/qwik-city/api.json +++ b/packages/docs/src/routes/api/qwik-city/api.json @@ -26,7 +26,7 @@ } ], "kind": "TypeAlias", - "content": "```typescript\nexport type ActionConstructor = {\n | void | null, VALIDATOR extends TypedDataValidator, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (data: GetValidatorType, event: RequestEventAction) => ValueOrPromise, options: {\n readonly id?: string;\n readonly validation: [VALIDATOR, ...REST];\n }): Action>> | FailReturn>>, GetValidatorType, false>;\n | void | null, VALIDATOR extends TypedDataValidator>(actionQrl: (data: GetValidatorType, event: RequestEventAction) => ValueOrPromise, options: {\n readonly id?: string;\n readonly validation: [VALIDATOR];\n }): Action>>>, GetValidatorType, false>;\n | void | null, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (data: JSONObject, event: RequestEventAction) => ValueOrPromise, options: {\n readonly id?: string;\n readonly validation: REST;\n }): Action>>>;\n | void | null, VALIDATOR extends TypedDataValidator, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (data: GetValidatorType, event: RequestEventAction) => ValueOrPromise, options: VALIDATOR, ...rest: REST): Action>> | FailReturn>>, GetValidatorType, false>;\n | void | null, VALIDATOR extends TypedDataValidator>(actionQrl: (data: GetValidatorType, event: RequestEventAction) => ValueOrPromise, options: VALIDATOR): Action>>>, GetValidatorType, false>;\n | void | null, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (form: JSONObject, event: RequestEventAction) => ValueOrPromise, ...rest: REST): Action>>>;\n (actionQrl: (form: JSONObject, event: RequestEventAction) => ValueOrPromise, options?: {\n readonly id?: string;\n }): Action>;\n};\n```\n**References:** [TypedDataValidator](#typeddatavalidator), [DataValidator](#datavalidator), [GetValidatorType](#getvalidatortype), [Action](#action), [StrictUnion](#strictunion), [FailReturn](#failreturn), [ValidatorErrorType](#validatorerrortype), [FailOfRest](#failofrest), [JSONObject](#jsonobject)", + "content": "```typescript\nexport type ActionConstructor = {\n | void | null, VALIDATOR extends TypedDataValidator, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (data: GetValidatorOutputType, event: RequestEventAction) => ValueOrPromise, options: {\n readonly id?: string;\n readonly validation: [VALIDATOR, ...REST];\n }): Action>> | FailReturn>>, GetValidatorInputType, false>;\n | void | null, VALIDATOR extends TypedDataValidator>(actionQrl: (data: GetValidatorOutputType, event: RequestEventAction) => ValueOrPromise, options: {\n readonly id?: string;\n readonly validation: [VALIDATOR];\n }): Action>>>, GetValidatorInputType, false>;\n | void | null, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (data: JSONObject, event: RequestEventAction) => ValueOrPromise, options: {\n readonly id?: string;\n readonly validation: REST;\n }): Action>>>;\n | void | null, VALIDATOR extends TypedDataValidator, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (data: GetValidatorOutputType, event: RequestEventAction) => ValueOrPromise, options: VALIDATOR, ...rest: REST): Action>> | FailReturn>>, GetValidatorInputType, false>;\n | void | null, VALIDATOR extends TypedDataValidator>(actionQrl: (data: GetValidatorOutputType, event: RequestEventAction) => ValueOrPromise, options: VALIDATOR): Action>>>, GetValidatorInputType, false>;\n | void | null, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (form: JSONObject, event: RequestEventAction) => ValueOrPromise, ...rest: REST): Action>>>;\n (actionQrl: (form: JSONObject, event: RequestEventAction) => ValueOrPromise, options?: {\n readonly id?: string;\n }): Action>;\n};\n```\n**References:** [TypedDataValidator](#typeddatavalidator), [DataValidator](#datavalidator), [GetValidatorOutputType](#getvalidatoroutputtype), [Action](#action), [StrictUnion](#strictunion), [FailReturn](#failreturn), [ValidatorErrorType](#validatorerrortype), [GetValidatorInputType](#getvalidatorinputtype), [FailOfRest](#failofrest), [JSONObject](#jsonobject)", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts", "mdFile": "qwik-city.actionconstructor.md" }, @@ -268,6 +268,34 @@ "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/form-component.tsx", "mdFile": "qwik-city.formsubmitsuccessdetail.md" }, + { + "name": "GetValidatorInputType", + "id": "getvalidatorinputtype", + "hierarchy": [ + { + "name": "GetValidatorInputType", + "id": "getvalidatorinputtype" + } + ], + "kind": "TypeAlias", + "content": "```typescript\nexport type GetValidatorInputType = VALIDATOR extends ValibotDataValidator ? v.InferInput : VALIDATOR extends ZodDataValidator ? z.input : never;\n```\n**References:** [TypedDataValidator](#typeddatavalidator)", + "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts", + "mdFile": "qwik-city.getvalidatorinputtype.md" + }, + { + "name": "GetValidatorOutputType", + "id": "getvalidatoroutputtype", + "hierarchy": [ + { + "name": "GetValidatorOutputType", + "id": "getvalidatoroutputtype" + } + ], + "kind": "TypeAlias", + "content": "```typescript\nexport type GetValidatorOutputType = VALIDATOR extends ValibotDataValidator ? v.InferOutput : VALIDATOR extends ZodDataValidator ? z.output : never;\n```\n**References:** [TypedDataValidator](#typeddatavalidator)", + "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts", + "mdFile": "qwik-city.getvalidatoroutputtype.md" + }, { "name": "GetValidatorType", "id": "getvalidatortype", @@ -278,7 +306,7 @@ } ], "kind": "TypeAlias", - "content": "```typescript\nexport type GetValidatorType = VALIDATOR extends TypedDataValidator ? zod.infer : never;\n```\n**References:** [TypedDataValidator](#typeddatavalidator)", + "content": "```typescript\nexport type GetValidatorType = GetValidatorOutputType;\n```\n**References:** [TypedDataValidator](#typeddatavalidator), [GetValidatorOutputType](#getvalidatoroutputtype)", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts", "mdFile": "qwik-city.getvalidatortype.md" }, @@ -782,7 +810,7 @@ } ], "kind": "TypeAlias", - "content": "```typescript\nexport type TypedDataValidator = {\n __zod: zod.ZodSchema;\n validate(ev: RequestEvent, data: unknown): Promise>;\n};\n```", + "content": "```typescript\nexport type TypedDataValidator = ValibotDataValidator | ZodDataValidator;\n```", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts", "mdFile": "qwik-city.typeddatavalidator.md" }, @@ -842,6 +870,34 @@ "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/use-functions.ts", "mdFile": "qwik-city.usenavigate.md" }, + { + "name": "valibot$", + "id": "valibot_", + "hierarchy": [ + { + "name": "valibot$", + "id": "valibot_" + } + ], + "kind": "Variable", + "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n\n\n```typescript\nvalibot$: ValibotConstructor\n```", + "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/server-functions.ts", + "mdFile": "qwik-city.valibot_.md" + }, + { + "name": "valibotQrl", + "id": "valibotqrl", + "hierarchy": [ + { + "name": "valibotQrl", + "id": "valibotqrl" + } + ], + "kind": "Variable", + "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n\n\n```typescript\nvalibotQrl: ValibotConstructorQRL\n```", + "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/server-functions.ts", + "mdFile": "qwik-city.valibotqrl.md" + }, { "name": "validator$", "id": "validator_", @@ -866,7 +922,7 @@ } ], "kind": "TypeAlias", - "content": "```typescript\nexport type ValidatorErrorKeyDotNotation = T extends object ? {\n [K in keyof T & string]: T[K] extends (infer U)[] ? U extends object ? `${Prefix}${K}[]` | `${Prefix}${K}[]${ValidatorErrorKeyDotNotation}` : `${Prefix}${K}[]` : T[K] extends object ? ValidatorErrorKeyDotNotation : `${Prefix}${K}`;\n}[keyof T & string] : never;\n```\n**References:** [ValidatorErrorKeyDotNotation](#validatorerrorkeydotnotation)", + "content": "```typescript\nexport type ValidatorErrorKeyDotNotation = IsAny extends true ? never : T extends object ? {\n [K in keyof T & string]: IsAny extends true ? never : T[K] extends (infer U)[] ? IsAny extends true ? never : U extends object ? `${Prefix}${K}[]` | ValidatorErrorKeyDotNotation : `${Prefix}${K}[]` : T[K] extends object ? ValidatorErrorKeyDotNotation : `${Prefix}${K}`;\n}[keyof T & string] : never;\n```\n**References:** [ValidatorErrorKeyDotNotation](#validatorerrorkeydotnotation)", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts", "mdFile": "qwik-city.validatorerrorkeydotnotation.md" }, @@ -936,7 +992,7 @@ } ], "kind": "TypeAlias", - "content": "```typescript\nexport type ZodConstructor = {\n (schema: T): TypedDataValidator>;\n (schema: (z: typeof zod, ev: RequestEvent) => T): TypedDataValidator>;\n (schema: T): TypedDataValidator;\n (schema: (z: typeof zod, ev: RequestEvent) => T): TypedDataValidator;\n};\n```\n**References:** [TypedDataValidator](#typeddatavalidator)", + "content": "```typescript\nexport type ZodConstructor = {\n (schema: T): ZodDataValidator>;\n (schema: (zod: typeof z.z, ev: RequestEvent) => T): ZodDataValidator>;\n (schema: T): ZodDataValidator;\n (schema: (zod: typeof z.z, ev: RequestEvent) => T): ZodDataValidator;\n};\n```", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts", "mdFile": "qwik-city.zodconstructor.md" }, diff --git a/packages/docs/src/routes/api/qwik-city/index.md b/packages/docs/src/routes/api/qwik-city/index.md index 338c5f116b4..212e33ef9f6 100644 --- a/packages/docs/src/routes/api/qwik-city/index.md +++ b/packages/docs/src/routes/api/qwik-city/index.md @@ -30,7 +30,7 @@ export type ActionConstructor = { REST extends [DataValidator, ...DataValidator[]], >( actionQrl: ( - data: GetValidatorType, + data: GetValidatorOutputType, event: RequestEventAction, ) => ValueOrPromise, options: { @@ -40,10 +40,10 @@ export type ActionConstructor = { ): Action< StrictUnion< | OBJ - | FailReturn>> + | FailReturn>> | FailReturn> >, - GetValidatorType, + GetValidatorInputType, false >; < @@ -51,7 +51,7 @@ export type ActionConstructor = { VALIDATOR extends TypedDataValidator, >( actionQrl: ( - data: GetValidatorType, + data: GetValidatorOutputType, event: RequestEventAction, ) => ValueOrPromise, options: { @@ -60,9 +60,9 @@ export type ActionConstructor = { }, ): Action< StrictUnion< - OBJ | FailReturn>> + OBJ | FailReturn>> >, - GetValidatorType, + GetValidatorInputType, false >; < @@ -84,7 +84,7 @@ export type ActionConstructor = { REST extends [DataValidator, ...DataValidator[]], >( actionQrl: ( - data: GetValidatorType, + data: GetValidatorOutputType, event: RequestEventAction, ) => ValueOrPromise, options: VALIDATOR, @@ -92,10 +92,10 @@ export type ActionConstructor = { ): Action< StrictUnion< | OBJ - | FailReturn>> + | FailReturn>> | FailReturn> >, - GetValidatorType, + GetValidatorInputType, false >; < @@ -103,15 +103,15 @@ export type ActionConstructor = { VALIDATOR extends TypedDataValidator, >( actionQrl: ( - data: GetValidatorType, + data: GetValidatorOutputType, event: RequestEventAction, ) => ValueOrPromise, options: VALIDATOR, ): Action< StrictUnion< - OBJ | FailReturn>> + OBJ | FailReturn>> >, - GetValidatorType, + GetValidatorInputType, false >; < @@ -136,7 +136,7 @@ export type ActionConstructor = { }; ``` -**References:** [TypedDataValidator](#typeddatavalidator), [DataValidator](#datavalidator), [GetValidatorType](#getvalidatortype), [Action](#action), [StrictUnion](#strictunion), [FailReturn](#failreturn), [ValidatorErrorType](#validatorerrortype), [FailOfRest](#failofrest), [JSONObject](#jsonobject) +**References:** [TypedDataValidator](#typeddatavalidator), [DataValidator](#datavalidator), [GetValidatorOutputType](#getvalidatoroutputtype), [Action](#action), [StrictUnion](#strictunion), [FailReturn](#failreturn), [ValidatorErrorType](#validatorerrortype), [GetValidatorInputType](#getvalidatorinputtype), [FailOfRest](#failofrest), [JSONObject](#jsonobject) [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts) @@ -1376,14 +1376,44 @@ T [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/form-component.tsx) +## GetValidatorInputType + +```typescript +export type GetValidatorInputType = + VALIDATOR extends ValibotDataValidator + ? v.InferInput + : VALIDATOR extends ZodDataValidator + ? z.input + : never; +``` + +**References:** [TypedDataValidator](#typeddatavalidator) + +[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts) + +## GetValidatorOutputType + +```typescript +export type GetValidatorOutputType = + VALIDATOR extends ValibotDataValidator + ? v.InferOutput + : VALIDATOR extends ZodDataValidator + ? z.output + : never; +``` + +**References:** [TypedDataValidator](#typeddatavalidator) + +[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts) + ## GetValidatorType ```typescript export type GetValidatorType = - VALIDATOR extends TypedDataValidator ? zod.infer : never; + GetValidatorOutputType; ``` -**References:** [TypedDataValidator](#typeddatavalidator) +**References:** [TypedDataValidator](#typeddatavalidator), [GetValidatorOutputType](#getvalidatoroutputtype) [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts) @@ -2324,13 +2354,7 @@ export type StrictUnion = Prettify>; ## TypedDataValidator ```typescript -export type TypedDataValidator = { - __zod: zod.ZodSchema; - validate( - ev: RequestEvent, - data: unknown, - ): Promise>; -}; +export type TypedDataValidator = ValibotDataValidator | ZodDataValidator; ``` [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts) @@ -2387,6 +2411,26 @@ useNavigate: () => RouteNavigate; [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/use-functions.ts) +## valibot$ + +> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. + +```typescript +valibot$: ValibotConstructor; +``` + +[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/server-functions.ts) + +## valibotQrl + +> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. + +```typescript +valibotQrl: ValibotConstructorQRL; +``` + +[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/server-functions.ts) + ## validator$ ```typescript @@ -2398,22 +2442,26 @@ validator$: ValidatorConstructor; ## ValidatorErrorKeyDotNotation ```typescript -export type ValidatorErrorKeyDotNotation< - T, - Prefix extends string = "", -> = T extends object - ? { - [K in keyof T & string]: T[K] extends (infer U)[] - ? U extends object - ? - | `${Prefix}${K}[]` - | `${Prefix}${K}[]${ValidatorErrorKeyDotNotation}` - : `${Prefix}${K}[]` - : T[K] extends object - ? ValidatorErrorKeyDotNotation - : `${Prefix}${K}`; - }[keyof T & string] - : never; +export type ValidatorErrorKeyDotNotation = + IsAny extends true + ? never + : T extends object + ? { + [K in keyof T & string]: IsAny extends true + ? never + : T[K] extends (infer U)[] + ? IsAny extends true + ? never + : U extends object + ? + | `${Prefix}${K}[]` + | ValidatorErrorKeyDotNotation + : `${Prefix}${K}[]` + : T[K] extends object + ? ValidatorErrorKeyDotNotation + : `${Prefix}${K}`; + }[keyof T & string] + : never; ``` **References:** [ValidatorErrorKeyDotNotation](#validatorerrorkeydotnotation) @@ -2467,19 +2515,17 @@ zod$: ZodConstructor; ```typescript export type ZodConstructor = { - (schema: T): TypedDataValidator>; - ( - schema: (z: typeof zod, ev: RequestEvent) => T, - ): TypedDataValidator>; - (schema: T): TypedDataValidator; - ( - schema: (z: typeof zod, ev: RequestEvent) => T, - ): TypedDataValidator; + (schema: T): ZodDataValidator>; + ( + schema: (zod: typeof z.z, ev: RequestEvent) => T, + ): ZodDataValidator>; + (schema: T): ZodDataValidator; + ( + schema: (zod: typeof z.z, ev: RequestEvent) => T, + ): ZodDataValidator; }; ``` -**References:** [TypedDataValidator](#typeddatavalidator) - [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts) ## zodQrl diff --git a/packages/qwik-city/package.json b/packages/qwik-city/package.json index b36f9543da6..5412b1fdaf5 100644 --- a/packages/qwik-city/package.json +++ b/packages/qwik-city/package.json @@ -9,6 +9,7 @@ "source-map": "^0.7.4", "svgo": "^3.3", "undici": "*", + "valibot": ">=0.36.0 <2", "vfile": "6.0.1", "vite": "^5", "vite-imagetools": "^7", diff --git a/packages/qwik-city/src/runtime/src/api.md b/packages/qwik-city/src/runtime/src/api.md index fb15214a174..c7033bc468b 100644 --- a/packages/qwik-city/src/runtime/src/api.md +++ b/packages/qwik-city/src/runtime/src/api.md @@ -24,9 +24,10 @@ import { RequestEventCommon } from '@builder.io/qwik-city/middleware/request-han import { RequestEventLoader } from '@builder.io/qwik-city/middleware/request-handler'; import { RequestHandler } from '@builder.io/qwik-city/middleware/request-handler'; import type { ResolveSyncValue } from '@builder.io/qwik-city/middleware/request-handler'; +import type * as v from 'valibot'; import type { ValueOrPromise } from '@builder.io/qwik'; import { z } from 'zod'; -import type * as zod from 'zod'; +import type * as z_2 from 'zod'; // @public (undocumented) export type Action, OPTIONAL extends boolean = true> = { @@ -35,20 +36,20 @@ export type Action, OPTIONAL extends boo // @public (undocumented) export type ActionConstructor = { - | void | null, VALIDATOR extends TypedDataValidator, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (data: GetValidatorType, event: RequestEventAction) => ValueOrPromise, options: { + | void | null, VALIDATOR extends TypedDataValidator, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (data: GetValidatorOutputType, event: RequestEventAction) => ValueOrPromise, options: { readonly id?: string; readonly validation: [VALIDATOR, ...REST]; - }): Action>> | FailReturn>>, GetValidatorType, false>; - | void | null, VALIDATOR extends TypedDataValidator>(actionQrl: (data: GetValidatorType, event: RequestEventAction) => ValueOrPromise, options: { + }): Action>> | FailReturn>>, GetValidatorInputType, false>; + | void | null, VALIDATOR extends TypedDataValidator>(actionQrl: (data: GetValidatorOutputType, event: RequestEventAction) => ValueOrPromise, options: { readonly id?: string; readonly validation: [VALIDATOR]; - }): Action>>>, GetValidatorType, false>; + }): Action>>>, GetValidatorInputType, false>; | void | null, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (data: JSONObject, event: RequestEventAction) => ValueOrPromise, options: { readonly id?: string; readonly validation: REST; }): Action>>>; - | void | null, VALIDATOR extends TypedDataValidator, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (data: GetValidatorType, event: RequestEventAction) => ValueOrPromise, options: VALIDATOR, ...rest: REST): Action>> | FailReturn>>, GetValidatorType, false>; - | void | null, VALIDATOR extends TypedDataValidator>(actionQrl: (data: GetValidatorType, event: RequestEventAction) => ValueOrPromise, options: VALIDATOR): Action>>>, GetValidatorType, false>; + | void | null, VALIDATOR extends TypedDataValidator, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (data: GetValidatorOutputType, event: RequestEventAction) => ValueOrPromise, options: VALIDATOR, ...rest: REST): Action>> | FailReturn>>, GetValidatorInputType, false>; + | void | null, VALIDATOR extends TypedDataValidator>(actionQrl: (data: GetValidatorOutputType, event: RequestEventAction) => ValueOrPromise, options: VALIDATOR): Action>>>, GetValidatorInputType, false>; | void | null, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (form: JSONObject, event: RequestEventAction) => ValueOrPromise, ...rest: REST): Action>>>; (actionQrl: (form: JSONObject, event: RequestEventAction) => ValueOrPromise, options?: { readonly id?: string; @@ -235,8 +236,17 @@ export interface FormSubmitSuccessDetail { value: T; } +// Warning: (ae-forgotten-export) The symbol "ValibotDataValidator" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "ZodDataValidator" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type GetValidatorInputType = VALIDATOR extends ValibotDataValidator ? v.InferInput : VALIDATOR extends ZodDataValidator ? z_2.input : never; + // @public (undocumented) -export type GetValidatorType = VALIDATOR extends TypedDataValidator ? zod.infer : never; +export type GetValidatorOutputType = VALIDATOR extends ValibotDataValidator ? v.InferOutput : VALIDATOR extends ZodDataValidator ? z_2.output : never; + +// @public (undocumented) +export type GetValidatorType = GetValidatorOutputType; // @public (undocumented) export const globalAction$: ActionConstructor; @@ -452,10 +462,7 @@ export type StaticGenerateHandler = ({ env, }: { export type StrictUnion = Prettify>; // @public (undocumented) -export type TypedDataValidator = { - __zod: zod.ZodSchema; - validate(ev: RequestEvent, data: unknown): Promise>; -}; +export type TypedDataValidator = ValibotDataValidator | ZodDataValidator; // Warning: (ae-forgotten-export) The symbol "ContentState" needs to be exported by the entry point index.d.ts // @@ -471,14 +478,26 @@ export const useLocation: () => RouteLocation; // @public (undocumented) export const useNavigate: () => RouteNavigate; +// Warning: (ae-forgotten-export) The symbol "ValibotConstructor" needs to be exported by the entry point index.d.ts +// +// @alpha (undocumented) +export const valibot$: ValibotConstructor; + +// Warning: (ae-forgotten-export) The symbol "ValibotConstructorQRL" needs to be exported by the entry point index.d.ts +// +// @alpha (undocumented) +export const valibotQrl: ValibotConstructorQRL; + // Warning: (ae-forgotten-export) The symbol "ValidatorConstructor" needs to be exported by the entry point index.d.ts // // @public (undocumented) export const validator$: ValidatorConstructor; +// Warning: (ae-forgotten-export) The symbol "IsAny" needs to be exported by the entry point index.d.ts +// // @public (undocumented) -export type ValidatorErrorKeyDotNotation = T extends object ? { - [K in keyof T & string]: T[K] extends (infer U)[] ? U extends object ? `${Prefix}${K}[]` | `${Prefix}${K}[]${ValidatorErrorKeyDotNotation}` : `${Prefix}${K}[]` : T[K] extends object ? ValidatorErrorKeyDotNotation : `${Prefix}${K}`; +export type ValidatorErrorKeyDotNotation = IsAny extends true ? never : T extends object ? { + [K in keyof T & string]: IsAny extends true ? never : T[K] extends (infer U)[] ? IsAny extends true ? never : U extends object ? `${Prefix}${K}[]` | ValidatorErrorKeyDotNotation : `${Prefix}${K}[]` : T[K] extends object ? ValidatorErrorKeyDotNotation : `${Prefix}${K}`; }[keyof T & string] : never; // @public (undocumented) @@ -507,10 +526,10 @@ export const zod$: ZodConstructor; // @public (undocumented) export type ZodConstructor = { - (schema: T): TypedDataValidator>; - (schema: (z: typeof zod, ev: RequestEvent) => T): TypedDataValidator>; - (schema: T): TypedDataValidator; - (schema: (z: typeof zod, ev: RequestEvent) => T): TypedDataValidator; + (schema: T): ZodDataValidator>; + (schema: (zod: typeof z_2.z, ev: RequestEvent) => T): ZodDataValidator>; + (schema: T): ZodDataValidator; + (schema: (zod: typeof z_2.z, ev: RequestEvent) => T): ZodDataValidator; }; // Warning: (ae-forgotten-export) The symbol "ZodConstructorQRL" needs to be exported by the entry point index.d.ts diff --git a/packages/qwik-city/src/runtime/src/index.ts b/packages/qwik-city/src/runtime/src/index.ts index b9eed4a3817..3c2b1c3689b 100644 --- a/packages/qwik-city/src/runtime/src/index.ts +++ b/packages/qwik-city/src/runtime/src/index.ts @@ -59,6 +59,7 @@ export { routeAction$, routeActionQrl } from './server-functions'; export { globalAction$, globalActionQrl } from './server-functions'; export { routeLoader$, routeLoaderQrl } from './server-functions'; export { server$, serverQrl } from './server-functions'; +export { valibot$, valibotQrl } from './server-functions'; export { zod$, zodQrl } from './server-functions'; export { validator$, validatorQrl } from './server-functions'; @@ -71,6 +72,8 @@ export type { TypedDataValidator, DataValidator, GetValidatorType, + GetValidatorInputType, + GetValidatorOutputType, FailOfRest, ActionReturn, StrictUnion, diff --git a/packages/qwik-city/src/runtime/src/server-functions.ts b/packages/qwik-city/src/runtime/src/server-functions.ts index ee2190fa9f4..48ab3efe5c2 100644 --- a/packages/qwik-city/src/runtime/src/server-functions.ts +++ b/packages/qwik-city/src/runtime/src/server-functions.ts @@ -13,6 +13,7 @@ import { type ValueOrPromise, } from '@builder.io/qwik'; +import * as v from 'valibot'; import { z } from 'zod'; import type { RequestEventLoader } from '../../middleware/request-handler/types'; import { QACTION_KEY, QDATA_KEY, QFN_KEY } from './constants'; @@ -40,8 +41,12 @@ import type { ValidatorConstructor, ValidatorConstructorQRL, ValidatorReturn, + ValibotConstructor, + ValibotConstructorQRL, + ValibotDataValidator, ZodConstructor, ZodConstructorQRL, + ZodDataValidator, } from './types'; import { useAction, useLocation, useQwikCityEnv } from './use-functions'; @@ -224,53 +229,73 @@ export const validatorQrl = (( /** @public */ export const validator$: ValidatorConstructor = /*#__PURE__*/ implicit$FirstArg(validatorQrl); -/** @public */ -export const zodQrl = (( +const flattenValibotIssues = (issues: v.GenericIssue[]) => { + return issues.reduce>((acc, issue) => { + if (issue.path) { + const hasArrayType = issue.path.some((path) => path.type === 'array'); + if (hasArrayType) { + const keySuffix = issue.expected === 'Array' ? '[]' : ''; + const key = + issue.path + .map((item) => (item.type === 'array' ? '*' : item.key)) + .join('.') + .replace(/\.\*/g, '[]') + keySuffix; + acc[key] = acc[key] || []; + if (Array.isArray(acc[key])) { + (acc[key] as string[]).push(issue.message); + } + return acc; + } else { + acc[issue.path.map((item) => item.key).join('.')] = issue.message; + } + } + return acc; + }, {}); +}; + +/** @alpha */ +export const valibotQrl: ValibotConstructorQRL = ( qrl: QRL< - z.ZodRawShape | z.Schema | ((z: typeof import('zod').z, ev: RequestEvent) => z.ZodRawShape) + | v.GenericSchema + | v.GenericSchemaAsync + | ((ev: RequestEvent) => v.GenericSchema | v.GenericSchemaAsync) > -): DataValidator => { +): ValibotDataValidator => { if (isServer) { return { + __brand: 'valibot', async validate(ev, inputData) { - const schema: Promise = qrl.resolve().then((obj) => { - if (typeof obj === 'function') { - obj = obj(z, ev); - } - if (obj instanceof z.Schema) { - return obj; - } else { - return z.object(obj); - } - }); + const schema: v.GenericSchema | v.GenericSchemaAsync = await qrl + .resolve() + .then((obj) => (typeof obj === 'function' ? obj(ev) : obj)); const data = inputData ?? (await ev.parseBody()); - const result = await (await schema).safeParseAsync(data); + const result = await v.safeParseAsync(schema, data); if (result.success) { - return result; + return { + success: true, + data: result.output, + }; } else { if (isDev) { - console.error( - '\nVALIDATION ERROR\naction$() zod validated failed', - '\n - Issues:', - result.error.issues - ); + console.error('ERROR: Valibot validation failed', result.issues); } - const zodErrorsFlatten = result.error.flatten(); - const fieldErrors = flattenZodIssues(result.error.issues); return { success: false, status: 400, error: { - formErrors: zodErrorsFlatten.formErrors, - fieldErrors: fieldErrors, + formErrors: v.flatten(result.issues).root ?? [], + fieldErrors: flattenValibotIssues(result.issues), }, }; } }, }; } - return undefined as any; -}) as ZodConstructorQRL; + return undefined as never; +}; + +/** @alpha */ +export const valibot$: ValibotConstructor = /*#__PURE__*/ implicit$FirstArg(valibotQrl); const flattenZodIssues = (issues: z.ZodIssue | z.ZodIssue[]) => { issues = Array.isArray(issues) ? issues : [issues]; @@ -284,7 +309,6 @@ const flattenZodIssues = (issues: z.ZodIssue | z.ZodIssue[]) => { .map((path) => (typeof path === 'number' ? '*' : path)) .join('.') .replace(/\.\*/g, '[]') + keySuffix; - acc[key] = acc[key] || []; if (Array.isArray(acc[key])) { (acc[key] as string[]).push(issue.message); @@ -298,7 +322,50 @@ const flattenZodIssues = (issues: z.ZodIssue | z.ZodIssue[]) => { }; /** @public */ -export const zod$ = /*#__PURE__*/ implicit$FirstArg(zodQrl) as ZodConstructor; +export const zodQrl: ZodConstructorQRL = ( + qrl: QRL< + z.ZodRawShape | z.Schema | ((z: typeof import('zod').z, ev: RequestEvent) => z.ZodRawShape) + > +): ZodDataValidator => { + if (isServer) { + return { + __brand: 'zod', + async validate(ev, inputData) { + const schema: z.Schema = await qrl.resolve().then((obj) => { + if (typeof obj === 'function') { + obj = obj(z, ev); + } + if (obj instanceof z.Schema) { + return obj; + } else { + return z.object(obj); + } + }); + const data = inputData ?? (await ev.parseBody()); + const result = await schema.safeParseAsync(data); + if (result.success) { + return result; + } else { + if (isDev) { + console.error('ERROR: Zod validation failed', result.error.issues); + } + return { + success: false, + status: 400, + error: { + formErrors: result.error.flatten().formErrors, + fieldErrors: flattenZodIssues(result.error.issues), + }, + }; + } + }, + }; + } + return undefined as never; +}; + +/** @public */ +export const zod$: ZodConstructor = /*#__PURE__*/ implicit$FirstArg(zodQrl); const deepFreeze = (obj: any) => { Object.getOwnPropertyNames(obj).forEach((prop) => { diff --git a/packages/qwik-city/src/runtime/src/server-functions.unit.ts b/packages/qwik-city/src/runtime/src/server-functions.unit.ts index 982eba71ed5..2e581e62ac4 100644 --- a/packages/qwik-city/src/runtime/src/server-functions.unit.ts +++ b/packages/qwik-city/src/runtime/src/server-functions.unit.ts @@ -1,6 +1,7 @@ import { describe, expectTypeOf, test } from 'vitest'; +import { z } from 'zod'; import { server$ } from './server-functions'; -import type { RequestEventBase } from './types'; +import type { RequestEventBase, ValidatorErrorType } from './types'; describe('types', () => { test('matching', () => () => { @@ -64,4 +65,100 @@ describe('types', () => { }> >(); }); + + test('easy zod type', () => () => { + const zodSchema = z.object({ + username: z.string(), + password: z.string(), + }); + type ErrorType = ValidatorErrorType>['fieldErrors']; + + expectTypeOf().toEqualTypeOf<{ + username?: string; + password?: string; + }>(); + }); + + test('array zod type with string', () => () => { + const zodSchema = z.object({ + arrayWithStrings: z.array(z.string()), + }); + type ErrorType = ValidatorErrorType>['fieldErrors']; + + expectTypeOf().toEqualTypeOf<{ + ['arrayWithStrings[]']?: string[]; + }>(); + }); + + test('array zod type with object', () => () => { + const zodSchema = z.object({ + persons: z.array( + z.object({ + name: z.string(), + age: z.number(), + }) + ), + }); + type ErrorType = ValidatorErrorType>['fieldErrors']; + + expectTypeOf().toEqualTypeOf<{ + 'persons[]'?: string[]; + 'persons[].name'?: string[]; + 'persons[].age'?: string[]; + }>(); + }); + + test('Complex zod type', () => () => { + const BaseUserSchema = z.object({ + id: z.string().uuid(), + username: z.string().min(3).max(20), + email: z.string().email(), + createdAt: z.date().default(new Date()), + isActive: z.boolean().default(true), + someAnyType: z.any(), + roles: z.array(z.enum(['user', 'admin', 'moderator'])).default(['user']), + preferences: z + .object({ + theme: z.enum(['light', 'dark']).default('light'), + notifications: z.boolean().default(true), + }) + .optional(), + }); + + // Schema for an Admin user with additional fields + const AdminUserSchema = BaseUserSchema.extend({ + adminSince: z.date(), + permissions: z.array(z.string()), + }).refine((data) => data.roles.includes('admin'), { + message: 'Admin role must be included in roles', + }); + + // Schema for a Moderator user with additional fields + const ModeratorUserSchema = BaseUserSchema.extend({ + moderatedSections: z.array(z.string()), + }).refine((data) => data.roles.includes('moderator'), { + message: 'Moderator role must be included in roles', + }); + + // Union of all user types + const UserSchema = z.union([AdminUserSchema, ModeratorUserSchema, BaseUserSchema]); + + type ErrorType = ValidatorErrorType>['fieldErrors']; + type EqualType = { + username?: string; + id?: string; + email?: string; + isActive?: string; + preferences?: string; + 'roles[]'?: string[]; + 'permissions[]'?: string[]; + 'moderatedSections[]'?: string[]; + }; + + expectTypeOf().toEqualTypeOf(); + + expectTypeOf().not.toEqualTypeOf<{ + someAnyType?: string; + }>(); + }); }); diff --git a/packages/qwik-city/src/runtime/src/types.ts b/packages/qwik-city/src/runtime/src/types.ts index 49cf554a958..d73371a75a0 100644 --- a/packages/qwik-city/src/runtime/src/types.ts +++ b/packages/qwik-city/src/runtime/src/types.ts @@ -15,7 +15,8 @@ import type { ResolveSyncValue, EnvGetter, } from '@builder.io/qwik-city/middleware/request-handler'; -import type * as zod from 'zod'; +import type * as v from 'valibot'; +import type * as z from 'zod'; export type { Cookie, @@ -344,9 +345,25 @@ export type JSONValue = string | number | boolean | { [x: string]: JSONValue } | /** @public */ export type JSONObject = { [x: string]: JSONValue }; +/** @public */ +export type GetValidatorInputType = + VALIDATOR extends ValibotDataValidator + ? v.InferInput + : VALIDATOR extends ZodDataValidator + ? z.input + : never; + +/** @public */ +export type GetValidatorOutputType = + VALIDATOR extends ValibotDataValidator + ? v.InferOutput + : VALIDATOR extends ZodDataValidator + ? z.output + : never; + /** @public */ export type GetValidatorType = - VALIDATOR extends TypedDataValidator ? zod.infer : never; + GetValidatorOutputType; /** @public */ export interface CommonLoaderActionOptions { @@ -361,18 +378,27 @@ export type FailOfRest = REST extends rea ? ERROR : never; -/** @public */ -export type ValidatorErrorKeyDotNotation = T extends object - ? { - [K in keyof T & string]: T[K] extends (infer U)[] - ? U extends object - ? `${Prefix}${K}[]` | `${Prefix}${K}[]${ValidatorErrorKeyDotNotation}` - : `${Prefix}${K}[]` - : T[K] extends object - ? ValidatorErrorKeyDotNotation - : `${Prefix}${K}`; - }[keyof T & string] - : never; +type IsAny = 0 extends 1 & Type ? true : false; + +/** @public */ +export type ValidatorErrorKeyDotNotation = + IsAny extends true + ? never + : T extends object + ? { + [K in keyof T & string]: IsAny extends true + ? never + : T[K] extends (infer U)[] + ? IsAny extends true + ? never + : U extends object + ? `${Prefix}${K}[]` | ValidatorErrorKeyDotNotation + : `${Prefix}${K}[]` + : T[K] extends object + ? ValidatorErrorKeyDotNotation + : `${Prefix}${K}`; + }[keyof T & string] + : never; /** @public */ export type ValidatorErrorType = { @@ -393,7 +419,7 @@ export type ActionConstructor = { REST extends [DataValidator, ...DataValidator[]], >( actionQrl: ( - data: GetValidatorType, + data: GetValidatorOutputType, event: RequestEventAction ) => ValueOrPromise, options: { @@ -403,17 +429,17 @@ export type ActionConstructor = { ): Action< StrictUnion< | OBJ - | FailReturn>> + | FailReturn>> | FailReturn> >, - GetValidatorType, + GetValidatorInputType, false >; // Use options object, use typed data validator | void | null, VALIDATOR extends TypedDataValidator>( actionQrl: ( - data: GetValidatorType, + data: GetValidatorOutputType, event: RequestEventAction ) => ValueOrPromise, options: { @@ -421,8 +447,8 @@ export type ActionConstructor = { readonly validation: [VALIDATOR]; } ): Action< - StrictUnion>>>, - GetValidatorType, + StrictUnion>>>, + GetValidatorInputType, false >; @@ -442,7 +468,7 @@ export type ActionConstructor = { REST extends [DataValidator, ...DataValidator[]], >( actionQrl: ( - data: GetValidatorType, + data: GetValidatorOutputType, event: RequestEventAction ) => ValueOrPromise, options: VALIDATOR, @@ -450,23 +476,23 @@ export type ActionConstructor = { ): Action< StrictUnion< | OBJ - | FailReturn>> + | FailReturn>> | FailReturn> >, - GetValidatorType, + GetValidatorInputType, false >; // Use typed data validator | void | null, VALIDATOR extends TypedDataValidator>( actionQrl: ( - data: GetValidatorType, + data: GetValidatorOutputType, event: RequestEventAction ) => ValueOrPromise, options: VALIDATOR ): Action< - StrictUnion>>>, - GetValidatorType, + StrictUnion>>>, + GetValidatorInputType, false >; @@ -494,7 +520,7 @@ export type ActionConstructorQRL = { REST extends [DataValidator, ...DataValidator[]], >( actionQrl: QRL< - (data: GetValidatorType, event: RequestEventAction) => ValueOrPromise + (data: GetValidatorOutputType, event: RequestEventAction) => ValueOrPromise >, options: { readonly id?: string; @@ -503,25 +529,25 @@ export type ActionConstructorQRL = { ): Action< StrictUnion< | OBJ - | FailReturn>> + | FailReturn>> | FailReturn> >, - GetValidatorType, + GetValidatorInputType, false >; // Use options object, use typed data validator | void | null, VALIDATOR extends TypedDataValidator>( actionQrl: QRL< - (data: GetValidatorType, event: RequestEventAction) => ValueOrPromise + (data: GetValidatorOutputType, event: RequestEventAction) => ValueOrPromise >, options: { readonly id?: string; readonly validation: [VALIDATOR]; } ): Action< - StrictUnion>>>, - GetValidatorType, + StrictUnion>>>, + GetValidatorInputType, false >; @@ -541,29 +567,29 @@ export type ActionConstructorQRL = { REST extends [DataValidator, ...DataValidator[]], >( actionQrl: QRL< - (data: GetValidatorType, event: RequestEventAction) => ValueOrPromise + (data: GetValidatorOutputType, event: RequestEventAction) => ValueOrPromise >, options: VALIDATOR, ...rest: REST ): Action< StrictUnion< | OBJ - | FailReturn>> + | FailReturn>> | FailReturn> >, - GetValidatorType, + GetValidatorInputType, false >; // Use typed data validator | void | null, VALIDATOR extends TypedDataValidator>( actionQrl: QRL< - (data: GetValidatorType, event: RequestEventAction) => ValueOrPromise + (data: GetValidatorOutputType, event: RequestEventAction) => ValueOrPromise >, options: VALIDATOR ): Action< - StrictUnion>>>, - GetValidatorType, + StrictUnion>>>, + GetValidatorInputType, false >; @@ -772,12 +798,6 @@ export type DataValidator = {}> = { validate(ev: RequestEvent, data: unknown): Promise>; }; -/** @public */ -export type TypedDataValidator = { - __zod: zod.ZodSchema; - validate(ev: RequestEvent, data: unknown): Promise>; -}; - export type ValidatorConstructor = { ( validator: (ev: RequestEvent, data: unknown) => ValueOrPromise @@ -790,28 +810,65 @@ export type ValidatorConstructorQRL = { ): T extends ValidatorReturnFail ? DataValidator : DataValidator; }; +/** @alpha */ +export type ValibotDataValidator< + T extends v.GenericSchema | v.GenericSchemaAsync = v.GenericSchema | v.GenericSchemaAsync, +> = { + readonly __brand: 'valibot'; + validate( + ev: RequestEvent, + data: unknown + ): Promise>>>; +}; + +/** @alpha */ +export type ValibotConstructor = { + (schema: T): ValibotDataValidator; + ( + schema: (ev: RequestEvent) => T + ): ValibotDataValidator; +}; + +/** @alpha */ +export type ValibotConstructorQRL = { + (schema: QRL): ValibotDataValidator; + ( + schema: QRL<(ev: RequestEvent) => T> + ): ValibotDataValidator; +}; + +/** @public */ +export type ZodDataValidator = { + readonly __brand: 'zod'; + validate( + ev: RequestEvent, + data: unknown + ): Promise>>>; +}; + /** @public */ export type ZodConstructor = { - (schema: T): TypedDataValidator>; - ( - schema: (z: typeof zod, ev: RequestEvent) => T - ): TypedDataValidator>; - (schema: T): TypedDataValidator; - (schema: (z: typeof zod, ev: RequestEvent) => T): TypedDataValidator; + (schema: T): ZodDataValidator>; + ( + schema: (zod: typeof z.z, ev: RequestEvent) => T + ): ZodDataValidator>; + (schema: T): ZodDataValidator; + (schema: (zod: typeof z.z, ev: RequestEvent) => T): ZodDataValidator; }; /** @public */ export type ZodConstructorQRL = { - (schema: QRL): TypedDataValidator>; - ( - schema: QRL<(zs: typeof zod, ev: RequestEvent) => T> - ): TypedDataValidator>; - (schema: QRL): TypedDataValidator; - ( - schema: QRL<(z: typeof zod, ev: RequestEvent) => T> - ): TypedDataValidator; + (schema: QRL): ZodDataValidator>; + ( + schema: QRL<(zod: typeof z.z, ev: RequestEvent) => T> + ): ZodDataValidator>; + (schema: QRL): ZodDataValidator; + (schema: QRL<(zod: typeof z.z, ev: RequestEvent) => T>): ZodDataValidator; }; +/** @public */ +export type TypedDataValidator = ValibotDataValidator | ZodDataValidator; + /** @public */ export interface ServerConfig { // TODO: create id registry diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc5be56faaa..10efa3aecf0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -594,6 +594,9 @@ importers: undici: specifier: '*' version: 6.6.2 + valibot: + specifier: '>=0.36.0 <2' + version: 0.39.0(typescript@5.4.5) vfile: specifier: 6.0.1 version: 6.0.1 @@ -6540,7 +6543,6 @@ packages: libsql@0.3.18: resolution: {integrity: sha512-lvhKr7WV3NLWRbXkjn/MeKqXOAqWKU0PX9QYrvDh7fneukapj+iUQ4qgJASrQyxcCrEsClXCQiiK5W6OoYPAlA==} - cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] light-my-request@5.11.1: @@ -9296,6 +9298,14 @@ packages: valibot@0.33.3: resolution: {integrity: sha512-/fuY1DlX8uiQ7aphlzrrI2DbG0YJk84JMgvz2qKpUIdXRNsS53varfo4voPjSrjUr5BSV2K0miSEJUOlA5fQFg==} + valibot@0.39.0: + resolution: {integrity: sha512-d+vE8SDRNy9zKg6No5MHz2tdz8H6CW8X3OdqYdmlhnoqQmEoM6Hu0hJUrZv3tPSVrzZkIIMCtdCQtMzcM6NCWw==} + peerDependencies: + typescript: 5.4.5 + peerDependenciesMeta: + typescript: + optional: true + validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -19264,6 +19274,10 @@ snapshots: valibot@0.33.3: {} + valibot@0.39.0(typescript@5.4.5): + optionalDependencies: + typescript: 5.4.5 + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0