diff --git a/.changeset/quiet-berries-occur.md b/.changeset/quiet-berries-occur.md new file mode 100644 index 0000000..23eaf5b --- /dev/null +++ b/.changeset/quiet-berries-occur.md @@ -0,0 +1,5 @@ +--- +"safe-fn": patch +--- + +Renames `Ok()` and `Err()` to `ok()` and `err()` to avoid overlap with types. Exports `ok()`, `err()`, `Ok`, `Err`, `Result` to be available to users. diff --git a/README.md b/README.md index ea64fc7..c939ceb 100644 --- a/README.md +++ b/README.md @@ -43,18 +43,18 @@ export const createUser = SafeFn.new() .action(async (input) => { try { const user = await db.user.insert(input.parsedInput); - return Ok(user); + return ok(user); } catch (error) { if (error instanceof DbError) { if (error.code === "UNIQUE_CONSTRAINT") { - return Err({ + return err({ code: "DUPLICATE_USER", message: "User already exists", }); } } - return Err({ + return err({ code: "INTERNAL_ERROR", message: "Something went wrong", }); @@ -86,7 +86,7 @@ const safeFn = SafeFn.new() }), ) .error((error) => { - return Err({ + return err({ code: "CAUGHT_ERROR", cause, }); @@ -94,14 +94,14 @@ const safeFn = SafeFn.new() .action(async ({ parsedInput }) => { const isProfane = await isProfaneName(parsedInput.firstName); if (isProfane) { - return Err({ + return err({ code: "PROFANE_NAME", message: "Name is profane", }); } const fullName = `${parsedInput.firstName} ${parsedInput.lastName}`; - return Ok({ fullName }); + return ok({ fullName }); }); ``` diff --git a/packages/safe-fn/src/index.ts b/packages/safe-fn/src/index.ts index 58b5e36..ec9b0f2 100644 --- a/packages/safe-fn/src/index.ts +++ b/packages/safe-fn/src/index.ts @@ -1,3 +1,4 @@ +import { type Err, type Ok, type Result, err, ok } from "./result"; import { SafeFn } from "./safe-fn"; import type { @@ -23,14 +24,19 @@ export type { AnySafeFn, AnySafeFnThrownHandler, BuilderSteps, + Err, InferInputSchema, InferOutputSchema, InferRunArgs, InferUnparsedInput, + Ok, + Result, SafeFnActionFn, SafeFnReturn, SafeFnReturnData, SafeFnReturnError, SafeFnRunArgs, TSafeFn, + err, + ok, }; diff --git a/packages/safe-fn/src/result.ts b/packages/safe-fn/src/result.ts index 1a8407f..cb9ae6b 100644 --- a/packages/safe-fn/src/result.ts +++ b/packages/safe-fn/src/result.ts @@ -8,7 +8,7 @@ export type Ok = { }; export type InferOkData = T extends Ok ? TData : never; -export const Ok = (data: TData): Ok => ({ +export const ok = (data: TData): Ok => ({ success: true, data, error: undefined as never, @@ -22,7 +22,7 @@ export type Err = { export type InferErrError = T extends Err ? TError : never; export type AnyErr = Err; -export const Err = (error: TError): Err => ({ +export const err = (error: TError): Err => ({ success: false, error, data: undefined as never, diff --git a/packages/safe-fn/src/safe-fn.test-d.ts b/packages/safe-fn/src/safe-fn.test-d.ts index 403d366..2ebf550 100644 --- a/packages/safe-fn/src/safe-fn.test-d.ts +++ b/packages/safe-fn/src/safe-fn.test-d.ts @@ -1,6 +1,6 @@ import { describe, expectTypeOf, test } from "vitest"; import { z } from "zod"; -import { Err, Ok, type InferErrError, type Result } from "./result"; +import { err, ok, type Err, type InferErrError, type Result } from "./result"; import { SafeFn } from "./safe-fn"; import type { SafeFnDefaultThrowHandler, @@ -265,7 +265,7 @@ describe("run", () => { const inputSchema = z.string(); const safeFn = SafeFn.new() .input(inputSchema) - .action((args) => Ok(args.parsedInput)); + .action((args) => ok(args.parsedInput)); type RunInput = Parameters[0]; @@ -281,7 +281,7 @@ describe("run", () => { }); const safeFn = SafeFn.new() .input(inputSchema) - .action((args) => Ok(args.parsedInput)); + .action((args) => ok(args.parsedInput)); type RunInput = Parameters[0]; @@ -299,7 +299,7 @@ describe("run", () => { .transform(({ test }) => ({ test, newProperty: "test" })); const safeFn = SafeFn.new() .input(inputSchema) - .action((args) => Ok(args.parsedInput)); + .action((args) => ok(args.parsedInput)); type RunInput = Parameters[0]; @@ -320,7 +320,7 @@ describe("run", () => { // >(); // }); test("should infer success return type from action when no output schema is provided", async () => { - const safeFn = SafeFn.new().action(() => Ok("data" as const)); + const safeFn = SafeFn.new().action(() => ok("data" as const)); expectTypeOf(safeFn.run({})).resolves.toMatchTypeOf< Result<"data", any> @@ -331,7 +331,7 @@ describe("run", () => { const outputSchema = z.string().transform((data) => data + "!"); const safeFn = SafeFn.new() .output(outputSchema) - .action(() => Ok("")); + .action(() => ok("")); const res = await safeFn.run({}); expectTypeOf(res).toMatchTypeOf< @@ -342,7 +342,7 @@ describe("run", () => { describe("error", () => { test("should infer Err return as default when no error function is set", async () => { - const safeFn = SafeFn.new().action(() => Ok("data" as const)); + const safeFn = SafeFn.new().action(() => ok("data" as const)); type Res = Awaited>; type InferredErrError = InferErrError; @@ -352,7 +352,7 @@ describe("run", () => { }); test("should infer Err return type from action when no error function is set", async () => { - const safeFn = SafeFn.new().action(() => Err("my error" as const)); + const safeFn = SafeFn.new().action(() => err("my error" as const)); type Res = Awaited>; type InferredErrError = InferErrError; expectTypeOf().toEqualTypeOf< @@ -363,9 +363,9 @@ describe("run", () => { test("should infer Err return type from action when error function is set", async () => { const safeFn = SafeFn.new() .action(() => { - return Err("error" as const); + return err("error" as const); }) - .error(() => Err("thrown" as const)); + .error(() => err("thrown" as const)); type Res = Awaited>; type InferredErrError = InferErrError; @@ -471,7 +471,7 @@ describe("internals", () => { describe("error", () => { test("should properly type the _uncaughtErrorHandler function", () => { - const safeFn = SafeFn.new().error((error) => Err("hello" as const)); + const safeFn = SafeFn.new().error((error) => err("hello" as const)); type res = ReturnType; expectTypeOf(safeFn._uncaughtErrorHandler).toEqualTypeOf< @@ -484,7 +484,7 @@ describe("parent", () => { test("should properly type the _parent function", () => { const safeFn1 = SafeFn.new() .input(z.object({ name: z.string() })) - .action(() => Ok("")); + .action(() => ok("")); const safeFn2 = SafeFn.new(safeFn1).input(z.object({ age: z.number() })); expectTypeOf(safeFn2._parent).toEqualTypeOf(safeFn1); }); @@ -499,7 +499,7 @@ describe("parent", () => { const input2 = z.object({ age: z.number() }); const safeFn1 = SafeFn.new() .input(input1) - .action(() => Ok("")); + .action(() => ok("")); const safeFn2 = SafeFn.new(safeFn1).input(input2); type S2ParsedInput = Parameters< @@ -525,7 +525,7 @@ describe("parent", () => { const safeFn1 = SafeFn.new() .input(input1) - .action(() => Ok("")); + .action(() => ok("")); const safeFn2 = SafeFn.new(safeFn1).input(input2); type S2ParsedInput = Parameters< @@ -539,7 +539,7 @@ describe("parent", () => { test("should take parsedInput from child when parent has no input schema", () => { const input = z.object({ name: z.string() }); - const safeFn1 = SafeFn.new().action((args) => Ok(args.parsedInput)); + const safeFn1 = SafeFn.new().action((args) => ok(args.parsedInput)); const safeFn2 = SafeFn.new(safeFn1).input(input); type S2ParsedInput = Parameters< @@ -553,7 +553,7 @@ describe("parent", () => { const input = z.object({ name: z.string() }); const safeFn1 = SafeFn.new() .input(input) - .action(() => Ok("")); + .action(() => ok("")); const safeFn2 = SafeFn.new(safeFn1); type S2ParsedInput = Parameters< @@ -566,7 +566,7 @@ describe("parent", () => { describe("ctx", () => { test("should type ctx as unwrapped OK value from parent", () => { - const safeFn1 = SafeFn.new().action(() => Ok("ctx return" as const)); + const safeFn1 = SafeFn.new().action(() => ok("ctx return" as const)); const safeFn2 = SafeFn.new(safeFn1); type S2Ctx = Parameters< @@ -576,7 +576,7 @@ describe("parent", () => { }); test("should type ctx as empty object if parent never returns", () => { - const safeFn1 = SafeFn.new().action(() => Err("ctx return" as const)); + const safeFn1 = SafeFn.new().action(() => err("ctx return" as const)); const safeFn2 = SafeFn.new(safeFn1); type S2Ctx = Parameters< diff --git a/packages/safe-fn/src/safe-fn.test.ts b/packages/safe-fn/src/safe-fn.test.ts index 68c9d27..58825ad 100644 --- a/packages/safe-fn/src/safe-fn.test.ts +++ b/packages/safe-fn/src/safe-fn.test.ts @@ -1,6 +1,6 @@ import { assert, describe, expect, test } from "vitest"; import { z } from "zod"; -import { Err, Ok } from "./result"; +import { err, ok } from "./result"; import { SafeFn } from "./safe-fn"; describe("SafeFn", () => { @@ -28,7 +28,7 @@ describe("output", () => { describe("action", () => { test("should set the action function", () => { - const actionFn = () => Ok("data"); + const actionFn = () => ok("data"); const safeFn = SafeFn.new().action(actionFn); expect(safeFn._actionFn).toEqual(actionFn); }); @@ -45,7 +45,7 @@ describe("internals", () => { const inputSchema = z.string(); const safeFn = SafeFn.new().input(inputSchema); const res = await safeFn._parseInput("data"); - expect(res).toEqual(Ok("data")); + expect(res).toEqual(ok("data")); }); // TODO: mabe write this better @@ -63,7 +63,7 @@ describe("internals", () => { const inputSchema = z.string().transform((data) => data + "!"); const safeFn = SafeFn.new().input(inputSchema); const res = await safeFn._parseInput("data"); - expect(res).toEqual(Ok("data!")); + expect(res).toEqual(ok("data!")); }); }); @@ -77,7 +77,7 @@ describe("internals", () => { const outputSchema = z.string(); const safeFn = SafeFn.new().output(outputSchema); const res = await safeFn._parseOutput("data"); - expect(res).toEqual(Ok("data")); + expect(res).toEqual(ok("data")); }); test("should return Err when output is invalid", async () => { @@ -94,7 +94,7 @@ describe("internals", () => { const outputSchema = z.string().transform((data) => data + "!"); const safeFn = SafeFn.new().output(outputSchema); const res = await safeFn._parseOutput("data"); - expect(res).toEqual(Ok("data!")); + expect(res).toEqual(ok("data!")); }); }); }); @@ -103,41 +103,41 @@ describe("run", () => { describe("input", () => { test("should set parsedInput to undefined when no input schema is defined", async () => { const res = await SafeFn.new() - .action((args) => Ok(args.parsedInput)) + .action((args) => ok(args.parsedInput)) .run({}); - expect(res).toEqual(Ok(undefined)); + expect(res).toEqual(ok(undefined)); }); test("should pass unparsedInput when no input schema is defined", async () => { const res = await SafeFn.new() - .action((args) => Ok(args.unparsedInput)) + .action((args) => ok(args.unparsedInput)) .run("data"); - expect(res).toEqual(Ok("data")); + expect(res).toEqual(ok("data")); }); test("should pass unparsedInput when input schema is defined", async () => { const inputSchema = z.any().transform((_) => undefined); const res = await SafeFn.new() .input(inputSchema) - .action((args) => Ok(args.unparsedInput)) + .action((args) => ok(args.unparsedInput)) .run("data"); - expect(res).toEqual(Ok("data")); + expect(res).toEqual(ok("data")); }); test("should pass parsedInput when input schema is defined", async () => { const inputSchema = z.any().transform((_) => "parsed"); const res = await SafeFn.new() .input(inputSchema) - .action((args) => Ok(args.parsedInput)) + .action((args) => ok(args.parsedInput)) .run("data"); - expect(res).toEqual(Ok("parsed")); + expect(res).toEqual(ok("parsed")); }); test("should return Err when input is invalid", async () => { const inputSchema = z.string(); const res = await SafeFn.new() .input(inputSchema) - .action((args) => Ok(args.parsedInput)) + .action((args) => ok(args.parsedInput)) // @ts-expect-error .run(123); @@ -154,18 +154,18 @@ describe("run", () => { describe("output", () => { test("should return action result when no output schema is defined", async () => { const res = await SafeFn.new() - .action((args) => Ok("data")) + .action((args) => ok("data")) .run({}); - expect(res).toEqual(Ok("data")); + expect(res).toEqual(ok("data")); }); test("should return parsed output when output schema is defined", async () => { const outputSchema = z.any().transform((_) => "parsed"); const res = await SafeFn.new() .output(outputSchema) - .action((args) => Ok("Any string")) + .action((args) => ok("Any string")) .run({}); - expect(res).toEqual(Ok("parsed")); + expect(res).toEqual(ok("parsed")); }); test("should return Err when output is invalid", async () => { @@ -173,7 +173,7 @@ describe("run", () => { const res = await SafeFn.new() .output(outputSchema) // @ts-expect-error - .action((args) => Ok(123)) + .action((args) => ok(123)) .run({}); expect(res.success).toBe(false); expect(res.data).toBeUndefined(); @@ -196,7 +196,7 @@ describe("run", () => { .action(() => { throw new Error("error"); }) - .error(() => Err("error")); + .error(() => err("error")); expect(() => safeFn.run({})).not.toThrow(); }); @@ -207,7 +207,7 @@ describe("run", () => { throw new Error("a new error"); }) .error((error) => { - return Err(error); + return err(error); }); const res = await safeFn.run({}); @@ -223,7 +223,7 @@ describe("run", () => { describe("error", () => { test("should set the error handler", () => { - const errorHandler = () => Err("error"); + const errorHandler = () => err("error"); const safeFn = SafeFn.new().error(errorHandler); expect(safeFn._uncaughtErrorHandler).toEqual(errorHandler); }); @@ -231,17 +231,17 @@ describe("error", () => { describe("procedure", () => { test("should set parent procedure", () => { - const safeFn1 = SafeFn.new().action(() => Ok("")); + const safeFn1 = SafeFn.new().action(() => ok("")); const safeFn2 = SafeFn.new(safeFn1); expect(safeFn2._parent).toEqual(safeFn1); }); test("should return parent return value as ctx", () => { - const safeFn1 = SafeFn.new().action(() => Ok("hello")); + const safeFn1 = SafeFn.new().action(() => ok("hello")); const safeFn2 = SafeFn.new(safeFn1).action((args) => { - return Ok(args.ctx); + return ok(args.ctx); }); - expect(safeFn2.run({})).resolves.toEqual(Ok("hello")); + expect(safeFn2.run({})).resolves.toEqual(ok("hello")); }); }); diff --git a/packages/safe-fn/src/safe-fn.ts b/packages/safe-fn/src/safe-fn.ts index 2b92ec1..f5c0c6e 100644 --- a/packages/safe-fn/src/safe-fn.ts +++ b/packages/safe-fn/src/safe-fn.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { Err, Ok, type Result } from "./result"; +import { err, ok, type Err, type Result } from "./result"; import type { AnyRunnableSafeFn, AnySafeFnThrownHandler, @@ -73,11 +73,11 @@ export class SafeFn< inputSchema: undefined, outputSchema: undefined, actionFn: () => - Err({ + err({ code: "NO_ACTION", } as const), uncaughtErrorHandler: (error: unknown) => - Err({ + err({ code: "UNCAUGHT_ERROR", cause: error, } as const), @@ -280,10 +280,10 @@ export class SafeFn< const res = await this._inputSchema.safeParseAsync(input); if (res.success) { - return Ok(res.data); + return ok(res.data); } - return Err({ + return err({ code: "INPUT_PARSING", cause: res.error, }) as Err>; @@ -304,10 +304,10 @@ export class SafeFn< const res = await this._outputSchema.safeParseAsync(output); if (res.success) { - return Ok(res.data); + return ok(res.data); } - return Err({ + return err({ code: "OUTPUT_PARSING", cause: res.error, }) as Err>; diff --git a/packages/safe-fn/src/types.ts b/packages/safe-fn/src/types.ts index a0ed735..0e8b3e0 100644 --- a/packages/safe-fn/src/types.ts +++ b/packages/safe-fn/src/types.ts @@ -1,4 +1,4 @@ -import type { ZodTypeAny, z } from "zod"; +import type { z, ZodTypeAny } from "zod"; import type { AnyResult, Err, @@ -248,8 +248,8 @@ export type SafeFnReturnData< * @param TActionFn the action function of the safe function * @param TThrownHandler the thrown handler of the safe function * @returns the error type of the return value of the safe function after unsuccessful execution. This is a union of all possible error types that can be thrown by the safe function consisting off: - * - A union of all `Err()` returns of the action function - * - A union of all `Err()` returns of the uncaught error handler + * - A union of all `Err` returns of the action function + * - A union of all `Err` returns of the uncaught error handler * - A `SafeFnInputParseError` if the input schema is defined and the input could not be parsed * - A `SafeFnOutputParseError` if the output schema is defined and the output could not be parsed * Note that this is wrapped in a `Result` type. @@ -304,7 +304,7 @@ export type SafeFnRunArgs< * @param TOutputSchema a Zod schema or undefined * @param TActionFn the action function of the safe function * @param TThrownHandler the thrown handler of the safe function - * @returns the return value of the safe function after execution. This is a `Result` type that can either be an `Ok()` or an `Err()`. + * @returns the return value of the safe function after execution. This is a `Result` type that can either be an `Ok` or an `Err`. * * @example * ```ts @@ -321,7 +321,7 @@ export type SafeFnRunArgs< }), ) .error((error) => { - return Err({ + return err({ code: "CAUGHT_ERROR", error, }); @@ -329,14 +329,14 @@ export type SafeFnRunArgs< .action(async ({ parsedInput }) => { const isProfane = await isProfaneName(parsedInput.firstName); if (isProfane) { - return Err({ + return err({ code: "PROFANE_NAME", message: "Name is profane", }); } const fullName = `${parsedInput.firstName} ${parsedInput.lastName}`; - return Ok({ fullName }); + return ok({ fullName }); }); * ```