initial commit
This commit is contained in:
145
benchmarks/scenarios/error-propagation.bench.ts
Normal file
145
benchmarks/scenarios/error-propagation.bench.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { STANDARD } from '../helpers/bench-options.js';
|
||||
import {
|
||||
ok,
|
||||
err,
|
||||
none,
|
||||
ntOk,
|
||||
ntErr,
|
||||
TsOk,
|
||||
TsErr,
|
||||
TsNone,
|
||||
OxOk,
|
||||
OxErr,
|
||||
OxNone,
|
||||
E,
|
||||
O,
|
||||
pipe,
|
||||
TmResult,
|
||||
TmMaybe,
|
||||
} from '../helpers/imports.js';
|
||||
|
||||
const fn = (x: number) => x + 1;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Error propagation through 20 map calls
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Scenario: Err through 20 map calls', () => {
|
||||
const nm = err('fail');
|
||||
const nt = ntErr('fail');
|
||||
const ts = TsErr<number, string>('fail');
|
||||
const ox = OxErr('fail');
|
||||
const fp = E.left('fail') as E.Either<string, number>;
|
||||
const tm = TmResult.err<number, string>('fail');
|
||||
|
||||
bench('native-monad', () => {
|
||||
let r = nm as any;
|
||||
for (let i = 0; i < 20; i++) r = r.map(fn);
|
||||
}, STANDARD);
|
||||
|
||||
bench('neverthrow', () => {
|
||||
let r = nt as any;
|
||||
for (let i = 0; i < 20; i++) r = r.map(fn);
|
||||
}, STANDARD);
|
||||
|
||||
bench('ts-results-es', () => {
|
||||
let r = ts as any;
|
||||
for (let i = 0; i < 20; i++) r = r.map(fn);
|
||||
}, STANDARD);
|
||||
|
||||
bench('oxide.ts', () => {
|
||||
let r = ox as any;
|
||||
for (let i = 0; i < 20; i++) r = r.map(fn);
|
||||
}, STANDARD);
|
||||
|
||||
bench('fp-ts', () => {
|
||||
let r = fp;
|
||||
for (let i = 0; i < 20; i++) r = pipe(r, E.map(fn));
|
||||
}, STANDARD);
|
||||
|
||||
bench('true-myth', () => {
|
||||
let r = tm as any;
|
||||
for (let i = 0; i < 20; i++) r = r.map(fn);
|
||||
}, STANDARD);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Error propagation through 20 flatMap calls
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Scenario: Err through 20 flatMap calls', () => {
|
||||
const nm = err('fail');
|
||||
const nt = ntErr('fail');
|
||||
const ts = TsErr<number, string>('fail');
|
||||
const ox = OxErr('fail');
|
||||
const fp = E.left('fail') as E.Either<string, number>;
|
||||
const tm = TmResult.err<number, string>('fail');
|
||||
|
||||
bench('native-monad', () => {
|
||||
let r = nm as any;
|
||||
for (let i = 0; i < 20; i++) r = r.flatMap((x: number) => ok(x + 1));
|
||||
}, STANDARD);
|
||||
|
||||
bench('neverthrow', () => {
|
||||
let r = nt as any;
|
||||
for (let i = 0; i < 20; i++) r = r.andThen((x: number) => ntOk(x + 1));
|
||||
}, STANDARD);
|
||||
|
||||
bench('ts-results-es', () => {
|
||||
let r = ts as any;
|
||||
for (let i = 0; i < 20; i++) r = r.andThen((x: number) => TsOk(x + 1));
|
||||
}, STANDARD);
|
||||
|
||||
bench('oxide.ts', () => {
|
||||
let r = ox as any;
|
||||
for (let i = 0; i < 20; i++) r = r.andThen((x: number) => OxOk(x + 1));
|
||||
}, STANDARD);
|
||||
|
||||
bench('fp-ts', () => {
|
||||
let r = fp;
|
||||
for (let i = 0; i < 20; i++) r = pipe(r, E.flatMap(x => E.right(x + 1)));
|
||||
}, STANDARD);
|
||||
|
||||
bench('true-myth', () => {
|
||||
let r = tm as any;
|
||||
for (let i = 0; i < 20; i++) r = r.andThen((x: number) => TmResult.ok(x + 1));
|
||||
}, STANDARD);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// None propagation through 20 map calls (Option)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Scenario: None through 20 map calls', () => {
|
||||
const nm = none();
|
||||
const ts = TsNone;
|
||||
const ox = OxNone;
|
||||
const fp = O.none as O.Option<number>;
|
||||
const tm = TmMaybe.nothing<number>();
|
||||
|
||||
bench('native-monad', () => {
|
||||
let r = nm as any;
|
||||
for (let i = 0; i < 20; i++) r = r.map(fn);
|
||||
}, STANDARD);
|
||||
|
||||
bench('ts-results-es', () => {
|
||||
let r = ts as any;
|
||||
for (let i = 0; i < 20; i++) r = r.map(fn);
|
||||
}, STANDARD);
|
||||
|
||||
bench('oxide.ts', () => {
|
||||
let r = ox as any;
|
||||
for (let i = 0; i < 20; i++) r = r.map(fn);
|
||||
}, STANDARD);
|
||||
|
||||
bench('fp-ts', () => {
|
||||
let r = fp;
|
||||
for (let i = 0; i < 20; i++) r = pipe(r, O.map(fn));
|
||||
}, STANDARD);
|
||||
|
||||
bench('true-myth', () => {
|
||||
let r = tm as any;
|
||||
for (let i = 0; i < 20; i++) r = r.map(fn);
|
||||
}, STANDARD);
|
||||
});
|
||||
171
benchmarks/scenarios/memory.bench.ts
Normal file
171
benchmarks/scenarios/memory.bench.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { QUICK } from '../helpers/bench-options.js';
|
||||
import {
|
||||
ok,
|
||||
err,
|
||||
some,
|
||||
none,
|
||||
okSome,
|
||||
okNone,
|
||||
errNone,
|
||||
ntOk,
|
||||
ntErr,
|
||||
TsOk,
|
||||
TsErr,
|
||||
TsSome,
|
||||
TsNone,
|
||||
OxOk,
|
||||
OxErr,
|
||||
OxSome,
|
||||
OxNone,
|
||||
E,
|
||||
O,
|
||||
TmResult,
|
||||
TmMaybe,
|
||||
} from '../helpers/imports.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Singleton reuse: None / OkNone
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Memory: 1000x None creation (singleton vs constructor)', () => {
|
||||
bench('native-monad none() — frozen singleton', () => {
|
||||
for (let i = 0; i < 1000; i++) none();
|
||||
}, QUICK);
|
||||
|
||||
bench('ts-results-es None — constant', () => {
|
||||
for (let i = 0; i < 1000; i++) { const _ = TsNone; }
|
||||
}, QUICK);
|
||||
|
||||
bench('oxide.ts None — constant', () => {
|
||||
for (let i = 0; i < 1000; i++) { const _ = OxNone; }
|
||||
}, QUICK);
|
||||
|
||||
bench('fp-ts O.none — constant', () => {
|
||||
for (let i = 0; i < 1000; i++) { const _ = O.none; }
|
||||
}, QUICK);
|
||||
|
||||
bench('true-myth Maybe.nothing() — constructor', () => {
|
||||
for (let i = 0; i < 1000; i++) TmMaybe.nothing();
|
||||
}, QUICK);
|
||||
});
|
||||
|
||||
describe('Memory: 1000x OkNone creation', () => {
|
||||
bench('native-monad okNone() — frozen singleton', () => {
|
||||
for (let i = 0; i < 1000; i++) okNone();
|
||||
}, QUICK);
|
||||
|
||||
bench('fp-ts E.right(O.none) — two allocations', () => {
|
||||
for (let i = 0; i < 1000; i++) E.right(O.none);
|
||||
}, QUICK);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constructor allocation: Ok / Some / OkSome
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Memory: 1000x Ok construction', () => {
|
||||
bench('native-monad ok(i) — class instance', () => {
|
||||
for (let i = 0; i < 1000; i++) ok(i);
|
||||
}, QUICK);
|
||||
|
||||
bench('neverthrow ok(i) — class instance', () => {
|
||||
for (let i = 0; i < 1000; i++) ntOk(i);
|
||||
}, QUICK);
|
||||
|
||||
bench('ts-results-es Ok(i) — class instance', () => {
|
||||
for (let i = 0; i < 1000; i++) TsOk(i);
|
||||
}, QUICK);
|
||||
|
||||
bench('oxide.ts Ok(i) — tagged object', () => {
|
||||
for (let i = 0; i < 1000; i++) OxOk(i);
|
||||
}, QUICK);
|
||||
|
||||
bench('fp-ts E.right(i) — tagged object', () => {
|
||||
for (let i = 0; i < 1000; i++) E.right(i);
|
||||
}, QUICK);
|
||||
|
||||
bench('true-myth Result.ok(i) — class instance', () => {
|
||||
for (let i = 0; i < 1000; i++) TmResult.ok(i);
|
||||
}, QUICK);
|
||||
});
|
||||
|
||||
describe('Memory: 1000x Some construction', () => {
|
||||
bench('native-monad some(i)', () => {
|
||||
for (let i = 0; i < 1000; i++) some(i);
|
||||
}, QUICK);
|
||||
|
||||
bench('ts-results-es Some(i)', () => {
|
||||
for (let i = 0; i < 1000; i++) TsSome(i);
|
||||
}, QUICK);
|
||||
|
||||
bench('oxide.ts Some(i)', () => {
|
||||
for (let i = 0; i < 1000; i++) OxSome(i);
|
||||
}, QUICK);
|
||||
|
||||
bench('fp-ts O.some(i)', () => {
|
||||
for (let i = 0; i < 1000; i++) O.some(i);
|
||||
}, QUICK);
|
||||
|
||||
bench('true-myth Maybe.just(i)', () => {
|
||||
for (let i = 0; i < 1000; i++) TmMaybe.just(i);
|
||||
}, QUICK);
|
||||
});
|
||||
|
||||
describe('Memory: 1000x Err construction', () => {
|
||||
bench('native-monad err(i)', () => {
|
||||
for (let i = 0; i < 1000; i++) err(`err-${i}`);
|
||||
}, QUICK);
|
||||
|
||||
bench('neverthrow err(i)', () => {
|
||||
for (let i = 0; i < 1000; i++) ntErr(`err-${i}`);
|
||||
}, QUICK);
|
||||
|
||||
bench('ts-results-es Err(i)', () => {
|
||||
for (let i = 0; i < 1000; i++) TsErr(`err-${i}`);
|
||||
}, QUICK);
|
||||
|
||||
bench('oxide.ts Err(i)', () => {
|
||||
for (let i = 0; i < 1000; i++) OxErr(`err-${i}`);
|
||||
}, QUICK);
|
||||
|
||||
bench('fp-ts E.left(i)', () => {
|
||||
for (let i = 0; i < 1000; i++) E.left(`err-${i}`);
|
||||
}, QUICK);
|
||||
|
||||
bench('true-myth Result.err(i)', () => {
|
||||
for (let i = 0; i < 1000; i++) TmResult.err(`err-${i}`);
|
||||
}, QUICK);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Three-state: OkSome vs nested allocation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Memory: 1000x OkSome (single vs nested allocation)', () => {
|
||||
bench('native-monad okSome(i) — single class', () => {
|
||||
for (let i = 0; i < 1000; i++) okSome(i);
|
||||
}, QUICK);
|
||||
|
||||
bench('fp-ts E.right(O.some(i)) — two objects', () => {
|
||||
for (let i = 0; i < 1000; i++) E.right(O.some(i));
|
||||
}, QUICK);
|
||||
|
||||
bench('ts-results-es Ok(Some(i)) — two classes', () => {
|
||||
for (let i = 0; i < 1000; i++) TsOk(TsSome(i));
|
||||
}, QUICK);
|
||||
});
|
||||
|
||||
describe('Memory: 1000x ErrNone (single vs nested allocation)', () => {
|
||||
bench('native-monad errNone(i) — single class', () => {
|
||||
for (let i = 0; i < 1000; i++) errNone(`err-${i}`);
|
||||
}, QUICK);
|
||||
|
||||
bench('fp-ts E.left(i) — single object', () => {
|
||||
for (let i = 0; i < 1000; i++) E.left(`err-${i}`);
|
||||
}, QUICK);
|
||||
|
||||
bench('ts-results-es Err(i) — single class', () => {
|
||||
for (let i = 0; i < 1000; i++) TsErr(`err-${i}`);
|
||||
}, QUICK);
|
||||
});
|
||||
127
benchmarks/scenarios/parsing.bench.ts
Normal file
127
benchmarks/scenarios/parsing.bench.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { STANDARD } from '../helpers/bench-options.js';
|
||||
import {
|
||||
ok,
|
||||
err,
|
||||
Result,
|
||||
ntOk,
|
||||
ntErr,
|
||||
ntFromThrowable,
|
||||
TsOk,
|
||||
TsErr,
|
||||
OxOk,
|
||||
OxErr,
|
||||
E,
|
||||
pipe,
|
||||
TmResult,
|
||||
} from '../helpers/imports.js';
|
||||
|
||||
const validJson = '{"name":"Alice","age":30,"email":"alice@example.com"}';
|
||||
const invalidJson = 'not json {{{';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// JSON parsing — valid input
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Scenario: JSON parse + extract field — valid input', () => {
|
||||
bench('native-monad', () => {
|
||||
Result.from(() => JSON.parse(validJson), () => 'parse error')
|
||||
.map(obj => obj.name as string)
|
||||
.flatMap(name => (name.length > 0 ? ok(name) : err('empty name')));
|
||||
}, STANDARD);
|
||||
|
||||
bench('neverthrow', () => {
|
||||
ntFromThrowable(() => JSON.parse(validJson), () => 'parse error')()
|
||||
.map(obj => obj.name as string)
|
||||
.andThen(name => (name.length > 0 ? ntOk(name) : ntErr('empty name')));
|
||||
}, STANDARD);
|
||||
|
||||
bench('ts-results-es', () => {
|
||||
let r;
|
||||
try { r = TsOk(JSON.parse(validJson)); }
|
||||
catch { r = TsErr('parse error'); }
|
||||
r.map((obj: any) => obj.name as string)
|
||||
.andThen((name: string) => (name.length > 0 ? TsOk(name) : TsErr('empty name')));
|
||||
}, STANDARD);
|
||||
|
||||
bench('oxide.ts', () => {
|
||||
let r;
|
||||
try { r = OxOk(JSON.parse(validJson)); }
|
||||
catch { r = OxErr('parse error'); }
|
||||
r.map((obj: any) => obj.name as string)
|
||||
.andThen((name: string) => (name.length > 0 ? OxOk(name) : OxErr('parse error')));
|
||||
}, STANDARD);
|
||||
|
||||
bench('fp-ts', () => {
|
||||
pipe(
|
||||
E.tryCatch(() => JSON.parse(validJson), () => 'parse error'),
|
||||
E.map(obj => obj.name as string),
|
||||
E.flatMap(name => (name.length > 0 ? E.right(name) : E.left('empty name'))),
|
||||
);
|
||||
}, STANDARD);
|
||||
|
||||
bench('true-myth', () => {
|
||||
TmResult.tryOr('parse error', () => JSON.parse(validJson))
|
||||
.map(obj => obj.name as string)
|
||||
.andThen(name => (name.length > 0 ? TmResult.ok(name) : TmResult.err('empty name')));
|
||||
}, STANDARD);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// JSON parsing — invalid input
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Scenario: JSON parse + extract field — invalid input', () => {
|
||||
bench('native-monad', () => {
|
||||
Result.from(() => JSON.parse(invalidJson), () => 'parse error')
|
||||
.map(obj => obj.name as string)
|
||||
.flatMap(name => (name.length > 0 ? ok(name) : err('empty name')));
|
||||
}, STANDARD);
|
||||
|
||||
bench('neverthrow', () => {
|
||||
ntFromThrowable(() => JSON.parse(invalidJson), () => 'parse error')()
|
||||
.map(obj => obj.name as string)
|
||||
.andThen(name => (name.length > 0 ? ntOk(name) : ntErr('empty name')));
|
||||
}, STANDARD);
|
||||
|
||||
bench('fp-ts', () => {
|
||||
pipe(
|
||||
E.tryCatch(() => JSON.parse(invalidJson), () => 'parse error'),
|
||||
E.map(obj => obj.name as string),
|
||||
E.flatMap(name => (name.length > 0 ? E.right(name) : E.left('empty name'))),
|
||||
);
|
||||
}, STANDARD);
|
||||
|
||||
bench('true-myth', () => {
|
||||
TmResult.tryOr('parse error', () => JSON.parse(invalidJson))
|
||||
.map(obj => obj.name as string)
|
||||
.andThen(name => (name.length > 0 ? TmResult.ok(name) : TmResult.err('empty name')));
|
||||
}, STANDARD);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Number parsing — valid
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Scenario: number parsing — valid', () => {
|
||||
const input = '42.5';
|
||||
|
||||
bench('native-monad', () => {
|
||||
Result.from(() => {
|
||||
const n = Number(input);
|
||||
if (Number.isNaN(n)) throw new Error('NaN');
|
||||
return n;
|
||||
}).flatMap(n => (n > 0 ? ok(n) : err('not positive')));
|
||||
}, STANDARD);
|
||||
|
||||
bench('fp-ts', () => {
|
||||
pipe(
|
||||
E.tryCatch(() => {
|
||||
const n = Number(input);
|
||||
if (Number.isNaN(n)) throw new Error('NaN');
|
||||
return n;
|
||||
}, () => 'parse error'),
|
||||
E.flatMap(n => (n > 0 ? E.right(n) : E.left('not positive'))),
|
||||
);
|
||||
}, STANDARD);
|
||||
});
|
||||
148
benchmarks/scenarios/validation-pipeline.bench.ts
Normal file
148
benchmarks/scenarios/validation-pipeline.bench.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user