cleanup, patterns
This commit is contained in:
@@ -1,14 +1,8 @@
|
|||||||
import { bind, ok, tap, tapError, pipe } from '../library/result.js';
|
import { bind, ok, pipe } from '../library/result.js';
|
||||||
import { output, outputError } from '../library/logging.js';
|
import { log, logWith, formatMoney } 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';
|
||||||
|
|
||||||
const log = (src, msg) => (result) =>
|
|
||||||
tap((x) => output(src, msg)(x))(tapError((e) => outputError(src, e))(result));
|
|
||||||
|
|
||||||
const logWith = (src, renderText) => (result) =>
|
|
||||||
tap((x) => output(src, renderText(x))(x))(tapError((e) => outputError(src, e))(result));
|
|
||||||
|
|
||||||
export const createWithdrawMoney = (repo) =>
|
export const createWithdrawMoney = (repo) =>
|
||||||
ok((accountId, amount) =>
|
ok((accountId, amount) =>
|
||||||
pipe(
|
pipe(
|
||||||
@@ -16,7 +10,7 @@ export const createWithdrawMoney = (repo) =>
|
|||||||
log('App load', 'Account loaded'),
|
log('App load', 'Account loaded'),
|
||||||
log('App exec wdrwl', `[App] Executing withdrawal of ${amount.toFixed(2)}...`),
|
log('App exec wdrwl', `[App] Executing withdrawal of ${amount.toFixed(2)}...`),
|
||||||
bind((account) => withdraw(account, createMoney(amount))),
|
bind((account) => withdraw(account, createMoney(amount))),
|
||||||
logWith('App wdrwl done', (a) => `Withdrawal applied. New balance: ${a.balance.amount.toFixed(2)}`),
|
logWith('App wdrwl done', (a) => `Withdrawal applied. New balance: ${formatMoney(a.balance)}`),
|
||||||
bind(repo.saveAccount),
|
bind(repo.saveAccount),
|
||||||
log('App acc svd', '[App] Account persisted.')
|
log('App acc svd', '[App] Account persisted.')
|
||||||
)
|
)
|
||||||
|
|||||||
+15
-12
@@ -1,4 +1,5 @@
|
|||||||
import { ok, fail } from '../library/result.js';
|
import { ok, fail } from '../library/result.js';
|
||||||
|
import { match, when, any } from '../library/patterns.js';
|
||||||
|
|
||||||
export const openingBalanceMustBeNonNegative = { type: 'OpeningBalanceMustBeNonNegative' };
|
export const openingBalanceMustBeNonNegative = { type: 'OpeningBalanceMustBeNonNegative' };
|
||||||
|
|
||||||
@@ -20,15 +21,17 @@ export const openAccount = (id, openingBalance) =>
|
|||||||
? fail(openingBalanceMustBeNonNegative)
|
? fail(openingBalanceMustBeNonNegative)
|
||||||
: ok({ id, balance: openingBalance });
|
: ok({ id, balance: openingBalance });
|
||||||
|
|
||||||
export const withdraw = (account, amount) => {
|
export const withdraw = (account, amount) =>
|
||||||
if (amount.amount <= 0) {
|
match(
|
||||||
return fail(amountMustBePositive);
|
when(({ amount: a }) => a.amount <= 0, () => fail(amountMustBePositive)),
|
||||||
}
|
when(
|
||||||
if (account.balance.amount < amount.amount) {
|
({ account: acc, amount: amt }) => acc.balance.amount < amt.amount,
|
||||||
return fail(insufficientBalance(account.balance, amount));
|
({ account: acc, amount: amt }) => fail(insufficientBalance(acc.balance, amt))
|
||||||
}
|
),
|
||||||
return ok({
|
when(any, ({ account: acc, amount: amt }) =>
|
||||||
...account,
|
ok({
|
||||||
balance: { amount: account.balance.amount - amount.amount },
|
...acc,
|
||||||
});
|
balance: { amount: acc.balance.amount - amt.amount },
|
||||||
};
|
})
|
||||||
|
)
|
||||||
|
)({ account, amount });
|
||||||
|
|||||||
+4
-10
@@ -1,17 +1,11 @@
|
|||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { catchResult, bind, tap, tapError, mapError, pipe } from './library/result.js';
|
import { catchResult, bind, mapError, pipe } from './library/result.js';
|
||||||
import { output, outputError } from './library/logging.js';
|
import { log, logWith, formatMoney } from './library/logging.js';
|
||||||
import { createInMemoryRepository, innerAccountError } from './infrastructure/repository.js';
|
import { createInMemoryRepository, innerAccountError } from './infrastructure/repository.js';
|
||||||
import { openAccount } from './domain/account.js';
|
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';
|
||||||
|
|
||||||
const log = (src, msg) => (result) =>
|
|
||||||
tap((x) => output(src, msg)(x))(tapError((e) => outputError(src, e))(result));
|
|
||||||
|
|
||||||
const logWith = (src, renderText) => (result) =>
|
|
||||||
tap((x) => output(src, renderText(x))(x))(tapError((e) => outputError(src, e))(result));
|
|
||||||
|
|
||||||
console.log('[js-fp3] Starting withdraw money demo...');
|
console.log('[js-fp3] Starting withdraw money demo...');
|
||||||
|
|
||||||
const accountId = randomUUID();
|
const accountId = randomUUID();
|
||||||
@@ -30,11 +24,11 @@ pipe(
|
|||||||
bind((withdrawMoney) =>
|
bind((withdrawMoney) =>
|
||||||
pipe(
|
pipe(
|
||||||
openAccount(accountId, createMoney(200)),
|
openAccount(accountId, createMoney(200)),
|
||||||
logWith('js-fp3 seed', (account) => `Seeding account ${account.id} with opening balance ${account.balance.amount.toFixed(2)}`),
|
logWith('js-fp3 seed', (account) => `Seeding account ${account.id} with opening balance ${formatMoney(account.balance)}`),
|
||||||
bind(repo.saveAccount),
|
bind(repo.saveAccount),
|
||||||
log('js-fp3 exec', `Executing withdrawal ${withdrawalAmount.toFixed(2)} from account ${accountId}`),
|
log('js-fp3 exec', `Executing withdrawal ${withdrawalAmount.toFixed(2)} from account ${accountId}`),
|
||||||
bind(() => withdrawMoney(accountId, withdrawalAmount)),
|
bind(() => withdrawMoney(accountId, withdrawalAmount)),
|
||||||
logWith('js-fp3 new balance', (account) => `New balance is ${account.balance.amount.toFixed(2)}`),
|
logWith('js-fp3 new balance', (account) => `New balance is ${formatMoney(account.balance)}`),
|
||||||
mapError((ae) => innerAccountError(ae))
|
mapError((ae) => innerAccountError(ae))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ok, fail } from '../library/result.js';
|
import { ok, fail } from '../library/result.js';
|
||||||
import { logReturn } from '../library/logging.js';
|
import { logReturn, formatMoney } from '../library/logging.js';
|
||||||
import { accountNotFound } from '../domain/account.js';
|
import { accountNotFound } from '../domain/account.js';
|
||||||
|
|
||||||
export const repositoryCreationFailed = (message) => ({
|
export const repositoryCreationFailed = (message) => ({
|
||||||
@@ -29,7 +29,7 @@ export const createInMemoryRepository = () => {
|
|||||||
const save = (account) => {
|
const save = (account) => {
|
||||||
store.set(account.id, account);
|
store.set(account.id, account);
|
||||||
return logReturn(
|
return logReturn(
|
||||||
`[Repo] Saved account ${account.id} with balance ${account.balance.amount.toFixed(2)}`,
|
`[Repo] Saved account ${account.id} with balance ${formatMoney(account.balance)}`,
|
||||||
ok(account)
|
ok(account)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+11
@@ -9,6 +9,17 @@ export declare const outputWith: <T>(
|
|||||||
|
|
||||||
export declare const outputError: <E>(src: string, error: E) => void;
|
export declare const outputError: <E>(src: string, error: E) => void;
|
||||||
|
|
||||||
|
export declare const logSuccess: <T, E>(src: string, message: string) => (
|
||||||
|
result: import('./result.js').Result<T, E>
|
||||||
|
) => import('./result.js').Result<T, E>;
|
||||||
|
|
||||||
|
export declare const logSuccessWith: <T, E>(
|
||||||
|
src: string,
|
||||||
|
renderText: (x: T) => string
|
||||||
|
) => (result: import('./result.js').Result<T, E>) => import('./result.js').Result<T, E>;
|
||||||
|
|
||||||
|
export declare const formatMoney: (money: { amount: number }) => string;
|
||||||
|
|
||||||
export declare const log: <T, E>(src: string, message: string) => (
|
export declare const log: <T, E>(src: string, message: string) => (
|
||||||
result: import('./result.js').Result<T, E>
|
result: import('./result.js').Result<T, E>
|
||||||
) => import('./result.js').Result<T, E>;
|
) => import('./result.js').Result<T, E>;
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { tap, tapError } from './result.js';
|
import { tap, tapError, pipe } from './result.js';
|
||||||
|
import { formatMoney as formatMoneyFromDomain } from '../domain/money.js';
|
||||||
|
|
||||||
|
export { formatMoneyFromDomain as formatMoney };
|
||||||
|
|
||||||
export const logReturn = (message, r) => {
|
export const logReturn = (message, r) => {
|
||||||
console.log(message);
|
console.log(message);
|
||||||
@@ -19,6 +22,12 @@ export const outputError = (src, error) => {
|
|||||||
console.error(`\x1b[1;31m[${src} ERROR]\x1b[0m ${value}`);
|
console.error(`\x1b[1;31m[${src} ERROR]\x1b[0m ${value}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const log = (src, message) => tap(output(src, message));
|
export const logSuccess = (src, message) => tap(output(src, message));
|
||||||
|
|
||||||
export const logWith = (src, renderText) => tap(outputWith(src, renderText));
|
export const logSuccessWith = (src, renderText) => tap(outputWith(src, renderText));
|
||||||
|
|
||||||
|
export const log = (src, message) => (result) =>
|
||||||
|
pipe(result, tap(output(src, message)), tapError((e) => outputError(src, e)));
|
||||||
|
|
||||||
|
export const logWith = (src, renderText) => (result) =>
|
||||||
|
pipe(result, tap((x) => output(src, renderText(x))(x)), tapError((e) => outputError(src, e)));
|
||||||
|
|||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
export type Predicate<T> = (value: T) => boolean;
|
||||||
|
export type Handler<T, R> = (value: T) => R;
|
||||||
|
export type Branch<T, R> = [Predicate<T>, Handler<T, R>];
|
||||||
|
|
||||||
|
export declare const match: <T, R>(...branches: Branch<T, R>[]) => (value: T) => R;
|
||||||
|
|
||||||
|
export declare const when: <T, R>(predicate: Predicate<T>, handler: Handler<T, R>) => Branch<T, R>;
|
||||||
|
|
||||||
|
export declare const any: Predicate<any>;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export const match = (...branches) => (value) => {
|
||||||
|
for (const [predicate, handler] of branches) {
|
||||||
|
if (predicate(value)) return handler(value);
|
||||||
|
}
|
||||||
|
throw new Error('No matching pattern');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const when = (predicate, handler) => [predicate, handler];
|
||||||
|
|
||||||
|
export const any = () => true;
|
||||||
Reference in New Issue
Block a user