Skip to content

Commit

Permalink
upgrade to [email protected]
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti committed Feb 16, 2018
1 parent 86bb8ed commit 796fb54
Show file tree
Hide file tree
Showing 14 changed files with 360 additions and 360 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
**Note**: Gaps between patch versions are faulty/broken releases. **Note**: A feature tagged as Experimental is in a
high state of flux, you're at risk of it changing without notice.

# 0.3.0

* **Breaking Change**
* upgrade to `[email protected]` (@gcanti)

# 0.2.0

* **Breaking Change**
Expand Down
151 changes: 78 additions & 73 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,16 @@ State changes are tracked by the phanton type `S`.
```ts
class Conn<S> {
readonly _S: S
constructor(readonly req: express.Request, readonly res: express.Response) {}
clearCookie: (name: string, options: CookieOptions) => void
endResponse: () => void
getBody: () => mixed
getHeader: (name: string) => mixed
getParams: () => mixed
getQuery: () => mixed
setBody: (body: mixed) => void
setCookie: (name: string, value: string, options: CookieOptions) => void
setHeader: (name: string, value: string) => void
setStatus: (status: Status) => void
}
```

Expand All @@ -58,31 +67,34 @@ The default interpreter, `MiddlewareTask`, is based on [fp-ts](https://github.co
```ts
import * as express from 'express'
import { status, closeHeaders, send } from 'hyper-ts/lib/MiddlewareTask'
import { Status } from 'hyper-ts'
import { middleware } from 'hyper-ts/lib/MiddlewareTask'
import { toExpressRequestHandler } from 'hyper-ts/lib/toExpressRequestHandler'

const hello = status(Status.OK)
.ichain(() => closeHeaders)
.ichain(() => send('Hello hyper-ts!'))
const hello = middleware
.status(Status.OK)
.ichain(() => middleware.closeHeaders)
.ichain(() => middleware.send('Hello hyper-ts on express!'))

const app = express()
app.get('/', hello.toRequestHandler())
app.listen(3000, () => console.log('App listening on port 3000!'))
express()
.get('/', toExpressRequestHandler(hello))
.listen(3000, () => console.log('Express listening on port 3000. Use: GET /'))
```

## Type safety

Invalid operations are prevented statically

```ts
import { status, closeHeaders, send, header } from 'hyper-ts/lib/MiddlewareTask'
import { middleware } from 'hyper-ts/lib/MiddlewareTask'
import { Status } from 'hyper-ts'

const hello = status(Status.OK)
.ichain(() => closeHeaders)
.ichain(() => send('Hello hyper-ts!'))
middleware
.status(Status.OK)
.ichain(() => middleware.closeHeaders)
.ichain(() => middleware.send('Hello hyper-ts!'))
// try to write a header after sending the body
.ichain(() => header(['field', 'value'])) // error: Type '"HeadersOpen"' is not assignable to type '"ResponseEnded"'
.ichain(() => middleware.headers({ field: 'value' })) // error: Type '"HeadersOpen"' is not assignable to type '"ResponseEnded"'
```

No more `"Can't set headers after they are sent."` errors.
Expand All @@ -108,7 +120,7 @@ Here I'm using `t.string` but you can pass _any_ `io-ts` runtime type
```ts
import { IntegerFromString } from 'io-ts-types/lib/number/IntegerFromString'

// validation succeeds only if `req.param.user_id` is an integer
// validation succeeds only if `req.param.user_id` can be parsed to an integer
param('user_id', IntegerFromString)
```

Expand Down Expand Up @@ -153,58 +165,53 @@ ensure this requirement statically

```ts
import * as express from 'express'
import {
status,
closeHeaders,
send,
MiddlewareTask,
param,
of,
Handler,
unsafeResponseStateTransition
} from 'hyper-ts/lib/MiddlewareTask'
import { Status, StatusOpen } from 'hyper-ts'
import { MiddlewareTask, param, Handler, unsafeResponseStateTransition, middleware } from 'hyper-ts/lib/MiddlewareTask'
import { Status, StatusOpen, Conn } from 'hyper-ts'
import { Option, some, none } from 'fp-ts/lib/Option'
import * as t from 'io-ts'
import * as task from 'fp-ts/lib/Task'
import { Task, task } from 'fp-ts/lib/Task'
import { tuple } from 'fp-ts/lib/function'
import { IntegerFromString } from 'io-ts-types/lib/number/IntegerFromString'
import { toExpressRequestHandler } from 'hyper-ts/lib/toExpressRequestHandler'

// the new connection state
type Authenticated = 'Authenticated'

interface Authentication
extends MiddlewareTask<StatusOpen, StatusOpen, Option<MiddlewareTask<StatusOpen, Authenticated, void>>> {}

const withAuthentication = (strategy: (req: express.Request) => task.Task<boolean>): Authentication =>
const withAuthentication = (strategy: (c: Conn<StatusOpen>) => Task<boolean>): Authentication =>
new MiddlewareTask(c => {
return strategy(c.req).map(authenticated => tuple(authenticated ? some(unsafeResponseStateTransition) : none, c))
return strategy(c).map(authenticated => tuple(authenticated ? some(unsafeResponseStateTransition) : none, c))
})

// dummy authentication process
const tokenAuthentication = withAuthentication(req => task.of(t.string.is(req.get('token'))))
const tokenAuthentication = withAuthentication(c => task.of(t.string.is(c.getHeader('token'))))

// dummy ResponseStateTransition (like closeHeaders)
// dummy ResponseStateTransition (like middleware.closeHeaders)
const authenticated: MiddlewareTask<Authenticated, StatusOpen, void> = unsafeResponseStateTransition

//
// error handling combinators
//

const badRequest = (message: string) =>
status(Status.BadRequest)
.ichain(() => closeHeaders)
.ichain(() => send(message))
middleware
.status(Status.BadRequest)
.ichain(() => middleware.closeHeaders)
.ichain(() => middleware.send(message))

const notFound = (message: string) =>
status(Status.NotFound)
.ichain(() => closeHeaders)
.ichain(() => send(message))
middleware
.status(Status.NotFound)
.ichain(() => middleware.closeHeaders)
.ichain(() => middleware.send(message))

const unauthorized = (message: string) =>
status(Status.Unauthorized)
.ichain(() => closeHeaders)
.ichain(() => send(message))
middleware
.status(Status.Unauthorized)
.ichain(() => middleware.closeHeaders)
.ichain(() => middleware.send(message))

//
// user
Expand All @@ -215,32 +222,34 @@ interface User {
}

// the result of this function requires a successful authentication upstream
const loadUser = (id: number) => authenticated.ichain(() => of(id === 1 ? some<User>({ name: 'Giulio' }) : none))
const loadUser = (id: number) =>
authenticated.ichain(() => middleware.of(id === 1 ? some<User>({ name: 'Giulio' }) : none))

const getUserId = param('user_id', IntegerFromString)

const sendUser = (user: User) =>
status(Status.OK)
.ichain(() => closeHeaders)
.ichain(() => send(`Hello ${user.name}!`))
middleware
.status(Status.OK)
.ichain(() => middleware.closeHeaders)
.ichain(() => middleware.send(`Hello ${user.name}!`))

const user: Handler = getUserId.ichain(oid =>
oid.fold(
() => badRequest('Invalid user id'),
id =>
tokenAuthentication.ichain(oAuthenticated =>
oAuthenticated.fold(
oAuthenticated.foldL(
() => unauthorized('Unauthorized user'),
authenticated =>
authenticated.ichain(() => loadUser(id).ichain(ou => ou.fold(() => notFound('User not found'), sendUser)))
authenticated.ichain(() => loadUser(id).ichain(ou => ou.foldL(() => notFound('User not found'), sendUser)))
)
)
)
)

const app = express()
app.get('/:user_id', user.toRequestHandler())
app.listen(3000, () => console.log('App listening on port 3000!'))
express()
.get('/:user_id', toExpressRequestHandler(user))
.listen(3000, () => console.log('Express listening on port 3000'))
```

# Using the State monad for writing tests
Expand All @@ -249,47 +258,43 @@ There's another interpreter for testing purposes: `MiddlewareState`

```ts
import * as express from 'express'
import { MonadMiddleware, StatusOpen, ResponseEnded, Conn, param, Status } from 'hyper-ts'
import { monadMiddlewareTask } from 'hyper-ts/lib/MiddlewareTask'
import { monadMiddlewareState } from 'hyper-ts/lib/MiddlewareState'
import { HKT3, HKT3S, HKT3As } from 'fp-ts/lib/HKT'
import { MonadMiddleware, MonadMiddleware3, StatusOpen, ResponseEnded, Conn, param, Status } from 'hyper-ts'
import { middleware as middlewareTask } from 'hyper-ts/lib/MiddlewareTask'
import { middleware as middlewareState } from 'hyper-ts/lib/MiddlewareState'
import { HKT3, URIS3, Type3 } from 'fp-ts/lib/HKT'
import * as t from 'io-ts'
import { toExpressRequestHandler } from 'hyper-ts/lib/toExpressRequestHandler'

function program<M extends HKT3S>(R: MonadMiddleware<M>): HKT3As<M, StatusOpen, ResponseEnded, void>
function program<M extends URIS3>(R: MonadMiddleware3<M>): Type3<M, StatusOpen, ResponseEnded, void>
function program<M>(R: MonadMiddleware<M>): HKT3<M, StatusOpen, ResponseEnded, void>
function program<M>(R: MonadMiddleware<M>): HKT3<M, StatusOpen, ResponseEnded, void> {
return R.ichain(
e =>
R.ichain(
() => R.send(`Hello ${e.getOrElseValue('Anonymous')}!`),
R.ichain(() => R.closeHeaders, R.status(Status.OK))
),
param(R)('name', t.string)
return R.ichain(param(R)('name', t.string), e =>
R.ichain(R.ichain(R.status(Status.OK), () => R.closeHeaders), () => R.send(`Hello ${e.getOrElse('Anonymous')}!`))
)
}

// interpreted in Task
const helloTask = program(monadMiddlewareTask)
const programTask = program(middlewareTask)

// interpreted in State
const helloState = program(monadMiddlewareState)
const programState = program(middlewareState)

// fake Conn
const c: Conn<StatusOpen> = {
req: {
params: {}
},
res: {
status: () => null,
send: () => null
}
getParams: () => ({}),
setStatus: () => null,
setBody: () => null
} as any

console.log(helloState.eval(c).run([]))
console.log(programState.eval(c).run([]))

const app = express()
app.get('/:name?', helloTask.toRequestHandler())
app.listen(3000, () => console.log('App listening on port 3000!'))
//
// express app
//

express()
.get('/:name?', toExpressRequestHandler(programTask))
.listen(3000, () => console.log('Express listening on port 3000'))

/*
Output:
Expand Down
20 changes: 8 additions & 12 deletions examples/express.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import * as express from 'express'
import { Status, StatusOpen, ResponseEnded } from '../src'
import { status, closeHeaders, send, MiddlewareTask } from '../src/MiddlewareTask'
import { ExpressConn } from '../src/adapters/express'
import { Status } from '../src'
import { middleware } from '../src/MiddlewareTask'
import { toExpressRequestHandler } from '../src/toExpressRequestHandler'

const hello = status(Status.OK)
.ichain(() => closeHeaders)
.ichain(() => send('Hello hyper-ts on express!'))

export const toRequestHandler = (task: MiddlewareTask<StatusOpen, ResponseEnded, void>): express.RequestHandler => (
req,
res
) => task.eval(new ExpressConn(req, res)).run()
const hello = middleware
.status(Status.OK)
.ichain(() => middleware.closeHeaders)
.ichain(() => middleware.send('Hello hyper-ts on express!'))

express()
.get('/', toRequestHandler(hello))
.get('/', toExpressRequestHandler(hello))
.listen(3000, () => console.log('Express listening on port 3000. Use: GET /'))
18 changes: 8 additions & 10 deletions examples/koa.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import * as Koa from 'koa'
import { Status, StatusOpen, ResponseEnded } from '..'
import { status, closeHeaders, send, MiddlewareTask } from '../src/MiddlewareTask'
import { KoaConn } from '../src/adapters/koa'
import { Status } from '..'
import { middleware } from '../src/MiddlewareTask'
import { toKoaRequestHandler } from '../src/toKoaRequestHandler'

const hello = status(Status.OK)
.ichain(() => closeHeaders)
.ichain(() => send('Hello hyper-ts on koa!'))
const hello = middleware
.status(Status.OK)
.ichain(() => middleware.closeHeaders)
.ichain(() => middleware.send('Hello hyper-ts on koa!'))

const app = new Koa()

const toRequestHandler = (task: MiddlewareTask<StatusOpen, ResponseEnded, void>): Koa.Middleware => ctx =>
task.eval(new KoaConn(ctx)).run()

app.use(toRequestHandler(hello)).listen(3000, () => {
app.use(toKoaRequestHandler(hello)).listen(3000, () => {
console.log('Koa listening on port 3000. Use: GET /')
})
17 changes: 17 additions & 0 deletions examples/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"strict": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true,
"target": "es5",
"moduleResolution": "node",
"forceConsistentCasingInFileNames": true,
"lib": [
"es6",
"dom"
]
}
}
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hyper-ts",
"version": "0.2.0",
"version": "0.3.0",
"description": "hyper-ts description",
"files": ["lib"],
"main": "lib/index.js",
Expand All @@ -9,7 +9,7 @@
"lint": "tslint src/**/*.ts test/**/*.ts",
"typings-checker":
"typings-checker --allow-expect-error --project typings-checker/tsconfig.json typings-checker/index.ts",
"mocha": "mocha -r ts-node/register test/*.ts",
"mocha": "TS_NODE_CACHE=false mocha -r ts-node/register test/*.ts",
"prettier":
"prettier --no-semi --single-quote --print-width 120 --parser typescript --list-different \"{src,test}/**/*.ts\"",
"fix-prettier":
Expand All @@ -29,14 +29,15 @@
},
"homepage": "https://github.com/gcanti/hyper-ts",
"dependencies": {
"fp-ts": "^0.6.8",
"io-ts": "^0.9.5"
"fp-ts": "^1.0.1",
"io-ts": "^1.0.2"
},
"devDependencies": {
"@types/mocha": "2.2.38",
"@types/node": "8.0.19",
"@types/qs": "^6.5.1",
"autocannon": "^0.16.5",
"io-ts-types": "^0.3.0",
"mocha": "3.2.0",
"prettier": "1.9.2",
"qs": "^6.5.1",
Expand Down
Loading

0 comments on commit 796fb54

Please sign in to comment.