breaking: allow handleError to influence status code#16162
Conversation
...by returning `{ status: ..., ... }` from `handleError`.
Breaking change because theoretically someone could've used this as part of `App.Error` before, and now it's kind of outside that, a reserved property on `handleError`'s return object.
Closes #14442
🦋 Changeset detectedLatest commit: 16f552d The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
|
/autofix |
Co-authored-by: Tee Ming <chewteeming01@gmail.com>
| status: number; | ||
| message: string; | ||
| }) => MaybePromise<void | App.Error>; | ||
| }) => MaybePromise<void | (App.Error & { status?: number })>; |
There was a problem hiding this comment.
what if instead of doing it like this, App.Error always had a status property? we would update the error(...) helper like so:
// just a status, default message, works as today
error(418);
// status and message, works as today
error(418, `I'm a teapot`);
// pass an `App.Error` object — unlike today, omit the first argument
error({
id: crypto.randomUUID(),
status: 418,
message: `I'm a teapot`
});There was a problem hiding this comment.
I suppose another option for the custom App.Error case would be this, if it's possible type-wise
error(418, `I'm a teapot`, {
id: crypto.randomUUID()
});There was a problem hiding this comment.
Agree on adding the status property to our interface. Prevents people from overriding it in app.d.ts with a different type (ours takes precedence and causes the type in app.d.ts to be ignored). Not sure how to get it to error at the interface so that folks know it doesn't take effect. I guess that becomes obvious if they do try to assign a value to it.
There was a problem hiding this comment.
if it's possible type-wise
just took a run at this
type Custom = App.Error extends { status: number; message: string }
? { status: number; message: string } extends App.Error
? false
: true
: never;
declare function error(
...args: Custom extends true
? [status: number, message: string, body: Omit<App.Error, 'status' | 'message'>]
: [status: number, message: string]
): never;
Rich-Harris
left a comment
There was a problem hiding this comment.
blocking pending resolution of #16162 (review)
…rapper, so async `handleValidationError` hooks no longer type-check even though the runtime awaits the result.
This commit fixes the issue reported at packages/kit/src/exports/public.d.ts:972
## Bug
In commit `60cd975`, the `HandleValidationError` type in `packages/kit/src/exports/public.d.ts` was changed:
```ts
- (input: { issues: Issue[]; event: RequestEvent }) => MaybePromise<App.Error>;
+ (input: { issues: Issue[]; event: RequestEvent }) => AppErrorWithOptionalStatus;
```
The switch from `App.Error` to `AppErrorWithOptionalStatus` is intentional (status is now optional — the runtime does `error(body.status ?? 400, body)`). But dropping the `MaybePromise` wrapper is a regression.
### Why it's a bug / failure mode
The runtime explicitly awaits the hook result in `packages/kit/src/runtime/app/server/remote/shared.js`:
```js
const body = await state.handleValidationError({ issues: result.issues, event });
error(body.status ?? 400, body);
```
So returning a `Promise` works at runtime. With the type no longer permitting a `Promise`, a user with an async hook gets a TypeScript error:
```ts
export async function handleValidationError({ issues }) {
return { message: await translate(issues[0].message) };
}
```
This is inconsistent with the sibling hooks `HandleServerError` / `HandleClientError`, which use `MaybePromise<void | AppErrorWithOptionalStatus>`, and the type had been `MaybePromise<App.Error>` since its introduction.
## Fix
Restore the `MaybePromise` wrapper:
```ts
(input: { issues: Issue[]; event: RequestEvent }) => MaybePromise<AppErrorWithOptionalStatus>;
```
Applied to both `packages/kit/src/exports/public.d.ts` (line 972) and the generated `packages/kit/types/index.d.ts` (line 945) to keep them in sync. `MaybePromise` is already imported/in scope in both files (used by the sibling hooks).
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: dummdidumm <sholthausen@web.de>
App.Error now has status property
...by returning
{ status: ..., ... }fromhandleError.Breaking change because theoretically someone could've used this as part of
App.Errorbefore, and now it's kind of outside that, a reserved property onhandleError's return object.Closes #14442