Skip to content

Commit

Permalink
Merge pull request #56 from janglad/react-types
Browse files Browse the repository at this point in the history
React types
  • Loading branch information
janglad authored Sep 20, 2024
2 parents d55ae59 + 2d5f272 commit 498bb25
Show file tree
Hide file tree
Showing 16 changed files with 105 additions and 84 deletions.
7 changes: 7 additions & 0 deletions .changeset/ten-oranges-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"safe-fn-react": minor
"safe-fn": minor
---

- Force upgrade to Neverthrow@8 due to types not being compataible
- Fix hook typings
5 changes: 5 additions & 0 deletions apps/docs/content/docs/create/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import { Tab, Tabs } from "fumadocs-ui/components/tabs";

## Installation

<Callout type="info">
Please note that due to an incompatibility in types, NeverThrow version 8 or
higher is required.
</Callout>

<Tabs items={["npm", "pnpm", "yarn", "bun"]}>
{/* <!-- prettier-ignore --> */}
<Tab value="npm">
Expand Down
4 changes: 2 additions & 2 deletions packages/safe-fn-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@
"@types/react": "^18.3.3",
"@typescript-eslint/parser": "^8.4.0",
"eslint": "^8.57.0",
"neverthrow": "^7.1.0",
"neverthrow": "^8.0.0",
"react": "^18.3.1",
"typescript": "^5.5.4"
},
"dependencies": {
"safe-fn": "workspace:^"
},
"peerDependencies": {
"neverthrow": "^7.1.0",
"neverthrow": "^8.0.0",
"react": "^18.3.1"
}
}
4 changes: 2 additions & 2 deletions packages/safe-fn-react/src/useServerAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export const useServerAction = <TAction extends AnySafeFnAction>(
callbackCatch,
)({
unsafeRawInput: argsRef.current,
value: result.value as Awaited<InferSafeFnActionOkData<TAction>>,
value: result.value as InferSafeFnActionOkData<TAction>,
});
} else if (
result !== undefined &&
Expand All @@ -102,7 +102,7 @@ export const useServerAction = <TAction extends AnySafeFnAction>(
callbackCatch,
)({
unsafeRawInput: argsRef.current,
result: result as Awaited<InferSafeFnActionReturn<TAction>> | undefined,
result: result as InferSafeFnActionReturn<TAction> | undefined,
});
}

Expand Down
4 changes: 2 additions & 2 deletions packages/safe-fn/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@
"@repo/typescript-config": "workspace:^",
"@vitest/coverage-v8": "^2.0.5",
"@vitest/ui": "^2.0.5",
"neverthrow": "^7.1.0",
"neverthrow": "^8.0.0",
"typescript": "^5.5.4",
"vitest": "^2.0.5",
"zod": "^3.23.8"
},
"peerDependencies": {
"neverthrow": "^7.1.0",
"neverthrow": "^8.0.0",
"zod": "^3.23.8"
}
}
28 changes: 17 additions & 11 deletions packages/safe-fn/src/result.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Err, err, Ok, ok, Result, ResultAsync } from "neverthrow";
import { err, ok, Result, ResultAsync } from "neverthrow";

export type InferOkData<T> = T extends Ok<infer TData, any> ? TData : never;
export type InferOkData<T> = T extends Result<infer TData, any> ? TData : never;
export type InferAsyncOkData<T> =
T extends ResultAsync<infer TData, any> ? TData : never;

export type InferErrError<T> =
T extends Err<any, infer TError> ? TError : never;
T extends Result<any, infer TError> ? TError : never;
export type InferAsyncErrError<T> =
T extends ResultAsync<any, infer TError> ? TError : never;

Expand Down Expand Up @@ -34,31 +34,37 @@ export type MergeResultAsync<T1, T2> =
*
* Can be converted to a `Result` using `actionResultToResult()`
*/
export type ActionResult<T, E> = ActionOk<T> | ActionErr<E>;
export type ActionResult<T, E> = ActionOk<T, E> | ActionErr<T, E>;

export type ActionOk<T> = {
export type ActionOk<T, E> = {
ok: true;
value: T;
};
export const actionOk = <T>(value: T): ActionOk<T> => ({ ok: true, value });
export const actionOk = <T>(value: T): ActionOk<T, never> => ({
ok: true,
value,
});
export type InferActionOkData<T> =
T extends ActionOk<infer TData> ? TData : never;
T extends ActionOk<infer TData, any> ? TData : never;

export type ActionErr<E> = {
export type ActionErr<T, E> = {
ok: false;
error: E;
};
export const actionErr = <E>(error: E): ActionErr<E> => ({ ok: false, error });
export const actionErr = <E>(error: E): ActionErr<never, E> => ({
ok: false,
error,
});
export type InferActionErrError<T> =
T extends ActionErr<infer TError> ? TError : never;
T extends ActionErr<any, infer TError> ? TError : never;
/**
* Converts a `ResultAsync<T,E>` to a `<ActionResult<T,E>`.
*/
export type ResultAsyncToActionResult<T> =
T extends ResultAsync<infer D, infer E> ? ActionResult<D, E> : never;

export type ActionResultToResultAsync<T> =
T extends Promise<ActionResult<infer D, infer E>> ? ResultAsync<D, E> : never;
T extends ActionResult<infer D, infer E> ? ResultAsync<D, E> : never;

export type ActionResultToResult<T> =
T extends ActionResult<infer D, infer E> ? Result<D, E> : never;
Expand Down
2 changes: 1 addition & 1 deletion packages/safe-fn/src/runnable-safe-fn.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ok, Result, ResultAsync, safeTry } from "neverthrow";
import { ok, type Result, ResultAsync, safeTry } from "neverthrow";

import {
actionErr,
Expand Down
2 changes: 1 addition & 1 deletion packages/safe-fn/src/safe-fn-builder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from "zod";

import { err, Result } from "neverthrow";
import { err, type Result } from "neverthrow";
import type { MergeResults } from "./result";
import {
RunnableSafeFn,
Expand Down
74 changes: 38 additions & 36 deletions packages/safe-fn/src/safe-fn.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Err, err, Ok, ok, type Result } from "neverthrow";
import { err, ok, type Result } from "neverthrow";
import { assert, describe, expectTypeOf, test } from "vitest";
import { z } from "zod";
import { type ActionResult } from "./result";
import { createSafeFn } from "./safe-fn-builder";
import type { TSafeFnDefaultCatchHandlerErr } from "./types/catch-handler";
import type { TSafeFnDefaultCatchHandlerErrError } from "./types/catch-handler";
import type {
TSafeFnInputParseError,
TSafeFnOutputParseError,
Expand Down Expand Up @@ -690,15 +690,15 @@ describe("runnableSafeFn", () => {
assert(resultAsync.isErr());
assert(resultSafe.isErr());

expectTypeOf(resultSync.error).toEqualTypeOf<
TSafeFnDefaultCatchHandlerErr["error"]
>();
expectTypeOf(resultAsync.error).toEqualTypeOf<
TSafeFnDefaultCatchHandlerErr["error"]
>();
expectTypeOf(resultSafe.error).toEqualTypeOf<
TSafeFnDefaultCatchHandlerErr["error"]
>();
expectTypeOf(
resultSync.error,
).toEqualTypeOf<TSafeFnDefaultCatchHandlerErrError>();
expectTypeOf(
resultAsync.error,
).toEqualTypeOf<TSafeFnDefaultCatchHandlerErrError>();
expectTypeOf(
resultSafe.error,
).toEqualTypeOf<TSafeFnDefaultCatchHandlerErrError>();
});

test("should type Err as custom when catch handler is provided", async () => {
Expand Down Expand Up @@ -745,13 +745,13 @@ describe("runnableSafeFn", () => {
assert(resultSafe.isErr());

expectTypeOf(resultSync.error).toEqualTypeOf<
"hello" | TSafeFnDefaultCatchHandlerErr["error"]
"hello" | TSafeFnDefaultCatchHandlerErrError
>();
expectTypeOf(resultAsync.error).toEqualTypeOf<
"hello" | TSafeFnDefaultCatchHandlerErr["error"]
"hello" | TSafeFnDefaultCatchHandlerErrError
>();
expectTypeOf(resultSafe.error).toEqualTypeOf<
"hello" | TSafeFnDefaultCatchHandlerErr["error"]
"hello" | TSafeFnDefaultCatchHandlerErrError
>();
});

Expand Down Expand Up @@ -795,13 +795,13 @@ describe("runnableSafeFn", () => {
const resultSafe = await safeFnSafe.run();

expectTypeOf(resultSync).toEqualTypeOf<
Result<never, "hello" | TSafeFnDefaultCatchHandlerErr["error"]>
Result<never, "hello" | TSafeFnDefaultCatchHandlerErrError>
>();
expectTypeOf(resultAsync).toEqualTypeOf<
Result<never, "hello" | TSafeFnDefaultCatchHandlerErr["error"]>
Result<never, "hello" | TSafeFnDefaultCatchHandlerErrError>
>();
expectTypeOf(resultSafe).toEqualTypeOf<
Result<never, "hello" | TSafeFnDefaultCatchHandlerErr["error"]>
Result<never, "hello" | TSafeFnDefaultCatchHandlerErrError>
>();
});

Expand Down Expand Up @@ -845,7 +845,7 @@ describe("runnableSafeFn", () => {

type ExpectedResult = Result<
"hello",
"world" | TSafeFnDefaultCatchHandlerErr["error"]
"world" | TSafeFnDefaultCatchHandlerErrError
>;

expectTypeOf(resultSync).toEqualTypeOf<ExpectedResult>();
Expand Down Expand Up @@ -889,13 +889,13 @@ describe("runnableSafeFn", () => {
assert(resSafe.isErr());

expectTypeOf(resSync.error).toEqualTypeOf<
"hello" | "world" | TSafeFnDefaultCatchHandlerErr["error"]
"hello" | "world" | TSafeFnDefaultCatchHandlerErrError
>();
expectTypeOf(resAsync.error).toEqualTypeOf<
"hello" | "world" | TSafeFnDefaultCatchHandlerErr["error"]
"hello" | "world" | TSafeFnDefaultCatchHandlerErrError
>();
expectTypeOf(resSafe.error).toEqualTypeOf<
"hello" | "world" | TSafeFnDefaultCatchHandlerErr["error"]
"hello" | "world" | TSafeFnDefaultCatchHandlerErrError
>();
});

Expand Down Expand Up @@ -939,21 +939,21 @@ describe("runnableSafeFn", () => {
resAsync.error.code;

expectTypeOf(resSync.error).toEqualTypeOf<
| TSafeFnDefaultCatchHandlerErr["error"]
| TSafeFnDefaultCatchHandlerErrError
| {
code: "INPUT_PARSING";
cause: z.ZodError<SchemaTransformedInput>;
}
>();
expectTypeOf(resAsync.error).toEqualTypeOf<
| TSafeFnDefaultCatchHandlerErr["error"]
| TSafeFnDefaultCatchHandlerErrError
| {
code: "INPUT_PARSING";
cause: z.ZodError<SchemaTransformedInput>;
}
>();
expectTypeOf(resSafe.error).toEqualTypeOf<
| TSafeFnDefaultCatchHandlerErr["error"]
| TSafeFnDefaultCatchHandlerErrError
| {
code: "INPUT_PARSING";
cause: z.ZodError<SchemaTransformedInput>;
Expand All @@ -969,7 +969,7 @@ describe("runnableSafeFn", () => {
assert(resNestedChildSync.isErr());

expectTypeOf(resNestedChildSync.error).toEqualTypeOf<
| TSafeFnDefaultCatchHandlerErr["error"]
| TSafeFnDefaultCatchHandlerErrError
| {
code: "INPUT_PARSING";
cause: z.ZodError<SchemaTransformedInput>;
Expand Down Expand Up @@ -1014,7 +1014,7 @@ describe("runnableSafeFn", () => {
type ExpectedCtx = "hello" | undefined;
type ExpectedCtxInput = [SchemaTransformedOutput] | undefined;
type ExpectedRunErrError =
| TSafeFnDefaultCatchHandlerErr["error"]
| TSafeFnDefaultCatchHandlerErrError
| {
code: "INPUT_PARSING";
cause: z.ZodError<
Expand All @@ -1023,7 +1023,7 @@ describe("runnableSafeFn", () => {
};

type ExpectedActionErrError =
| TSafeFnDefaultCatchHandlerErr["error"]
| TSafeFnDefaultCatchHandlerErrError
| {
code: "INPUT_PARSING";
cause: {
Expand Down Expand Up @@ -1095,7 +1095,7 @@ describe("runnableSafeFn", () => {
type ExpectedCtxInput = [SchemaTransformedOutput];
type ExpectedOkData = "world";
type ExpectedRunErrError =
| TSafeFnDefaultCatchHandlerErr["error"]
| TSafeFnDefaultCatchHandlerErrError
| {
code: "INPUT_PARSING";
cause: z.ZodError<
Expand All @@ -1104,7 +1104,7 @@ describe("runnableSafeFn", () => {
};

type ExpectedActionErrError =
| TSafeFnDefaultCatchHandlerErr["error"]
| TSafeFnDefaultCatchHandlerErrError
| {
code: "INPUT_PARSING";
cause: {
Expand All @@ -1125,23 +1125,23 @@ describe("runnableSafeFn", () => {
input: ExpectedInput;
ctx: ExpectedCtx;
ctxInput: ExpectedCtxInput;
result: Ok<ExpectedOkData, never>;
result: Result<ExpectedOkData, never>;
}
| {
asAction: true;
unsafeRawInput: ExpectedUnsafeRawInput;
input: ExpectedInput | undefined;
ctx: ExpectedCtx | undefined;
ctxInput: ExpectedCtxInput | undefined;
result: Err<never, ExpectedActionErrError>;
result: Result<never, ExpectedActionErrError>;
}
| {
asAction: false;
unsafeRawInput: ExpectedUnsafeRawInput;
input: ExpectedInput | undefined;
ctx: ExpectedCtx | undefined;
ctxInput: ExpectedCtxInput | undefined;
result: Err<never, ExpectedRunErrError>;
result: Result<never, ExpectedRunErrError>;
}
>;
expectTypeOf<ExpectedArgs>().toEqualTypeOf<OnCompleteArgs>();
Expand Down Expand Up @@ -1331,12 +1331,14 @@ describe("runnableSafeFn", () => {
// @ts-expect-error - input is not compatible
const resSafe = await safeFnSafeParent.createAction()();

const testAction = safeFnSyncParent.createAction();

assert(!resSync.ok);
assert(!resAsync.ok);
assert(!resSafe.ok);

expectTypeOf(resSync.error).toEqualTypeOf<
| TSafeFnDefaultCatchHandlerErr["error"]
| TSafeFnDefaultCatchHandlerErrError
| {
code: "INPUT_PARSING";
cause: {
Expand All @@ -1349,7 +1351,7 @@ describe("runnableSafeFn", () => {
}
>();
expectTypeOf(resAsync.error).toEqualTypeOf<
| TSafeFnDefaultCatchHandlerErr["error"]
| TSafeFnDefaultCatchHandlerErrError
| {
code: "INPUT_PARSING";
cause: {
Expand All @@ -1362,7 +1364,7 @@ describe("runnableSafeFn", () => {
}
>();
expectTypeOf(resSafe.error).toEqualTypeOf<
| TSafeFnDefaultCatchHandlerErr["error"]
| TSafeFnDefaultCatchHandlerErrError
| {
code: "INPUT_PARSING";
cause: {
Expand All @@ -1384,7 +1386,7 @@ describe("runnableSafeFn", () => {
assert(resNestedChildSync.isErr());

expectTypeOf(resNestedChildSync.error).toEqualTypeOf<
| TSafeFnDefaultCatchHandlerErr["error"]
| TSafeFnDefaultCatchHandlerErrError
| {
code: "INPUT_PARSING";
cause: z.ZodError<SchemaTransformedInput>;
Expand Down
2 changes: 1 addition & 1 deletion packages/safe-fn/src/safe-fn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ describe("runnable-safe-fn", () => {
unsafeRawInput: { name: "John", age: 100 },
ctx: "Parent!",
ctxInput: [{ age: 100 }],
result: ok("Ok!"),
result: ok("Ok!") as TODO,
} satisfies CallbackArgs["onComplete"]);
});
});
Expand Down
Loading

0 comments on commit 498bb25

Please sign in to comment.