Skip to content

Commit 8e662a6

Browse files
committed
refactor(qwik-router): lean the major — extract isServerError and the submit-rejection contract
isServerError (minor) and always-reject-on-abort (patch) move to follow-up PRs for a separate release. Interim abort contract: a programmatic submit() rejects; <Form> surfaces aborts via submitcompleted detail.aborted. The ServerError reserved-key hardening stays (patch changeset).
1 parent 60e891a commit 8e662a6

21 files changed

Lines changed: 27 additions & 180 deletions

File tree

.changeset/is-server-error-guard.md

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/router': patch
3+
---
4+
5+
`ServerError` no longer flattens reserved payload keys (`message`, `status`, `data`, ...) onto the error — `error.message` is always a string and payloads can't clobber `Error` internals.

.changeset/submit-rejects-servererror-hardening.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

e2e/qwik-e2e/apps/qwikrouter-test/src/routes/(common)/actions/abort/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { component$, useSignal } from '@qwik.dev/core';
2-
import { Form, isServerError, routeAction$, useLocation } from '@qwik.dev/router';
2+
import { Form, routeAction$, useLocation } from '@qwik.dev/router';
33

44
export const useAbortAction = routeAction$((form, ev) => {
55
if (form.boom) {
@@ -22,7 +22,8 @@ export default component$(() => {
2222
await action.submit({ boom: true });
2323
caught.value = 'resolved';
2424
} catch (err) {
25-
caught.value = isServerError(err) ? `caught:${err.status}` : 'caught:unknown';
25+
const status = err instanceof Error ? (err as { status?: number }).status : undefined;
26+
caught.value = status ? `caught:${status}` : 'caught:unknown';
2627
}
2728
}}
2829
>

packages/docs/src/routes/api/qwik-router-middleware-request-handler/api.json

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -356,20 +356,6 @@
356356
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/request-handler/types.ts",
357357
"mdFile": "router.internalrequest.md"
358358
},
359-
{
360-
"name": "isServerError",
361-
"id": "isservererror",
362-
"hierarchy": [
363-
{
364-
"name": "isServerError",
365-
"id": "isservererror"
366-
}
367-
],
368-
"kind": "Function",
369-
"content": "Checks whether an error is a `ServerError`<!-- -->. Structural rather than `instanceof` so it also matches ServerErrors that crossed a serialization boundary.\n\n\n```typescript\nexport declare function isServerError<E>(err: ServerError<E> | Error | undefined): err is ServerError<E>;\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nerr\n\n\n</td><td>\n\n[ServerError](#servererror-variable)<!-- -->&lt;E&gt; \\| Error \\| undefined\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\nerr is [ServerError](#servererror-variable)<!-- -->&lt;E&gt;",
370-
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/request-handler/fail.ts",
371-
"mdFile": "router.isservererror.md"
372-
},
373359
{
374360
"name": "mergeHeadersCookies",
375361
"id": "mergeheaderscookies",

packages/docs/src/routes/api/qwik-router-middleware-request-handler/index.mdx

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -872,48 +872,6 @@ export type InternalRequest = false | "loader" | "action";
872872
873873
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/request-handler/types.ts)
874874
875-
<h2 id="isservererror">isServerError</h2>
876-
877-
Checks whether an error is a `ServerError`. Structural rather than `instanceof` so it also matches ServerErrors that crossed a serialization boundary.
878-
879-
```typescript
880-
export declare function isServerError<E>(
881-
err: ServerError<E> | Error | undefined,
882-
): err is ServerError<E>;
883-
```
884-
885-
<table><thead><tr><th>
886-
887-
Parameter
888-
889-
</th><th>
890-
891-
Type
892-
893-
</th><th>
894-
895-
Description
896-
897-
</th></tr></thead>
898-
<tbody><tr><td>
899-
900-
err
901-
902-
</td><td>
903-
904-
[ServerError](#servererror-variable)&lt;E&gt; \| Error \| undefined
905-
906-
</td><td>
907-
908-
</td></tr>
909-
</tbody></table>
910-
911-
**Returns:**
912-
913-
err is [ServerError](#servererror-variable)&lt;E&gt;
914-
915-
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/request-handler/fail.ts)
916-
917875
<h2 id="mergeheaderscookies">mergeHeadersCookies</h2>
918876
919877
```typescript

packages/docs/src/routes/api/qwik-router/api.json

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -408,20 +408,6 @@
408408
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts",
409409
"mdFile": "router.httperrorprops.md"
410410
},
411-
{
412-
"name": "isServerError",
413-
"id": "isservererror",
414-
"hierarchy": [
415-
{
416-
"name": "isServerError",
417-
"id": "isservererror"
418-
}
419-
],
420-
"kind": "Function",
421-
"content": "Checks whether an error is a `ServerError`<!-- -->. Structural rather than `instanceof` so it also matches ServerErrors that crossed a serialization boundary.\n\n\n```typescript\nexport declare function isServerError<E>(err: ServerError<E> | Error | undefined): err is ServerError<E>;\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nerr\n\n\n</td><td>\n\n[ServerError](#servererror-variable)<!-- -->&lt;E&gt; \\| Error \\| undefined\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\nerr is [ServerError](#servererror-variable)<!-- -->&lt;E&gt;",
422-
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/request-handler/fail.ts",
423-
"mdFile": "router.isservererror.md"
424-
},
425411
{
426412
"name": "JSONObject",
427413
"id": "jsonobject",

packages/docs/src/routes/api/qwik-router/index.mdx

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,48 +1130,6 @@ export type HttpStatus = {
11301130

11311131
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/runtime/src/types.ts)
11321132

1133-
<h2 id="isservererror">isServerError</h2>
1134-
1135-
Checks whether an error is a `ServerError`. Structural rather than `instanceof` so it also matches ServerErrors that crossed a serialization boundary.
1136-
1137-
```typescript
1138-
export declare function isServerError<E>(
1139-
err: ServerError<E> | Error | undefined,
1140-
): err is ServerError<E>;
1141-
```
1142-
1143-
<table><thead><tr><th>
1144-
1145-
Parameter
1146-
1147-
</th><th>
1148-
1149-
Type
1150-
1151-
</th><th>
1152-
1153-
Description
1154-
1155-
</th></tr></thead>
1156-
<tbody><tr><td>
1157-
1158-
err
1159-
1160-
</td><td>
1161-
1162-
[ServerError](#servererror-variable)&lt;E&gt; \| Error \| undefined
1163-
1164-
</td><td>
1165-
1166-
</td></tr>
1167-
</tbody></table>
1168-
1169-
**Returns:**
1170-
1171-
err is [ServerError](#servererror-variable)&lt;E&gt;
1172-
1173-
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-router/src/middleware/request-handler/fail.ts)
1174-
11751133
<h2 id="jsonobject">JSONObject</h2>
11761134

11771135
```typescript

packages/docs/src/routes/docs/(qwikrouter)/route-loader/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ if (product.error) {
119119
}
120120
```
121121

122-
<Note>When an error reaches you as `unknown` (a `catch` block around `submit()` or `resolveValue()`), narrow it with the `isServerError()` guard exported from `@qwik.dev/router`. Prefer it over `instanceof ServerError`, which is `false` on the client after deserialization.</Note>
122+
<Note>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.</Note>
123123

124124
### Aborting to the error page
125125

packages/docs/src/routes/docs/upgrade/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ What moved is the consumer side. In v1, `fail()` results landed on `action.value
269269

270270
- The `StrictUnion` success/failure value unions are gone — no more `action.value?.failed` narrowing. `FailReturn<T>` 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.
271271
- 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`.
272-
- 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()`), narrow with the `isServerError()` guard (`instanceof ServerError` is `false` on the client after deserialization).
272+
- 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.
273273

274274
### Serialization
275275

0 commit comments

Comments
 (0)