-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
[fix] #6823 - some properties in type of object returned from ActionData is missing #6869
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
🦋 Changeset detectedLatest commit: 22dd00f 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 |
|
Seems like checks are failing cause vite 3.1.2 is out? |
|
@williamviktorsson I´ve found a simpler (more readable) way: export type AwaitedActions<T extends Record<string, (...args: any) => any>> = {
[Key in keyof T]: OptionalUnion<UnpackValidationError<Awaited<ReturnType<T[Key]>>>>;
}[keyof T];
type OptionalUnion<U, A = Partial<Record<KeysOfUnion<U>, never>>> = U extends unknown
? Omit<A, keyof U> & U
: never;
type KeysOfUnion<T> = T extends T ? keyof T : never;EDIT: forget about this, this produces incorrect types I don´t like the way the type is displayed in vscode. Not sure if there is a way to make that more readable. |
I was thinking about this today. The autocompletion is as good as it will get but its rather difficult to decipher the types from mouseover on the actiondata type. The type unions should stay as the PR but it would be nice to exclude all the Omit, Partial, Record types from the final type and just pull out the keys and values of the inner objects. |
|
This one seems a little better (and shorter): export type AwaitedActions<T extends Record<string, (...args: any) => any>> = {
[Key in keyof T]: OptionalUnion<UnpackValidationError<Awaited<ReturnType<T[Key]>>>>;
}[keyof T];
type OptionalUnion<
U extends Record<string, unknown>,
A extends keyof U = U extends U ? keyof U : never
> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;Not sure if we can combine the two objects in another way so we can get rid of the |
|
@williamviktorsson and @david-plugge Thanks for this. Question about // src/routes/auth/sign-in/+page.server.ts
type Success = {
authRedirect: string;
message: string;
};
// arghh...
type Invalid = {
status: number;
data: TypedForm<SignInFormData>;
};
export const actions: Actions = {
default: async (event: RequestEvent): Promise<Success | Invalid> => {
try {
// return Success for progressive fetch request, throw redirect otherwise
} catch (error) {
// FormValidationError is my class (not the SK ValidationError)
if (error instanceof FormValidationError) {
return invalid(StatusCodes.BAD_REQUEST, {
data: error.data,
errors: error.errors
});
}
throw error;
}
}
};After this PR, could I get rid of my default: async (event: RequestEvent): Promise<Success | TypedForm<SignInFormData>> => {...} |
|
Here is another solution. I´m not able to mark the undefined properties as optional yet... Still playing around a bit. export type AwaitedActions<T extends Record<string, (...args: any) => any>> = {
[Key in keyof T]: OptionalUnion<UnpackValidationError<Awaited<ReturnType<T[Key]>>>>;
}[keyof T];
type OptionalUnion<
U extends Record<string, unknown>,
A = Record<U extends U ? keyof U : never, never>
> = U extends unknown ? { [P in keyof U | keyof A]: P extends keyof U ? U[P] : undefined } : never; |
|
@cdcarson yes that should work! Here is what i get with my latest implementation import { invalid, type ValidationError, type RequestEvent } from '@sveltejs/kit'
class FormValidationError {
data!: number;
errors!: string;
}
export const actions = {
default: async (event: RequestEvent): Promise<{ success: true} | ValidationError<FormValidationError>> => {
try {
// return Success for progressive fetch request, throw redirect otherwise
return {
success: true
};
} catch (error) {
// FormValidationError is my class (not the SK ValidationError)
if (error instanceof FormValidationError) {
return invalid(StatusCodes.BAD_REQUEST, {
data: error.data,
errors: error.errors
});
}
throw error;
}
}
};Edit: To make this actually work the |
@david-plugge Cool. You are talking about the |
|
@cdcarson i saw you had a related issue up, and there’s another one related to the exact thing David is helping us solve. |
Are you able to make undefined types |
I was trying exactly that for like half an hour, but i can´t figure it out. If that behaviour is preferred this version should be used. |
|
As a reminder, the ValidationError interface does need a unique symbol. Otherwise a returned object With the current sveltekit version: export const actions: Actions = {
async signin({ request }) {
if (fail) {
return {
status: 400,
data: {
fail: true
}
};
}
return {
success: true
};
}
};With a special hidden property added to the interface ValidationError<T extends Record<string, unknown> | undefined = undefined> {
__sveltekit_validation_error__: unknown
status: number;
data: T;
} |
|
Interesting, sounds very related to #6822 aswell. I hear you about the |
This comment was marked as off-topic.
This comment was marked as off-topic.
|
Oh, I see what you're doing. Ignore my last comment. |
|
If anyone wants to keep experimenting in the meantime, this type helps unpacking the resulting type which helps make that type more readable: type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never; |
That`s clean! Didn´t knew that is even possible, all my solutions just god rid of the optional modifier. So something like this should be the final solution (didn´t test it): export type AwaitedActions<T extends Record<string, (...args: any) => any>> = Expand<{
[Key in keyof T]: OptionalUnion<UnpackValidationError<Awaited<ReturnType<T[Key]>>>>;
}[keyof T]>;
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
type OptionalUnion<
U extends Record<string, unknown>,
A extends keyof U = U extends U ? keyof U : never
> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never; |
|
Too keep it consistent the same should be aplied to load functions since they behave the same when the data is not returned directly: export const load = () => {
if (userLoggedIn) {
const payload = {
posts: allPosts,
somePrivateStuff
}
return payload;
}
const payload = {
posts: allPosts
}
return payload;
} |
tried it, it's pretty much spot on. Updating PR. |
I gave you an invite to the PR fork if you want to have a go at this. |
20767ea to
12b2936
Compare
@dummdidumm what do you think about this? Edit: Maybe // .svelte-kit/types/src/routes/$types.d.ts
import type * as Kit from '@sveltejs/kit';
// Makes sure a type is "repackaged" and therefore more readable
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
type RouteParams = { }
type MaybeWithVoid<T> = {} extends T ? T | void : T;
export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];
type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>
type EnsureParentData<T> = T extends null | undefined ? {} : T;
type PageServerParentData = EnsureParentData<LayoutServerData>;
type PageParentData = EnsureParentData<LayoutData>;
type LayoutParams = RouteParams & { }
type LayoutParentData = EnsureParentData<{}>;
export type PageServerLoad<OutputData extends OutputDataShape<PageServerParentData> = OutputDataShape<PageServerParentData>> = Kit.ServerLoad<RouteParams, PageServerParentData, OutputData>;
export type PageServerLoadEvent = Parameters<PageServerLoad>[0];
export type ActionData = Expand<Kit.AwaitedActions<typeof import('./proxy+page.server.js').actions>>;
export type PageServerData = Expand<Kit.AwaitedProperties<Awaited<ReturnType<typeof import('./proxy+page.server.js').load>>><;
export type PageData = Expand<Omit<PageParentData, keyof PageServerData> & PageServerData>;
export type Action = Kit.Action<RouteParams>
export type Actions = Kit.Actions<RouteParams>
export type LayoutServerData = Expand<null>;
export type LayoutData = Expand<LayoutParentData>;
export type RequestEvent = Kit.RequestEvent<RouteParams>;If you only use the server load export type PageServerData = Kit.AwaitedProperties<Awaited<ReturnType<typeof import('./proxy+page.server.js').load>>>;
export type PageData = Omit<PageParentData, keyof PageServerData> & PageServerData; |
|
Both good points! Do you want to create a separate PR for that? I'll merge this one to keep it scoped to the form action typings. |
|
Great job @david-plugge , you deserve all the credit. I think this closed three issues. |
|
Hey @williamviktorsson , @david-plugge and @dummdidumm -- this is really, really great. Works perfectly, even without explicit types, even when I wrap the code to allow me to follow my throw/return preferences. Thanks! |







Fixes #6823
Fixes #6822
Also fixes the example from the docs: https://kit.svelte.dev/docs/form-actions#anatomy-of-an-action-validation-errors
Also makes sure types generated through the use of the
invalid(status,data)helper function behave similarly to types generated by simply returning objects in load functions / actions.Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
Tests
pnpm testand lint the project withpnpm lintandpnpm checkChangesets
pnpm changesetand following the prompts. All changesets should bepatchuntil SvelteKit 1.0