diff --git a/workspace/mauss/src/core/lambda/index.spec.ts b/workspace/mauss/src/core/lambda/index.spec.ts index c0b0265..29af052 100644 --- a/workspace/mauss/src/core/lambda/index.spec.ts +++ b/workspace/mauss/src/core/lambda/index.spec.ts @@ -1,11 +1,11 @@ import { suite } from 'uvu'; import * as assert from 'uvu/assert'; - import * as lambda from './index.js'; const suites = { 'curry/': suite('lambda/curry'), 'pipe/': suite('lambda/pipe'), + 'masked/reveal': suite('lambda/masked:reveal'), }; suites['curry/']('properly curry a function', () => { @@ -28,4 +28,18 @@ suites['pipe/']('properly apply functions in ltr order', () => { assert.equal(pipeline({ name: 'mom' }), ['M', 'O', 'M']); }); +suites['masked/reveal']('properly mask and reveal a value', () => { + const { mask, reveal } = lambda; + + const answer = mask.of(() => 42); + assert.equal(reveal(answer).expect('unreachable'), 42); + + let maybe: string | null | undefined; + let wrapped = mask.wrap(maybe); + assert.equal(reveal(wrapped).or('2023-04-04'), '2023-04-04'); + + wrapped = mask.wrap('2023-04-06'); + assert.equal(reveal(wrapped).expect('unreachable'), '2023-04-06'); +}); + Object.values(suites).forEach((v) => v.run()); diff --git a/workspace/mauss/src/core/lambda/index.ts b/workspace/mauss/src/core/lambda/index.ts index 116660a..719d94e 100644 --- a/workspace/mauss/src/core/lambda/index.ts +++ b/workspace/mauss/src/core/lambda/index.ts @@ -46,3 +46,51 @@ export function pipe(...functions: Validator) { return pipeline; }; } + +function error(msg?: string) { + const error = msg || ''; + return { kind: 'error' as const, error }; +} +function success(value: T) { + return { kind: 'success' as const, value }; +} + +type Result = ReturnType | ReturnType>; + +function cast(fn: (x: X) => x is Y) { + return (arg: X): Result => { + try { + return fn(arg) ? success(arg) : error(); + } catch { + return error(); + } + }; +} + +export const mask = { + of(fn: () => T): Result { + try { + return success(fn()); + } catch { + return error(); + } + }, + + async resolve(p: Promise): Promise> { + return p.then((v) => success(v)).catch(() => error()); + }, + + wrap: cast((i: T | undefined | null): i is T => i != null), +} as const; + +export function reveal(opt: Result) { + return { + expect(message: string) { + if (opt.kind === 'success') return opt.value; + throw new Error(message); + }, + or(fallback: T): T { + return opt.kind === 'success' ? opt.value : fallback; + }, + }; +}