Skip to content

Instantly share code, notes, and snippets.

@jamesdabbs
Created July 2, 2019 15:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamesdabbs/e675d511ec67c984f123be174a51bef7 to your computer and use it in GitHub Desktop.
Save jamesdabbs/e675d511ec67c984f123be174a51bef7 to your computer and use it in GitHub Desktop.
Refactoring a `sendPasswordReset` to be a (monadic) chain
import uuid from 'uuid/v4'
import {
Envelope, Token, User,
allUsers, deliverEmail, prompt, saveToken
} from '../util'
const promptFor = (message: string): string => {
console.log(message)
return prompt()
}
const findUser = (identity: string): User | undefined => {
for (let user of allUsers()) {
if (user.name === identity || user.email === identity) {
return user
}
}
return
}
const generateResetToken = (user: User): Token => {
const token = {
uuid: uuid(),
user
}
saveToken(token)
return token
}
const deliverResetToken = (token: Token): Envelope => {
const email = {
to: token.user.email,
body: `Your password reset token is ${token.uuid}`
}
return deliverEmail(email)
}
export const sendPasswordReset = (message: string): Envelope | undefined => {
const identity = promptFor(message)
const user = findUser(identity)
if (!user) { return }
const token = generateResetToken(user)
return deliverResetToken(token)
}
import uuid from 'uuid/v4'
import {
Envelope, Token, User,
allUsers, deliverEmail, prompt, saveToken
} from '../util'
type Either<A, B> = Left<A, B> | Right<A, B>
class Left<A, B> {
_value: A
constructor(a: A) {
this._value = a
}
chain<C>(_: (b: B) => Either<A, C>): Either<A, C> {
return left(this._value)
}
map<C>(f: (b: B) => C): Either<A, C> {
return left(this._value)
}
}
class Right<A, B> {
_value: B
constructor(b: B) {
this._value = b
}
chain<C>(f: (b: B) => Either<A, C>): Either<A, C> {
return f(this._value)
}
map<C>(f: (b: B) => C): Either<A, C> {
return right(f(this._value))
}
}
const left = <A>(a: A) => new Left<A, never>(a)
const right = <B>(b: B) => new Right<never, B>(b)
const promptFor = (message: string): Either<string, string> => {
console.log(message)
const response = prompt()
if (response.length === 0) {
return left('No email given')
} else {
return right(response)
}
}
const findUser = (identity: string): Either<string, User> => {
for (let user of allUsers()) {
if (user.name === identity || user.email === identity) {
return right(user)
}
}
return left(`Could not find user ${identity}`)
}
const generateResetToken = (user: User): Token => {
const token = {
uuid: uuid(),
user
}
saveToken(token)
return token
}
const deliverResetToken = (token: Token): Either<string, Envelope> => {
const email = {
to: token.user.email,
body: `Your password reset token is ${token.uuid}`
}
try {
return right(deliverEmail(email))
} catch (e) {
return left(e.message)
}
}
const pipeK = (...fns) => x => fns.reduceRight((v, f) => v.chain(f), x)
export const sendPasswordReset = (message: string): Either<string, Envelope> => {
return pipeK(
promptFor,
findUser,
x => right(generateResetToken(x)),
deliverResetToken
)(message)
return promptFor(message)
.chain(findUser)
// .map(generateResetToken)
.chain(x => right(generateResetToken(x)))
.chain(deliverResetToken)
}
import * as ps from 'prompt-sync'
export const compose = (...fns) => (x) => fns.reduceRight((v, f) => f(v), x)
export const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x)
export type User = {
id: number,
name: string,
email: string
}
export type Token = {
user: User
uuid: string
}
export type Email = {
to: string
body: string
}
export type Envelope = {
email: Email,
deliveredAt: Date
}
const users: User[] = [
{ id: 1, name: 'James', email: 'james.dabbs@gmail.com' },
{ id: 2, name: 'Rachel', email: 'rachel@example.com' },
{ id: 3, name: 'Tim', email: 'tim.doherty@procore.com' },
]
const tokens: Token[] = []
let emailDeliveries: Envelope[] = []
export const allUsers = () => users.slice(0)
export const deliverEmail = (email: Email) => {
const envelope = { email, deliveredAt: new Date() }
emailDeliveries.push(envelope)
return envelope
}
export const prompt = (): string => ps().prompt('')
export const saveToken = (token: Token) => { tokens.push(token) }
export const getDeliveries = () => emailDeliveries.slice(0)
export const clearDeliveries = () => emailDeliveries = []
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment