-
Notifications
You must be signed in to change notification settings - Fork 16
Introducing zact.input(...).mutation(...)? #20
Comments
I personally love this idea. I have been looking into tRPC's implementation of this "builder API" thing @t3dotgg usually mentions, as it is truly a much easier API to wrap your head around. As for this:
You could simple add it on top of the existing API, or even as a ZactBuilder thing. I would love to get my hands dirty with this but I am very new to typescript and OSS in general, and I am not even a SWE. I'll see if I can come up with something, or if someone is already working on it, we would love to know. |
I have tried to build this pattern myself, and here's what I learned:
Anyway, here's code snippet if you are interested. The type is heavily inspired by trpc codebase😉 const unsetMarker = Symbol('unsetMarker');
type UnsetMarker = typeof unsetMarker;
type InferParserType<TParser, TType extends 'in' | 'out'> = TParser extends UnsetMarker
? undefined
: TParser extends z.ZodType
? TParser[TType extends 'in' ? '_input' : '_output']
: never;
type ActionParams<TInput = unknown> = {
_input: TInput;
};
type ActionBuilder<TParams extends ActionParams> = {
input: <TParser extends z.ZodType>(input: TParser) => ActionBuilder<{_input: TParser}>;
action: <TOutput>(
action: (input: InferParserType<TParams['_input'], 'out'>) => Promise<TOutput>,
) => (input: InferParserType<TParams['_input'], 'in'>) => Promise<TOutput>;
};
type AnyActionBuilder = ActionBuilder<any>;
type ActionBuilderDef<TParams extends ActionParams<any>> = {
input: TParams['_input'];
};
type AnyActionBuilderDef = ActionBuilderDef<any>;
const createNewServerActionBuilder = (def: Partial<AnyActionBuilderDef>) => {
return createServerActionBuilder(def);
};
const createServerActionBuilder = (
initDef: Partial<AnyActionBuilderDef> = {},
): ActionBuilder<{
_input: UnsetMarker;
}> => {
const _def: ActionBuilderDef<{_input: z.ZodType | undefined}> = {
input: undefined,
...initDef,
};
return {
input: (input) => createNewServerActionBuilder({..._def, input}) as AnyActionBuilder,
action: (action) => {
return async (input) => {
if (_def.input) {
const result = _def.input.safeParse(input);
if (!result.success) {
throw fromZodError(result.error);
}
}
return await action(input);
};
},
};
};
export const zact = createServerActionBuilder();
// Usages
const action1 = zact.action(async () => {});
const action2 = zact.input(z.string()).action(async (id) => {}); |
Wow, this is exactly what I mentioned.
Not an overkill IMO. We can definitely make use of a middleware like "zact.use()" |
FYI, we're working on this with tRPC: https://github.com/trpc/examples-next-app-dir/blob/main/src/app/server-action/_actions.tsx If you want to help us improve, join our Discord and chat in one of the contributor channels :) |
@KATT That's amazing! Will do! |
This is really cool! Will try my best to find a way to help out you guys🙂 I'm currently building a really simple server action builder for my personal use that looks similar to tRPC builder pattern. If you are interested in building something similar, feel free to take a look. |
Hello. I hope we are familiar with tRPC's router definition pattern where we use method chaining like this. I personally love the idea and strongly believe that it is more intuitive than
zact(...)((input) => {})
As this is a breaking change that will introduce new abstraction, I would like to discuss it first before starting work on it.
Firstly, other than the fact that it looks more intuitive, would this solution help us achieve anything else? Secondly, would this cause any problems that we should be aware of?
The text was updated successfully, but these errors were encountered: