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,70 @@
import { bench, describe } from 'vitest';
import { EXTENDED } from '../helpers/bench-options.js';
import { SIZES } from '../helpers/data-generators.js';
import {
ok,
err,
Result,
ResultPromise,
ntOk,
ntErr,
NtResultAsync,
E,
pipe,
} from '../helpers/imports.js';
import * as TE from 'fp-ts/TaskEither';
// ---------------------------------------------------------------------------
// ResultPromise.from — success
// ---------------------------------------------------------------------------
describe('ResultPromise.from — success', () => {
bench('native-monad', async () => {
await ResultPromise.from(() => Promise.resolve(42));
}, EXTENDED);
bench('neverthrow', async () => {
await NtResultAsync.fromPromise(Promise.resolve(42), () => 'err');
}, EXTENDED);
bench('fp-ts', async () => {
await TE.tryCatch(() => Promise.resolve(42), () => 'err')();
}, EXTENDED);
});
// ---------------------------------------------------------------------------
// ResultPromise.from — failure
// ---------------------------------------------------------------------------
describe('ResultPromise.from — failure', () => {
bench('native-monad', async () => {
await ResultPromise.from(() => Promise.reject(new Error('fail')), () => 'err');
}, EXTENDED);
bench('neverthrow', async () => {
await NtResultAsync.fromPromise(Promise.reject(new Error('fail')), () => 'err');
}, EXTENDED);
bench('fp-ts', async () => {
await TE.tryCatch(() => Promise.reject(new Error('fail')), () => 'err')();
}, EXTENDED);
});
// ---------------------------------------------------------------------------
// ResultPromise.all — all Ok
// ---------------------------------------------------------------------------
for (const size of [10, 100] as const) {
describe(`ResultPromise.all — ${size} items, all Ok`, () => {
const nmPromises = () => Array.from({ length: size }, (_, i) => Promise.resolve(ok(i)));
const ntPromises = () => Array.from({ length: size }, (_, i) => NtResultAsync.fromSafePromise(Promise.resolve(i)));
bench('native-monad', async () => {
await ResultPromise.all(nmPromises());
}, EXTENDED);
bench('neverthrow', async () => {
await NtResultAsync.combine(ntPromises());
}, EXTENDED);
});
}

View File

@@ -0,0 +1,153 @@
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';
// ---------------------------------------------------------------------------
// 5-step pipeline — success path
// ---------------------------------------------------------------------------
describe('Result chaining — 5-step pipeline (all succeed)', () => {
bench('native-monad', () => {
ok(42)
.map(x => x * 2)
.flatMap(x => (x > 0 ? ok(x) : err('negative')))
.map(x => ({ value: x }))
.flatMap(x => ok({ ...x, label: 'result' }))
.map(x => JSON.stringify(x));
}, STANDARD);
bench('neverthrow', () => {
ntOk(42)
.map(x => x * 2)
.andThen(x => (x > 0 ? ntOk(x) : ntErr('negative')))
.map(x => ({ value: x }))
.andThen(x => ntOk({ ...x, label: 'result' }))
.map(x => JSON.stringify(x));
}, STANDARD);
bench('ts-results-es', () => {
TsOk<number, string>(42)
.map(x => x * 2)
.andThen(x => (x > 0 ? TsOk(x) : TsErr('negative')))
.map(x => ({ value: x }))
.andThen(x => TsOk<{ value: number; label: string }, string>({ ...x, label: 'result' }))
.map(x => JSON.stringify(x));
}, STANDARD);
bench('oxide.ts', () => {
OxOk(42)
.map(x => x * 2)
.andThen(x => (x > 0 ? OxOk(x) : OxErr('negative')))
.map(x => ({ value: x }))
.andThen(x => OxOk({ ...x, label: 'result' }))
.map(x => JSON.stringify(x));
}, STANDARD);
bench('fp-ts', () => {
pipe(
E.right(42),
E.map(x => x * 2),
E.flatMap(x => (x > 0 ? E.right(x) : E.left('negative'))),
E.map(x => ({ value: x })),
E.flatMap(x => E.right({ ...x, label: 'result' })),
E.map(x => JSON.stringify(x)),
);
}, STANDARD);
bench('true-myth', () => {
TmResult.ok<number, string>(42)
.map(x => x * 2)
.andThen(x => (x > 0 ? TmResult.ok(x) : TmResult.err('negative')))
.map(x => ({ value: x }))
.andThen(x => TmResult.ok({ ...x, label: 'result' }))
.map(x => JSON.stringify(x));
}, STANDARD);
});
// ---------------------------------------------------------------------------
// 10-step pipeline — error at step 1 (9 no-ops)
// ---------------------------------------------------------------------------
describe('Result chaining — error at step 1, 9 no-op maps', () => {
const nmErr = err('fail');
const ntErrVal = ntErr('fail');
const tsErr = TsErr<number, string>('fail');
const oxErr = OxErr('fail');
const fpErr = E.left('fail');
const tmErr = TmResult.err<number, string>('fail');
const fn = (x: number) => x + 1;
bench('native-monad', () => {
nmErr.map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn);
}, STANDARD);
bench('neverthrow', () => {
ntErrVal.map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn);
}, STANDARD);
bench('ts-results-es', () => {
tsErr.map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn);
}, STANDARD);
bench('oxide.ts', () => {
oxErr.map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn);
}, STANDARD);
bench('fp-ts', () => {
pipe(fpErr, E.map(fn), E.map(fn), E.map(fn), E.map(fn), E.map(fn), E.map(fn), E.map(fn), E.map(fn), E.map(fn));
}, STANDARD);
bench('true-myth', () => {
tmErr.map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn);
}, STANDARD);
});
// ---------------------------------------------------------------------------
// 20-step deep map chain — success path
// ---------------------------------------------------------------------------
describe('Result chaining — 20 maps on Ok', () => {
const fn = (x: number) => x + 1;
bench('native-monad', () => {
let r = ok(0);
for (let i = 0; i < 20; i++) r = r.map(fn);
}, STANDARD);
bench('neverthrow', () => {
let r = ntOk(0);
for (let i = 0; i < 20; i++) r = r.map(fn);
}, STANDARD);
bench('ts-results-es', () => {
let r = TsOk<number, string>(0);
for (let i = 0; i < 20; i++) r = r.map(fn) as any;
}, STANDARD);
bench('oxide.ts', () => {
let r = OxOk(0);
for (let i = 0; i < 20; i++) r = r.map(fn) as any;
}, STANDARD);
bench('fp-ts', () => {
let r = E.right(0) as E.Either<string, number>;
for (let i = 0; i < 20; i++) r = pipe(r, E.map(fn));
}, STANDARD);
bench('true-myth', () => {
let r = TmResult.ok<number, string>(0);
for (let i = 0; i < 20; i++) r = r.map(fn) as any;
}, STANDARD);
});

View File

@@ -0,0 +1,177 @@
import { bench, describe } from 'vitest';
import { STANDARD, EXTENDED } from '../helpers/bench-options.js';
import { SIZES, errorIndices } from '../helpers/data-generators.js';
import {
ok,
err,
Result,
ntOk,
ntErr,
NtResult,
TsOk,
TsErr,
TsResult,
OxOk,
OxErr,
E,
pipe,
TmResult,
} from '../helpers/imports.js';
import * as RA from 'fp-ts/ReadonlyArray';
// ---------------------------------------------------------------------------
// Result.all — all Ok
// ---------------------------------------------------------------------------
for (const size of SIZES) {
const opts = size >= 10_000 ? EXTENDED : STANDARD;
const nmAll = Array.from({ length: size }, (_, i) => ok(i));
const ntAll = Array.from({ length: size }, (_, i) => ntOk(i));
const tsAll = Array.from({ length: size }, (_, i) => TsOk<number, string>(i));
const oxAll = Array.from({ length: size }, (_, i) => OxOk(i));
const fpAll = Array.from({ length: size }, (_, i) => E.right(i));
const tmAll = Array.from({ length: size }, (_, i) => TmResult.ok<number, string>(i));
describe(`Result.all — ${size} items, all Ok`, () => {
bench('native-monad', () => { Result.all(nmAll); }, opts);
bench('neverthrow', () => { NtResult.combine(ntAll); }, opts);
bench('ts-results-es', () => { TsResult.all(...tsAll); }, opts);
// oxide.ts: no built-in all/combine
bench('fp-ts', () => { pipe(fpAll, RA.sequence(E.Applicative)); }, opts);
// true-myth: no built-in all
bench('true-myth (manual)', () => {
const values: number[] = [];
for (const r of tmAll) {
if (TmResult.isErr(r)) return r;
values.push((r as { value: number }).value);
}
return TmResult.ok(values);
}, opts);
});
}
// ---------------------------------------------------------------------------
// Result.all — 20% Err (short-circuit)
// ---------------------------------------------------------------------------
for (const size of SIZES) {
const opts = size >= 10_000 ? EXTENDED : STANDARD;
const errIdx = errorIndices(size, 0.2);
const nmMixed = Array.from({ length: size }, (_, i) =>
errIdx.has(i) ? err('fail') : ok(i),
);
const ntMixed = Array.from({ length: size }, (_, i) =>
errIdx.has(i) ? ntErr('fail') : ntOk(i),
);
const tsMixed = Array.from({ length: size }, (_, i) =>
errIdx.has(i) ? TsErr<number, string>('fail') : TsOk<number, string>(i),
);
const fpMixed = Array.from({ length: size }, (_, i) =>
errIdx.has(i) ? E.left('fail' as const) : E.right(i),
);
const tmMixed = Array.from({ length: size }, (_, i) =>
errIdx.has(i) ? TmResult.err<number, string>('fail') : TmResult.ok<number, string>(i),
);
describe(`Result.all — ${size} items, 20% Err (short-circuit)`, () => {
bench('native-monad', () => { Result.all(nmMixed); }, opts);
bench('neverthrow', () => { NtResult.combine(ntMixed as any); }, opts);
bench('ts-results-es', () => { TsResult.all(...tsMixed); }, opts);
bench('fp-ts', () => { pipe(fpMixed, RA.sequence(E.Applicative)); }, opts);
bench('true-myth (manual)', () => {
const values: number[] = [];
for (const r of tmMixed) {
if (TmResult.isErr(r)) return r;
values.push((r as { value: number }).value);
}
return TmResult.ok(values);
}, opts);
});
}
// ---------------------------------------------------------------------------
// Result.partition
// ---------------------------------------------------------------------------
for (const size of SIZES) {
const opts = size >= 10_000 ? EXTENDED : STANDARD;
const errIdx = errorIndices(size, 0.2);
const nmMixed = Array.from({ length: size }, (_, i) =>
errIdx.has(i) ? err('fail') : ok(i),
);
const ntMixed = Array.from({ length: size }, (_, i) =>
errIdx.has(i) ? ntErr('fail') : ntOk(i),
);
const fpMixed = Array.from({ length: size }, (_, i) =>
errIdx.has(i) ? E.left('fail' as const) : E.right(i),
);
describe(`Result.partition — ${size} items`, () => {
bench('native-monad', () => { Result.partition(nmMixed); }, opts);
bench('neverthrow (manual)', () => {
const values: number[] = [];
const errors: string[] = [];
for (const r of ntMixed) {
if (r.isOk()) values.push(r.value);
else errors.push(r.error);
}
return { values, errors };
}, opts);
bench('fp-ts', () => { RA.separate(fpMixed); }, opts);
});
}
// ---------------------------------------------------------------------------
// Result.compact
// ---------------------------------------------------------------------------
for (const size of SIZES) {
const opts = size >= 10_000 ? EXTENDED : STANDARD;
const errIdx = errorIndices(size, 0.2);
const nmMixed = Array.from({ length: size }, (_, i) =>
errIdx.has(i) ? err('fail') : ok(i),
);
const ntMixed = Array.from({ length: size }, (_, i) =>
errIdx.has(i) ? ntErr('fail') : ntOk(i),
);
describe(`Result.compact — ${size} items`, () => {
bench('native-monad', () => { Result.compact(nmMixed); }, opts);
bench('neverthrow (manual)', () => {
const values: number[] = [];
for (const r of ntMixed) {
if (r.isOk()) values.push(r.value);
}
return values;
}, opts);
});
}
// ---------------------------------------------------------------------------
// Result.some / Result.every
// ---------------------------------------------------------------------------
for (const size of [100, 1_000]) {
const errIdx = errorIndices(size, 0.2);
const nmMixed = Array.from({ length: size }, (_, i) =>
errIdx.has(i) ? err('fail') : ok(i),
);
const ntMixed = Array.from({ length: size }, (_, i) =>
errIdx.has(i) ? ntErr('fail') : ntOk(i),
);
describe(`Result.some — ${size} items`, () => {
bench('native-monad', () => { Result.some(nmMixed); }, STANDARD);
bench('neverthrow (manual)', () => { ntMixed.some(r => r.isOk()); }, STANDARD);
});
describe(`Result.every — ${size} items`, () => {
bench('native-monad', () => { Result.every(nmMixed); }, STANDARD);
bench('neverthrow (manual)', () => { ntMixed.every(r => r.isOk()); }, STANDARD);
});
}

View File

@@ -0,0 +1,59 @@
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';
// ---------------------------------------------------------------------------
// Ok / Right / success construction
// ---------------------------------------------------------------------------
describe('Result construction — Ok (primitive)', () => {
bench('native-monad', () => { ok(42); }, STANDARD);
bench('neverthrow', () => { ntOk(42); }, STANDARD);
bench('ts-results-es', () => { TsOk(42); }, STANDARD);
bench('oxide.ts', () => { OxOk(42); }, STANDARD);
bench('fp-ts', () => { E.right(42); }, STANDARD);
bench('true-myth', () => { TmResult.ok(42); }, STANDARD);
});
describe('Result construction — Err (primitive)', () => {
bench('native-monad', () => { err('fail'); }, STANDARD);
bench('neverthrow', () => { ntErr('fail'); }, STANDARD);
bench('ts-results-es', () => { TsErr('fail'); }, STANDARD);
bench('oxide.ts', () => { OxErr('fail'); }, STANDARD);
bench('fp-ts', () => { E.left('fail'); }, STANDARD);
bench('true-myth', () => { TmResult.err('fail'); }, STANDARD);
});
describe('Result construction — Ok (object payload)', () => {
const value = { id: 1, name: 'Alice', roles: ['admin', 'user'] };
bench('native-monad', () => { ok(value); }, STANDARD);
bench('neverthrow', () => { ntOk(value); }, STANDARD);
bench('ts-results-es', () => { TsOk(value); }, STANDARD);
bench('oxide.ts', () => { OxOk(value); }, STANDARD);
bench('fp-ts', () => { E.right(value); }, STANDARD);
bench('true-myth', () => { TmResult.ok(value); }, STANDARD);
});
describe('Result construction — Err (Error object)', () => {
const error = new Error('something went wrong');
bench('native-monad', () => { err(error); }, STANDARD);
bench('neverthrow', () => { ntErr(error); }, STANDARD);
bench('ts-results-es', () => { TsErr(error); }, STANDARD);
bench('oxide.ts', () => { OxErr(error); }, STANDARD);
bench('fp-ts', () => { E.left(error); }, STANDARD);
bench('true-myth', () => { TmResult.err(error); }, STANDARD);
});

View File

@@ -0,0 +1,68 @@
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';
// ---------------------------------------------------------------------------
// toNullable — success path
// ---------------------------------------------------------------------------
describe('Result.toNullable — Ok', () => {
const nm = ok(42);
const ox = OxOk(42);
bench('native-monad', () => { nm.toNullable(); }, STANDARD);
// neverthrow: no toNullable — use match or _unsafeUnwrap
// ts-results-es: no toNullable
bench('oxide.ts', () => { ox.into(); }, STANDARD);
bench('fp-ts', () => { pipe(E.right(42), E.toUnion); }, STANDARD);
bench('true-myth', () => { TmResult.ok<number, string>(42).isOk ? 42 : null; }, STANDARD);
});
// ---------------------------------------------------------------------------
// toNullable — failure path
// ---------------------------------------------------------------------------
describe('Result.toNullable — Err', () => {
const nm = err('fail');
const ox = OxErr('fail');
bench('native-monad', () => { nm.toNullable(); }, STANDARD);
bench('oxide.ts', () => { ox.into(); }, STANDARD);
bench('fp-ts', () => { pipe(E.left('fail'), E.toUnion); }, STANDARD);
});
// ---------------------------------------------------------------------------
// toString
// ---------------------------------------------------------------------------
describe('Result.toString — Ok', () => {
const nm = ok(42);
const ox = OxOk(42);
bench('native-monad', () => { nm.toString(); }, STANDARD);
// neverthrow: no toString
// ts-results-es: no toString
bench('oxide.ts', () => { ox.toString(); }, STANDARD);
// fp-ts: no toString
// true-myth: no toString
});
describe('Result.toString — Err', () => {
const nm = err('fail');
const ox = OxErr('fail');
bench('native-monad', () => { nm.toString(); }, STANDARD);
bench('oxide.ts', () => { ox.toString(); }, STANDARD);
});

View File

@@ -0,0 +1,39 @@
import { bench, describe } from 'vitest';
import { STANDARD } from '../helpers/bench-options.js';
import {
Result,
ntFromThrowable,
E,
TmResult,
} from '../helpers/imports.js';
// ---------------------------------------------------------------------------
// Result.from — success (no throw)
// ---------------------------------------------------------------------------
describe('Result.from — success (no throw)', () => {
const fn = () => 42;
bench('native-monad', () => { Result.from(fn); }, STANDARD);
bench('neverthrow', () => { ntFromThrowable(fn)(); }, STANDARD);
bench('fp-ts', () => { E.tryCatch(fn, () => 'error'); }, STANDARD);
bench('true-myth', () => { TmResult.tryOr('error', fn); }, STANDARD);
// ts-results-es: no from/tryCatch
// oxide.ts: no from/tryCatch
});
// ---------------------------------------------------------------------------
// Result.from — failure (throws)
// ---------------------------------------------------------------------------
describe('Result.from — failure (throws)', () => {
const thrower = () => {
throw new Error('fail');
};
const mapErr = () => 'error' as const;
bench('native-monad', () => { Result.from(thrower, mapErr); }, STANDARD);
bench('neverthrow', () => { ntFromThrowable(thrower, mapErr)(); }, STANDARD);
bench('fp-ts', () => { E.tryCatch(thrower, mapErr); }, STANDARD);
bench('true-myth', () => { TmResult.tryOr('error', thrower); }, STANDARD);
});

View File

@@ -0,0 +1,208 @@
import { bench, describe } from 'vitest';
import { STANDARD } from '../helpers/bench-options.js';
import {
ok,
err,
ntOk,
ntErr,
TsOk,
TsErr,
OxOk,
OxErr,
oxMatch,
E,
pipe,
TmResult,
} from '../helpers/imports.js';
const double = (x: number) => x * 2;
const toUpper = (e: string) => e.toUpperCase();
// ---------------------------------------------------------------------------
// map — success path
// ---------------------------------------------------------------------------
describe('Result.map — success path', () => {
const nm = ok(42);
const nt = ntOk(42);
const ts = TsOk(42);
const ox = OxOk(42);
const fp = E.right(42);
const tm = TmResult.ok<number, string>(42);
bench('native-monad', () => { nm.map(double); }, STANDARD);
bench('neverthrow', () => { nt.map(double); }, STANDARD);
bench('ts-results-es', () => { ts.map(double); }, STANDARD);
bench('oxide.ts', () => { ox.map(double); }, STANDARD);
bench('fp-ts', () => { pipe(fp, E.map(double)); }, STANDARD);
bench('true-myth', () => { tm.map(double); }, STANDARD);
});
// ---------------------------------------------------------------------------
// map — failure path (no-op)
// ---------------------------------------------------------------------------
describe('Result.map — failure path (no-op)', () => {
const nm = err('fail');
const nt = ntErr('fail');
const ts = TsErr('fail');
const ox = OxErr('fail');
const fp = E.left('fail');
const tm = TmResult.err<number, string>('fail');
bench('native-monad', () => { nm.map(double); }, STANDARD);
bench('neverthrow', () => { nt.map(double); }, STANDARD);
bench('ts-results-es', () => { ts.map(double); }, STANDARD);
bench('oxide.ts', () => { ox.map(double); }, STANDARD);
bench('fp-ts', () => { pipe(fp, E.map(double)); }, STANDARD);
bench('true-myth', () => { tm.map(double); }, STANDARD);
});
// ---------------------------------------------------------------------------
// flatMap — success path
// ---------------------------------------------------------------------------
describe('Result.flatMap — success path', () => {
const nm = ok(42);
const nt = ntOk(42);
const ts = TsOk<number, string>(42);
const ox = OxOk(42);
const fp = E.right(42);
const tm = TmResult.ok<number, string>(42);
bench('native-monad', () => { nm.flatMap(x => ok(x * 2)); }, STANDARD);
bench('neverthrow', () => { nt.andThen(x => ntOk(x * 2)); }, STANDARD);
bench('ts-results-es', () => { ts.andThen(x => TsOk(x * 2)); }, STANDARD);
bench('oxide.ts', () => { ox.andThen(x => OxOk(x * 2)); }, STANDARD);
bench('fp-ts', () => { pipe(fp, E.flatMap(x => E.right(x * 2))); }, STANDARD);
bench('true-myth', () => { tm.andThen(x => TmResult.ok(x * 2)); }, STANDARD);
});
// ---------------------------------------------------------------------------
// flatMap — failure path (no-op)
// ---------------------------------------------------------------------------
describe('Result.flatMap — failure path (no-op)', () => {
const nm = err('fail');
const nt = ntErr('fail');
const ts = TsErr<number, string>('fail');
const ox = OxErr('fail');
const fp = E.left('fail');
const tm = TmResult.err<number, string>('fail');
bench('native-monad', () => { nm.flatMap(x => ok(x * 2)); }, STANDARD);
bench('neverthrow', () => { nt.andThen(x => ntOk(x * 2)); }, STANDARD);
bench('ts-results-es', () => { ts.andThen(x => TsOk(x * 2)); }, STANDARD);
bench('oxide.ts', () => { ox.andThen(x => OxOk(x * 2)); }, STANDARD);
bench('fp-ts', () => { pipe(fp, E.flatMap(x => E.right(x * 2))); }, STANDARD);
bench('true-myth', () => { tm.andThen(x => TmResult.ok(x * 2)); }, STANDARD);
});
// ---------------------------------------------------------------------------
// mapErr — error path
// ---------------------------------------------------------------------------
describe('Result.mapErr — error path', () => {
const nm = err('fail');
const nt = ntErr('fail');
const ts = TsErr<number, string>('fail');
const ox = OxErr('fail');
const fp = E.left('fail');
const tm = TmResult.err<number, string>('fail');
bench('native-monad', () => { nm.mapErr(toUpper); }, STANDARD);
bench('neverthrow', () => { nt.mapErr(toUpper); }, STANDARD);
bench('ts-results-es', () => { ts.mapErr(toUpper); }, STANDARD);
bench('oxide.ts', () => { ox.mapErr(toUpper); }, STANDARD);
bench('fp-ts', () => { pipe(fp, E.mapLeft(toUpper)); }, STANDARD);
bench('true-myth', () => { tm.mapErr(toUpper); }, STANDARD);
});
// ---------------------------------------------------------------------------
// mapErr — success path (no-op)
// ---------------------------------------------------------------------------
describe('Result.mapErr — success path (no-op)', () => {
const nm = ok(42);
const nt = ntOk(42);
const ts = TsOk<number, string>(42);
const ox = OxOk(42);
const fp = E.right(42);
const tm = TmResult.ok<number, string>(42);
bench('native-monad', () => { nm.mapErr(toUpper); }, STANDARD);
bench('neverthrow', () => { nt.mapErr(toUpper); }, STANDARD);
bench('ts-results-es', () => { ts.mapErr(toUpper); }, STANDARD);
bench('oxide.ts', () => { ox.mapErr(toUpper); }, STANDARD);
bench('fp-ts', () => { pipe(fp, E.mapLeft(toUpper)); }, STANDARD);
bench('true-myth', () => { tm.mapErr(toUpper); }, STANDARD);
});
// ---------------------------------------------------------------------------
// match — success path
// ---------------------------------------------------------------------------
describe('Result.match — success path', () => {
const nm = ok(42);
const nt = ntOk(42);
const ox = OxOk(42);
const fp = E.right(42);
const tm = TmResult.ok<number, string>(42);
bench('native-monad', () => { nm.match(x => x * 2, () => 0); }, STANDARD);
bench('neverthrow', () => { nt.match(x => x * 2, () => 0); }, STANDARD);
// ts-results-es has no match — skip
bench('oxide.ts', () => { oxMatch(ox, { Ok: (x: number) => x * 2, Err: () => 0 }); }, STANDARD);
bench('fp-ts', () => { pipe(fp, E.match(() => 0, x => x * 2)); }, STANDARD);
bench('true-myth', () => { tm.match({ Ok: x => x * 2, Err: () => 0 }); }, STANDARD);
});
// ---------------------------------------------------------------------------
// match — failure path
// ---------------------------------------------------------------------------
describe('Result.match — failure path', () => {
const nm = err('fail');
const nt = ntErr('fail');
const ox = OxErr('fail');
const fp = E.left('fail');
const tm = TmResult.err<number, string>('fail');
bench('native-monad', () => { nm.match(() => 0, e => e.length); }, STANDARD);
bench('neverthrow', () => { nt.match(() => 0, e => e.length); }, STANDARD);
bench('oxide.ts', () => { oxMatch(ox, { Ok: () => 0, Err: (e: string) => e.length }); }, STANDARD);
bench('fp-ts', () => { pipe(fp, E.match(e => e.length, () => 0)); }, STANDARD);
bench('true-myth', () => { tm.match({ Ok: () => 0, Err: e => e.length }); }, STANDARD);
});
// ---------------------------------------------------------------------------
// filter — success path (predicate passes)
// ---------------------------------------------------------------------------
describe('Result.filter — predicate passes', () => {
const nm = ok(42);
const fp = E.right(42);
bench('native-monad', () => { nm.filter(x => x > 0, () => 'negative'); }, STANDARD);
// neverthrow: no filter — skip
// ts-results-es: no filter — skip
// oxide.ts: filter returns Option, not Result — different semantics, skip
bench('fp-ts', () => {
pipe(fp, E.filterOrElse(x => x > 0, () => 'negative' as const));
}, STANDARD);
// true-myth: no filter — skip
});
// ---------------------------------------------------------------------------
// filter — predicate fails
// ---------------------------------------------------------------------------
describe('Result.filter — predicate fails', () => {
const nm = ok(-1);
const fp = E.right(-1);
bench('native-monad', () => { nm.filter(x => x > 0, () => 'negative'); }, STANDARD);
bench('fp-ts', () => {
pipe(fp, E.filterOrElse(x => x > 0, () => 'negative' as const));
}, STANDARD);
});

View File

@@ -0,0 +1,74 @@
import { bench, describe } from 'vitest';
import { STANDARD } from '../helpers/bench-options.js';
import {
ok,
err,
ntOk,
ntErr,
TsOk,
TsErr,
OxOk,
OxErr,
E,
TmResult,
} from '../helpers/imports.js';
// ---------------------------------------------------------------------------
// isOk — on Ok variant
// ---------------------------------------------------------------------------
describe('Result.isOk — on Ok', () => {
const nm = ok(42);
const nt = ntOk(42);
const ts = TsOk(42);
const ox = OxOk(42);
const fp = E.right(42);
const tm = TmResult.ok<number, string>(42);
bench('native-monad', () => { nm.isOk(); }, STANDARD);
bench('neverthrow', () => { nt.isOk(); }, STANDARD);
bench('ts-results-es', () => { ts.isOk(); }, STANDARD);
bench('oxide.ts', () => { ox.isOk(); }, STANDARD);
bench('fp-ts', () => { E.isRight(fp); }, STANDARD);
bench('true-myth', () => { tm.isOk; }, STANDARD);
});
// ---------------------------------------------------------------------------
// isOk — on Err variant
// ---------------------------------------------------------------------------
describe('Result.isOk — on Err', () => {
const nm = err('fail');
const nt = ntErr('fail');
const ts = TsErr('fail');
const ox = OxErr('fail');
const fp = E.left('fail');
const tm = TmResult.err<number, string>('fail');
bench('native-monad', () => { nm.isOk(); }, STANDARD);
bench('neverthrow', () => { nt.isOk(); }, STANDARD);
bench('ts-results-es', () => { ts.isOk(); }, STANDARD);
bench('oxide.ts', () => { ox.isOk(); }, STANDARD);
bench('fp-ts', () => { E.isRight(fp); }, STANDARD);
bench('true-myth', () => { tm.isOk; }, STANDARD);
});
// ---------------------------------------------------------------------------
// isErr — on Err variant
// ---------------------------------------------------------------------------
describe('Result.isErr — on Err', () => {
const nm = err('fail');
const nt = ntErr('fail');
const ts = TsErr('fail');
const ox = OxErr('fail');
const fp = E.left('fail');
const tm = TmResult.err<number, string>('fail');
bench('native-monad', () => { nm.isErr(); }, STANDARD);
bench('neverthrow', () => { nt.isErr(); }, STANDARD);
bench('ts-results-es', () => { ts.isErr(); }, STANDARD);
bench('oxide.ts', () => { ox.isErr(); }, STANDARD);
bench('fp-ts', () => { E.isLeft(fp); }, STANDARD);
bench('true-myth', () => { tm.isErr; }, STANDARD);
});