Comparing version 0.0.16 to 0.0.17
@@ -15,2 +15,6 @@ /// <reference types="node" /> | ||
(format?: BodyOptions): Promise<BodyJson>; | ||
<T>(format: BodyOptions & { | ||
multipart: true; | ||
}): Promise<[T, FilePart[]]>; | ||
<T>(format?: BodyOptions): Promise<T>; | ||
} | ||
@@ -17,0 +21,0 @@ export declare type BodyOptions = { |
@@ -34,3 +34,3 @@ "use strict"; | ||
} | ||
throw ex_1.default.UnprocessableEntity(`Value ${key} cannot be empty`, { | ||
throw ex_1.default.UnprocessableEntity(`Value ${key} is required`, { | ||
body, | ||
@@ -88,4 +88,2 @@ required | ||
return true; | ||
if (typeof value === 'string' && value.trim() === '') | ||
return true; | ||
return false; | ||
@@ -92,0 +90,0 @@ } |
@@ -7,2 +7,3 @@ "use strict"; | ||
const ex_1 = __importDefault(require("../utils/ex")); | ||
const sanitize_1 = require("../utils/sanitize"); | ||
function streamReader(stream, maxPayloadSize) { | ||
@@ -20,4 +21,4 @@ return new Promise(function (resolve, reject) { | ||
headers: { | ||
'content-type': getHeader(stream, 'Content-Type'), | ||
'content-disposition': getHeader(stream, 'Content-Disposition'), | ||
'content-type': (0, sanitize_1.getHeader)(stream, 'Content-Type'), | ||
'content-disposition': (0, sanitize_1.getHeader)(stream, 'Content-Disposition'), | ||
}, | ||
@@ -43,10 +44,1 @@ data: Buffer.concat(chunks) | ||
exports.default = streamReader; | ||
function getHeader(stream, name) { | ||
if ('getHeader' in stream) { | ||
return String(stream.getHeader(name) || '').trim(); | ||
} | ||
else if ('headers' in stream) { | ||
return String(stream.headers[name.toLowerCase()] || '').trim(); | ||
} | ||
return ''; | ||
} |
@@ -7,2 +7,3 @@ /// <reference types="node" /> | ||
declare type OptionsInput = { | ||
override?: ConfigInput; | ||
method?: string; | ||
@@ -24,3 +25,3 @@ url?: string; | ||
}; | ||
declare function inject(app: IKequapp, override: ConfigInput | undefined, options: OptionsInput): InjectResponse; | ||
declare function inject(app: IKequapp, options: OptionsInput): InjectResponse; | ||
export { inject }; |
@@ -11,5 +11,5 @@ "use strict"; | ||
const setup_config_1 = require("./utils/setup-config"); | ||
function inject(app, override, options) { | ||
if (override) | ||
(0, setup_config_1.validateConfig)(override); | ||
function inject(app, options) { | ||
if (options.override) | ||
(0, setup_config_1.validateConfig)(options.override); | ||
const _options = Object.assign({}, options); | ||
@@ -19,2 +19,4 @@ if (_options.search) { | ||
} | ||
const _override = options.override; | ||
delete _options.override; | ||
const _end = options.body; | ||
@@ -24,3 +26,3 @@ delete _options.body; | ||
const res = new mock_res_1.default(); | ||
app(req, res, override); | ||
app(req, res, _override); | ||
if (_end !== null && !req.writableEnded) { | ||
@@ -27,0 +29,0 @@ req.end(_end); |
@@ -57,3 +57,3 @@ "use strict"; | ||
if (payload !== undefined && !bundle.res.writableEnded) { | ||
const contentType = String(bundle.res.getHeader('Content-Type') || ''); | ||
const contentType = (0, sanitize_1.getHeader)(bundle.res, 'Content-Type'); | ||
const renderer = findRenderer(config.renderers, contentType); | ||
@@ -60,0 +60,0 @@ yield renderer(payload, bundle); |
@@ -0,2 +1,5 @@ | ||
/// <reference types="node" /> | ||
import { IncomingMessage, ServerResponse } from 'http'; | ||
export declare function sanitizePathname(pathname?: string): string; | ||
export declare function sanitizeContentType(contentType?: string): string; | ||
export declare function getHeader(stream: IncomingMessage | ServerResponse, name: string): string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.sanitizeContentType = exports.sanitizePathname = void 0; | ||
exports.getHeader = exports.sanitizeContentType = exports.sanitizePathname = void 0; | ||
function sanitizePathname(pathname = '') { | ||
@@ -16,1 +16,11 @@ const result = pathname.replace(/[\\/]+$/, ''); | ||
exports.sanitizeContentType = sanitizeContentType; | ||
function getHeader(stream, name) { | ||
if ('getHeader' in stream) { | ||
return String(stream.getHeader(name) || '').trim(); | ||
} | ||
else if ('headers' in stream) { | ||
return String(stream.headers[name.toLowerCase()] || '').trim(); | ||
} | ||
return ''; | ||
} | ||
exports.getHeader = getHeader; |
{ | ||
"name": "kequapp", | ||
"version": "0.0.16", | ||
"version": "0.0.17", | ||
"description": "Versatile, non-intrusive, tiny webapp framework", | ||
@@ -5,0 +5,0 @@ "main": "dist/main.js", |
147
readme.md
@@ -106,10 +106,46 @@ # Kequapp | ||
| ---------- | ------------------------------------------------- | | ||
| `req` | The node `req` object. | | ||
| `res` | The node `res` object. | | ||
| `url` | Url requested by the client. | | ||
| `req` | The node [`req`](https://nodejs.org/api/http.html#class-httpclientrequest) object. | | ||
| `res` | The node [`res`](https://nodejs.org/api/http.html#class-httpserverresponse) object. | | ||
| `url` | [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) requested by the client. | | ||
| `context` | Params shared between handler functions. | | ||
| `params` | Params extracted from the pathname. | | ||
| `getBody` | Function to extract params from the request body. | | ||
| `logger` | Logger specified during setup. | | ||
| `logger` | Logger specified during instantiation. | | ||
### Querystring | ||
Querystring values are available from the Javascript [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object found on the `url` object. | ||
```javascript | ||
app.route('/hotels', ({ url }) => { | ||
const page = url.searchParams.get('page'); | ||
const categories = url.searchParams.getAll('categories'); | ||
// page ~= '2' | ||
// categories ~= ['ac', 'hottub'] | ||
}); | ||
``` | ||
### Cookies | ||
Cookies are just a special header, managed by the client. It's easier to encode and decode cookies with use of an external library as there is no similar function built into node. | ||
```javascript | ||
const cookie = require('cookie'); // npm i cookie | ||
``` | ||
```javascript | ||
function withCookies ({ req, context }) { | ||
const cookies = cookie.parse(req.headers.cookie); | ||
// cookies ~= { myCookie: 'hello' } | ||
context.cookies = cookies; | ||
} | ||
const cookiesBranch = app.branch(withCookies); | ||
app.route('/login', ({ res }) => { | ||
res.setHeader('Set-Cookie', [cookie.serialize('myCookie', 'hello')]); | ||
}); | ||
``` | ||
### Body | ||
@@ -186,3 +222,3 @@ | ||
Additional normalization is available. Specifying `required` ensures that the field is not `null`, `undefined`, or an empty string. There are also `numbers` and `booleans`. Full control is offered using `validate()` and `postProcess()`. | ||
Additional normalization is available. Specifying `required` ensures that the field is not `null` or `undefined` (though might be empty). There are also `numbers` and `booleans`. Full control is offered using `validate()` and `postProcess()`. | ||
@@ -225,46 +261,12 @@ Note body normalization is ignored with `raw` or `skipNormalize`. | ||
| parameter | description | | ||
| ---------- | -------------------------------------------- | | ||
| `arrays` | Value is returned as an array. | | ||
| `required` | Value or values are not `null`, `undefined`, or an empty string. | | ||
| `numbers` | Value or values are converted to numbers. | | ||
| `booleans` | Value or values are converted to booleans. | | ||
| `skipNormalize` | Skip normalization. | | ||
| parameter | description | | ||
| ---------- | ---------------------------------------------- | | ||
| `arrays` | Value is returned as an array. | | ||
| `required` | Value or values are not `null` or `undefined`. | | ||
| `numbers` | Value or values are converted to numbers. | | ||
| `booleans` | Value or values are converted to booleans. | | ||
| `skipNormalize` | Skip normalization. | | ||
### Querystring | ||
Further clarification. Required will throw an error if the value is missing. Arrays return an empty array if no value is provided. Numbers will throw an error if any value is provided which parses into `NaN`. Booleans return false if no value is provided, the value is falsy, `'0'`, or `'false'`. | ||
Querystring parameters are available from the Javascript [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object found in `url`. | ||
```javascript | ||
app.route('/hotels', ({ url }) => { | ||
const page = url.searchParams.get('page'); | ||
const categories = url.searchParams.getAll('categories'); | ||
// page ~= 2 | ||
// categories ~= ['ac', 'hottub'] | ||
}); | ||
``` | ||
### Cookies | ||
Cookies are just a special header, managed by the client. It's easier to encode and decode cookies with use of an external library as there is no similar function built into node. | ||
```javascript | ||
const cookie = require('cookie'); // npm i cookie | ||
``` | ||
```javascript | ||
function withCookies ({ req, context }) { | ||
const cookies = cookie.parse(req.headers.cookie); | ||
// cookies ~= { myCookie: 'hello' } | ||
context.cookies = cookies; | ||
} | ||
const cookiesBranch = app.branch(withCookies); | ||
app.route('/login', ({ res }) => { | ||
res.setHeader('Set-Cookie', [cookie.serialize('myCookie', 'hello')]); | ||
}); | ||
``` | ||
### Exceptions | ||
@@ -343,7 +345,7 @@ | ||
It is possible to test your application without spinning up a server using the `inject()` tool. The first parameter is your app, then a config override for your app, followed by options largely used to populate the request. | ||
It is possible to test your application without spinning up a server using the `inject()` tool. The first parameter is your app, then options largely used to populate the request. An optional `overrides` parameter can be provided to override configuration options from your app. | ||
Returned `req` and `res` objects are from the npm `mock-req` and `mock-res` modules respectively. Ensure you have both [`mock-req`](https://www.npmjs.com/package/mock-req) and [`mock-res`](https://www.npmjs.com/package/mock-res) installed in your project. | ||
It also returns `getResponse()` which is a utility you may use to wait for your application to respond. Alternatively you may inspect what your application is doing in realtime using the `req`, and `res` objects manually. | ||
It also returns `getResponse()` which is a utility you may use to wait for your application to respond. Alternatively you may inspect what your application is doing in realtime using the `req`, and `res` objects. | ||
@@ -357,3 +359,4 @@ ```javascript | ||
it('reads the authorization header', async function () { | ||
const { getResponse, res } = inject(app, { logger }, { | ||
const { getResponse, res } = inject(app, { | ||
overrides: { logger }, | ||
url: '/admin/dashboard', | ||
@@ -377,15 +380,12 @@ headers: { | ||
```javascript | ||
it('reads the body of a request', async function () { | ||
const { getResponse, res } = inject(app, { logger }, { | ||
method: 'POST', | ||
url: '/user', | ||
headers: { | ||
'Content-Type': 'application/json; charset=utf-8' | ||
}, | ||
body: '{ "name": "April" }' | ||
}); | ||
const { getResponse, res } = inject(app, { | ||
method: 'POST', | ||
url: '/user', | ||
headers: { | ||
'Content-Type': 'application/json; charset=utf-8' | ||
}, | ||
body: '{ "name": "April" }' | ||
}); | ||
const body = await getResponse(); | ||
assert.strictEqual(body, 'User creation April!'); | ||
const body = await getResponse(); | ||
}); | ||
@@ -395,18 +395,15 @@ ``` | ||
```javascript | ||
it('reads the body of a request', async function () { | ||
const { getResponse, req, res } = inject(app, { logger }, { | ||
method: 'POST', | ||
url: '/user', | ||
headers: { | ||
'Content-Type': 'application/json; charset=utf-8' | ||
}, | ||
body: null | ||
}); | ||
const { getResponse, req, res } = inject(app, { | ||
method: 'POST', | ||
url: '/user', | ||
headers: { | ||
'Content-Type': 'application/json; charset=utf-8' | ||
}, | ||
body: null | ||
}); | ||
req.end('{ "name": "April" }'); | ||
req.end('{ "name": "April" }'); | ||
const body = await getResponse(); | ||
assert.strictEqual(body, 'User creation April!'); | ||
const body = await getResponse(); | ||
}); | ||
``` |
66381
1433
403