+
html
@@ -1703,84 +1788,25 @@ string
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/request-handler/rewrite-handler.ts)
-ServerError
+ServerError
```typescript
-export declare class ServerError extends Error
+ServerError: {
+ new (status: number, data: T): ServerError;
+ readonly prototype: ServerErrorImpl;
+}
```
-**Extends:** Error
-
-
-
-Constructor
-
-
-
-Modifiers
-
-
-
-Description
-
-
-
-
-(constructor)(status, data)
-
-
-
-
-
-Constructs a new instance of the `ServerError` class
-
-
-
-
-
-
-Property
-
-
-
-Modifiers
-
-
-
-Type
-
-
-
-Description
-
-
-
-
-[data](#servererror-data)
-
-
-
-
-
-T
-
-
-
-
-
-
-[status](#servererror-status)
-
-
-
-
-
-number
+[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/request-handler/server-error.ts)
-
+ServerError
-
-
+```typescript
+ServerError: {
+ new (status: number, data: T): ServerError;
+ readonly prototype: ServerErrorImpl;
+}
+```
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/request-handler/server-error.ts)
@@ -2065,9 +2091,3 @@ _(Optional)_
**Returns:**
void
-
-status
-
-```typescript
-status: number;
-```
diff --git a/packages/docs/src/routes/api/qwik-router/api.json b/packages/docs/src/routes/api/qwik-router/api.json
index 32299105f98..e4e7d35af84 100644
--- a/packages/docs/src/routes/api/qwik-router/api.json
+++ b/packages/docs/src/routes/api/qwik-router/api.json
@@ -12,7 +12,7 @@
}
],
"kind": "TypeAlias",
- "content": "```typescript\nexport type Action, OPTIONAL extends boolean = true> = {\n (): ActionStore;\n};\n```\n**References:** [ActionStore](#actionstore)",
+ "content": "```typescript\nexport type Action, OPTIONAL extends boolean = true, ERROR = unknown> = {\n (): ActionStore;\n};\n```\n**References:** [ActionStore](#actionstore)",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
"mdFile": "router.action.md"
},
@@ -26,7 +26,7 @@
}
],
"kind": "TypeAlias",
- "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)",
+ "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, GetValidatorInputType, false, ValidatorErrorType> | FailOfRest | FailPayload>;\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, ValidatorErrorType> | FailPayload>;\n | void | null, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (data: JSONObject, event: RequestEventAction) => ValueOrPromise, options: {\n readonly id?: string;\n readonly validation: REST;\n }): Action, Record, true, FailOfRest | FailPayload>;\n | void | null, VALIDATOR extends TypedDataValidator, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (data: GetValidatorOutputType, event: RequestEventAction) => ValueOrPromise, options: VALIDATOR, ...rest: REST): Action, GetValidatorInputType, false, ValidatorErrorType> | FailOfRest | FailPayload>;\n | void | null, VALIDATOR extends TypedDataValidator>(actionQrl: (data: GetValidatorOutputType, event: RequestEventAction) => ValueOrPromise, options: VALIDATOR): Action, GetValidatorInputType, false, ValidatorErrorType> | FailPayload>;\n | void | null, REST extends [DataValidator, ...DataValidator[]]>(actionQrl: (form: JSONObject, event: RequestEventAction) => ValueOrPromise, ...rest: REST): Action, Record, true, FailOfRest | FailPayload>;\n (actionQrl: (form: JSONObject, event: RequestEventAction) => ValueOrPromise, options?: {\n readonly id?: string;\n }): Action, Record, true, FailPayload>;\n};\n```\n**References:** [TypedDataValidator](#typeddatavalidator), [DataValidator](#datavalidator), [GetValidatorOutputType](#getvalidatoroutputtype), [Action](#action), [GetValidatorInputType](#getvalidatorinputtype), [ValidatorErrorType](#validatorerrortype), [FailOfRest](#failofrest), [JSONObject](#jsonobject)",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
"mdFile": "router.actionconstructor.md"
},
@@ -40,7 +40,7 @@
}
],
"kind": "TypeAlias",
- "content": "```typescript\nexport type ActionReturn = {\n readonly status?: number;\n readonly value: RETURN;\n};\n```",
+ "content": "```typescript\nexport type ActionReturn = {\n readonly status?: number;\n readonly value: (unknown extends RETURN ? RETURN : StrictUnion) | undefined;\n readonly error: ServerError> | undefined;\n};\n```\n**References:** [StrictUnion](#strictunion)",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
"mdFile": "router.actionreturn.md"
},
@@ -54,7 +54,7 @@
}
],
"kind": "TypeAlias",
- "content": "```typescript\nexport type ActionStore = {\n readonly actionPath: string;\n readonly isRunning: boolean;\n readonly status?: number;\n readonly formData: FormData | undefined;\n readonly value: RETURN | undefined;\n readonly submit: QRL Promise> : (form: INPUT | FormData | SubmitEvent) => Promise>>;\n readonly submitted: boolean;\n};\n```\n**References:** [ActionReturn](#actionreturn)",
+ "content": "```typescript\nexport type ActionStore = {\n readonly actionPath: string;\n readonly isRunning: boolean;\n readonly status?: number;\n readonly formData: FormData | undefined;\n readonly value: (unknown extends RETURN ? RETURN : StrictUnion) | undefined;\n readonly error: ServerError> | undefined;\n readonly submit: QRL Promise> : (form: INPUT | FormData | SubmitEvent) => Promise>>;\n readonly submitted: boolean;\n};\n```\n**References:** [StrictUnion](#strictunion), [ActionReturn](#actionreturn)",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
"mdFile": "router.actionstore.md"
},
@@ -296,20 +296,6 @@
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
"mdFile": "router.failofrest.md"
},
- {
- "name": "FailReturn",
- "id": "failreturn",
- "hierarchy": [
- {
- "name": "FailReturn",
- "id": "failreturn"
- }
- ],
- "kind": "TypeAlias",
- "content": "```typescript\nexport type FailReturn = T & Failed;\n```",
- "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
- "mdFile": "router.failreturn.md"
- },
{
"name": "Form",
"id": "form",
@@ -348,7 +334,7 @@
}
],
"kind": "Interface",
- "content": "```typescript\nexport interface FormSubmitCompletedDetail \n```\n\n\n\n\nProperty\n\n\n \n\nModifiers\n\n\n \n\nType\n\n\n \n\nDescription\n\n\n \n\n\nstatus\n\n\n \n\n\n \n\nnumber\n\n\n \n\n\n \n\n\nvalue\n\n\n \n\n\n \n\nT\n\n\n \n\n\n \n
",
+ "content": "```typescript\nexport interface FormSubmitCompletedDetail \n```\n\n\n\n\nProperty\n\n\n \n\nModifiers\n\n\n \n\nType\n\n\n \n\nDescription\n\n\n \n\n\naborted?\n\n\n \n\n\n \n\n[ServerError](#servererror-variable)\n\n\n \n\n_(Optional)_ Set when the submission aborted (a thrown `error()` or an unexpected server error).\n\n\n \n\n\nerror\n\n\n \n\n\n \n\n[ServerError](#servererror-variable)<ERROR> \\| undefined\n\n\n \n\nThe `ServerError` from a returned `fail()` or a failed validator.\n\n\n \n\n\nstatus\n\n\n \n\n\n \n\nnumber\n\n\n \n\n\n \n\n\nvalue\n\n\n \n\n\n \n\nT \\| undefined\n\n\n \n\nThe action's successful return value. `undefined` when the action failed or aborted.\n\n\n \n
",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/form-component.tsx",
"mdFile": "router.formsubmitsuccessdetail.md"
},
@@ -488,7 +474,7 @@
}
],
"kind": "TypeAlias",
- "content": "```typescript\nexport type Loader = {\n (): LoaderSignal;\n};\n```\n**References:** [LoaderSignal](#loadersignal)",
+ "content": "```typescript\nexport type Loader = {\n (): LoaderSignal;\n};\n```\n**References:** [LoaderSignal](#loadersignal)",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
"mdFile": "router.loader_2.md"
},
@@ -502,7 +488,7 @@
}
],
"kind": "TypeAlias",
- "content": "```typescript\nexport type LoaderSignal = (TYPE extends () => ValueOrPromise ? Signal> : Signal) & Pick;\n```",
+ "content": "```typescript\nexport type LoaderSignal = ([TYPE] extends [\n () => ValueOrPromise\n] ? Signal> : Signal) & Pick & {\n error: ServerError> | TransportError | undefined;\n};\n```\n**References:** [StrictUnion](#strictunion), [TransportError](#transporterror)",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
"mdFile": "router.loadersignal.md"
},
@@ -982,6 +968,34 @@
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
"mdFile": "router.serverdata.md"
},
+ {
+ "name": "ServerError",
+ "id": "servererror",
+ "hierarchy": [
+ {
+ "name": "ServerError",
+ "id": "servererror"
+ }
+ ],
+ "kind": "TypeAlias",
+ "content": "```typescript\nServerError: {\n new (status: number, data: T): ServerError;\n readonly prototype: ServerErrorImpl;\n}\n```",
+ "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/request-handler/server-error.ts",
+ "mdFile": "router.servererror.md"
+ },
+ {
+ "name": "ServerError",
+ "id": "servererror",
+ "hierarchy": [
+ {
+ "name": "ServerError",
+ "id": "servererror"
+ }
+ ],
+ "kind": "Variable",
+ "content": "```typescript\nServerError: {\n new (status: number, data: T): ServerError;\n readonly prototype: ServerErrorImpl;\n}\n```",
+ "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/request-handler/server-error.ts",
+ "mdFile": "router.servererror.md"
+ },
{
"name": "ServerFunction",
"id": "serverfunction",
@@ -1066,6 +1080,20 @@
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
"mdFile": "router.strictunion.md"
},
+ {
+ "name": "TransportError",
+ "id": "transporterror",
+ "hierarchy": [
+ {
+ "name": "TransportError",
+ "id": "transporterror"
+ }
+ ],
+ "kind": "TypeAlias",
+ "content": "A client-side transport failure on the same `.error` slot as ServerError. The server failure's fields exist as `?: never` so plain property checks discriminate the union.\n\n\n```typescript\nexport type TransportError = Error & {\n [K in keyof StrictUnion]?: never;\n} & {\n status?: never;\n data?: never;\n};\n```\n**References:** [StrictUnion](#strictunion)",
+ "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
+ "mdFile": "router.transporterror.md"
+ },
{
"name": "TypedDataValidator",
"id": "typeddatavalidator",
@@ -1258,10 +1286,38 @@
}
],
"kind": "TypeAlias",
- "content": "```typescript\nexport type ValidatorReturn = {}> = ValidatorReturnSuccess | ValidatorReturnFail;\n```",
+ "content": "```typescript\nexport type ValidatorReturn = {}> = ValidatorReturnSuccess | ValidatorReturnFail;\n```\n**References:** [ValidatorReturnSuccess](#validatorreturnsuccess), [ValidatorReturnFail](#validatorreturnfail)",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
"mdFile": "router.validatorreturn.md"
},
+ {
+ "name": "ValidatorReturnFail",
+ "id": "validatorreturnfail",
+ "hierarchy": [
+ {
+ "name": "ValidatorReturnFail",
+ "id": "validatorreturnfail"
+ }
+ ],
+ "kind": "TypeAlias",
+ "content": "```typescript\nexport type ValidatorReturnFail = {}> = {\n readonly success: false;\n readonly error: T;\n readonly status?: number;\n};\n```",
+ "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
+ "mdFile": "router.validatorreturnfail.md"
+ },
+ {
+ "name": "ValidatorReturnSuccess",
+ "id": "validatorreturnsuccess",
+ "hierarchy": [
+ {
+ "name": "ValidatorReturnSuccess",
+ "id": "validatorreturnsuccess"
+ }
+ ],
+ "kind": "TypeAlias",
+ "content": "```typescript\nexport type ValidatorReturnSuccess = {\n readonly success: true;\n readonly data?: unknown;\n};\n```",
+ "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
+ "mdFile": "router.validatorreturnsuccess.md"
+ },
{
"name": "zod$",
"id": "zod_",
diff --git a/packages/docs/src/routes/api/qwik-router/index.mdx b/packages/docs/src/routes/api/qwik-router/index.mdx
index bd90e998a54..4ed3a9832fc 100644
--- a/packages/docs/src/routes/api/qwik-router/index.mdx
+++ b/packages/docs/src/routes/api/qwik-router/index.mdx
@@ -11,8 +11,9 @@ export type Action<
RETURN,
INPUT = Record,
OPTIONAL extends boolean = true,
+ ERROR = unknown,
> = {
- (): ActionStore;
+ (): ActionStore;
};
```
@@ -38,13 +39,12 @@ export type ActionConstructor = {
readonly validation: [VALIDATOR, ...REST];
},
): Action<
- StrictUnion<
- | OBJ
- | FailReturn>>
- | FailReturn>
- >,
+ ExcludeFail,
GetValidatorInputType,
- false
+ false,
+ | ValidatorErrorType>
+ | FailOfRest
+ | FailPayload
>;
<
OBJ extends Record | void | null,
@@ -59,11 +59,10 @@ export type ActionConstructor = {
readonly validation: [VALIDATOR];
},
): Action<
- StrictUnion<
- OBJ | FailReturn>>
- >,
+ ExcludeFail,
GetValidatorInputType,
- false
+ false,
+ ValidatorErrorType> | FailPayload
>;
<
OBJ extends Record | void | null,
@@ -77,7 +76,12 @@ export type ActionConstructor = {
readonly id?: string;
readonly validation: REST;
},
- ): Action>>>;
+ ): Action<
+ ExcludeFail,
+ Record,
+ true,
+ FailOfRest | FailPayload
+ >;
<
OBJ extends Record | void | null,
VALIDATOR extends TypedDataValidator,
@@ -90,13 +94,12 @@ export type ActionConstructor = {
options: VALIDATOR,
...rest: REST
): Action<
- StrictUnion<
- | OBJ
- | FailReturn>>
- | FailReturn>
- >,
+ ExcludeFail,
GetValidatorInputType,
- false
+ false,
+ | ValidatorErrorType>
+ | FailOfRest
+ | FailPayload
>;
<
OBJ extends Record | void | null,
@@ -108,11 +111,10 @@ export type ActionConstructor = {
) => ValueOrPromise,
options: VALIDATOR,
): Action<
- StrictUnion<
- OBJ | FailReturn>>
- >,
+ ExcludeFail,
GetValidatorInputType,
- false
+ false,
+ ValidatorErrorType> | FailPayload
>;
<
OBJ extends Record | void | null,
@@ -123,7 +125,12 @@ export type ActionConstructor = {
event: RequestEventAction,
) => ValueOrPromise,
...rest: REST
- ): Action>>>;
+ ): Action<
+ ExcludeFail,
+ Record,
+ true,
+ FailOfRest | FailPayload
+ >;
(
actionQrl: (
form: JSONObject,
@@ -132,44 +139,61 @@ export type ActionConstructor = {
options?: {
readonly id?: string;
},
- ): Action>;
+ ): Action, Record, true, FailPayload>;
};
```
-**References:** [TypedDataValidator](#typeddatavalidator), [DataValidator](#datavalidator), [GetValidatorOutputType](#getvalidatoroutputtype), [Action](#action), [StrictUnion](#strictunion), [FailReturn](#failreturn), [ValidatorErrorType](#validatorerrortype), [GetValidatorInputType](#getvalidatorinputtype), [FailOfRest](#failofrest), [JSONObject](#jsonobject)
+**References:** [TypedDataValidator](#typeddatavalidator), [DataValidator](#datavalidator), [GetValidatorOutputType](#getvalidatoroutputtype), [Action](#action), [GetValidatorInputType](#getvalidatorinputtype), [ValidatorErrorType](#validatorerrortype), [FailOfRest](#failofrest), [JSONObject](#jsonobject)
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)
ActionReturn
```typescript
-export type ActionReturn = {
+export type ActionReturn = {
readonly status?: number;
- readonly value: RETURN;
+ readonly value:
+ | (unknown extends RETURN ? RETURN : StrictUnion)
+ | undefined;
+ readonly error: ServerError> | undefined;
};
```
+**References:** [StrictUnion](#strictunion)
+
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)
ActionStore
```typescript
-export type ActionStore = {
+export type ActionStore<
+ RETURN,
+ INPUT,
+ OPTIONAL extends boolean = true,
+ ERROR = unknown,
+> = {
readonly actionPath: string;
readonly isRunning: boolean;
readonly status?: number;
readonly formData: FormData | undefined;
- readonly value: RETURN | undefined;
+ readonly value:
+ | (unknown extends RETURN ? RETURN : StrictUnion)
+ | undefined;
+ readonly error: ServerError> | undefined;
readonly submit: QRL<
OPTIONAL extends true
- ? (form?: INPUT | FormData | SubmitEvent) => Promise>
- : (form: INPUT | FormData | SubmitEvent) => Promise>
+ ? (
+ form?: INPUT | FormData | SubmitEvent,
+ ) => Promise>
+ : (
+ form: INPUT | FormData | SubmitEvent,
+ ) => Promise>
>;
readonly submitted: boolean;
};
```
-**References:** [ActionReturn](#actionreturn)
+**References:** [StrictUnion](#strictunion), [ActionReturn](#actionreturn)
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)
@@ -794,14 +818,6 @@ export type FailOfRest =
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)
-FailReturn
-
-```typescript
-export type FailReturn = T & Failed;
-```
-
-[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)
-
```typescript
@@ -972,7 +988,7 @@ Defaults to `false`
```typescript
-export interface FormSubmitCompletedDetail
+export interface FormSubmitCompletedDetail
```
@@ -994,6 +1010,36 @@ Description
+aborted?
+
+
+
+
+
+[ServerError](#servererror-variable)
+
+
+
+_(Optional)_ Set when the submission aborted (a thrown `error()` or an unexpected server error).
+
+
+
+
+error
+
+
+
+
+
+[ServerError](#servererror-variable)<ERROR> \| undefined
+
+
+
+The `ServerError` from a returned `fail()` or a failed validator.
+
+
+
+
status
@@ -1013,10 +1059,12 @@ value
-T
+T \| undefined
+The action's successful return value. `undefined` when the action failed or aborted.
+
@@ -1251,8 +1299,8 @@ _(Optional)_
Loader_2
```typescript
-export type Loader = {
- (): LoaderSignal;
+export type Loader = {
+ (): LoaderSignal;
};
```
@@ -1263,14 +1311,18 @@ export type Loader = {
LoaderSignal
```typescript
-export type LoaderSignal = (TYPE extends () => ValueOrPromise<
- infer VALIDATOR
->
+export type LoaderSignal = ([TYPE] extends [
+ () => ValueOrPromise,
+]
? Signal>
: Signal) &
- Pick;
+ Pick & {
+ error: ServerError> | TransportError | undefined;
+ };
```
+**References:** [StrictUnion](#strictunion), [TransportError](#transporterror)
+
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)
NavigationType_2
@@ -2609,6 +2661,28 @@ export type ServerData = {
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)
+ServerError
+
+```typescript
+ServerError: {
+ new (status: number, data: T): ServerError;
+ readonly prototype: ServerErrorImpl;
+}
+```
+
+[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/request-handler/server-error.ts)
+
+ServerError
+
+```typescript
+ServerError: {
+ new (status: number, data: T): ServerError;
+ readonly prototype: ServerErrorImpl;
+}
+```
+
+[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/request-handler/server-error.ts)
+
ServerFunction
```typescript
@@ -2741,6 +2815,23 @@ export type StrictUnion = Prettify>;
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)
+TransportError
+
+A client-side transport failure on the same `.error` slot as ServerError. The server failure's fields exist as `?: never` so plain property checks discriminate the union.
+
+```typescript
+export type TransportError = Error & {
+ [K in keyof StrictUnion]?: never;
+} & {
+ status?: never;
+ data?: never;
+};
+```
+
+**References:** [StrictUnion](#strictunion)
+
+[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)
+
TypedDataValidator
```typescript
@@ -3057,6 +3148,31 @@ export type ValidatorReturn = {}> =
| ValidatorReturnFail;
```
+**References:** [ValidatorReturnSuccess](#validatorreturnsuccess), [ValidatorReturnFail](#validatorreturnfail)
+
+[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)
+
+ValidatorReturnFail
+
+```typescript
+export type ValidatorReturnFail = {}> = {
+ readonly success: false;
+ readonly error: T;
+ readonly status?: number;
+};
+```
+
+[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)
+
+ValidatorReturnSuccess
+
+```typescript
+export type ValidatorReturnSuccess = {
+ readonly success: true;
+ readonly data?: unknown;
+};
+```
+
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)
zod$
diff --git a/packages/docs/src/routes/docs/(qwikrouter)/action/index.mdx b/packages/docs/src/routes/docs/(qwikrouter)/action/index.mdx
index 860e8b756f7..b2114c72e38 100644
--- a/packages/docs/src/routes/docs/(qwikrouter)/action/index.mdx
+++ b/packages/docs/src/routes/docs/(qwikrouter)/action/index.mdx
@@ -290,7 +290,7 @@ export default component$(() => {
- {action.value?.failed && {action.value.fieldErrors?.firstName}
}
+ {action.error && {action.error.fieldErrors?.firstName}
}
Add user
{action.value?.success && (
@@ -301,7 +301,7 @@ export default component$(() => {
});
```
-When submitting data to a `routeAction()`, the data is validated against the Zod schema. If the data is invalid, the action will put the validation error in the `routeAction.value` property.
+When submitting data to a `routeAction()`, the data is validated against the Zod schema. If the data is invalid, the action fails: the validation error surfaces on `action.error` (a `ServerError`), and the field-level messages are exposed directly on the error as `action.error.fieldErrors` (the validator error fields are spread onto the `ServerError`).
Please refer to the [Zod documentation](https://zod.dev/) for more information on how to use Zod schemas.
@@ -366,7 +366,7 @@ export const useProductRecommendations = routeAction$(
## Action Failures
-In order to return non-success values, the action must use the `fail()` method.
+To signal an expected failure from an action — invalid input, a domain rule that didn't pass — destructure `fail` from the `RequestEvent` and `return fail(status, data)`:
```tsx /fail/
import { routeAction$, zod$, z } from '@qwik.dev/router';
@@ -390,7 +390,11 @@ export const useAddUser = routeAction$(
);
```
-Failures are stored in the `action.value` property, just like the success value. However, the `action.value.failed` property is set to `true` when the action fails. Futhermore, failure messages can be found in the `fieldErrors` object according to properties defined in your Zod schema.
+Failures surface on the reactive `action.error` property as a `ServerError`, separate from the success value on `action.value`. The error carries `error.status` (the HTTP status) and `error.data` (the canonical payload object), and the payload's fields are also exposed flat on the error, so you can read them directly: `error.message`, `error.fieldErrors`, and so on. The page keeps rendering normally — the response just carries the failure's HTTP status (and is never cached).
+
+The data you pass to `fail()` is fully type-inferred into `action.error` (unioned with any `zod$()`/`valibot$()`/`validator$()` error types), while `action.value` stays the success type only — it is `undefined` whenever the action failed.
+
+For validators, the validator error fields are exposed flat on the error the same way: failure messages live in `action.error.fieldErrors` according to the properties defined in your Zod schema.
The `fieldErrors` become a dot notation object. See [Complex forms](/docs/advanced/complex-forms) for more information.
@@ -404,14 +408,38 @@ export default component$(() => {
);
});
```
-Thanks to Typescript type discrimination, you can use the `action.value.failed` property to discriminate between success and failure.
+Branch on `action.error` to render failure UI, and on `action.value` for the success result — they are never both set at once.
+
+### Aborting to the error page
+
+`fail()` is for failures the page should display inline. To abort the request entirely and render the nearest error page instead — for example a 404 for a missing record — destructure `error` from the `RequestEvent` and `throw error(status, data)`:
+
+```tsx /error/
+import { routeAction$, zod$, z } from '@qwik.dev/router';
+
+export const useDeletePost = routeAction$(
+ async (data, { error }) => {
+ const post = await db.posts.get(data.id);
+ if (!post) {
+ // Aborts the request — the error page renders with a 404 status.
+ throw error(404, 'Post not found');
+ }
+ await db.posts.delete(post.id);
+ return { success: true };
+ },
+ zod$({ id: z.string() })
+);
+```
+
+A thrown `error()` never lands on `action.error`: the action records no state and `action.submit()` rejects with the error. `
);
diff --git a/packages/docs/src/routes/docs/(qwikrouter)/error-handling/index.mdx b/packages/docs/src/routes/docs/(qwikrouter)/error-handling/index.mdx
index f69060cf8cb..75a7570d2a3 100644
--- a/packages/docs/src/routes/docs/(qwikrouter)/error-handling/index.mdx
+++ b/packages/docs/src/routes/docs/(qwikrouter)/error-handling/index.mdx
@@ -10,19 +10,19 @@ import { Note } from '~/components/note/note';
# Error handling
-When an error is thrown in a loader or `server$` function, a 500 error is returned to the client along with the error. This is useful during development but isn't always desirable for production systems. Qwik provides the tools necessary to customise how errors are handled.
+When a plain error is thrown in a loader, action, or `server$` function, the request aborts and a sanitized 500 error is returned to the client. The full error is visible during development, but the details are hidden in production. Qwik provides the tools necessary to customise how errors are handled.
-Throwing a `ServerError` instance allows you to return custom errors to the browser with a different status code and serialised data.
+Throwing a `ServerError` instance (or the `requestEvent.error()` helper, which creates one) allows you to abort the request on purpose with a different status code and serialised data — the nearest error page renders with that status.
-Loaders also provide a helper function on the event object to easily create new ServerErrors.
+Throwing is for **aborting** to the error page. For expected failures that the page should display inline (form validation, domain rules), `return requestEvent.fail(status, data)` instead — the failure surfaces on the reactive `loader.error` / `action.error` and the page keeps rendering. See [routeLoader$](/docs/route-loader/#signaling-failure-from-a-loader) and [routeAction$](/docs/action/#action-failures).
```tsx
-// Throw ServerErrors from a routerLoader$
+// Throw ServerErrors from a routeLoader$
const useProduct = routeLoader$(async (ev) => {
const product = await fetch('api/product/1')
if (!product) {
- // Throw a 404 with a custom payload
+ // Throw a 404 with a custom payload — the error page renders with a 404 status
throw new ServerError(404, 'Product not found')
// Or use the existing helper function
@@ -44,7 +44,7 @@ const getPrices = server$(() => {
export default component$(() => {
const product = useProduct()
- useVisibleTask(() => {
+ useVisibleTask$(() => {
getPrices()
.then()
.catch(err => {
@@ -65,6 +65,8 @@ export default component$(() => {
Intercepting errors with middleware has a few usecases: you might want to hide error details in production systems, add structured error logging, or map the error status codes from RPC API calls to HTTP status codes. This is all achieveable with middleware in a `plugin` file.
+Thrown errors — `throw error(...)`, thrown `ServerError`s, and unexpected errors — propagate up through `next()`, so middleware can catch them. Note that a returned `fail(...)` is **not** an exception: it becomes the loader/action `.error` state and never passes through the interceptor.
+
```tsx
// src/routes/plugin@errors.ts
import { type RequestHandler } from '@qwik.dev/router'
diff --git a/packages/docs/src/routes/docs/(qwikrouter)/route-loader/index.mdx b/packages/docs/src/routes/docs/(qwikrouter)/route-loader/index.mdx
index 7548fcf3bb5..71821bbd6ef 100644
--- a/packages/docs/src/routes/docs/(qwikrouter)/route-loader/index.mdx
+++ b/packages/docs/src/routes/docs/(qwikrouter)/route-loader/index.mdx
@@ -81,8 +81,7 @@ export default component$(() => {
return Loading…
;
}
if (product.error) {
- // ServerError — read .status and .data
- return {product.error.status}: {product.error.data.message}
;
+ return {product.error.message}
;
}
return Product name: {product.value.name}
;
});
@@ -92,10 +91,52 @@ export default component$(() => {
### Signaling failure from a loader
-Loaders signal failure two ways. Both put the signal into error state with `signal.error` set to a `ServerError` carrying `.status` (HTTP status) and `.data` (the payload):
+To signal an expected failure — a result the page should render inline — destructure `fail` from the `RequestEvent` and `return fail(status, data)`. This puts the signal into error state, with `signal.error` set to a `ServerError` carrying `.status` (the HTTP status) and `.data` (the canonical payload object you passed to `fail()`), with the payload's fields also exposed flat on the error (e.g. `.message`). The page still renders — the response just carries the failure's HTTP status (and is never cached).
-- **`return requestEvent.fail(status, data)`** — return a tagged failure. `signal.error.data` is `{ failed: true, ...data }`.
-- **`throw requestEvent.error(status, data)`** — throw a `ServerError` directly. `signal.error.data` is `data`.
+```tsx
+export const useProduct = routeLoader$(async ({ params, fail }) => {
+ const product = await db.products.findById(params.productId);
+ if (!product) {
+ // signal.error.status === 404, signal.error.data === { message: 'Product not found' }
+ return fail(404, { message: 'Product not found' });
+ }
+ return product;
+});
+```
+
+Read it back on `signal.error`, never on `signal.value` — `.value` holds the success type only and is `undefined` on failure; they are never both set at once. The data you pass to `fail()` is fully type-inferred into `signal.error`, so failure payloads are as strongly typed as success values.
+
+On the client, `signal.error` can also hold a plain `Error` when the loader's JSON fetch itself failed (a network problem). Its `status` and payload fields are typed optional across that union, so a plain property check tells the two apart:
+
+```tsx
+if (product.error) {
+ if (product.error.status) {
+ // server failure — typed: .status, .data, and your fail() payload fields
+ console.warn(product.error.status, product.error.data);
+ } else {
+ // plain Error: the fetch itself failed
+ }
+}
+```
+
+When an error reaches you as `unknown` (a `catch` block around `resolveValue()`), check it structurally — `err instanceof Error && typeof err.status === 'number'` — since `instanceof ServerError` is `false` on the client after deserialization.
+
+### Aborting to the error page
+
+`fail()` is for failures your page renders inline. When the right response is the error page itself — a 404 for a missing product, say — destructure `error` from the `RequestEvent` and `throw error(status, data)` instead:
+
+```tsx
+export const useProduct = routeLoader$(async ({ params, error }) => {
+ const product = await db.products.findById(params.productId);
+ if (!product) {
+ // Aborts the request — the nearest error page renders with a 404 status.
+ throw error(404, 'Product not found');
+ }
+ return product;
+});
+```
+
+A thrown `error()` never lands on `signal.error`: it aborts the request to the nearest error handler (the route's error page, or a middleware `catch`). During SPA navigation the client falls back to a full-page load so the server can render the real error page. Plain (non-`error()`) throws behave like unexpected errors and result in a sanitized 500.
## Multiple `routeLoader$`s
@@ -329,7 +370,7 @@ export default component$(() => {
| Echo the just-submitted form value back to the page | Loader returns the URL-derived value; the component overlays `action.value` on top, e.g. `action.value?.name ?? loader.value.name`. |
| Recompute derived data from the submission | Put the inputs in the URL (search params, or `goto()` after the action) so the loader reads them like any other route input. |
| Refresh authoritative data after a mutation | The action writes to your store (DB, KV, etc.); the loader reads from that store. The action's `invalidate` list re-runs the loader and it picks up the new state — no action result needed. |
-| Show success / error feedback | Read the action signal directly: `action.value`, `action.isRunning`, `action.value?.failed`. Ephemeral UI feedback doesn't belong in a loader. |
+| Show success / error feedback | Read the action signal directly: `action.value`, `action.isRunning`, `action.error`. Ephemeral UI feedback doesn't belong in a loader. |
## Options reference
diff --git a/packages/docs/src/routes/docs/(qwikrouter)/validator/index.mdx b/packages/docs/src/routes/docs/(qwikrouter)/validator/index.mdx
index 91885057a0b..192195f002a 100644
--- a/packages/docs/src/routes/docs/(qwikrouter)/validator/index.mdx
+++ b/packages/docs/src/routes/docs/(qwikrouter)/validator/index.mdx
@@ -37,20 +37,23 @@ export const useAction = routeAction$(
);
```
-When submitting a request to a `routeAction()`, the request event and data undergo validation against the defined validator. If the validation fails, the action will place the validation error in the `routeAction.value` property.
+When submitting a request to a `routeAction()`, the request event and data undergo validation against the defined validator. If the validation fails, the action surfaces the validation error on the `routeAction.error` property (a `ServerError`), separate from the success value on `routeAction.value`.
```tsx
export default component$(() => {
const action = useAction();
- // action value is undefined before submitting
- if (action.value) {
- if (action.value.failed) {
- // action failed if query string has no secret
- action.value satisfies { failed: true; message: string };
- } else {
- action.value satisfies { searchResult: string };
- }
+ // both are undefined before submitting; only one is set after
+ if (action.error) {
+ // validation failed if query string has no secret
+ // action.error is a ServerError: .status is the HTTP status, and the
+ // validator error fields are spread directly onto the error, so you read
+ // them flat (e.g. action.error.message). The canonical payload object is
+ // still available as action.error.data.
+ action.error.message satisfies string;
+ action.error.data satisfies { message: string };
+ } else if (action.value) {
+ action.value satisfies { searchResult: string };
}
return (
@@ -144,26 +147,28 @@ interface Fail {
}
```
-Validator behaves like using the `fail()` method in the action or loader when it returns a failed object.
+When a validator returns a failed object, the framework turns it into a `ServerError` — the same outcome as `return fail(status, data)` inside the action or loader. The error surfaces on `action.error`, with `action.error.status` set to the validator's `status`, and the page keeps rendering (the failure is meant to be displayed inline; `action.value` stays `undefined`). The validator's `error` object fields are spread directly onto the error, so you read them flat (e.g. `action.error.message`); the canonical payload object is also available as `action.error.data`.
-```tsx /status/#a /errorObject/#b
+```tsx /status/#a /errorData/#b
const status = 500;
-const errorObject = { message: "123" };
+const errorData = { message: "123" };
export const useAction = routeAction$(
async (_, { fail }) => {
- return fail(status, errorObject);
+ return fail(status, errorData);
},
validator$(async () => {
return {
success: false,
status,
- errorObject,
+ error: errorData,
};
}),
);
```
+To abort the request and render the error page instead of surfacing an inline failure, `throw error(status, data)` from the action or loader body — a thrown `error()` never lands on `action.error`.
+
## Use `validator$()` with `zod$()` together in actions
For actions, the typed data validator `zod$()` should be the second argument of `routeAction$`, followed by other data validators `validator$()`s.
diff --git a/packages/docs/src/routes/docs/upgrade/index.mdx b/packages/docs/src/routes/docs/upgrade/index.mdx
index 180b2a6d316..49165b83074 100644
--- a/packages/docs/src/routes/docs/upgrade/index.mdx
+++ b/packages/docs/src/routes/docs/upgrade/index.mdx
@@ -251,6 +251,26 @@ export default component$(() => {
If your root component is reactive (reads signals), use `` instead. `useQwikRouter()` only runs once during SSR.
+### Action and loader failures: `value.failed` → `error`
+
+The producer side is **unchanged**: keep using `return requestEvent.fail(status, data)` for expected failures (form validation, domain rules), and `throw requestEvent.error(status, data)` still aborts to the nearest error page, exactly like v1.
+
+What moved is the consumer side. In v1, `fail()` results landed on `action.value` with a `failed: true` marker, so `.value` was a union of the success and failure shapes. In v2, failures surface on the reactive `action.error` / `loader.error` as a `ServerError` — `.status` is the HTTP status, `.data` is the canonical payload, and the payload's fields are also exposed flat (e.g. `error.fieldErrors`). `.value` is now the success type only and is `undefined` on failure; `.value` and `.error` are never both set.
+
+```tsx
+// v1
+{action.value?.failed && {action.value.fieldErrors?.name}
}
+{action.value?.success && Done!
}
+
+// v2
+{action.error && {action.error.fieldErrors?.name}
}
+{action.value?.success && Done!
}
+```
+
+- The `StrictUnion` success/failure value unions are gone — no more `action.value?.failed` narrowing. `FailReturn` still exists but is symbol-branded; it no longer has a `failed: true` property and never appears in `.value`'s type. `fail()` payloads are type-inferred into `.error` instead.
+- Loaders that used `throw error(status, data)` to render error pages **keep that behavior** — thrown `error()`s abort to the error page and never land on `.error`.
+- On loaders, `.error` can also hold a plain `Error` on the client (the loader's fetch itself failed). Its `status` and payload fields are typed optional across the union, so a plain property check discriminates (`if (loader.error?.status)`). Reading `loader.value` while the signal is in error state throws — check `.error` first. In `catch` blocks (around `submit()` or `resolveValue()`), check errors structurally (`typeof err.status === 'number'`) — `instanceof ServerError` is `false` on the client after deserialization.
+
### Serialization
v1 serialized state into `