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

Performance #71

Merged
merged 20 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/good-lemons-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"safe-fn": minor
---

- About a ~10x performance increase for single `safe-fn` instances
- About a ~17x performance increase when testing a `safe-fn` with 10 parents
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ const { execute, result, isPending } = useServerAction(createTodoAction);
```

However, the magic of the integration of NeverThrow really comes out if your other functions are also returning a `Result` or `ResultAsync`. Instead of passing a regular function via `handler()` you can also use `safeHandler()`.
This builds on Neverthrow's `safeTry()` and takes in a generator function. This generator function receives the same args as `handler()`, but it allows you to `yield *` other results (note that in `Neverthrow` versions before 8.0.0 you need to use `.safeUnwrap()`).
This builds on Neverthrow's `safeTry()` and takes in a generator function. This generator function receives the same args as `handler()`, but it allows you to `yield *` other results (note that in `Neverthrow` versions before 8.1.0 you need to use `.safeUnwrap()`).
This is meant to emulate Rust's `?` operator and allows a very ergonomic way to write "return early if this fails, otherwise continue" logic.

This function has the same return type as the `handler()` example above.
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/content/docs/create/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ 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.
Please note that due to an incompatibility in types, NeverThrow version 8.1.0
or higher is required.
</Callout>

<Tabs items={["npm", "pnpm", "yarn", "bun"]}>
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ const { execute, result, isPending } = useServerAction(createTodoAction);
```

However, the magic of the integration of NeverThrow really comes out if your other functions are also returning a `Result` or `ResultAsync`. Instead of passing a regular function via `handler()` you can also use `safeHandler()`.
This builds on Neverthrow's `safeTry()` and takes in a generator function. This generator function receives the same args as `handler()`, but it allows you to `yield *` other results (note that in `Neverthrow` versions before 8.0.0 you need to use `.safeUnwrap()`).
This builds on Neverthrow's `safeTry()` and takes in a generator function. This generator function receives the same args as `handler()`, but it allows you to `yield *` other results (note that in `Neverthrow` versions before 8.1.0 you need to use `.safeUnwrap()`).
This is meant to emulate Rust's `?` operator and allows a very ergonomic way to write "return early if this fails, otherwise continue" logic.

This function has the same return type as the `handler()` example above.
Expand Down
21 changes: 21 additions & 0 deletions packages/benchmark/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { safeFnAsyncBench } from "./safe-fn";
import { safeFnAsyncWithCallbacksBench } from "./safe-fn-callbacks";
import { safeFnAsyncGenBench } from "./safe-fn-gen";
import { zsaBench } from "./zsa";

const runBench = async () => {
await safeFnAsyncBench.run();
console.log(safeFnAsyncBench.name);
console.table(safeFnAsyncBench.table());
await safeFnAsyncGenBench.run();
console.log(safeFnAsyncGenBench.name);
console.table(safeFnAsyncGenBench.table());
await zsaBench.run();
console.log(zsaBench.name);
console.table(zsaBench.table());
await safeFnAsyncWithCallbacksBench.run();
console.log(safeFnAsyncWithCallbacksBench.name);
console.table(safeFnAsyncWithCallbacksBench.table());
};

runBench();
19 changes: 19 additions & 0 deletions packages/benchmark/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"private": true,
"version": "0.0.0",
"name": "@repo/benchmark",
"devDependencies": {
"@repo/typescript-config": "workspace:^",
"typescript": "^5.5.4"
},
"scripts": {
"bench": "bun run index.ts"
},
"dependencies": {
"neverthrow": "^8.0.0",
"safe-fn": "workspace:^",
"tinybench": "^3.0.6",
"zod": "^3.23.8",
"zsa": "^0.6.0"
}
}
60 changes: 60 additions & 0 deletions packages/benchmark/safe-fn-callbacks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { createSafeFn } from "safe-fn";
import { Bench } from "tinybench";

import { ok } from "neverthrow";
import { z } from "zod";

const schema = z.unknown();

const handlerFn = async (args: any) => ok(args);

const callbackFn = async (args: any) => {};

const rootSafeFn = createSafeFn()
.input(schema)
.output(schema)
.handler(handlerFn)
.onComplete(callbackFn)
.onError(callbackFn)
.onSuccess(callbackFn)
.onStart(callbackFn);

const addSafeFn = (root: any) => {
return createSafeFn()
.use(root)
.input(schema)
.output(schema)
.handler(handlerFn)
.onComplete(callbackFn)
.onError(callbackFn)
.onSuccess(callbackFn)
.onStart(callbackFn);
};

const getSafeFnWithNMiddlewares = (n: number) => {
let safeFn: any = rootSafeFn;
for (let i = 0; i < n; i++) {
safeFn = addSafeFn(safeFn);
}
return safeFn;
};

const with10 = getSafeFnWithNMiddlewares(10);
const with100 = getSafeFnWithNMiddlewares(100);
const with1000 = getSafeFnWithNMiddlewares(1000);

export const safeFnAsyncWithCallbacksBench = new Bench({
name: "Safe FN Middleware With Callbacks",
})
.add("with1", async () => {
const res = await rootSafeFn.run({});
})
.add("with10", async () => {
const res = await with10.run({});
})
.add("with100", async () => {
const res = await with100.run({});
})
.add("with1000", async () => {
const res = await with1000.run({});
});
51 changes: 51 additions & 0 deletions packages/benchmark/safe-fn-gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ok } from "neverthrow";
import { createSafeFn } from "safe-fn";
import { Bench } from "tinybench";
import { z } from "zod";

const schema = z.unknown();

const handlerFn = async function* (args: any) {
return ok(args);
};

const rootSafeFn = createSafeFn()
.input(schema)
.output(schema)
.safeHandler(handlerFn);

const addSafeFn = (root: any) => {
return createSafeFn()
.use(root)
.input(schema)
.output(schema)
.safeHandler(handlerFn);
};

const getSafeFnWithNMiddlewares = (n: number) => {
let safeFn: any = rootSafeFn;
for (let i = 0; i < n; i++) {
safeFn = addSafeFn(safeFn);
}
return safeFn;
};

const with10 = getSafeFnWithNMiddlewares(10);
const with100 = getSafeFnWithNMiddlewares(100);
const with1000 = getSafeFnWithNMiddlewares(1000);

export const safeFnAsyncGenBench = new Bench({
name: "Safe FN Async Gen Middleware",
})
.add("with1", async () => {
const res = await rootSafeFn.run({});
})
.add("with10", async () => {
const res = await with10.run({});
})
.add("with100", async () => {
const res = await with100.run({});
})
.add("with1000", async () => {
const res = await with1000.run({});
});
50 changes: 50 additions & 0 deletions packages/benchmark/safe-fn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { createSafeFn } from "safe-fn";
import { Bench } from "tinybench";

import { ok } from "neverthrow";
import { z } from "zod";

const schema = z.unknown();

const handlerFn = async (args: any) => ok(args);

const rootSafeFn = createSafeFn()
.input(schema)
.output(schema)
.handler(handlerFn);

const addSafeFn = (root: any) => {
return createSafeFn()
.use(root)
.input(schema)
.output(schema)
.handler(handlerFn);
};

const getSafeFnWithNMiddlewares = (n: number) => {
let safeFn: any = rootSafeFn;
for (let i = 0; i < n; i++) {
safeFn = addSafeFn(safeFn);
}
return safeFn;
};

const with10 = getSafeFnWithNMiddlewares(10);
const with100 = getSafeFnWithNMiddlewares(100);
const with1000 = getSafeFnWithNMiddlewares(1000);
console.log("Done with this");
export const safeFnAsyncBench = new Bench({
name: "Safe FN Middleware",
})
.add("with1", async () => {
const res = await rootSafeFn.run({});
})
.add("with10", async () => {
const res = await with10.run({});
})
.add("with100", async () => {
const res = await with100.run({});
})
.add("with1000", async () => {
const res = await with1000.run({});
});
3 changes: 3 additions & 0 deletions packages/benchmark/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "@repo/typescript-config/base.json"
}
51 changes: 51 additions & 0 deletions packages/benchmark/zsa.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Bench } from "tinybench";
import { z } from "zod";

import { createServerAction, createServerActionProcedure } from "zsa";

const schema = z.unknown();

const rootProcedure = createServerActionProcedure()
.input(schema)
.output(schema)
.handler((e) => e);

const bareAction = createServerAction()
.input(schema)
.output(schema)
.handler((e) => e);

const getProcedureWithNMiddlewares = (n: number) => {
let procedure: any = rootProcedure;
for (let i = 1; i < n; i++) {
procedure = createServerActionProcedure(procedure)
.input(schema)
.output(schema)
.handler((e) => e);
}
return procedure
.createServerAction()
.input(schema)
.output(schema)
.handler((e) => e);
};

const with10 = getProcedureWithNMiddlewares(10);
const with100 = getProcedureWithNMiddlewares(100);
const with1000 = getProcedureWithNMiddlewares(1000);

export const zsaBench = new Bench({
name: "ZSA",
})
.add("with1", async () => {
const res = await bareAction({});
})
.add("with10", async () => {
const res = await with10({});
})
.add("with100", async () => {
const res = await with100({});
})
.add("with1000", async () => {
const res = await with1000({});
});
2 changes: 1 addition & 1 deletion packages/safe-fn-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ const { execute, result, isPending } = useServerAction(createTodoAction);
```

However, the magic of the integration of NeverThrow really comes out if your other functions are also returning a `Result` or `ResultAsync`. Instead of passing a regular function via `handler()` you can also use `safeHandler()`.
This builds on Neverthrow's `safeTry()` and takes in a generator function. This generator function receives the same args as `handler()`, but it allows you to `yield *` other results (note that in `Neverthrow` versions before 8.0.0 you need to use `.safeUnwrap()`).
This builds on Neverthrow's `safeTry()` and takes in a generator function. This generator function receives the same args as `handler()`, but it allows you to `yield *` other results (note that in `Neverthrow` versions before 8.1.0 you need to use `.safeUnwrap()`).
This is meant to emulate Rust's `?` operator and allows a very ergonomic way to write "return early if this fails, otherwise continue" logic.

This function has the same return type as the `handler()` example above.
Expand Down
2 changes: 1 addition & 1 deletion packages/safe-fn-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"@types/react": "^18.3.3",
"@typescript-eslint/parser": "^8.4.0",
"eslint": "^8.57.0",
"neverthrow": "^8.0.0",
"neverthrow": "^8.1.1",
"react": "^18.3.1",
"typescript": "^5.5.4"
},
Expand Down
Loading
Loading