Skip to content

Commit

Permalink
released v0.2.0 - feature: added ThrowableRouter
Browse files Browse the repository at this point in the history
  • Loading branch information
kwhitley committed Mar 27, 2021
1 parent aad8cad commit b670cc3
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 52 deletions.
4 changes: 2 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ module.exports = {
],
'radix': 'error',
'require-atomic-updates': 'off',
'require-await': 'error',
'require-await': 'off',
'require-jsdoc': 'error',
'require-unicode-regexp': 'off',
'rest-spread-spacing': [
Expand All @@ -245,7 +245,7 @@ module.exports = {
'sort-imports': 'error',
'sort-vars': 'off',
'space-before-blocks': 'error',
'space-before-function-paren': 'error',
'space-before-function-paren': 'off',
'space-in-parens': [
'error',
'never'
Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,39 @@ An assortment of delicious extras for the calorie-light [itty-router](https://ww
npm install itty-router itty-router-extras
```

# Includes the following:

## class
**`StatusError(statusCode: number, message: string): Error`**
throw these to control HTTP status codes that itty responds with.

## middleware (add inline as route handlers)
**`withContent`**
safely parses and embeds content request bodies (e.g. text/json) as `request.content`

**`withCookies`**
embeds cookies into request as `request.cookies` (object)

**`withParams`**
embeds route params directly into request as a convenience

## response
**`error(status: number, message: string): Response`**
returns JSON-formatted Response with `{ error: message, status }` and the matching status code on the response.

**`json(content: object, options: object): Response`**
returns JSON-formatted Response with options passed to the Response (e.g. headers, status, etc)

**`status(status: number, message?: string): Response`**
returns JSON-formatted Response with `{ message, status }` and the matching status code on the response.

**`text(content: string, options: object): Response`**
returns plaintext-formatted Response with options passed to the Response (e.g. headers, status, etc). This is simply a normal Response, but included for code-consistency with `json()`

### routers
**`ThrowableRouter(options?: object): Proxy`**
this is a convenience wrapper around [itty-router](https://www.npmjs.com/package/itty-router) that simply adds automatic exception handling, rather than requiring `try/catch` blocks within your middleware/handlers, or manually calling a `.catch(error)` on the `router.handle`.

## Example
```js
import { Router } from 'itty-router'
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "itty-router-extras",
"version": "0.1.1",
"version": "0.2.0",
"description": "An assortment of delicious extras for the calorie-light itty-router.",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down Expand Up @@ -54,7 +54,7 @@
"fs-extra": "^9.1.0",
"gzip-size": "^6.0.0",
"isomorphic-fetch": "^3.0.0",
"itty-router": "^2.1.9",
"itty-router": "^2.2.0",
"jest": "^26.6.3",
"rimraf": "^3.0.2",
"terser": "^5.6.1",
Expand Down
9 changes: 9 additions & 0 deletions src/classes/StatusError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class StatusError extends Error {
constructor(status = 500, message = 'Internal Error.') {
super(message)
this.name = 'StatusError'
this.status = status
}
}

module.exports = { StatusError }
16 changes: 16 additions & 0 deletions src/classes/StatusError.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const { StatusError } = require('./StatusError')

const message = 'You messed up!'

describe('class/StatusError', () => {
describe(`StatusError(status = '500', message = 'Internal Error')`, () => {
it('returns a JSON Response with { message } and status', async () => {
const error = new StatusError(400, 'Bad Request')

expect(error instanceof Error).toBe(true)
expect(error.status).toBe(400)
expect(error.name).toBe('StatusError')
expect(error.message).toBe('Bad Request')
})
})
})
3 changes: 3 additions & 0 deletions src/classes/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
...require('./StatusError'),
}
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
module.exports = {
...require('./middleware'),
...require('./response'),
...require('./routers'),
...require('./router'),
...require('./classes'),
}
24 changes: 24 additions & 0 deletions src/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
describe('itty-router-extras', () => {
it('returns all exports', async () => {
const exports = require('./index')
const expectedExports = [
'StatusError',
'withContent',
'withCookies',
'withParams',
'error',
'json',
'missing',
'status',
'text',
'ThrowableRouter',
]

for (const e of expectedExports) {
if (!exports.hasOwnProperty(e)) {
console.log('missing export:', e)
}
expect(exports.hasOwnProperty(e)).toBe(true)
}
})
})
5 changes: 4 additions & 1 deletion src/response/createResponseType.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
const createResponseType = (format = 'text/plain; charset=utf-8') =>
(body, options = {}) => {
const { headers = {}, ...rest } = options

if (typeof body === 'object') {
return new Response(JSON.stringify(body), {
headers: {
'Content-Type': format,
...headers,
},
...options,
...rest,
})
}

Expand Down
51 changes: 9 additions & 42 deletions src/router/ThrowableRouter.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,14 @@
const { json } = require('../response')
'use strict'

// WRAPPER FUNCTION FOR THROWABLE HANDLERS
const throwable = (options = {}) => fn => async (request, ...rest) => {
const {
error = {},
cors = true,
} = options
const { message, status = 500 } = error
const { Router } = require('itty-router')
const { error } = require('../response')

try {
let response = await fn(request, ...rest)

if (cors && response && response instanceof Response) {
addCorsHeaders(request)(response)
}

return response
} catch (err) {
let response = json({
message: message || err.message,
stack: err.stack,
status: err.status || status
}, { status: err.status || status })

if (cors) {
addCorsHeaders(request)(response)
}

return response
}
}

const ThrowableRouter = (options = {}) => {
const { middleware = [], ...other } = options

return new Proxy(Router(options), {
get: (obj, prop) =>
(route, ...handlers) => {
let options = typeof handlers[handlers.length - 1] === 'object' && handlers.pop() || {}

return obj[prop](route, withParams, ...middleware, ...handlers.map(throwable(other)))
}
const ThrowableRouter = (options = {}) =>
new Proxy(Router(options), {
get: (obj, prop) => (...args) =>
prop === 'handle'
? obj[prop](...args).catch(err => error(err.status || 500, err.message))
: obj[prop](...args)
})
}

module.exports = { ThrowableRouter }
15 changes: 15 additions & 0 deletions src/router/ThrowableRouter.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { ThrowableRouter } = require('./ThrowableRouter')

describe('router/ThrowableRouter', () => {
describe(`ThrowableRouter(options = {})`, () => {
it('is an itty proxy', async () => {
const origin = {}
const router = ThrowableRouter(origin)

router.get('/foo', () => {})

expect(typeof origin.r).toBe('object')
expect(origin.r.length).toBe(1)
})
})
})
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2540,10 +2540,10 @@ istanbul-reports@^3.0.2:
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"

itty-router@^2.1.9:
version "2.1.9"
resolved "https://registry.yarnpkg.com/itty-router/-/itty-router-2.1.9.tgz#cf03c06a1e105e903888e43a7a11803ba47bcc22"
integrity sha512-n4hc2DXXdEfWKXj7HNV3cOsD2tc09/Pp5gZ6LWap0eB0iQRgQIWsLAGSyJiMkXXjrejbm2FhSSj3DCyfJ3cCUw==
itty-router@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/itty-router/-/itty-router-2.2.0.tgz#f7fc8094d7b9fe72ecea075c0d9c5ab12b90953a"
integrity sha512-p6UayEs+BcAzIqN0Eou1N5xZKXjcQw0DEVy/eeE04WoREKrra1Vj/eYqGRWR0lceJUGFEGvK0LuVbTrg95n+Xw==

jest-changed-files@^26.6.2:
version "26.6.2"
Expand Down

0 comments on commit b670cc3

Please sign in to comment.