Skip to content

Commit c59375c

Browse files
fix: Adjust fail method and ActionFailure type (#11260)
* fix: Adjust fail method and ActionFailure type - add ActionFailure interface and use that publicly instead of the class. Prevents the false impression that you could do "instanceof" on the return type, fixes #10361 - add uniqueSymbol to ActionFailure instance so we can distinguish it from a regular return with the same shape - fix setup to actually run test/types * test * regenerate types --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 608e3ef commit c59375c

File tree

8 files changed

+84
-43
lines changed

8 files changed

+84
-43
lines changed

.changeset/swift-clocks-kneel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
fix: Adjust fail method and ActionFailure type

packages/kit/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
],
5858
"scripts": {
5959
"lint": "prettier --config ../../.prettierrc --check .",
60-
"check": "tsc",
60+
"check": "tsc && cd ./test/types && tsc",
6161
"check:all": "tsc && pnpm -r --filter=\"./**\" check",
6262
"format": "prettier --config ../../.prettierrc --write .",
6363
"test": "pnpm test:unit && pnpm test:integration",

packages/kit/src/exports/index.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,14 +156,31 @@ export function text(body, init) {
156156
});
157157
}
158158

159+
/**
160+
* Create an `ActionFailure` object.
161+
* @param {number} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
162+
* @overload
163+
* @param {number} status
164+
* @returns {import('./public.js').ActionFailure<undefined>}
165+
*/
159166
/**
160167
* Create an `ActionFailure` object.
161168
* @template {Record<string, unknown> | undefined} [T=undefined]
162169
* @param {number} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
163-
* @param {T} [data] Data associated with the failure (e.g. validation errors)
164-
* @returns {ActionFailure<T>}
170+
* @param {T} data Data associated with the failure (e.g. validation errors)
171+
* @overload
172+
* @param {number} status
173+
* @param {T} data
174+
* @returns {import('./public.js').ActionFailure<T>}
175+
*/
176+
/**
177+
* Create an `ActionFailure` object.
178+
* @param {number} status
179+
* @param {any} [data]
180+
* @returns {import('./public.js').ActionFailure<any>}
165181
*/
166182
export function fail(status, data) {
183+
// @ts-expect-error unique symbol missing
167184
return new ActionFailure(status, data);
168185
}
169186

packages/kit/src/exports/public.d.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,10 @@ import {
1717
RequestOptions,
1818
RouteSegment
1919
} from '../types/private.js';
20-
import { ActionFailure } from '../runtime/control.js';
2120
import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from 'types';
2221
import type { PluginOptions } from '@sveltejs/vite-plugin-svelte';
2322

2423
export { PrerenderOption } from '../types/private.js';
25-
export { ActionFailure };
2624

2725
/**
2826
* [Adapters](https://kit.svelte.dev/docs/adapters) are responsible for taking the production build and turning it into something that can be deployed to a platform of your choosing.
@@ -58,6 +56,14 @@ type OptionalUnion<
5856
A extends keyof U = U extends U ? keyof U : never
5957
> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
6058

59+
declare const uniqueSymbol: unique symbol;
60+
61+
export interface ActionFailure<T extends Record<string, unknown> | undefined = undefined> {
62+
status: number;
63+
data: T;
64+
[uniqueSymbol]: true; // necessary or else UnpackValidationError could wrongly unpack objects with the same shape as ActionFailure
65+
}
66+
6167
type UnpackValidationError<T> = T extends ActionFailure<infer X>
6268
? X
6369
: T extends void

packages/kit/src/runtime/control.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class Redirect {
3636
export class ActionFailure {
3737
/**
3838
* @param {number} status
39-
* @param {T} [data]
39+
* @param {T} data
4040
*/
4141
constructor(status, data) {
4242
this.status = status;

packages/kit/test/types/actions.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import Kit from '@sveltejs/kit';
1+
import * as Kit from '@sveltejs/kit';
22

33
// Test: Action types inferred correctly and transformed into a union
44
type Actions = {
55
foo: () => Promise<void>;
66
bar: () => Promise<{ success: boolean } | Kit.ActionFailure<{ message: string }>>;
7+
baz: () => Kit.ActionFailure<{ foo: string }> | { status: number; data: string };
78
};
89

910
let form: Kit.AwaitedActions<Actions> = null as any;
@@ -23,3 +24,17 @@ form2.message = '';
2324
form2.success = true;
2425
// @ts-expect-error - cannot both be present at the same time
2526
form2 = { message: '', success: true };
27+
28+
// Test: ActionFailure is correctly infered to be different from the normal return type even if they have the same shape
29+
type Actions3 = {
30+
bar: () => Kit.ActionFailure<{ foo: string }> | { status: number; data: { bar: string } };
31+
};
32+
let form3: Kit.AwaitedActions<Actions3> = null as any;
33+
form3.foo = '';
34+
form3.status = 200;
35+
// @ts-expect-error - cannot both be present at the same time
36+
form3 = { foo: '', status: 200 };
37+
38+
let foo: any = null;
39+
// @ts-expect-error ActionFailure is not a class and so you can't do instanceof
40+
foo instanceof Kit.ActionFailure;

packages/kit/test/types/load.test.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
1-
import Kit, { Deferred } from '@sveltejs/kit';
1+
import * as Kit from '@sveltejs/kit';
22

33
// Test: Return types inferred correctly and transformed into a union
4-
type LoadReturn1 = { success: true } | { message: Promise<string> };
4+
type LoadReturn1 =
5+
| { success: true; message?: undefined }
6+
| { success?: undefined; message: string };
57

6-
let result1: Kit.AwaitedProperties<LoadReturn1> = null as any;
8+
let result1: Kit.LoadProperties<LoadReturn1> = null as any;
79
result1.message = '';
810
result1.success = true;
911
// @ts-expect-error - cannot both be present at the same time
1012
result1 = { message: '', success: true };
11-
12-
// Test: Return types keep promise for Deferred
13-
type LoadReturn2 = { success: true } | Deferred<{ message: Promise<string>; eager: true }>;
14-
15-
let result2: Kit.AwaitedProperties<LoadReturn2> = null as any;
16-
result2.message = Promise.resolve('');
17-
result2.eager = true;
18-
result2.success = true;
19-
// @ts-expect-error - cannot both be present at the same time
20-
result2 = { message: '', success: true };

packages/kit/types/index.d.ts

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ declare module '@sveltejs/kit' {
3838
A extends keyof U = U extends U ? keyof U : never
3939
> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
4040

41+
const uniqueSymbol: unique symbol;
42+
43+
export interface ActionFailure<T extends Record<string, unknown> | undefined = undefined> {
44+
status: number;
45+
data: T;
46+
[uniqueSymbol]: true; // necessary or else UnpackValidationError could wrongly unpack objects with the same shape as ActionFailure
47+
}
48+
4149
type UnpackValidationError<T> = T extends ActionFailure<infer X>
4250
? X
4351
: T extends void
@@ -1494,28 +1502,6 @@ declare module '@sveltejs/kit' {
14941502
}
14951503

14961504
type TrailingSlash = 'never' | 'always' | 'ignore';
1497-
class HttpError_1 {
1498-
1499-
constructor(status: number, body: {
1500-
message: string;
1501-
} extends App.Error ? (App.Error | string | undefined) : App.Error);
1502-
status: number;
1503-
body: App.Error;
1504-
toString(): string;
1505-
}
1506-
class Redirect_1 {
1507-
1508-
constructor(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308, location: string);
1509-
status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308;
1510-
location: string;
1511-
}
1512-
1513-
export class ActionFailure<T extends Record<string, unknown> | undefined = undefined> {
1514-
1515-
constructor(status: number, data?: T | undefined);
1516-
status: number;
1517-
data: T | undefined;
1518-
}
15191505
interface Asset {
15201506
file: string;
15211507
size: number;
@@ -1739,12 +1725,17 @@ declare module '@sveltejs/kit' {
17391725
* @param init Options such as `status` and `headers` that will be added to the response. A `Content-Length` header will be added automatically.
17401726
*/
17411727
export function text(body: string, init?: ResponseInit | undefined): Response;
1728+
/**
1729+
* Create an `ActionFailure` object.
1730+
* @param status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
1731+
* */
1732+
export function fail(status: number): ActionFailure<undefined>;
17421733
/**
17431734
* Create an `ActionFailure` object.
17441735
* @param status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
17451736
* @param data Data associated with the failure (e.g. validation errors)
17461737
* */
1747-
export function fail<T extends Record<string, unknown> | undefined = undefined>(status: number, data?: T | undefined): ActionFailure<T>;
1738+
export function fail<T extends Record<string, unknown> | undefined = undefined>(status: number, data: T): ActionFailure<T>;
17481739
/**
17491740
* Populate a route ID with params to resolve a pathname.
17501741
* @example
@@ -1762,6 +1753,21 @@ declare module '@sveltejs/kit' {
17621753
export type LessThan<TNumber extends number, TArray extends any[] = []> = TNumber extends TArray['length'] ? TArray[number] : LessThan<TNumber, [...TArray, TArray['length']]>;
17631754
export type NumericRange<TStart extends number, TEnd extends number> = Exclude<TEnd | LessThan<TEnd>, LessThan<TStart>>;
17641755
export const VERSION: string;
1756+
class HttpError_1 {
1757+
1758+
constructor(status: number, body: {
1759+
message: string;
1760+
} extends App.Error ? (App.Error | string | undefined) : App.Error);
1761+
status: number;
1762+
body: App.Error;
1763+
toString(): string;
1764+
}
1765+
class Redirect_1 {
1766+
1767+
constructor(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308, location: string);
1768+
status: 301 | 302 | 303 | 307 | 308 | 300 | 304 | 305 | 306;
1769+
location: string;
1770+
}
17651771
}
17661772

17671773
declare module '@sveltejs/kit/hooks' {

0 commit comments

Comments
 (0)