Last active
September 13, 2022 20:55
-
-
Save martinsandredev/9c69bca57b9b5d7d229bd35797873e49 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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