@e22m4u/js-trie-router
Advanced tools
Comparing version 0.0.1 to 0.0.2
{ | ||
"name": "@e22m4u/js-trie-router", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"description": "Trie-based router for Node.js", | ||
@@ -5,0 +5,0 @@ "type": "module", |
126
README.md
@@ -9,3 +9,3 @@ ## @e22m4u/js-trie-router | ||
- Parses JSON-body automatically. | ||
- Parses a query string and the Cookie header. | ||
- Parses a query string and a `cookie` header. | ||
- Supports `preHandler` and `postHandler` hooks. | ||
@@ -20,2 +20,5 @@ - Asynchronous request handler. | ||
To load an ES-module set `"type": "module"` in the `package.json` | ||
or use the `.mjs` extension. | ||
## Overview | ||
@@ -49,22 +52,33 @@ | ||
The first parameter of the `Router` handler is the `RequestContext` instance. | ||
The first parameter of a route handler is a `RequestContext` instance. | ||
- `container: ServiceContainer` | ||
- `req: IncomingMessage` | ||
- `res: ServerResponse` | ||
- `query: ParsedQuery` | ||
- `headers: ParsedHeaders` | ||
- `cookie: ParsedCookie` | ||
- `container: ServiceContainer` is an instance of the [ServiceContainer](https://npmjs.com/package/@e22m4u/js-service) | ||
- `req: IncomingMessage` is a native request from the `http` module | ||
- `res: ServerResponse` is a native response from the `http` module | ||
- `params: ParsedParams` is a key-value object of path parameters | ||
- `query: ParsedQuery` is a key-value object of a parsed query string | ||
- `headers: ParsedHeaders` is a key-value object of request headers | ||
- `cookie: ParsedCookie` is a key-value object of a parsed `cookie` header | ||
- `method: string` is a request method in lower case like `get`, `post` etc. | ||
- `path: string` is a request pathname with a query string | ||
- `pathname: string` is a request pathname without a query string | ||
The `RequestContext` can be destructured. | ||
Here are possible values of RequestContext properties. | ||
```js | ||
router.defineRoute({ | ||
// ... | ||
handler({req, res, query, headers, cookie}) { | ||
console.log(req); // IncomingMessage | ||
console.log(res); // ServerResponse | ||
console.log(query); // {id: '10', ...} | ||
console.log(headers); // {'cookie': 'foo=bar', ...} | ||
console.log(cookie); // {foo: 'bar', ...} | ||
method: 'get', | ||
path: '/users/:id', | ||
handler(ctx) { | ||
// GET /users/10?include=city | ||
// Cookie: foo=bar; baz=qux; | ||
console.log(ctx.req); // IncomingMessage | ||
console.log(ctx.res); // ServerResponse | ||
console.log(ctx.params); // {id: 10} | ||
console.log(ctx.query); // {include: 'city'} | ||
console.log(ctx.headers); // {cookie: 'foo=bar; baz=qux;'} | ||
console.log(ctx.cookie); // {foo: 'bar', baz: 'qux'} | ||
console.log(ctx.method); // "get" | ||
console.log(ctx.path); // "/users/10?include=city" | ||
console.log(ctx.pathname); // "/users/10" | ||
// ... | ||
@@ -77,6 +91,6 @@ }, | ||
Return values of the `Route` handler will be sent as described below. | ||
Return values of a route handler will be sent as described below. | ||
| type | content-type | | ||
|---------|--------------------------| | ||
| value | content-type | | ||
|-----------|--------------------------| | ||
| `string` | text/plain | | ||
@@ -101,4 +115,4 @@ | `number` | application/json | | ||
If the `ServerResponse` has been sent manually, then the return | ||
value will be ignored. | ||
If the `ServerResponse` has been sent manually, then a return | ||
value of the route handler will be ignored. | ||
@@ -116,2 +130,65 @@ ```js | ||
### Route hooks | ||
A route definition allows you to set following hooks: | ||
- `preHandler` is executed before a route handler. | ||
- `postHandler` is executed after a route handler. | ||
If the `preHandler` hook returns a value other than `undefined` | ||
or `null`, it will be used as the server response. | ||
```js | ||
router.defineRoute({ | ||
// ... | ||
preHandler(ctx) { | ||
return 'Are you authenticated?'; | ||
}, | ||
handler(ctx) { | ||
// the request handler will be skipped because | ||
// the "preHandler" hook returns a non-empty value | ||
return 'Hello world!'; | ||
}, | ||
}); | ||
``` | ||
A return value of the route handler will be passed as the second | ||
argument to the `preHandler` hook. | ||
```js | ||
router.defineRoute({ | ||
// ... | ||
handler(ctx) { | ||
return 'Hello world!'; | ||
}, | ||
preHandler(ctx, data) { | ||
// after the route handler | ||
return data.toUpperCase(); // HELLO WORLD! | ||
}, | ||
}); | ||
``` | ||
### Global hooks | ||
A `Router` instance allows you to set following global hooks: | ||
- `preHandler` is executed before each route handler. | ||
- `postHandler` is executed after each route handler. | ||
The `addHook` method of a `Router` instance accepts a hook name as the first | ||
parameter and the hook function as the second. | ||
```js | ||
router.addHook('preHandler', (ctx) => { | ||
// executes before each route handler | ||
}); | ||
router.addHook('postHandler', (ctx, data) => { | ||
// executes after each route handler | ||
}); | ||
``` | ||
Similar to a route hook, if a global hook returns a value other than | ||
`undefined` or `null`, that value will be used as the server response. | ||
## Debug | ||
@@ -131,4 +208,11 @@ | ||
## Contribution | ||
- Bug fixes. | ||
- Grammar correction. | ||
- Documentation improvements. | ||
- Vulnerability fixes. | ||
## License | ||
MIT |
import {Service} from '../service.js'; | ||
import {parseCookie} from '../utils/index.js'; | ||
import {getRequestPath} from '../utils/index.js'; | ||
import {getRequestPathname} from '../utils/index.js'; | ||
@@ -27,3 +27,3 @@ /** | ||
req.method, | ||
getRequestPath(req), | ||
getRequestPathname(req), | ||
); | ||
@@ -30,0 +30,0 @@ } |
import querystring from 'querystring'; | ||
import {Service} from '../service.js'; | ||
import {getRequestPath} from '../utils/index.js'; | ||
import {getRequestPathname} from '../utils/index.js'; | ||
@@ -27,3 +27,3 @@ /** | ||
req.method, | ||
getRequestPath(req), | ||
getRequestPathname(req), | ||
); | ||
@@ -30,0 +30,0 @@ } |
@@ -43,2 +43,17 @@ import {ServerResponse} from 'http'; | ||
/** | ||
* Method. | ||
*/ | ||
get method(): string; | ||
/** | ||
* Path. | ||
*/ | ||
get path(): string; | ||
/** | ||
* Pathname. | ||
*/ | ||
get pathname(): string; | ||
/** | ||
* Constructor. | ||
@@ -45,0 +60,0 @@ * |
@@ -5,2 +5,3 @@ import {Errorf} from '@e22m4u/js-format'; | ||
import {ServiceContainer} from '@e22m4u/js-service'; | ||
import {getRequestPathname} from './utils/index.js'; | ||
@@ -68,2 +69,39 @@ /** | ||
/** | ||
* Method. | ||
* | ||
* @returns {string} | ||
*/ | ||
get method() { | ||
return this.req.method.toLowerCase(); | ||
} | ||
/** | ||
* Path. | ||
* | ||
* @returns {string} | ||
*/ | ||
get path() { | ||
return this.req.url; | ||
} | ||
/** | ||
* Pathname. | ||
* | ||
* @type {string|undefined} | ||
* @private | ||
*/ | ||
_pathname = undefined; | ||
/** | ||
* Pathname. | ||
* | ||
* @returns {string} | ||
*/ | ||
get pathname() { | ||
if (this._pathname != null) return this._pathname; | ||
this._pathname = getRequestPathname(this.req); | ||
return this._pathname; | ||
} | ||
/** | ||
* Constructor. | ||
@@ -70,0 +108,0 @@ * |
@@ -89,2 +89,46 @@ import {expect} from './chai.js'; | ||
}); | ||
describe('method', function () { | ||
it('returns the method name in lower case', function () { | ||
const req = createRequestMock({method: 'POST'}); | ||
const res = createResponseMock(); | ||
const cnt = new ServiceContainer(); | ||
const ctx = new RequestContext(cnt, req, res); | ||
expect(ctx.method).to.be.eq('post'); | ||
}); | ||
}); | ||
describe('path', function () { | ||
it('returns the request pathname with the query string', function () { | ||
const req = createRequestMock({path: '/pathname?foo=bar'}); | ||
const res = createResponseMock(); | ||
const cnt = new ServiceContainer(); | ||
const ctx = new RequestContext(cnt, req, res); | ||
expect(req.url).to.be.eq('/pathname?foo=bar'); | ||
expect(ctx.path).to.be.eq('/pathname?foo=bar'); | ||
}); | ||
}); | ||
describe('pathname', function () { | ||
it('returns the request pathname without the query string', function () { | ||
const req = createRequestMock({path: '/pathname?foo=bar'}); | ||
const res = createResponseMock(); | ||
const cnt = new ServiceContainer(); | ||
const ctx = new RequestContext(cnt, req, res); | ||
expect(req.url).to.be.eq('/pathname?foo=bar'); | ||
expect(ctx.pathname).to.be.eq('/pathname'); | ||
}); | ||
it('sets the cache to the "_pathname" property and uses is for next accesses', function () { | ||
const req = createRequestMock({path: '/pathname'}); | ||
const res = createResponseMock(); | ||
const cnt = new ServiceContainer(); | ||
const ctx = new RequestContext(cnt, req, res); | ||
expect(ctx._pathname).to.be.undefined; | ||
expect(ctx.pathname).to.be.eq('/pathname'); | ||
expect(ctx._pathname).to.be.eq('/pathname'); | ||
ctx._pathname = '/overridden'; | ||
expect(ctx.pathname).to.be.eq('/overridden'); | ||
}); | ||
}); | ||
}); |
@@ -5,3 +5,3 @@ import {Errorf} from '@e22m4u/js-format'; | ||
import {createDebugger} from './utils/index.js'; | ||
import {getRequestPath} from './utils/index.js'; | ||
import {getRequestPathname} from './utils/index.js'; | ||
@@ -177,3 +177,3 @@ /** | ||
handle(context) { | ||
const requestPath = getRequestPath(context.req); | ||
const requestPath = getRequestPathname(context.req); | ||
debug( | ||
@@ -180,0 +180,0 @@ 'Invoking the Route handler for the request %s %v.', |
import {inspect} from 'util'; | ||
import {Service} from '../service.js'; | ||
import getStatusMessage from 'statuses'; | ||
import {getRequestPath} from '../utils/index.js'; | ||
import {getRequestPathname} from '../utils/index.js'; | ||
@@ -64,3 +64,3 @@ /** | ||
req.method, | ||
getRequestPath(req), | ||
getRequestPathname(req), | ||
); | ||
@@ -83,5 +83,5 @@ } | ||
req.method, | ||
getRequestPath(req), | ||
getRequestPathname(req), | ||
); | ||
} | ||
} |
@@ -38,3 +38,3 @@ import {Service} from './service.js'; | ||
* handler(ctx) { ... }, // Request handler function. | ||
* postHandler(ctx, data) { ... }, // The "postHandler" is executed after a route handler | ||
* postHandler(ctx, data) { ... }, // The "postHandler" is executed after a route handler. | ||
* }); | ||
@@ -41,0 +41,0 @@ * ``` |
@@ -13,3 +13,2 @@ import {Readable} from 'stream'; | ||
query?: object; | ||
hash?: string; | ||
cookie?: object; | ||
@@ -16,0 +15,0 @@ headers?: object; |
@@ -17,3 +17,2 @@ import {Socket} from 'net'; | ||
* query?: object; | ||
* hash?: string; | ||
* cookie?: object; | ||
@@ -78,8 +77,2 @@ * headers?: object; | ||
} | ||
if (patch.hash != null && typeof patch.hash !== 'string') | ||
throw new Errorf( | ||
'The parameter "hash" of "createRequestMock" ' + | ||
'should be a String, but %v given.', | ||
patch.hash, | ||
); | ||
if ( | ||
@@ -148,3 +141,3 @@ (patch.cookie != null && | ||
createRequestStream(patch.secure, patch.body, patch.encoding); | ||
req.url = createRequestUrl(patch.path || '/', patch.query, patch.hash); | ||
req.url = createRequestUrl(patch.path || '/', patch.query); | ||
req.headers = createRequestHeaders( | ||
@@ -205,6 +198,5 @@ patch.host, | ||
* @param {string|object|null|undefined} query | ||
* @param {string|null|undefined} hash | ||
* @returns {string} | ||
*/ | ||
function createRequestUrl(path, query, hash) { | ||
function createRequestUrl(path, query) { | ||
if (typeof path !== 'string') | ||
@@ -226,8 +218,2 @@ throw new Errorf( | ||
} | ||
if (hash != null && typeof hash !== 'string') | ||
throw new Errorf( | ||
'The parameter "hash" of "createRequestUrl" ' + | ||
'should be a String, but %v given.', | ||
path, | ||
); | ||
let url = ('/' + path).replace('//', '/'); | ||
@@ -240,4 +226,2 @@ if (typeof query === 'object') { | ||
} | ||
hash = (hash || '').replace('#', ''); | ||
if (hash) url += `#${hash}`; | ||
return url; | ||
@@ -244,0 +228,0 @@ } |
@@ -131,22 +131,2 @@ import {Socket} from 'net'; | ||
it('requires the parameter "hash" to be a String', function () { | ||
const throwable = v => () => createRequestMock({hash: v}); | ||
const error = v => | ||
format( | ||
'The parameter "hash" of "createRequestMock" ' + | ||
'should be a String, but %s given.', | ||
v, | ||
); | ||
expect(throwable(10)).to.throw(error('10')); | ||
expect(throwable(0)).to.throw(error('0')); | ||
expect(throwable(true)).to.throw(error('true')); | ||
expect(throwable(false)).to.throw(error('false')); | ||
expect(throwable([])).to.throw(error('Array')); | ||
expect(throwable({})).to.throw(error('Object')); | ||
throwable('str')(); | ||
throwable('')(); | ||
throwable(undefined)(); | ||
throwable(null)(); | ||
}); | ||
it('requires the parameter "cookie" to be a String or Object', function () { | ||
@@ -293,3 +273,3 @@ const throwable = v => () => createRequestMock({cookie: v}); | ||
it('uses the default path "/" without query and hash', function () { | ||
it('uses the default path "/" without a query string', function () { | ||
const req = createRequestMock(); | ||
@@ -382,17 +362,6 @@ expect(req.url).to.be.eq('/'); | ||
it('sets the hash value to the request url', async function () { | ||
const req = createRequestMock({hash: 'myHash'}); | ||
expect(req.url).to.be.eq('/#myHash'); | ||
}); | ||
it('sets the hash value to the request url with the prefix "#"', async function () { | ||
const req = createRequestMock({hash: '#myHash'}); | ||
expect(req.url).to.be.eq('/#myHash'); | ||
}); | ||
it('set parameters "path", "query" and "hash" to the request url', function () { | ||
it('set parameters "path" and "query" to the request url', function () { | ||
const req1 = createRequestMock({ | ||
path: 'test', | ||
query: 'p1=foo&p2=bar', | ||
hash: 'myHash1', | ||
}); | ||
@@ -402,6 +371,5 @@ const req2 = createRequestMock({ | ||
query: {p1: 'baz', p2: 'qux'}, | ||
hash: '#myHash2', | ||
}); | ||
expect(req1.url).to.be.eq('/test?p1=foo&p2=bar#myHash1'); | ||
expect(req2.url).to.be.eq('/test?p1=baz&p2=qux#myHash2'); | ||
expect(req1.url).to.be.eq('/test?p1=foo&p2=bar'); | ||
expect(req2.url).to.be.eq('/test?p1=baz&p2=qux'); | ||
}); | ||
@@ -408,0 +376,0 @@ |
@@ -7,3 +7,2 @@ export * from './is-promise.js'; | ||
export * from './is-response-sent.js'; | ||
export * from './get-request-path.js'; | ||
export * from './is-readable-stream.js'; | ||
@@ -13,1 +12,2 @@ export * from './is-writable-stream.js'; | ||
export * from './create-cookie-string.js'; | ||
export * from './get-request-pathname.js'; |
@@ -7,3 +7,2 @@ export * from './is-promise.js'; | ||
export * from './is-response-sent.js'; | ||
export * from './get-request-path.js'; | ||
export * from './is-readable-stream.js'; | ||
@@ -13,1 +12,2 @@ export * from './is-writable-stream.js'; | ||
export * from './create-cookie-string.js'; | ||
export * from './get-request-pathname.js'; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
218392
6298
212