diff --git a/packages/docs/src/routes/api/qwik-city/api.json b/packages/docs/src/routes/api/qwik-city/api.json index 4e3e7dd3d0a..cafda0c9626 100644 --- a/packages/docs/src/routes/api/qwik-city/api.json +++ b/packages/docs/src/routes/api/qwik-city/api.json @@ -866,7 +866,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" }, diff --git a/packages/docs/src/routes/api/qwik-city/index.md b/packages/docs/src/routes/api/qwik-city/index.md index 338c5f116b4..59cf32d0fb8 100644 --- a/packages/docs/src/routes/api/qwik-city/index.md +++ b/packages/docs/src/routes/api/qwik-city/index.md @@ -2398,22 +2398,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) diff --git a/packages/docs/src/routes/docs/(qwikcity)/advanced/complex-forms/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/advanced/complex-forms/index.mdx index 5ac6d8940bf..553d6020f9c 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/advanced/complex-forms/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/advanced/complex-forms/index.mdx @@ -234,3 +234,6 @@ For this example the `fieldErrors` look like this: } ``` +> [!WARNING] +> If you use `z.any()` you have to handle the everything yourself in the action. The `fieldErrors` will never contain this field. + diff --git a/packages/qwik-city/src/runtime/src/api.md b/packages/qwik-city/src/runtime/src/api.md index fb15214a174..5704d2f6e16 100644 --- a/packages/qwik-city/src/runtime/src/api.md +++ b/packages/qwik-city/src/runtime/src/api.md @@ -476,9 +476,11 @@ export const useNavigate: () => RouteNavigate; // @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) 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..872042a72a4 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 '.'; 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..44369e41601 100644 --- a/packages/qwik-city/src/runtime/src/types.ts +++ b/packages/qwik-city/src/runtime/src/types.ts @@ -362,17 +362,27 @@ export type FailOfRest = REST extends rea : 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; +export type IsAny = 0 extends T & 1 ? 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 = {