Skip to content
This repository has been archived by the owner on Sep 27, 2023. It is now read-only.

Introducing zact.input(...).mutation(...)? #20

Open
Quatton opened this issue May 6, 2023 · 6 comments
Open

Introducing zact.input(...).mutation(...)? #20

Quatton opened this issue May 6, 2023 · 6 comments

Comments

@Quatton
Copy link

Quatton commented May 6, 2023

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?

@VictorCalderon
Copy link

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:

As this is a breaking change that will introduce new abstraction, I would like to discuss it first before starting work on it.

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.

@chungweileong94
Copy link

chungweileong94 commented May 11, 2023

I have tried to build this pattern myself, and here's what I learned:

  • You get better readability compared to what I like to call "parenthesis hell", like zact(...)((...) => { ... }), if you get what I mean.
  • It might be overkill if we don't have any additional functionality, like .output(...) etc.

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) => {});

@Quatton
Copy link
Author

Quatton commented May 11, 2023

Wow, this is exactly what I mentioned.

It might be overkill if we don't have any additional functionality, like .output(...) etc.

Not an overkill IMO. We can definitely make use of a middleware like "zact.use()"

@KATT
Copy link

KATT commented May 25, 2023

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 :)

@Quatton
Copy link
Author

Quatton commented May 25, 2023

 @KATT That's amazing! Will do!

@chungweileong94
Copy link

chungweileong94 commented May 27, 2023

FYI, we're working on this with tRPC: https://github.com/trpc/examples-next-app-dir/blob/main/src/app/server-action/_actions.tsx

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants