separate types
This commit is contained in:
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
import { Result } from '../library/result.js';
|
||||||
|
import { Repository, AppError } from '../infrastructure/repository.js';
|
||||||
|
import { Account, AccountError } from '../domain/account.js';
|
||||||
|
|
||||||
|
export type AccountResult = Result<Account, AccountError>;
|
||||||
|
|
||||||
|
export type WithdrawMoneyFn = (accountId: string, amount: number) => AccountResult;
|
||||||
|
|
||||||
|
export type CreateWithdrawMoneyResult = Result<WithdrawMoneyFn, AppError>;
|
||||||
|
|
||||||
|
export declare const createWithdrawMoney: (repo: Repository) => CreateWithdrawMoneyResult;
|
||||||
@@ -3,36 +3,12 @@ import { output, outputError } from '../library/logging.js';
|
|||||||
import { withdraw } from '../domain/account.js';
|
import { withdraw } from '../domain/account.js';
|
||||||
import { createMoney } from '../domain/money.js';
|
import { createMoney } from '../domain/money.js';
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {import('../infrastructure/repository.js').Repository} Repository
|
|
||||||
* @typedef {import('../domain/account.js').Account} Account
|
|
||||||
* @typedef {import('../domain/account.js').AccountError} AccountError
|
|
||||||
* @typedef {import('../infrastructure/repository.js').AppError} AppError
|
|
||||||
* @typedef {import('../library/result.js').Result<Account, AccountError>} AccountResult
|
|
||||||
* @typedef {(accountId: string, amount: number) => AccountResult} WithdrawMoneyFn
|
|
||||||
* @typedef {import('../library/result.js').Result<WithdrawMoneyFn, AppError>} CreateWithdrawMoneyResult
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} src
|
|
||||||
* @param {string} msg
|
|
||||||
* @returns {<T, E>(result: import('../library/result.js').Result<T, E>) => import('../library/result.js').Result<T, E>}
|
|
||||||
*/
|
|
||||||
const log = (src, msg) => (result) =>
|
const log = (src, msg) => (result) =>
|
||||||
tap((x) => output(src, msg)(x))(tapError((e) => outputError(src, e))(result));
|
tap((x) => output(src, msg)(x))(tapError((e) => outputError(src, e))(result));
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} src
|
|
||||||
* @param {(x: T) => string} renderText
|
|
||||||
* @returns {<T, E>(result: import('../library/result.js').Result<T, E>) => import('../library/result.js').Result<T, E>}
|
|
||||||
*/
|
|
||||||
const logWith = (src, renderText) => (result) =>
|
const logWith = (src, renderText) => (result) =>
|
||||||
tap((x) => output(src, renderText(x))(x))(tapError((e) => outputError(src, e))(result));
|
tap((x) => output(src, renderText(x))(x))(tapError((e) => outputError(src, e))(result));
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Repository} repo
|
|
||||||
* @returns {CreateWithdrawMoneyResult}
|
|
||||||
*/
|
|
||||||
export const createWithdrawMoney = (repo) =>
|
export const createWithdrawMoney = (repo) =>
|
||||||
ok((accountId, amount) =>
|
ok((accountId, amount) =>
|
||||||
pipe(
|
pipe(
|
||||||
|
|||||||
Vendored
+53
@@ -0,0 +1,53 @@
|
|||||||
|
import { Result } from '../library/result.js';
|
||||||
|
import { Money } from './money.js';
|
||||||
|
|
||||||
|
export type Account = {
|
||||||
|
id: string;
|
||||||
|
balance: Money;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OpeningBalanceMustBeNonNegative = {
|
||||||
|
type: 'OpeningBalanceMustBeNonNegative';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AmountMustBePositive = {
|
||||||
|
type: 'AmountMustBePositive';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InsufficientBalance = {
|
||||||
|
type: 'InsufficientBalance';
|
||||||
|
balance: Money;
|
||||||
|
amount: Money;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AccountNotFound = {
|
||||||
|
type: 'AccountNotFound';
|
||||||
|
accountId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AccountError =
|
||||||
|
| OpeningBalanceMustBeNonNegative
|
||||||
|
| AmountMustBePositive
|
||||||
|
| InsufficientBalance
|
||||||
|
| AccountNotFound;
|
||||||
|
|
||||||
|
export declare const openingBalanceMustBeNonNegative: OpeningBalanceMustBeNonNegative;
|
||||||
|
|
||||||
|
export declare const amountMustBePositive: AmountMustBePositive;
|
||||||
|
|
||||||
|
export declare const insufficientBalance: (
|
||||||
|
balance: Money,
|
||||||
|
amount: Money
|
||||||
|
) => InsufficientBalance;
|
||||||
|
|
||||||
|
export declare const accountNotFound: (accountId: string) => AccountNotFound;
|
||||||
|
|
||||||
|
export declare const openAccount: (
|
||||||
|
id: string,
|
||||||
|
openingBalance: Money
|
||||||
|
) => Result<Account, AccountError>;
|
||||||
|
|
||||||
|
export declare const withdraw: (
|
||||||
|
account: Account,
|
||||||
|
amount: Money
|
||||||
|
) => Result<Account, AccountError>;
|
||||||
@@ -1,63 +1,25 @@
|
|||||||
import { ok, fail } from '../library/result.js';
|
import { ok, fail } from '../library/result.js';
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} Account
|
|
||||||
* @property {string} id
|
|
||||||
* @property {import('./money.js').Money} balance
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** @typedef {{ type: 'OpeningBalanceMustBeNonNegative' }} OpeningBalanceMustBeNonNegative */
|
|
||||||
/** @typedef {{ type: 'AmountMustBePositive' }} AmountMustBePositive */
|
|
||||||
/** @typedef {{ type: 'InsufficientBalance', balance: import('./money.js').Money, amount: import('./money.js').Money }} InsufficientBalance */
|
|
||||||
/** @typedef {{ type: 'AccountNotFound', accountId: string }} AccountNotFound */
|
|
||||||
|
|
||||||
/** @typedef {OpeningBalanceMustBeNonNegative | AmountMustBePositive | InsufficientBalance | AccountNotFound} AccountError */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {OpeningBalanceMustBeNonNegative}
|
|
||||||
*/
|
|
||||||
export const openingBalanceMustBeNonNegative = { type: 'OpeningBalanceMustBeNonNegative' };
|
export const openingBalanceMustBeNonNegative = { type: 'OpeningBalanceMustBeNonNegative' };
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {AmountMustBePositive}
|
|
||||||
*/
|
|
||||||
export const amountMustBePositive = { type: 'AmountMustBePositive' };
|
export const amountMustBePositive = { type: 'AmountMustBePositive' };
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('./money.js').Money} balance
|
|
||||||
* @param {import('./money.js').Money} amount
|
|
||||||
* @returns {InsufficientBalance}
|
|
||||||
*/
|
|
||||||
export const insufficientBalance = (balance, amount) => ({
|
export const insufficientBalance = (balance, amount) => ({
|
||||||
type: 'InsufficientBalance',
|
type: 'InsufficientBalance',
|
||||||
balance,
|
balance,
|
||||||
amount,
|
amount,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} accountId
|
|
||||||
* @returns {AccountNotFound}
|
|
||||||
*/
|
|
||||||
export const accountNotFound = (accountId) => ({
|
export const accountNotFound = (accountId) => ({
|
||||||
type: 'AccountNotFound',
|
type: 'AccountNotFound',
|
||||||
accountId,
|
accountId,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} id
|
|
||||||
* @param {import('./money.js').Money} openingBalance
|
|
||||||
* @returns {import('../library/result.js').Result<Account, AccountError>}
|
|
||||||
*/
|
|
||||||
export const openAccount = (id, openingBalance) =>
|
export const openAccount = (id, openingBalance) =>
|
||||||
openingBalance.amount < 0
|
openingBalance.amount < 0
|
||||||
? fail(openingBalanceMustBeNonNegative)
|
? fail(openingBalanceMustBeNonNegative)
|
||||||
: ok({ id, balance: openingBalance });
|
: ok({ id, balance: openingBalance });
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Account} account
|
|
||||||
* @param {import('./money.js').Money} amount
|
|
||||||
* @returns {import('../library/result.js').Result<Account, AccountError>}
|
|
||||||
*/
|
|
||||||
export const withdraw = (account, amount) => {
|
export const withdraw = (account, amount) => {
|
||||||
if (amount.amount <= 0) {
|
if (amount.amount <= 0) {
|
||||||
return fail(amountMustBePositive);
|
return fail(amountMustBePositive);
|
||||||
|
|||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
export type Money = {
|
||||||
|
amount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export declare const createMoney: (amount: number) => Money;
|
||||||
|
|
||||||
|
export declare const formatMoney: (money: Money) => string;
|
||||||
@@ -1,16 +1,3 @@
|
|||||||
/**
|
|
||||||
* @typedef {Object} Money
|
|
||||||
* @property {number} amount
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} amount
|
|
||||||
* @returns {Money}
|
|
||||||
*/
|
|
||||||
export const createMoney = (amount) => ({ amount });
|
export const createMoney = (amount) => ({ amount });
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Money} money
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export const formatMoney = (money) => money.amount.toFixed(2);
|
export const formatMoney = (money) => money.amount.toFixed(2);
|
||||||
|
|||||||
@@ -6,19 +6,9 @@ import { openAccount } from './domain/account.js';
|
|||||||
import { createMoney } from './domain/money.js';
|
import { createMoney } from './domain/money.js';
|
||||||
import { createWithdrawMoney } from './application/accountApp.js';
|
import { createWithdrawMoney } from './application/accountApp.js';
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} src
|
|
||||||
* @param {string} msg
|
|
||||||
* @returns {<T, E>(result: import('./library/result.js').Result<T, E>) => import('./library/result.js').Result<T, E>}
|
|
||||||
*/
|
|
||||||
const log = (src, msg) => (result) =>
|
const log = (src, msg) => (result) =>
|
||||||
tap((x) => output(src, msg)(x))(tapError((e) => outputError(src, e))(result));
|
tap((x) => output(src, msg)(x))(tapError((e) => outputError(src, e))(result));
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} src
|
|
||||||
* @param {(x: T) => string} renderText
|
|
||||||
* @returns {<T, E>(result: import('./library/result.js').Result<T, E>) => import('./library/result.js').Result<T, E>}
|
|
||||||
*/
|
|
||||||
const logWith = (src, renderText) => (result) =>
|
const logWith = (src, renderText) => (result) =>
|
||||||
tap((x) => output(src, renderText(x))(x))(tapError((e) => outputError(src, e))(result));
|
tap((x) => output(src, renderText(x))(x))(tapError((e) => outputError(src, e))(result));
|
||||||
|
|
||||||
|
|||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
import { Result } from '../library/result.js';
|
||||||
|
import { Account, AccountError } from '../domain/account.js';
|
||||||
|
|
||||||
|
export type Repository = {
|
||||||
|
loadAccount: (id: string) => Result<Account, AccountError>;
|
||||||
|
saveAccount: (account: Account) => Result<Account, AccountError>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AppErrorRepositoryCreationFailed = {
|
||||||
|
type: 'RepositoryCreationFailed';
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AppErrorInnerAccountError = {
|
||||||
|
type: 'InnerAccountError';
|
||||||
|
innerError: AccountError;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AppError =
|
||||||
|
| AppErrorRepositoryCreationFailed
|
||||||
|
| AppErrorInnerAccountError;
|
||||||
|
|
||||||
|
export declare const repositoryCreationFailed: (
|
||||||
|
message: string
|
||||||
|
) => AppErrorRepositoryCreationFailed;
|
||||||
|
|
||||||
|
export declare const innerAccountError: (
|
||||||
|
innerError: AccountError
|
||||||
|
) => AppErrorInnerAccountError;
|
||||||
|
|
||||||
|
export declare const createInMemoryRepository: () => Repository;
|
||||||
@@ -2,46 +2,19 @@ import { ok, fail } from '../library/result.js';
|
|||||||
import { logReturn } from '../library/logging.js';
|
import { logReturn } from '../library/logging.js';
|
||||||
import { accountNotFound } from '../domain/account.js';
|
import { accountNotFound } from '../domain/account.js';
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} Repository
|
|
||||||
* @property {(id: string) => import('../library/result.js').Result<import('../domain/account.js').Account, import('../domain/account.js').AccountError>} loadAccount
|
|
||||||
* @property {(account: import('../domain/account.js').Account) => import('../library/result.js').Result<import('../domain/account.js').Account, import('../domain/account.js').AccountError>} saveAccount
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** @typedef {{ type: 'RepositoryCreationFailed', message: string }} AppErrorRepositoryCreationFailed */
|
|
||||||
/** @typedef {{ type: 'InnerAccountError', innerError: import('../domain/account.js').AccountError }} AppErrorInnerAccountError */
|
|
||||||
|
|
||||||
/** @typedef {AppErrorRepositoryCreationFailed | AppErrorInnerAccountError} AppError */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} message
|
|
||||||
* @returns {AppErrorRepositoryCreationFailed}
|
|
||||||
*/
|
|
||||||
export const repositoryCreationFailed = (message) => ({
|
export const repositoryCreationFailed = (message) => ({
|
||||||
type: 'RepositoryCreationFailed',
|
type: 'RepositoryCreationFailed',
|
||||||
message,
|
message,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../domain/account.js').AccountError} innerError
|
|
||||||
* @returns {AppErrorInnerAccountError}
|
|
||||||
*/
|
|
||||||
export const innerAccountError = (innerError) => ({
|
export const innerAccountError = (innerError) => ({
|
||||||
type: 'InnerAccountError',
|
type: 'InnerAccountError',
|
||||||
innerError,
|
innerError,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Repository}
|
|
||||||
*/
|
|
||||||
export const createInMemoryRepository = () => {
|
export const createInMemoryRepository = () => {
|
||||||
/** @type {Map<string, import('../domain/account.js').Account>} */
|
|
||||||
const store = new Map();
|
const store = new Map();
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} id
|
|
||||||
* @returns {import('../library/result.js').Result<import('../domain/account.js').Account, import('../domain/account.js').AccountError>}
|
|
||||||
*/
|
|
||||||
const getById = (id) =>
|
const getById = (id) =>
|
||||||
store.has(id)
|
store.has(id)
|
||||||
? logReturn(
|
? logReturn(
|
||||||
@@ -53,10 +26,6 @@ export const createInMemoryRepository = () => {
|
|||||||
fail(accountNotFound(id))
|
fail(accountNotFound(id))
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('../domain/account.js').Account} account
|
|
||||||
* @returns {import('../library/result.js').Result<import('../domain/account.js').Account, import('../domain/account.js').AccountError>}
|
|
||||||
*/
|
|
||||||
const save = (account) => {
|
const save = (account) => {
|
||||||
store.set(account.id, account);
|
store.set(account.id, account);
|
||||||
return logReturn(
|
return logReturn(
|
||||||
|
|||||||
Vendored
+19
@@ -0,0 +1,19 @@
|
|||||||
|
export declare const logReturn: <T>(message: string, r: T) => T;
|
||||||
|
|
||||||
|
export declare const output: <T>(src: string, message: string) => (x: T) => void;
|
||||||
|
|
||||||
|
export declare const outputWith: <T>(
|
||||||
|
src: string,
|
||||||
|
renderText: (x: T) => string
|
||||||
|
) => (x: T) => void;
|
||||||
|
|
||||||
|
export declare const outputError: <E>(src: string, error: E) => void;
|
||||||
|
|
||||||
|
export declare const log: <T, E>(src: string, message: string) => (
|
||||||
|
result: import('./result.js').Result<T, E>
|
||||||
|
) => import('./result.js').Result<T, E>;
|
||||||
|
|
||||||
|
export declare const logWith: <T, E>(
|
||||||
|
src: string,
|
||||||
|
renderText: (x: T) => string
|
||||||
|
) => (result: import('./result.js').Result<T, E>) => import('./result.js').Result<T, E>;
|
||||||
@@ -1,59 +1,24 @@
|
|||||||
import { tap, tapError } from './result.js';
|
import { tap, tapError } from './result.js';
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @param {string} message
|
|
||||||
* @param {T} r
|
|
||||||
* @returns {T}
|
|
||||||
*/
|
|
||||||
export const logReturn = (message, r) => {
|
export const logReturn = (message, r) => {
|
||||||
console.log(message);
|
console.log(message);
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @param {string} src
|
|
||||||
* @param {string} message
|
|
||||||
* @returns {(x: T) => void}
|
|
||||||
*/
|
|
||||||
export const output = (src, message) => (x) => {
|
export const output = (src, message) => (x) => {
|
||||||
const value = typeof x === 'object' ? JSON.stringify(x) : x;
|
const value = typeof x === 'object' ? JSON.stringify(x) : x;
|
||||||
console.log(`[${src}] ${message} | ${value}`);
|
console.log(`[${src}] ${message} | ${value}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @param {string} src
|
|
||||||
* @param {(x: T) => string} renderText
|
|
||||||
* @returns {(x: T) => void}
|
|
||||||
*/
|
|
||||||
export const outputWith = (src, renderText) => (x) => {
|
export const outputWith = (src, renderText) => (x) => {
|
||||||
console.log(`[${src}] ${renderText(x)}`);
|
console.log(`[${src}] ${renderText(x)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @template E
|
|
||||||
* @param {string} src
|
|
||||||
* @param {E} error
|
|
||||||
*/
|
|
||||||
export const outputError = (src, error) => {
|
export const outputError = (src, error) => {
|
||||||
const value = typeof error === 'object' ? JSON.stringify(error) : error;
|
const value = typeof error === 'object' ? JSON.stringify(error) : error;
|
||||||
console.error(`\x1b[1;31m[${src} ERROR]\x1b[0m ${value}`);
|
console.error(`\x1b[1;31m[${src} ERROR]\x1b[0m ${value}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, E
|
|
||||||
* @param {string} src
|
|
||||||
* @param {string} message
|
|
||||||
* @returns {(result: import('./result.js').Result<T, E>) => import('./result.js').Result<T, E>}
|
|
||||||
*/
|
|
||||||
export const log = (src, message) => tap(output(src, message));
|
export const log = (src, message) => tap(output(src, message));
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, E
|
|
||||||
* @param {string} src
|
|
||||||
* @param {(x: T) => string} renderText
|
|
||||||
* @returns {(result: import('./result.js').Result<T, E>) => import('./result.js').Result<T, E>}
|
|
||||||
*/
|
|
||||||
export const logWith = (src, renderText) => tap(outputWith(src, renderText));
|
export const logWith = (src, renderText) => tap(outputWith(src, renderText));
|
||||||
|
|||||||
Vendored
+57
@@ -0,0 +1,57 @@
|
|||||||
|
export type Success<T> = {
|
||||||
|
isSuccess: true;
|
||||||
|
isFailure: false;
|
||||||
|
value: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Failure<E> = {
|
||||||
|
isSuccess: false;
|
||||||
|
isFailure: true;
|
||||||
|
error: E;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Result<T, E> = Success<T> | Failure<E>;
|
||||||
|
|
||||||
|
export declare const ok: <T, E>(value: T) => Result<T, E>;
|
||||||
|
|
||||||
|
export declare const fail: <T, E>(error: E) => Result<T, E>;
|
||||||
|
|
||||||
|
export declare const catchResult: <T, E>(
|
||||||
|
f: () => T,
|
||||||
|
exceptionMapper: (err: Error) => E
|
||||||
|
) => Result<T, E>;
|
||||||
|
|
||||||
|
export declare const bind: <TIn, TOut, E>(
|
||||||
|
binder: (value: TIn) => Result<TOut, E>
|
||||||
|
) => (result: Result<TIn, E>) => Result<TOut, E>;
|
||||||
|
|
||||||
|
export declare const map: <TIn, TOut, E>(
|
||||||
|
mapper: (value: TIn) => TOut
|
||||||
|
) => (result: Result<TIn, E>) => Result<TOut, E>;
|
||||||
|
|
||||||
|
export declare const tap: <T, E>(
|
||||||
|
action: (value: T) => void
|
||||||
|
) => (result: Result<T, E>) => Result<T, E>;
|
||||||
|
|
||||||
|
export declare const tapError: <T, E>(
|
||||||
|
action: (error: E) => void
|
||||||
|
) => (result: Result<T, E>) => Result<T, E>;
|
||||||
|
|
||||||
|
export declare const mapError: <T, EIn, EOut>(
|
||||||
|
mapper: (error: EIn) => EOut
|
||||||
|
) => (result: Result<T, EIn>) => Result<T, EOut>;
|
||||||
|
|
||||||
|
export declare const match: <T, E, TResult>(
|
||||||
|
onSuccess: (value: T) => TResult,
|
||||||
|
onFailure: (error: E) => TResult
|
||||||
|
) => (result: Result<T, E>) => TResult;
|
||||||
|
|
||||||
|
export declare const switch_: <T, E>(
|
||||||
|
onSuccess: (value: T) => void,
|
||||||
|
onFailure: (error: E) => void
|
||||||
|
) => (result: Result<T, E>) => void;
|
||||||
|
|
||||||
|
export declare const pipe: <T, E>(
|
||||||
|
result: Result<T, E>,
|
||||||
|
...fns: Array<(arg: Result<any, any>) => Result<any, any>>
|
||||||
|
) => Result<any, any>;
|
||||||
@@ -1,40 +1,15 @@
|
|||||||
/**
|
|
||||||
* @template T, E
|
|
||||||
* @typedef {Object} Result
|
|
||||||
* @property {boolean} isSuccess
|
|
||||||
* @property {boolean} isFailure
|
|
||||||
* @property {T} [value] - Present when isSuccess is true
|
|
||||||
* @property {E} [error] - Present when isFailure is true
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, E
|
|
||||||
* @param {T} value
|
|
||||||
* @returns {Result<T, E>}
|
|
||||||
*/
|
|
||||||
export const ok = (value) => ({
|
export const ok = (value) => ({
|
||||||
isSuccess: true,
|
isSuccess: true,
|
||||||
isFailure: false,
|
isFailure: false,
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, E
|
|
||||||
* @param {E} error
|
|
||||||
* @returns {Result<T, E>}
|
|
||||||
*/
|
|
||||||
export const fail = (error) => ({
|
export const fail = (error) => ({
|
||||||
isSuccess: false,
|
isSuccess: false,
|
||||||
isFailure: true,
|
isFailure: true,
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, E
|
|
||||||
* @param {() => T} f
|
|
||||||
* @param {(err: Error) => E} exceptionMapper
|
|
||||||
* @returns {Result<T, E>}
|
|
||||||
*/
|
|
||||||
export const catchResult = (f, exceptionMapper) => {
|
export const catchResult = (f, exceptionMapper) => {
|
||||||
try {
|
try {
|
||||||
return ok(f());
|
return ok(f());
|
||||||
@@ -43,77 +18,32 @@ export const catchResult = (f, exceptionMapper) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @template TIn, TOut, E
|
|
||||||
* @param {(value: TIn) => Result<TOut, E>} binder
|
|
||||||
* @returns {(result: Result<TIn, E>) => Result<TOut, E>}
|
|
||||||
*/
|
|
||||||
export const bind = (binder) => (result) =>
|
export const bind = (binder) => (result) =>
|
||||||
result.isSuccess ? binder(result.value) : fail(result.error);
|
result.isSuccess ? binder(result.value) : fail(result.error);
|
||||||
|
|
||||||
/**
|
|
||||||
* @template TIn, TOut, E
|
|
||||||
* @param {(value: TIn) => TOut} mapper
|
|
||||||
* @returns {(result: Result<TIn, E>) => Result<TOut, E>}
|
|
||||||
*/
|
|
||||||
export const map = (mapper) => (result) =>
|
export const map = (mapper) => (result) =>
|
||||||
result.isSuccess ? ok(mapper(result.value)) : fail(result.error);
|
result.isSuccess ? ok(mapper(result.value)) : fail(result.error);
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, E
|
|
||||||
* @param {(value: T) => void} action
|
|
||||||
* @returns {(result: Result<T, E>) => Result<T, E>}
|
|
||||||
*/
|
|
||||||
export const tap = (action) => (result) => {
|
export const tap = (action) => (result) => {
|
||||||
if (result.isSuccess) action(result.value);
|
if (result.isSuccess) action(result.value);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, E
|
|
||||||
* @param {(error: E) => void} action
|
|
||||||
* @returns {(result: Result<T, E>) => Result<T, E>}
|
|
||||||
*/
|
|
||||||
export const tapError = (action) => (result) => {
|
export const tapError = (action) => (result) => {
|
||||||
if (result.isFailure) action(result.error);
|
if (result.isFailure) action(result.error);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, EIn, EOut
|
|
||||||
* @param {(error: EIn) => EOut} mapper
|
|
||||||
* @returns {(result: Result<T, EIn>) => Result<T, EOut>}
|
|
||||||
*/
|
|
||||||
export const mapError = (mapper) => (result) =>
|
export const mapError = (mapper) => (result) =>
|
||||||
result.isSuccess ? ok(result.value) : fail(mapper(result.error));
|
result.isSuccess ? ok(result.value) : fail(mapper(result.error));
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, E, TResult
|
|
||||||
* @param {(value: T) => TResult} onSuccess
|
|
||||||
* @param {(error: E) => TResult} onFailure
|
|
||||||
* @returns {(result: Result<T, E>) => TResult}
|
|
||||||
*/
|
|
||||||
export const match = (onSuccess, onFailure) => (result) =>
|
export const match = (onSuccess, onFailure) => (result) =>
|
||||||
result.isSuccess ? onSuccess(result.value) : onFailure(result.error);
|
result.isSuccess ? onSuccess(result.value) : onFailure(result.error);
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, E
|
|
||||||
* @param {(value: T) => void} onSuccess
|
|
||||||
* @param {(error: E) => void} onFailure
|
|
||||||
* @returns {(result: Result<T, E>) => void}
|
|
||||||
*/
|
|
||||||
export const switch_ = (onSuccess, onFailure) => (result) => {
|
export const switch_ = (onSuccess, onFailure) => (result) => {
|
||||||
if (result.isSuccess) onSuccess(result.value);
|
if (result.isSuccess) onSuccess(result.value);
|
||||||
else onFailure(result.error);
|
else onFailure(result.error);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Pipe a result through a series of functions (left-to-right composition).
|
|
||||||
* This allows chaining without extending the prototype.
|
|
||||||
* @template T, E
|
|
||||||
* @param {Result<T, E>} result
|
|
||||||
* @param {...Function} fns
|
|
||||||
* @returns {Result<any, any>}
|
|
||||||
*/
|
|
||||||
export const pipe = (result, ...fns) =>
|
export const pipe = (result, ...fns) =>
|
||||||
fns.reduce((acc, fn) => fn(acc), result);
|
fns.reduce((acc, fn) => fn(acc), result);
|
||||||
|
|||||||
Reference in New Issue
Block a user