Skip to content

Instantly share code, notes, and snippets.

@martinsandredev
Last active September 13, 2022 20:55
Show Gist options
  • Save martinsandredev/9c69bca57b9b5d7d229bd35797873e49 to your computer and use it in GitHub Desktop.
Save martinsandredev/9c69bca57b9b5d7d229bd35797873e49 to your computer and use it in GitHub Desktop.
import * as RTE from "fp-ts/ReaderTaskEither";
import * as E from "fp-ts/Either";
import * as O from "fp-ts/Option";
import { Kind, URIS } from "fp-ts/HKT";
import { pipe } from "fp-ts/function";
import { Do } from "fp-ts-contrib/Do";
import { Monad1 } from "fp-ts/Monad";
interface MonadError<M extends URIS, E> extends Monad1<M> {
readonly throwError: <A>(e: E) => Kind<M, A>;
}
interface PersonNotFound {
_tag: "PersonNotFound";
}
type DomainError = PersonNotFound;
const DomainError = {
PersonNotFound: {
_tag: "PersonNotFound"
} as DomainError
} as const;
interface ConnectionTimeout {
_tag: "ConnectionTimeout";
}
type InfraError = ConnectionTimeout;
const InfraError = {
ConnectionTimeout: {
_tag: "ConnectionTimeout"
} as InfraError
} as const;
type Unit = void;
const unit: Unit = undefined as Unit;
class Person {
constructor(
readonly name: string,
readonly age: number,
readonly cpf: string
) {}
copy(props: Partial<Omit<Person, "copy">>) {
return new Person(
props.name ?? this.name,
props.age ?? this.age,
props.cpf ?? this.cpf
);
}
}
interface PersonRepository<F extends URIS> {
readonly findPersonByCpf: (cpf: string) => Kind<F, O.Option<Person>>;
readonly savePerson: (person: Person) => Kind<F, Unit>;
}
interface Console<F extends URIS> {
readonly println: (msg: string) => Kind<F, Unit>;
}
type Program<F extends URIS> = PersonRepository<F> &
Console<F> &
MonadError<F, DomainError>;
const updateName = <F extends URIS>(F: Program<F>) => (
cpf: string,
name: string
) =>
Do(F)
.do(F.println("Updating..."))
.bind("maybePerson", F.findPersonByCpf(cpf))
.doL(({ maybePerson }) =>
pipe(
maybePerson,
O.fold(
() => F.throwError(DomainError.PersonNotFound),
(person) => F.savePerson(person.copy({ name: name }))
)
)
)
.do(F.println("Updated!"))
.return(() => unit);
class DB {}
interface Env<F extends URIS> {
db: DB;
}
type AppM<A> = RTE.ReaderTaskEither<Env<URI>, DomainError | InfraError, A>;
const URI = "AppM";
type URI = "AppM";
declare module "fp-ts/HKT" {
interface URItoKind<A> {
readonly [URI]: AppM<A>;
}
}
const appMonad: MonadError<URI, DomainError | InfraError> = {
...RTE.Monad,
URI,
throwError: (error) => {
return RTE.left(error);
}
};
const consoleM: Console<URI> = {
println: (msg) =>
RTE.fromIOEither(() => {
console.log(msg);
return E.right(unit);
})
};
const personRepository = (console: Console<URI>): PersonRepository<URI> => ({
findPersonByCpf: (cpf: string) =>
Do(RTE.readerTaskEither)
.bind("env", RTE.ask<Env<URI>, DomainError | InfraError>())
.do(console.println("Fake reading DB..."))
.do(RTE.left(InfraError.ConnectionTimeout))
.return(() => O.some(new Person("Test", 18, "12345678912"))),
savePerson: () =>
Do(RTE.readerTaskEither)
.bind("env", RTE.ask<Env<URI>, DomainError | InfraError>())
.do(console.println("Fake writing DB..."))
.return(() => unit)
});
const interpreter: Program<URI> = {
...appMonad,
...personRepository(consoleM),
...consoleM
};
const program = updateName(interpreter)("12345678912", "Test");
program({
db: new DB()
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment