Skip to content
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

implement a global 'hook' for actions #13474

Open
turbosheep44 opened this issue Feb 17, 2025 · 1 comment
Open

implement a global 'hook' for actions #13474

turbosheep44 opened this issue Feb 17, 2025 · 1 comment
Labels
feature / enhancement New feature or request

Comments

@turbosheep44
Copy link

Describe the problem

I have some error handling logic which gets reused in practically every action, however there is currently no great way to reuse it.

Most of my actions look something like this:

export const actions = {
	async default(event) {
		const req: MyRequestType = await getFormData(event);
		const client = new RpcClient(event.locals.transport);

                let response;
		try {
			response = await client.createSomething(req);
		} catch (err) {
                        // common error handling here
                        if (err instanceof RpcError) {
                           return fail(400);
                        }

                        // fallback for unknown errors
                        return fail(500);
		}

		redirect(303, `/thing/${response.id}`);
	}
};

I've simplified the handling a bit, but you get the idea - for most requests everything inside the catch block is the same.

Currently, the best way I have come up with to handle this case is to wrap every action with an error handler method.

export const actions = {
	default: errorHandler(async (event) => {
		const req: MyRequestType = await getFormData(event);
		const client = new RpcClient(event.locals.transport);

		const response = await client.createSomething(req);

		redirect(303, `/thing/${response.id}`);
	})
};

function errorHandler<H extends (event: E) => Promise<T>, E, T>(
	handler: H
): (event: E) => Promise<T | ActionFailure> {
	return async (event: E) => {
		try {
			return await handler(event);
		} catch (err) {
			if (isRedirect(err)) throw err;

                        // common error handling here
                        if (err instanceof RpcError) {
                           return fail(400);
                        }

                        // fallback for unknown errors
                        return fail(500);
		}
	};
}

This solution works, but requires me to wrap every action with errorHandler and I'm not sure if i will run into a problem with the types at some point.

Describe the proposed solution

I would like to see something like the handle function in hooks.server.ts which could transform the response from any action. I would put my error handling logic in this handle so it applies to every action without having to remember to wrap everything with errorHandler.

Alternatives considered

Actions already use the handle from hooks.server.ts, however that handle requires that I return a Response so I loose the ability to use fail and progressively enhance forms.

Importance

would make my life easier

Additional Information

No response

@elliott-with-the-longest-name-on-github
Copy link
Contributor

IMO the function wrapper is the right way to do this. I mean, if you wanted to make it even easier, you could just change your errorHandler function to this (just tried it on one of my projects, works great, might need a couple of tweaks), then declare const actions = withErrorHandler({ default: () => {}, ... }).

import { fail, isRedirect, type Action, type ActionFailure } from '@sveltejs/kit';

export function withErrorHandler<const TActions extends Record<string, Action>>(
	actions: TActions
): { [key in keyof TActions]: ErrorHandledAction<TActions[key]> } {
	const wrappedActions = {} as { [key in keyof TActions]: ErrorHandledAction<TActions[key]> };
	for (const [key, action] of Object.entries(actions)) {
		// @ts-expect-error - TypeScript doesn't like this
		wrappedActions[key] = errorHandler(action);
	}
	return wrappedActions;
}

// todo: you need to specify the shape of your failures here
function errorHandler<TAction extends Action>(action: TAction): ErrorHandledAction<TAction> {
	return async (event) => {
		try {
			return (await action(event)) as ReturnType<TAction>;
		} catch (err) {
			if (isRedirect(err)) {
				throw err;
			}

			// common error handling here

			// fallback for unknown errors
			return fail(500);
		}
	};
}

type ErrorHandledAction<
	TAction extends Action,
	TFailureData extends Record<string, unknown> | undefined = undefined
> = (event: Parameters<TAction>[0]) => Promise<ReturnType<TAction> | ActionFailure<TFailureData>>;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature / enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants