Files
native-monad/benchmarks/scenarios/validation-pipeline.bench.ts
2026-03-30 08:34:35 +02:00

149 lines
6.0 KiB
TypeScript

import { bench, describe } from 'vitest';
import { STANDARD } from '../helpers/bench-options.js';
import {
ok,
err,
ntOk,
ntErr,
TsOk,
TsErr,
OxOk,
OxErr,
E,
pipe,
TmResult,
} from '../helpers/imports.js';
interface User {
name: string;
email: string;
age: number;
}
const validUser: User = { name: 'Alice', email: 'alice@example.com', age: 30 };
const invalidUser: User = { name: '', email: 'alice@example.com', age: 30 };
// ---------------------------------------------------------------------------
// 5-check validation — all pass
// ---------------------------------------------------------------------------
describe('Scenario: 5-check validation pipeline — all pass', () => {
bench('native-monad', () => {
ok<User, string>(validUser)
.flatMap(u => (u.name ? ok(u) : err('name required')))
.flatMap(u => (u.email.includes('@') ? ok(u) : err('invalid email')))
.flatMap(u => (u.age >= 18 ? ok(u) : err('must be 18+')))
.flatMap(u => (u.age <= 120 ? ok(u) : err('unrealistic age')))
.map(u => ({ ...u, validated: true }));
}, STANDARD);
bench('neverthrow', () => {
ntOk<User, string>(validUser)
.andThen(u => (u.name ? ntOk(u) : ntErr('name required')))
.andThen(u => (u.email.includes('@') ? ntOk(u) : ntErr('invalid email')))
.andThen(u => (u.age >= 18 ? ntOk(u) : ntErr('must be 18+')))
.andThen(u => (u.age <= 120 ? ntOk(u) : ntErr('unrealistic age')))
.map(u => ({ ...u, validated: true }));
}, STANDARD);
bench('ts-results-es', () => {
TsOk<User, string>(validUser)
.andThen(u => (u.name ? TsOk(u) : TsErr('name required')))
.andThen(u => (u.email.includes('@') ? TsOk(u) : TsErr('invalid email')))
.andThen(u => (u.age >= 18 ? TsOk(u) : TsErr('must be 18+')))
.andThen(u => (u.age <= 120 ? TsOk(u) : TsErr('unrealistic age')))
.map(u => ({ ...u, validated: true }));
}, STANDARD);
bench('oxide.ts', () => {
OxOk<User, string>(validUser)
.andThen(u => (u.name ? OxOk(u) : OxErr('name required')))
.andThen(u => (u.email.includes('@') ? OxOk(u) : OxErr('invalid email')))
.andThen(u => (u.age >= 18 ? OxOk(u) : OxErr('must be 18+')))
.andThen(u => (u.age <= 120 ? OxOk(u) : OxErr('unrealistic age')))
.map(u => ({ ...u, validated: true }));
}, STANDARD);
bench('fp-ts', () => {
pipe(
E.right<string, User>(validUser),
E.flatMap(u => (u.name ? E.right(u) : E.left('name required'))),
E.flatMap(u => (u.email.includes('@') ? E.right(u) : E.left('invalid email'))),
E.flatMap(u => (u.age >= 18 ? E.right(u) : E.left('must be 18+'))),
E.flatMap(u => (u.age <= 120 ? E.right(u) : E.left('unrealistic age'))),
E.map(u => ({ ...u, validated: true })),
);
}, STANDARD);
bench('true-myth', () => {
TmResult.ok<User, string>(validUser)
.andThen(u => (u.name ? TmResult.ok(u) : TmResult.err('name required')))
.andThen(u => (u.email.includes('@') ? TmResult.ok(u) : TmResult.err('invalid email')))
.andThen(u => (u.age >= 18 ? TmResult.ok(u) : TmResult.err('must be 18+')))
.andThen(u => (u.age <= 120 ? TmResult.ok(u) : TmResult.err('unrealistic age')))
.map(u => ({ ...u, validated: true }));
}, STANDARD);
});
// ---------------------------------------------------------------------------
// 5-check validation — fails at step 1 (early exit)
// ---------------------------------------------------------------------------
describe('Scenario: 5-check validation pipeline — fails at step 1', () => {
bench('native-monad', () => {
ok<User, string>(invalidUser)
.flatMap(u => (u.name ? ok(u) : err('name required')))
.flatMap(u => (u.email.includes('@') ? ok(u) : err('invalid email')))
.flatMap(u => (u.age >= 18 ? ok(u) : err('must be 18+')))
.flatMap(u => (u.age <= 120 ? ok(u) : err('unrealistic age')))
.map(u => ({ ...u, validated: true }));
}, STANDARD);
bench('neverthrow', () => {
ntOk<User, string>(invalidUser)
.andThen(u => (u.name ? ntOk(u) : ntErr('name required')))
.andThen(u => (u.email.includes('@') ? ntOk(u) : ntErr('invalid email')))
.andThen(u => (u.age >= 18 ? ntOk(u) : ntErr('must be 18+')))
.andThen(u => (u.age <= 120 ? ntOk(u) : ntErr('unrealistic age')))
.map(u => ({ ...u, validated: true }));
}, STANDARD);
bench('ts-results-es', () => {
TsOk<User, string>(invalidUser)
.andThen(u => (u.name ? TsOk(u) : TsErr('name required')))
.andThen(u => (u.email.includes('@') ? TsOk(u) : TsErr('invalid email')))
.andThen(u => (u.age >= 18 ? TsOk(u) : TsErr('must be 18+')))
.andThen(u => (u.age <= 120 ? TsOk(u) : TsErr('unrealistic age')))
.map(u => ({ ...u, validated: true }));
}, STANDARD);
bench('oxide.ts', () => {
OxOk<User, string>(invalidUser)
.andThen(u => (u.name ? OxOk(u) : OxErr('name required')))
.andThen(u => (u.email.includes('@') ? OxOk(u) : OxErr('invalid email')))
.andThen(u => (u.age >= 18 ? OxOk(u) : OxErr('must be 18+')))
.andThen(u => (u.age <= 120 ? OxOk(u) : OxErr('unrealistic age')))
.map(u => ({ ...u, validated: true }));
}, STANDARD);
bench('fp-ts', () => {
pipe(
E.right<string, User>(invalidUser),
E.flatMap(u => (u.name ? E.right(u) : E.left('name required'))),
E.flatMap(u => (u.email.includes('@') ? E.right(u) : E.left('invalid email'))),
E.flatMap(u => (u.age >= 18 ? E.right(u) : E.left('must be 18+'))),
E.flatMap(u => (u.age <= 120 ? E.right(u) : E.left('unrealistic age'))),
E.map(u => ({ ...u, validated: true })),
);
}, STANDARD);
bench('true-myth', () => {
TmResult.ok<User, string>(invalidUser)
.andThen(u => (u.name ? TmResult.ok(u) : TmResult.err('name required')))
.andThen(u => (u.email.includes('@') ? TmResult.ok(u) : TmResult.err('invalid email')))
.andThen(u => (u.age >= 18 ? TmResult.ok(u) : TmResult.err('must be 18+')))
.andThen(u => (u.age <= 120 ? TmResult.ok(u) : TmResult.err('unrealistic age')))
.map(u => ({ ...u, validated: true }));
}, STANDARD);
});