servie-send
Advanced tools
Comparing version 1.1.1 to 2.0.0
/// <reference types="node" /> | ||
import { Request, Response, CreateHeaders } from 'servie'; | ||
import { CreateBody } from 'servie/dist/body/node'; | ||
import { Request, Response, HeadersInit, CreateBody } from "servie/dist/node"; | ||
export interface SendOptions { | ||
statusCode?: number; | ||
headers?: CreateHeaders; | ||
status?: number; | ||
headers?: HeadersInit; | ||
contentType?: string; | ||
contentLength?: number; | ||
mtime?: Date; | ||
etag?: string; | ||
skipEtag?: boolean; | ||
jsonSpaces?: string | number; | ||
jsonReplacer?: (key: string, value: any) => string; | ||
etag?: boolean | string; | ||
} | ||
@@ -20,5 +16,12 @@ /** | ||
/** | ||
* JSON response configuration. | ||
*/ | ||
export interface JsonOptions extends SendOptions { | ||
jsonSpaces?: string | number; | ||
jsonReplacer?: (key: string, value: any) => string; | ||
} | ||
/** | ||
* Send JSON response. | ||
*/ | ||
export declare function sendJson(req: Request, payload: boolean | string | number | object, options?: SendOptions): Response; | ||
export declare function sendJson(req: Request, payload: boolean | string | number | object, options?: JsonOptions): Response; | ||
/** | ||
@@ -41,4 +44,4 @@ * Send the response as a stream. | ||
/** | ||
* Create an entity tag for cache identification. | ||
* Create an ETag from the payload body. | ||
*/ | ||
export declare function entityTag(body: string | Buffer): string; | ||
export declare function entityTag(body: string | Buffer): string | undefined; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const crypto_1 = require("crypto"); | ||
const servie_1 = require("servie"); | ||
const node_1 = require("servie/dist/body/node"); | ||
const node_1 = require("servie/dist/node"); | ||
const CACHE_CONTROL_NO_CACHE_REGEXP = /(?:^|,)\s*?no-cache\s*?(?:,|$)/; | ||
const TOKEN_LIST_REGEXP = / *, */; | ||
const ZERO_LENGTH_ENTITY_TAG = entityTag(''); | ||
const ZERO_LENGTH_ENTITY_TAG = toEntityTag(""); | ||
/** | ||
@@ -13,3 +12,3 @@ * Create an empty response. | ||
function sendEmpty(req, options = {}) { | ||
return send(req, undefined, options); | ||
return send(req, null, options); | ||
} | ||
@@ -21,5 +20,13 @@ exports.sendEmpty = sendEmpty; | ||
function sendJson(req, payload, options = {}) { | ||
const contentType = options.contentType || 'application/json'; | ||
const contentType = options.contentType || "application/json"; | ||
const { status, headers, contentLength, mtime, etag } = options; | ||
const data = JSON.stringify(payload, options.jsonReplacer, options.jsonSpaces); | ||
return send(req, data, Object.assign({}, options, { contentType })); | ||
return send(req, data, { | ||
status, | ||
headers, | ||
contentType, | ||
contentLength, | ||
mtime, | ||
etag | ||
}); | ||
} | ||
@@ -31,3 +38,3 @@ exports.sendJson = sendJson; | ||
function sendStream(req, payload, options = {}) { | ||
const contentType = options.contentType || 'application/octet-stream'; | ||
const contentType = options.contentType || "application/octet-stream"; | ||
return send(req, payload, Object.assign({}, options, { contentType })); | ||
@@ -40,3 +47,3 @@ } | ||
function sendText(req, payload, options = {}) { | ||
const contentType = options.contentType || 'text/plain'; | ||
const contentType = options.contentType || "text/plain"; | ||
return send(req, payload, Object.assign({}, options, { contentType })); | ||
@@ -49,3 +56,3 @@ } | ||
function sendHtml(req, payload, options = {}) { | ||
const contentType = options.contentType || 'text/html'; | ||
const contentType = options.contentType || "text/html"; | ||
return send(req, payload, Object.assign({}, options, { contentType })); | ||
@@ -58,9 +65,9 @@ } | ||
function send(req, payload, options = {}) { | ||
let statusCode = options.statusCode || 200; | ||
let body = req.method === 'HEAD' ? undefined : node_1.createBody(payload); | ||
const headers = servie_1.createHeaders(options.headers); | ||
const { mtime, contentType, contentLength, skipEtag } = options; | ||
const etag = options.etag || (skipEtag ? undefined : toEntityTag(payload)); | ||
let status = options.status || 200; | ||
let body = req.method === "HEAD" ? undefined : payload; | ||
const headers = new node_1.Headers(options.headers); | ||
const { mtime, contentType, contentLength } = options; | ||
const etag = computeEtag(body, options.etag); | ||
if (fresh(req, etag, mtime)) { | ||
statusCode = 304; | ||
status = 304; | ||
body = undefined; | ||
@@ -70,29 +77,43 @@ } | ||
if (contentType) | ||
headers.set('Content-Type', contentType); | ||
headers.set("Content-Type", contentType); | ||
if (contentLength) | ||
headers.set('Content-Length', String(contentLength)); | ||
headers.set("Content-Length", String(contentLength)); | ||
} | ||
if (etag) | ||
headers.set('ETag', etag); | ||
headers.set("ETag", etag); | ||
if (mtime) | ||
headers.set('Last-Modified', mtime.toUTCString()); | ||
return new servie_1.Response({ statusCode, headers, body }); | ||
headers.set("Last-Modified", mtime.toUTCString()); | ||
return new node_1.Response(body, { status, headers }); | ||
} | ||
exports.send = send; | ||
/** | ||
* Compute etags when requested. | ||
*/ | ||
function computeEtag(body, etag) { | ||
if (typeof etag === "string") | ||
return etag; | ||
if (!etag) | ||
return; | ||
if (body === undefined || body === null) | ||
return ZERO_LENGTH_ENTITY_TAG; | ||
if (typeof body === "string" || Buffer.isBuffer(body)) | ||
return entityTag(body); | ||
throw new TypeError("Etag can only be computed on a string or buffer"); | ||
} | ||
/** | ||
* Create an ETag from the payload body. | ||
*/ | ||
function toEntityTag(body) { | ||
if (typeof body === 'string' || Buffer.isBuffer(body)) { | ||
return body.length === 0 ? ZERO_LENGTH_ENTITY_TAG : entityTag(body); | ||
} | ||
function entityTag(body) { | ||
return body.length === 0 ? ZERO_LENGTH_ENTITY_TAG : toEntityTag(body); | ||
} | ||
exports.entityTag = entityTag; | ||
/** | ||
* Create an entity tag for cache identification. | ||
*/ | ||
function entityTag(body) { | ||
const hash = crypto_1.createHash('sha256').update(body).digest('base64'); | ||
function toEntityTag(body) { | ||
const hash = crypto_1.createHash("sha256") | ||
.update(body) | ||
.digest("base64"); | ||
return `"${hash}"`; | ||
} | ||
exports.entityTag = entityTag; | ||
/** | ||
@@ -104,7 +125,7 @@ * Check if a request is fresh. | ||
function fresh(req, etag, mtime) { | ||
const ifNoneMatch = req.headers.get('if-none-match'); | ||
const ifModifiedSince = req.headers.get('if-modified-since'); | ||
const ifNoneMatch = req.headers.get("if-none-match"); | ||
const ifModifiedSince = req.headers.get("if-modified-since"); | ||
if (!ifNoneMatch && !ifModifiedSince) | ||
return false; | ||
const cacheControl = req.headers.get('cache-control'); | ||
const cacheControl = req.headers.get("cache-control"); | ||
if (cacheControl && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl)) { | ||
@@ -114,15 +135,15 @@ return false; | ||
if (ifNoneMatch && etag) { | ||
const isStale = ifNoneMatch.split(TOKEN_LIST_REGEXP).every(match => { | ||
return match !== etag; | ||
const isFresh = ifNoneMatch.split(TOKEN_LIST_REGEXP).every(match => { | ||
return match === etag; | ||
}); | ||
if (isStale) | ||
return false; | ||
if (isFresh) | ||
return true; | ||
} | ||
if (ifModifiedSince && mtime) { | ||
const isStale = mtime.getTime() > Date.parse(ifModifiedSince); | ||
if (isStale) | ||
return false; | ||
const isFresh = mtime.getTime() <= Date.parse(ifModifiedSince); | ||
if (isFresh) | ||
return true; | ||
} | ||
return true; | ||
return false; | ||
} | ||
//# sourceMappingURL=index.js.map |
@@ -12,30 +12,29 @@ "use strict"; | ||
const index_1 = require("./index"); | ||
const servie_1 = require("servie"); | ||
const node_1 = require("servie/dist/node"); | ||
const stream_1 = require("stream"); | ||
const node_1 = require("servie/dist/body/node"); | ||
describe('servie-send', () => { | ||
it('should send text', () => __awaiter(this, void 0, void 0, function* () { | ||
const req = new servie_1.Request({ url: '/' }); | ||
const res = index_1.sendText(req, 'hello world'); | ||
expect(res.statusCode).toEqual(200); | ||
expect(res.allHeaders).toMatchSnapshot(); | ||
expect(yield res.body.text()).toEqual('hello world'); | ||
describe("servie-send", () => { | ||
it("should send text", () => __awaiter(this, void 0, void 0, function* () { | ||
const req = new node_1.Request("/"); | ||
const res = index_1.sendText(req, "hello world"); | ||
expect(res.status).toEqual(200); | ||
expect(res.headers).toMatchSnapshot(); | ||
expect(yield res.text()).toEqual("hello world"); | ||
})); | ||
it('should send an empty response', () => __awaiter(this, void 0, void 0, function* () { | ||
const req = new servie_1.Request({ url: '/' }); | ||
it("should send an empty response", () => __awaiter(this, void 0, void 0, function* () { | ||
const req = new node_1.Request("/"); | ||
const res = index_1.sendEmpty(req); | ||
expect(res.statusCode).toEqual(200); | ||
expect(res.allHeaders).toMatchSnapshot(); | ||
expect(yield res.body.text()).toEqual(''); | ||
expect(res.status).toEqual(200); | ||
expect(res.headers).toMatchSnapshot(); | ||
expect(yield res.text()).toEqual(""); | ||
})); | ||
it('should send json', () => __awaiter(this, void 0, void 0, function* () { | ||
const req = new servie_1.Request({ url: '/' }); | ||
const res = index_1.sendJson(req, { hello: 'world' }); | ||
expect(res.statusCode).toEqual(200); | ||
expect(res.allHeaders).toMatchSnapshot(); | ||
expect(yield res.body.text()).toEqual('{"hello":"world"}'); | ||
it("should send json", () => __awaiter(this, void 0, void 0, function* () { | ||
const req = new node_1.Request("/"); | ||
const res = index_1.sendJson(req, { hello: "world" }); | ||
expect(res.status).toEqual(200); | ||
expect(res.headers).toMatchSnapshot(); | ||
expect(yield res.text()).toEqual('{"hello":"world"}'); | ||
})); | ||
it('should send stream', () => __awaiter(this, void 0, void 0, function* () { | ||
const req = new servie_1.Request({ url: '/' }); | ||
let chunk = 'hello world'; | ||
it("should send stream", () => __awaiter(this, void 0, void 0, function* () { | ||
const req = new node_1.Request("/"); | ||
let chunk = "hello world"; | ||
const stream = new stream_1.Readable({ | ||
@@ -48,33 +47,43 @@ read() { | ||
const res = index_1.sendStream(req, stream); | ||
expect(res.statusCode).toEqual(200); | ||
expect(res.allHeaders).toMatchSnapshot(); | ||
expect(yield res.body.text()).toEqual('hello world'); | ||
expect(res.status).toEqual(200); | ||
expect(res.headers).toMatchSnapshot(); | ||
expect(yield res.text()).toEqual("hello world"); | ||
})); | ||
it('should send 304 with matching etag', () => __awaiter(this, void 0, void 0, function* () { | ||
const req = new servie_1.Request({ | ||
url: '/', | ||
headers: servie_1.createHeaders({ | ||
'If-None-Match': index_1.entityTag('') | ||
}), | ||
body: node_1.createBody('') | ||
it("should always send 200 when not computing etag", () => __awaiter(this, void 0, void 0, function* () { | ||
const req = new node_1.Request("/", { | ||
headers: { | ||
"If-None-Match": index_1.entityTag("") | ||
}, | ||
body: "" | ||
}); | ||
const res = index_1.sendText(req, ''); | ||
expect(res.statusCode).toEqual(304); | ||
expect(res.allHeaders).toMatchSnapshot(); | ||
expect(yield res.body.text()).toEqual(''); | ||
const res = index_1.sendText(req, ""); | ||
expect(res.status).toEqual(200); | ||
expect(res.headers).toMatchSnapshot(); | ||
expect(yield res.text()).toEqual(""); | ||
})); | ||
it('should send 200 with changed etag', () => __awaiter(this, void 0, void 0, function* () { | ||
const req = new servie_1.Request({ | ||
url: '/', | ||
headers: servie_1.createHeaders({ | ||
'If-None-Match': index_1.entityTag('content') | ||
}), | ||
body: node_1.createBody('') | ||
it("should send 304 with matching etag", () => __awaiter(this, void 0, void 0, function* () { | ||
const req = new node_1.Request("/", { | ||
headers: { | ||
"If-None-Match": index_1.entityTag("") | ||
}, | ||
body: "" | ||
}); | ||
const res = index_1.sendText(req, ''); | ||
expect(res.statusCode).toEqual(200); | ||
expect(res.allHeaders).toMatchSnapshot(); | ||
expect(yield res.body.text()).toEqual(''); | ||
const res = index_1.sendText(req, "", { etag: true }); | ||
expect(res.status).toEqual(304); | ||
expect(res.headers).toMatchSnapshot(); | ||
expect(yield res.text()).toEqual(""); | ||
})); | ||
it("should send 200 with changed etag", () => __awaiter(this, void 0, void 0, function* () { | ||
const req = new node_1.Request("/", { | ||
headers: { | ||
"If-None-Match": index_1.entityTag("content") | ||
}, | ||
body: "" | ||
}); | ||
const res = index_1.sendText(req, "", { etag: true }); | ||
expect(res.status).toEqual(200); | ||
expect(res.headers).toMatchSnapshot(); | ||
expect(yield res.text()).toEqual(""); | ||
})); | ||
}); | ||
//# sourceMappingURL=index.spec.js.map |
{ | ||
"name": "servie-send", | ||
"version": "1.1.1", | ||
"version": "2.0.0", | ||
"description": "Generate a HTTP response with client-side cache support", | ||
@@ -11,7 +11,9 @@ "main": "dist/index.js", | ||
"scripts": { | ||
"prettier": "prettier --write", | ||
"lint": "tslint \"src/**/*.ts\" --project tsconfig.json", | ||
"build": "rm -rf dist/ && tsc", | ||
"format": "npm run prettier -- .travis.yml README.md \"src/**/*.{js,ts}\"", | ||
"build": "rimraf dist && tsc", | ||
"specs": "jest --coverage", | ||
"test": "npm run -s lint && npm run -s build && npm run -s specs", | ||
"prepublish": "npm run build" | ||
"prepare": "npm run build" | ||
}, | ||
@@ -40,28 +42,52 @@ "repository": { | ||
"homepage": "https://github.com/serviejs/servie-send", | ||
"devDependencies": { | ||
"@types/jest": "^23.3.2", | ||
"@types/node": "^10.9.4", | ||
"jest": "^23.6.0", | ||
"rimraf": "^2.5.4", | ||
"servie": "^3.0.0", | ||
"throwback": "^3.0.0", | ||
"ts-jest": "^23.1.4", | ||
"tslint": "^5.9.1", | ||
"tslint-config-standard": "^8.0.1", | ||
"typescript": "^3.0.3" | ||
}, | ||
"peerDependencies": { | ||
"servie": ">=2.1 <4" | ||
}, | ||
"jest": { | ||
"roots": [ | ||
"<rootDir>/src/" | ||
], | ||
"transform": { | ||
".(ts|tsx)": "ts-jest" | ||
"\\.tsx?$": "ts-jest" | ||
}, | ||
"testRegex": "src/.*\\.(?:test|spec)\\.(tsx?|jsx?)$", | ||
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(tsx?|jsx?)$", | ||
"moduleFileExtensions": [ | ||
"ts", | ||
"tsx", | ||
"js" | ||
"js", | ||
"jsx", | ||
"json", | ||
"node" | ||
] | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "lint-staged" | ||
} | ||
}, | ||
"lint-staged": { | ||
"*.{js,json,css,md}": [ | ||
"npm run prettier", | ||
"git add" | ||
] | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"devDependencies": { | ||
"@types/jest": "^24.0.13", | ||
"@types/node": "^12.0.2", | ||
"husky": "^2.3.0", | ||
"jest": "^24.8.0", | ||
"lint-staged": "^8.1.7", | ||
"prettier": "^1.17.1", | ||
"rimraf": "^2.5.4", | ||
"servie": "^4.0.2", | ||
"throwback": "^4.0.0", | ||
"ts-jest": "^24.0.2", | ||
"tslint": "^5.9.1", | ||
"tslint-config-prettier": "^1.18.0", | ||
"tslint-config-standard": "^8.0.1", | ||
"typescript": "^3.4.5" | ||
}, | ||
"peerDependencies": { | ||
"servie": "^4.0.0" | ||
} | ||
} |
@@ -19,13 +19,29 @@ # Servie Send | ||
```ts | ||
import { sendText, sendHtml, sendJson, sendStream, sendEmpty } from 'servie-send' | ||
import { | ||
sendText, | ||
sendHtml, | ||
sendJson, | ||
sendStream, | ||
sendEmpty, | ||
entityTag | ||
} from "servie-send"; | ||
function handle (req) { | ||
return sendText(req, 'hello world!') | ||
return sendHtml(req, '<!doctype html>') | ||
return sendJson(req, { json: true }) | ||
return sendStream(req, fs.createReadStream('example.txt')) | ||
return sendEmpty(req) // Nothing in response. | ||
function handle(req) { | ||
return sendText(req, "hello world!"); | ||
return sendHtml(req, "<!doctype html>"); | ||
return sendJson(req, { json: true }); | ||
return sendStream(req, fs.createReadStream("example.txt")); | ||
return sendEmpty(req); // Nothing in response. | ||
} | ||
``` | ||
### Options | ||
- `status?` Change the default response status code (200). | ||
- `headers?` Define the headers to use for the response. | ||
- `contentType?` Define content length for the response. | ||
- `contentLength?` Define content length for the response. | ||
- `mtime?` Define the modification `Date` for the response. | ||
- `etag?` Define an ETag for the response (e.g. pre-computed with `entityTag()` or `true` for on-demand). | ||
## TypeScript | ||
@@ -32,0 +48,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
28844
271
53
14
1