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

refactor(types): export infer utility types #235

Merged
merged 8 commits into from
Aug 17, 2024
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