Skip to content

Commit

Permalink
refactor(types): export infer utility types (#235)
Browse files Browse the repository at this point in the history
Code in this PR adds and exports infer utility types for safe action client, middleware functions, safe actions and hooks.

re #233
  • Loading branch information
TheEdoRan authored Aug 17, 2024
1 parent 9cb9b12 commit 4876d2d
Show file tree
Hide file tree
Showing 7 changed files with 674 additions and 13 deletions.
4 changes: 2 additions & 2 deletions packages/next-safe-action/src/hooks-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from "react";
import {} from "react/experimental";
import type {} from "zod";
import type { InferIn, Schema } from "./adapters/types";
import type { HookActionStatus, HookBaseUtils, HookCallbacks, HookResult } from "./hooks.types";
import type { HookActionStatus, HookBaseUtils, HookCallbacks, HookResult, HookShorthandStatus } from "./hooks.types";

export const getActionStatus = <
ServerError,
Expand Down Expand Up @@ -42,7 +42,7 @@ export const getActionShorthandStatusObject = ({
}: {
status: HookActionStatus;
isTransitioning: boolean;
}) => {
}): HookShorthandStatus => {
return {
isIdle: status === "idle",
isExecuting: status === "executing",
Expand Down
17 changes: 12 additions & 5 deletions packages/next-safe-action/src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ import {} from "react/experimental";
import type {} from "zod";
import type { InferIn, Schema } from "./adapters/types";
import { getActionShorthandStatusObject, getActionStatus, useActionCallbacks, useExecuteOnMount } from "./hooks-utils";
import type { HookBaseUtils, HookCallbacks, HookResult, HookSafeActionFn } from "./hooks.types";
import type {
HookBaseUtils,
HookCallbacks,
HookResult,
HookSafeActionFn,
UseActionHookReturn,
UseOptimisticActionHookReturn,
} from "./hooks.types";
import { isError } from "./utils";

// HOOKS
Expand All @@ -29,7 +36,7 @@ export const useAction = <
>(
safeActionFn: HookSafeActionFn<ServerError, S, BAS, CVE, CBAVE, Data>,
utils?: HookBaseUtils<S> & HookCallbacks<ServerError, S, BAS, CVE, CBAVE, Data>
) => {
): UseActionHookReturn<ServerError, S, BAS, CVE, CBAVE, Data> => {
const [isTransitioning, startTransition] = React.useTransition();
const [result, setResult] = React.useState<HookResult<ServerError, S, BAS, CVE, CBAVE, Data>>({});
const [clientInput, setClientInput] = React.useState<S extends Schema ? InferIn<S> : void>();
Expand Down Expand Up @@ -124,7 +131,7 @@ export const useAction = <
return {
execute,
executeAsync,
input: clientInput,
input: clientInput as S extends Schema ? InferIn<S> : undefined,
result,
reset,
status,
Expand Down Expand Up @@ -154,7 +161,7 @@ export const useOptimisticAction = <
updateFn: (state: State, input: S extends Schema ? InferIn<S> : undefined) => State;
} & HookBaseUtils<S> &
HookCallbacks<ServerError, S, BAS, CVE, CBAVE, Data>
) => {
): UseOptimisticActionHookReturn<ServerError, S, BAS, CVE, CBAVE, Data, State> => {
const [isTransitioning, startTransition] = React.useTransition();
const [result, setResult] = React.useState<HookResult<ServerError, S, BAS, CVE, CBAVE, Data>>({});
const [clientInput, setClientInput] = React.useState<S extends Schema ? InferIn<S> : void>();
Expand Down Expand Up @@ -255,7 +262,7 @@ export const useOptimisticAction = <
return {
execute,
executeAsync,
input: clientInput,
input: clientInput as S extends Schema ? InferIn<S> : undefined,
result,
optimisticState,
reset,
Expand Down
110 changes: 108 additions & 2 deletions packages/next-safe-action/src/hooks.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InferIn, Schema } from "./adapters/types";
import type { SafeActionResult } from "./index.types";
import type { SafeActionFn, SafeActionResult, SafeStateActionFn } from "./index.types";
import type { MaybePromise, Prettify } from "./utils.types";

/**
Expand Down Expand Up @@ -83,6 +83,112 @@ export type HookSafeStateActionFn<
) => Promise<SafeActionResult<ServerError, S, BAS, CVE, CBAVE, Data>>;

/**
* Type of the action status returned by `useAction` and `useOptimisticAction` hooks.
* Type of the action status returned by `useAction`, `useOptimisticAction` and `useStateAction` hooks.
*/
export type HookActionStatus = "idle" | "executing" | "hasSucceeded" | "hasErrored";

/**
* Type of the shorthand status object returned by `useAction`, `useOptimisticAction` and `useStateAction` hooks.
*/
export type HookShorthandStatus = {
isIdle: boolean;
isExecuting: boolean;
isTransitioning: boolean;
isPending: boolean;
hasSucceeded: boolean;
hasErrored: boolean;
};

/**
* Type of the return object of the `useAction` hook.
*/
export type UseActionHookReturn<
ServerError,
S extends Schema | undefined,
BAS extends readonly Schema[],
CVE,
CBAVE,
Data,
> = {
execute: (input: S extends Schema ? InferIn<S> : void) => void;
executeAsync: (
input: S extends Schema ? InferIn<S> : void
) => Promise<SafeActionResult<ServerError, S, BAS, CVE, CBAVE, Data> | undefined>;
input: S extends Schema ? InferIn<S> : undefined;
result: Prettify<HookResult<ServerError, S, BAS, CVE, CBAVE, Data>>;
reset: () => void;
status: HookActionStatus;
} & HookShorthandStatus;

/**
* Type of the return object of the `useOptimisticAction` hook.
*/
export type UseOptimisticActionHookReturn<
ServerError,
S extends Schema | undefined,
BAS extends readonly Schema[],
CVE,
CBAVE,
Data,
State,
> = UseActionHookReturn<ServerError, S, BAS, CVE, CBAVE, Data> &
HookShorthandStatus & {
optimisticState: State;
};

/**
* Type of the return object of the `useStateAction` hook.
*/
export type UseStateActionHookReturn<
ServerError,
S extends Schema | undefined,
BAS extends readonly Schema[],
CVE,
CBAVE,
Data,
> = Omit<UseActionHookReturn<ServerError, S, BAS, CVE, CBAVE, Data>, "executeAsync" | "reset"> & HookShorthandStatus;

/**
* Type of the return object of the `useAction` hook.
*/
export type InferUseActionHookReturn<T extends Function> =
T extends SafeActionFn<
infer ServerError,
infer S extends Schema | undefined,
infer BAS extends readonly Schema[],
infer CVE,
infer CBAVE,
infer Data
>
? UseActionHookReturn<ServerError, S, BAS, CVE, CBAVE, Data>
: never;

/**
* Type of the return object of the `useOptimisticAction` hook.
*/
export type InferUseOptimisticActionHookReturn<T extends Function, State = any> =
T extends SafeActionFn<
infer ServerError,
infer S extends Schema | undefined,
infer BAS extends readonly Schema[],
infer CVE,
infer CBAVE,
infer Data
>
? UseOptimisticActionHookReturn<ServerError, S, BAS, CVE, CBAVE, Data, State>
: never;

/**
* Type of the return object of the `useStateAction` hook.
*/
export type InferUseStateActionHookReturn<T extends Function> =
T extends SafeStateActionFn<
infer ServerError,
infer S extends Schema | undefined,
infer BAS extends readonly Schema[],
infer CVE,
infer CBAVE,
infer Data
>
? UseStateActionHookReturn<ServerError, S, BAS, CVE, CBAVE, Data>
: never;
80 changes: 80 additions & 0 deletions packages/next-safe-action/src/index.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Infer, InferArray, InferIn, InferInArray, Schema, ValidationAdapter } from "./adapters/types";
import type { SafeActionClient } from "./safe-action-client";
import type { MaybePromise, Prettify } from "./utils.types";
import type { BindArgsValidationErrors, ValidationErrors } from "./validation-errors.types";

Expand Down Expand Up @@ -201,3 +202,82 @@ export type SafeActionUtils<
hasNotFound: boolean;
}) => MaybePromise<void>;
};

/**
* Infer input types of a safe action.
*/
export type InferSafeActionFnInput<T extends Function> = T extends
| SafeActionFn<any, infer S extends Schema | undefined, infer BAS extends readonly Schema[], any, any, any>
| SafeStateActionFn<any, infer S extends Schema | undefined, infer BAS extends readonly Schema[], any, any, any>
? S extends Schema
? {
clientInput: InferIn<S>;
bindArgsClientInputs: InferInArray<BAS>;
parsedInput: Infer<S>;
bindArgsParsedInputs: InferArray<BAS>;
}
: {
clientInput: undefined;
bindArgsClientInputs: InferInArray<BAS>;
parsedInput: undefined;
bindArgsParsedInputs: InferArray<BAS>;
}
: never;

/**
* Infer the result type of a safe action.
*/
export type InferSafeActionFnResult<T extends Function> = T extends
| SafeActionFn<
infer ServerError,
infer S extends Schema | undefined,
infer BAS extends readonly Schema[],
infer CVE,
infer CBAVE,
infer Data
>
| SafeStateActionFn<
infer ServerError,
infer S extends Schema | undefined,
infer BAS extends readonly Schema[],
infer CVE,
infer CBAVE,
infer Data
>
? SafeActionResult<ServerError, S, BAS, CVE, CBAVE, Data>
: never;

/**
* Infer the next context type returned by a middleware function using the `next` function.
*/
export type InferMiddlewareFnNextCtx<T> =
T extends MiddlewareFn<any, any, any, infer NextCtx extends object> ? NextCtx : never;

/**
* Infer the context type of a safe action client or middleware function.
*/
export type InferCtx<T> = T extends
| SafeActionClient<any, any, any, any, infer Ctx extends object, any, any, any, any, any>
| MiddlewareFn<any, any, infer Ctx extends object, any>
? Ctx
: never;

/**
* Infer the metadata type of a safe action client or middleware function.
*/
export type InferMetadata<T> = T extends
| SafeActionClient<any, any, any, infer MD, any, any, any, any, any, any>
| MiddlewareFn<any, infer MD, any, any>
? MD
: never;

/**
* Infer the server error type from a safe action client or a middleware function or a safe action function.
*/
export type InferServerError<T> = T extends
| SafeActionClient<infer ServerError, any, any, any, any, any, any, any, any, any>
| MiddlewareFn<infer ServerError, any, any, any>
| SafeActionFn<infer ServerError, any, any, any, any, any>
| SafeStateActionFn<infer ServerError, any, any, any, any, any>
? ServerError
: never;
6 changes: 3 additions & 3 deletions packages/next-safe-action/src/stateful-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {} from "react/experimental";
import type {} from "zod";
import type { InferIn, Schema } from "./adapters/types";
import { getActionShorthandStatusObject, getActionStatus, useActionCallbacks, useExecuteOnMount } from "./hooks-utils";
import type { HookBaseUtils, HookCallbacks, HookSafeStateActionFn } from "./hooks.types";
import type { HookBaseUtils, HookCallbacks, HookSafeStateActionFn, UseStateActionHookReturn } from "./hooks.types";
/**
* Use the stateful action from a Client Component via hook. Used for actions defined with [`stateAction`](https://next-safe-action.dev/docs/safe-action-client/instance-methods#action--stateaction).
* @param safeActionFn The action function
Expand All @@ -27,7 +27,7 @@ export const useStateAction = <
permalink?: string;
} & HookBaseUtils<S> &
HookCallbacks<ServerError, S, BAS, CVE, CBAVE, Data>
) => {
): UseStateActionHookReturn<ServerError, S, BAS, CVE, CBAVE, Data> => {
const [result, dispatcher, isExecuting] = React.useActionState(
safeActionFn,
utils?.initResult ?? {},
Expand Down Expand Up @@ -75,7 +75,7 @@ export const useStateAction = <

return {
execute,
input: clientInput,
input: clientInput as S extends Schema ? InferIn<S> : undefined,
result,
status,
...getActionShorthandStatusObject({ status, isTransitioning }),
Expand Down
Loading

0 comments on commit 4876d2d

Please sign in to comment.