initial commit

This commit is contained in:
2026-03-30 08:34:35 +02:00
commit 49d8ea67ba
103 changed files with 22387 additions and 0 deletions

View 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);
});

View 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);
});

View 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);
});

View 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);
});