Socket
Socket
Sign inDemoInstall

webpack-dev-middleware

Package Overview
Dependencies
Maintainers
4
Versions
117
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

webpack-dev-middleware - npm Package Compare versions

Comparing version 7.1.1 to 7.2.0

dist/utils/etag.js

162

dist/index.js

@@ -64,3 +64,3 @@ "use strict";

* @typedef {Object} ResponseData
* @property {string | Buffer | ReadStream} data
* @property {Buffer | ReadStream} data
* @property {number} byteLength

@@ -70,8 +70,8 @@ */

/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @callback ModifyResponseData
* @param {RequestInternal} req
* @param {ResponseInternal} res
* @param {string | Buffer | ReadStream} data
* @param {Buffer | ReadStream} data
* @param {number} byteLength

@@ -82,4 +82,4 @@ * @return {ResponseData}

/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @typedef {Object} Context

@@ -97,4 +97,4 @@ * @property {boolean} state

/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @typedef {WithoutUndefined<Context<RequestInternal, ResponseInternal>, "watching">} FilledContext

@@ -106,4 +106,4 @@ */

/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @typedef {NormalizedHeaders | ((req: RequestInternal, res: ResponseInternal, context: Context<RequestInternal, ResponseInternal>) => void | undefined | NormalizedHeaders) | undefined} Headers

@@ -113,4 +113,4 @@ */

/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal = IncomingMessage]
* @template {ServerResponse} [ResponseInternal = ServerResponse]
* @typedef {Object} Options

@@ -128,7 +128,9 @@ * @property {{[key: string]: string}} [mimeTypes]

* @property {ModifyResponseData<RequestInternal, ResponseInternal>} [modifyResponseData]
* @property {"weak" | "strong"} [etag]
* @property {boolean} [lastModified]
*/
/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @callback Middleware

@@ -177,4 +179,4 @@ * @param {RequestInternal} req

/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @typedef {Middleware<RequestInternal, ResponseInternal> & AdditionalMethods<RequestInternal, ResponseInternal>} API

@@ -196,4 +198,4 @@ */

/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @param {Compiler | MultiCompiler} compiler

@@ -289,2 +291,126 @@ * @param {Options<RequestInternal, ResponseInternal>} [options]

}
/**
* @template S
* @template O
* @typedef {Object} HapiPluginBase
* @property {(server: S, options: O) => void | Promise<void>} register
*/
/**
* @template S
* @template O
* @typedef {HapiPluginBase<S, O> & { pkg: { name: string } }} HapiPlugin
*/
/**
* @typedef {Options & { compiler: Compiler | MultiCompiler }} HapiOptions
*/
/**
* @template HapiServer
* @template {HapiOptions} HapiOptionsInternal
* @returns {HapiPlugin<HapiServer, HapiOptionsInternal>}
*/
function hapiWrapper() {
return {
pkg: {
name: "webpack-dev-middleware"
},
register(server, options) {
const {
compiler,
...rest
} = options;
if (!compiler) {
throw new Error("The compiler options is required.");
}
const devMiddleware = wdm(compiler, rest);
// @ts-ignore
server.decorate("server", "webpackDevMiddleware", devMiddleware);
// @ts-ignore
server.ext("onRequest", (request, h) => new Promise((resolve, reject) => {
devMiddleware(request.raw.req, request.raw.res, error => {
if (error) {
reject(error);
return;
}
resolve(request);
});
}).then(() => h.continue).catch(error => {
throw error;
}));
}
};
}
wdm.hapiWrapper = hapiWrapper;
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @param {Compiler | MultiCompiler} compiler
* @param {Options<RequestInternal, ResponseInternal>} [options]
* @returns {(ctx: any, next: Function) => Promise<void> | void}
*/
function koaWrapper(compiler, options) {
const devMiddleware = wdm(compiler, options);
/**
* @param {{ req: RequestInternal, res: ResponseInternal & import("./utils/compatibleAPI").ExpectedResponse, status: number, body: Buffer | import("fs").ReadStream | { message: string }, state: Object }} ctx
* @param {Function} next
* @returns {Promise<void>}
*/
const wrapper = async function webpackDevMiddleware(ctx, next) {
return new Promise((resolve, reject) => {
const {
req
} = ctx;
const {
res
} = ctx;
res.locals = ctx.state;
/**
* @param {number} status status code
*/
res.status = status => {
// eslint-disable-next-line no-param-reassign
ctx.status = status;
};
/**
* @param {import("fs").ReadStream} stream readable stream
*/
res.pipeInto = stream => {
// eslint-disable-next-line no-param-reassign
ctx.body = stream;
resolve();
};
/**
* @param {Buffer} content content
*/
res.send = content => {
// eslint-disable-next-line no-param-reassign
ctx.body = content;
resolve();
};
devMiddleware(req, res, err => {
if (err) {
reject(err);
return;
}
resolve(next());
}).catch(err => {
// eslint-disable-next-line no-param-reassign
ctx.status = err.statusCode || err.status || 500;
// eslint-disable-next-line no-param-reassign
ctx.body = {
message: err.message
};
});
});
};
wrapper.devMiddleware = devMiddleware;
return wrapper;
}
wdm.koaWrapper = koaWrapper;
module.exports = wdm;

@@ -5,12 +5,13 @@ "use strict";

const mime = require("mime-types");
const onFinishedStream = require("on-finished");
const getFilenameFromUrl = require("./utils/getFilenameFromUrl");
const {
getHeaderFromRequest,
getHeaderFromResponse,
setHeaderForResponse,
setStatusCode,
send,
sendError
pipe,
createReadStreamOrReadFileSync
} = require("./utils/compatibleAPI");
const ready = require("./utils/ready");
const parseTokenList = require("./utils/parseTokenList");
const memorize = require("./utils/memorize");

@@ -21,3 +22,6 @@ /** @typedef {import("./index.js").NextFunction} NextFunction */

/** @typedef {import("./index.js").NormalizedHeaders} NormalizedHeaders */
/** @typedef {import("fs").ReadStream} ReadStream */
const BYTES_RANGE_REGEXP = /^ *bytes/i;
/**

@@ -32,7 +36,79 @@ * @param {string} type

}
const BYTES_RANGE_REGEXP = /^ *bytes/i;
/**
* Parse an HTTP Date into a number.
*
* @param {string} date
* @returns {number}
*/
function parseHttpDate(date) {
const timestamp = date && Date.parse(date);
// istanbul ignore next: guard against date.js Date.parse patching
return typeof timestamp === "number" ? timestamp : NaN;
}
const CACHE_CONTROL_NO_CACHE_REGEXP = /(?:^|,)\s*?no-cache\s*?(?:,|$)/;
/**
* @param {import("fs").ReadStream} stream stream
* @param {boolean} suppress do need suppress?
* @returns {void}
*/
function destroyStream(stream, suppress) {
if (typeof stream.destroy === "function") {
stream.destroy();
}
if (typeof stream.close === "function") {
// Node.js core bug workaround
stream.on("open",
/**
* @this {import("fs").ReadStream}
*/
function onOpenClose() {
// @ts-ignore
if (typeof this.fd === "number") {
// actually close down the fd
this.close();
}
});
}
if (typeof stream.addListener === "function" && suppress) {
stream.removeAllListeners("error");
stream.addListener("error", () => {});
}
}
/** @type {Record<number, string>} */
const statuses = {
400: "Bad Request",
403: "Forbidden",
404: "Not Found",
416: "Range Not Satisfiable",
500: "Internal Server Error"
};
const parseRangeHeaders = memorize(
/**
* @param {string} value
* @returns {import("range-parser").Result | import("range-parser").Ranges}
*/
value => {
const [len, rangeHeader] = value.split("|");
// eslint-disable-next-line global-require
return require("range-parser")(Number(len), rangeHeader, {
combine: true
});
});
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @typedef {Object} SendErrorOptions send error options
* @property {Record<string, number | string | string[] | undefined>=} headers headers
* @property {import("./index").ModifyResponseData<Request, Response>=} modifyResponseData modify response data callback
*/
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("./index.js").FilledContext<Request, Response>} context

@@ -48,7 +124,2 @@ * @return {import("./index.js").Middleware<Request, Response>}

res.locals = res.locals || {};
if (req.method && !acceptedMethods.includes(req.method)) {
await goNext();
return;
}
ready(context, processRequest, req);
async function goNext() {

@@ -69,3 +140,219 @@ if (!context.options.serverSideRender) {

}
if (req.method && !acceptedMethods.includes(req.method)) {
await goNext();
return;
}
/**
* @param {number} status status
* @param {Partial<SendErrorOptions<Request, Response>>=} options options
* @returns {void}
*/
function sendError(status, options) {
// eslint-disable-next-line global-require
const escapeHtml = require("./utils/escapeHtml");
const content = statuses[status] || String(status);
let document = Buffer.from(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>${escapeHtml(content)}</pre>
</body>
</html>`, "utf-8");
// Clear existing headers
const headers = res.getHeaderNames();
for (let i = 0; i < headers.length; i++) {
res.removeHeader(headers[i]);
}
if (options && options.headers) {
const keys = Object.keys(options.headers);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = options.headers[key];
if (typeof value !== "undefined") {
res.setHeader(key, value);
}
}
}
// Send basic response
setStatusCode(res, status);
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.setHeader("Content-Security-Policy", "default-src 'none'");
res.setHeader("X-Content-Type-Options", "nosniff");
let byteLength = Buffer.byteLength(document);
if (options && options.modifyResponseData) {
({
data: document,
byteLength
} = /** @type {{ data: Buffer, byteLength: number }} */
options.modifyResponseData(req, res, document, byteLength));
}
res.setHeader("Content-Length", byteLength);
res.end(document);
}
function isConditionalGET() {
return req.headers["if-match"] || req.headers["if-unmodified-since"] || req.headers["if-none-match"] || req.headers["if-modified-since"];
}
function isPreconditionFailure() {
// if-match
const ifMatch = req.headers["if-match"];
// A recipient MUST ignore If-Unmodified-Since if the request contains
// an If-Match header field; the condition in If-Match is considered to
// be a more accurate replacement for the condition in
// If-Unmodified-Since, and the two are only combined for the sake of
// interoperating with older intermediaries that might not implement If-Match.
if (ifMatch) {
const etag = res.getHeader("ETag");
return !etag || ifMatch !== "*" && parseTokenList(ifMatch).every(match => match !== etag && match !== `W/${etag}` && `W/${match}` !== etag);
}
// if-unmodified-since
const ifUnmodifiedSince = req.headers["if-unmodified-since"];
if (ifUnmodifiedSince) {
const unmodifiedSince = parseHttpDate(ifUnmodifiedSince);
// A recipient MUST ignore the If-Unmodified-Since header field if the
// received field-value is not a valid HTTP-date.
if (!isNaN(unmodifiedSince)) {
const lastModified = parseHttpDate( /** @type {string} */res.getHeader("Last-Modified"));
return isNaN(lastModified) || lastModified > unmodifiedSince;
}
}
return false;
}
/**
* @returns {boolean} is cachable
*/
function isCachable() {
return res.statusCode >= 200 && res.statusCode < 300 || res.statusCode === 304;
}
/**
* @param {import("http").OutgoingHttpHeaders} resHeaders
* @returns {boolean}
*/
function isFresh(resHeaders) {
// Always return stale when Cache-Control: no-cache to support end-to-end reload requests
// https://tools.ietf.org/html/rfc2616#section-14.9.4
const cacheControl = req.headers["cache-control"];
if (cacheControl && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl)) {
return false;
}
// fields
const noneMatch = req.headers["if-none-match"];
const modifiedSince = req.headers["if-modified-since"];
// unconditional request
if (!noneMatch && !modifiedSince) {
return false;
}
// if-none-match
if (noneMatch && noneMatch !== "*") {
if (!resHeaders.etag) {
return false;
}
const matches = parseTokenList(noneMatch);
let etagStale = true;
for (let i = 0; i < matches.length; i++) {
const match = matches[i];
if (match === resHeaders.etag || match === `W/${resHeaders.etag}` || `W/${match}` === resHeaders.etag) {
etagStale = false;
break;
}
}
if (etagStale) {
return false;
}
}
// A recipient MUST ignore If-Modified-Since if the request contains an If-None-Match header field;
// the condition in If-None-Match is considered to be a more accurate replacement for the condition in If-Modified-Since,
// and the two are only combined for the sake of interoperating with older intermediaries that might not implement If-None-Match.
if (noneMatch) {
return true;
}
// if-modified-since
if (modifiedSince) {
const lastModified = resHeaders["last-modified"];
// A recipient MUST ignore the If-Modified-Since header field if the
// received field-value is not a valid HTTP-date, or if the request
// method is neither GET nor HEAD.
const modifiedStale = !lastModified || !(parseHttpDate(lastModified) <= parseHttpDate(modifiedSince));
if (modifiedStale) {
return false;
}
}
return true;
}
function isRangeFresh() {
const ifRange = /** @type {string | undefined} */
req.headers["if-range"];
if (!ifRange) {
return true;
}
// if-range as etag
if (ifRange.indexOf('"') !== -1) {
const etag = /** @type {string | undefined} */res.getHeader("ETag");
if (!etag) {
return true;
}
return Boolean(etag && ifRange.indexOf(etag) !== -1);
}
// if-range as modified date
const lastModified = /** @type {string | undefined} */
res.getHeader("Last-Modified");
if (!lastModified) {
return true;
}
return parseHttpDate(lastModified) <= parseHttpDate(ifRange);
}
/**
* @returns {string | undefined}
*/
function getRangeHeader() {
const rage = req.headers.range;
if (rage && BYTES_RANGE_REGEXP.test(rage)) {
return rage;
}
// eslint-disable-next-line no-undefined
return undefined;
}
/**
* @param {import("range-parser").Range} range
* @returns {[number, number]}
*/
function getOffsetAndLenFromRange(range) {
const offset = range.start;
const len = range.end - range.start + 1;
return [offset, len];
}
/**
* @param {number} offset
* @param {number} len
* @returns {[number, number]}
*/
function calcStartAndEnd(offset, len) {
const start = offset;
const end = Math.max(offset, offset + len - 1);
return [start, end];
}
async function processRequest() {
// Pipe and SendFile
/** @type {import("./utils/getFilenameFromUrl").Extra} */

@@ -78,3 +365,3 @@ const extra = {};

}
sendError(req, res, extra.errorCode, {
sendError(extra.errorCode, {
modifyResponseData: context.options.modifyResponseData

@@ -88,2 +375,9 @@ });

}
const {
size
} = /** @type {import("fs").Stats} */extra.stats;
let len = size;
let offset = 0;
// Send logic
let {

@@ -112,6 +406,6 @@ headers

headers.forEach(header => {
setHeaderForResponse(res, header.key, header.value);
res.setHeader(header.key, header.value);
});
}
if (!getHeaderFromResponse(res, "Content-Type")) {
if (!res.getHeader("Content-Type")) {
// content-type name(like application/javascript; charset=utf-8) or false

@@ -123,23 +417,107 @@ const contentType = mime.contentType(path.extname(filename));

if (contentType) {
setHeaderForResponse(res, "Content-Type", contentType);
res.setHeader("Content-Type", contentType);
} else if (context.options.mimeTypeDefault) {
setHeaderForResponse(res, "Content-Type", context.options.mimeTypeDefault);
res.setHeader("Content-Type", context.options.mimeTypeDefault);
}
}
if (!getHeaderFromResponse(res, "Accept-Ranges")) {
setHeaderForResponse(res, "Accept-Ranges", "bytes");
if (!res.getHeader("Accept-Ranges")) {
res.setHeader("Accept-Ranges", "bytes");
}
const rangeHeader = /** @type {string} */
getHeaderFromRequest(req, "range");
let len = /** @type {import("fs").Stats} */extra.stats.size;
let offset = 0;
if (rangeHeader && BYTES_RANGE_REGEXP.test(rangeHeader)) {
// eslint-disable-next-line global-require
const parsedRanges = require("range-parser")(len, rangeHeader, {
combine: true
});
if (context.options.lastModified && !res.getHeader("Last-Modified")) {
const modified = /** @type {import("fs").Stats} */
extra.stats.mtime.toUTCString();
res.setHeader("Last-Modified", modified);
}
/** @type {number} */
let start;
/** @type {number} */
let end;
/** @type {undefined | Buffer | ReadStream} */
let bufferOrStream;
/** @type {number} */
let byteLength;
const rangeHeader = getRangeHeader();
if (context.options.etag && !res.getHeader("ETag")) {
/** @type {import("fs").Stats | Buffer | ReadStream | undefined} */
let value;
// TODO cache etag generation?
if (context.options.etag === "weak") {
value = /** @type {import("fs").Stats} */extra.stats;
} else {
if (rangeHeader) {
const parsedRanges = /** @type {import("range-parser").Ranges | import("range-parser").Result} */
parseRangeHeaders(`${size}|${rangeHeader}`);
if (parsedRanges !== -2 && parsedRanges !== -1 && parsedRanges.length === 1) {
[offset, len] = getOffsetAndLenFromRange(parsedRanges[0]);
}
}
[start, end] = calcStartAndEnd(offset, len);
try {
const result = createReadStreamOrReadFileSync(filename, context.outputFileSystem, start, end);
value = result.bufferOrStream;
({
bufferOrStream,
byteLength
} = result);
} catch (_err) {
// Ignore here
}
}
if (value) {
// eslint-disable-next-line global-require
const result = await require("./utils/etag")(value);
// Because we already read stream, we can cache buffer to avoid extra read from fs
if (result.buffer) {
bufferOrStream = result.buffer;
}
res.setHeader("ETag", result.hash);
}
}
// Conditional GET support
if (isConditionalGET()) {
if (isPreconditionFailure()) {
sendError(412, {
modifyResponseData: context.options.modifyResponseData
});
return;
}
// For Koa
if (res.statusCode === 404) {
setStatusCode(res, 200);
}
if (isCachable() && isFresh({
etag: ( /** @type {string | undefined} */res.getHeader("ETag")),
"last-modified": ( /** @type {string | undefined} */
res.getHeader("Last-Modified"))
})) {
setStatusCode(res, 304);
// Remove content header fields
res.removeHeader("Content-Encoding");
res.removeHeader("Content-Language");
res.removeHeader("Content-Length");
res.removeHeader("Content-Range");
res.removeHeader("Content-Type");
res.end();
return;
}
}
if (rangeHeader) {
let parsedRanges = /** @type {import("range-parser").Ranges | import("range-parser").Result | []} */
parseRangeHeaders(`${size}|${rangeHeader}`);
// If-Range support
if (!isRangeFresh()) {
parsedRanges = [];
}
if (parsedRanges === -1) {
context.logger.error("Unsatisfiable range for 'Range' header.");
setHeaderForResponse(res, "Content-Range", getValueContentRangeHeader("bytes", len));
sendError(req, res, 416, {
res.setHeader("Content-Range", getValueContentRangeHeader("bytes", size));
sendError(416, {
headers: {

@@ -159,16 +537,80 @@ "Content-Range": res.getHeader("Content-Range")

setStatusCode(res, 206);
setHeaderForResponse(res, "Content-Range", getValueContentRangeHeader("bytes", len, /** @type {import("range-parser").Ranges} */parsedRanges[0]));
offset += parsedRanges[0].start;
len = parsedRanges[0].end - parsedRanges[0].start + 1;
res.setHeader("Content-Range", getValueContentRangeHeader("bytes", size, /** @type {import("range-parser").Ranges} */parsedRanges[0]));
[offset, len] = getOffsetAndLenFromRange(parsedRanges[0]);
}
}
const start = offset;
const end = Math.max(offset, offset + len - 1);
send(req, res, filename, start, end, goNext, {
modifyResponseData: context.options.modifyResponseData,
outputFileSystem: context.outputFileSystem
// When strong Etag generation is enabled we already read file, so we can skip extra fs call
if (!bufferOrStream) {
[start, end] = calcStartAndEnd(offset, len);
try {
({
bufferOrStream,
byteLength
} = createReadStreamOrReadFileSync(filename, context.outputFileSystem, start, end));
} catch (_ignoreError) {
await goNext();
return;
}
}
if (context.options.modifyResponseData) {
({
data: bufferOrStream,
byteLength
} = context.options.modifyResponseData(req, res, bufferOrStream,
// @ts-ignore
byteLength));
}
// @ts-ignore
res.setHeader("Content-Length", byteLength);
if (req.method === "HEAD") {
// For Koa
if (res.statusCode === 404) {
setStatusCode(res, 200);
}
res.end();
return;
}
const isPipeSupports = typeof ( /** @type {import("fs").ReadStream} */bufferOrStream.pipe) === "function";
if (!isPipeSupports) {
send(res, /** @type {Buffer} */bufferOrStream);
return;
}
// Cleanup
const cleanup = () => {
destroyStream( /** @type {import("fs").ReadStream} */bufferOrStream, true);
};
// Error handling
/** @type {import("fs").ReadStream} */
bufferOrStream.on("error", error => {
// clean up stream early
cleanup();
// Handle Error
switch ( /** @type {NodeJS.ErrnoException} */error.code) {
case "ENAMETOOLONG":
case "ENOENT":
case "ENOTDIR":
sendError(404, {
modifyResponseData: context.options.modifyResponseData
});
break;
default:
sendError(500, {
modifyResponseData: context.options.modifyResponseData
});
break;
}
});
pipe(res, /** @type {ReadStream} */bufferOrStream);
// Response finished, cleanup
onFinishedStream(res, cleanup);
}
ready(context, processRequest, req);
};
}
module.exports = wrapper;

@@ -132,2 +132,12 @@ {

"instanceof": "Function"
},
"etag": {
"description": "Enable or disable etag generation.",
"link": "https://github.com/webpack/webpack-dev-middleware#etag",
"enum": ["weak", "strong"]
},
"lastModified": {
"description": "Enable or disable `Last-Modified` header. Uses the file system's last modified value.",
"link": "https://github.com/webpack/webpack-dev-middleware#lastmodified",
"type": "boolean"
}

@@ -134,0 +144,0 @@ },

329

dist/utils/compatibleAPI.js
"use strict";
const onFinishedStream = require("on-finished");
const escapeHtml = require("./escapeHtml");
/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("../index.js").ServerResponse} ServerResponse */
/** @typedef {import("fs").ReadStream} ReadStream */
/**
* @typedef {Object} ExpectedRequest
* @property {(name: string) => string | undefined} get
*/
/**
* @typedef {Object} ExpectedResponse
* @property {(name: string) => string | string[] | undefined} get
* @property {(name: string, value: number | string | string[]) => void} set
* @property {(status: number) => void} status
* @property {(data: any) => void} send
* @property {(status: number) => void} [status]
* @property {(data: any) => void} [send]
* @property {(data: any) => void} [pipeInto]
*/
/**
* @template {ServerResponse} Response
* @template {ServerResponse & ExpectedResponse} Response
* @param {Response} res
* @returns {string[]}
*/
function getHeaderNames(res) {
if (typeof res.getHeaderNames !== "function") {
// @ts-ignore
// eslint-disable-next-line no-underscore-dangle
return Object.keys(res._headers || {});
}
return res.getHeaderNames();
}
/**
* @template {IncomingMessage} Request
* @param {Request} req
* @param {string} name
* @returns {string | string[] | undefined}
*/
function getHeaderFromRequest(req, name) {
// Express API
if (typeof ( /** @type {Request & ExpectedRequest} */req.get) === "function") {
return /** @type {Request & ExpectedRequest} */req.get(name);
}
// Node.js API
return req.headers[name];
}
/**
* @template {ServerResponse} Response
* @param {Response} res
* @param {string} name
* @returns {number | string | string[] | undefined}
*/
function getHeaderFromResponse(res, name) {
// Express API
if (typeof ( /** @type {Response & ExpectedResponse} */res.get) === "function") {
return /** @type {Response & ExpectedResponse} */res.get(name);
}
// Node.js API
return res.getHeader(name);
}
/**
* @template {ServerResponse} Response
* @param {Response} res
* @param {string} name
* @param {number | string | string[]} value
* @returns {void}
*/
function setHeaderForResponse(res, name, value) {
// Express API
if (typeof ( /** @type {Response & ExpectedResponse} */res.set) === "function") {
/** @type {Response & ExpectedResponse} */
res.set(name, typeof value === "number" ? String(value) : value);
return;
}
// Node.js API
res.setHeader(name, value);
}
/**
* @template {ServerResponse} Response
* @param {Response} res
* @param {Record<string, number | string | string[] | undefined>} headers
*/
function setHeadersForResponse(res, headers) {
const keys = Object.keys(headers);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = headers[key];
if (typeof value !== "undefined") {
setHeaderForResponse(res, key, value);
}
}
}
/**
* @template {ServerResponse} Response
* @param {Response} res
*/
function clearHeadersForResponse(res) {
const headers = getHeaderNames(res);
for (let i = 0; i < headers.length; i++) {
res.removeHeader(headers[i]);
}
}
/**
* @template {ServerResponse} Response
* @param {Response} res
* @param {number} code
*/
function setStatusCode(res, code) {
if (typeof ( /** @type {Response & ExpectedResponse} */res.status) === "function") {
/** @type {Response & ExpectedResponse} */
// Pseudo API
if (typeof res.status === "function") {
res.status(code);

@@ -127,2 +25,3 @@ return;

// Node.js API
// eslint-disable-next-line no-param-reassign

@@ -133,198 +32,74 @@ res.statusCode = code;

/**
* @param {import("fs").ReadStream} stream stream
* @param {boolean} suppress do need suppress?
* @returns {void}
* @template {ServerResponse} Response
* @param {Response & ExpectedResponse} res
* @param {import("fs").ReadStream} bufferOrStream
*/
function destroyStream(stream, suppress) {
if (typeof stream.destroy === "function") {
stream.destroy();
function pipe(res, bufferOrStream) {
// Pseudo API and Koa API
if (typeof ( /** @type {Response & ExpectedResponse} */res.pipeInto) === "function") {
// Writable stream into Readable stream
res.pipeInto(bufferOrStream);
return;
}
if (typeof stream.close === "function") {
// Node.js core bug workaround
stream.on("open",
/**
* @this {import("fs").ReadStream}
*/
function onOpenClose() {
// @ts-ignore
if (typeof this.fd === "number") {
// actually close down the fd
this.close();
}
});
}
if (typeof stream.addListener === "function" && suppress) {
stream.removeAllListeners("error");
stream.addListener("error", () => {});
}
// Node.js API and Express API and Hapi API
bufferOrStream.pipe(res);
}
/** @type {Record<number, string>} */
const statuses = {
400: "Bad Request",
403: "Forbidden",
404: "Not Found",
416: "Range Not Satisfiable",
500: "Internal Server Error"
};
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {Request} req response
* @param {Response} res response
* @param {number} status status
* @param {Partial<SendOptions<Request, Response>>=} options options
* @returns {void}
* @param {Response & ExpectedResponse} res
* @param {string | Buffer} bufferOrStream
*/
function sendError(req, res, status, options) {
const content = statuses[status] || String(status);
let document = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>${escapeHtml(content)}</pre>
</body>
</html>`;
// Clear existing headers
clearHeadersForResponse(res);
if (options && options.headers) {
setHeadersForResponse(res, options.headers);
function send(res, bufferOrStream) {
// Pseudo API and Express API and Koa API
if (typeof res.send === "function") {
res.send(bufferOrStream);
return;
}
// Send basic response
setStatusCode(res, status);
setHeaderForResponse(res, "Content-Type", "text/html; charset=utf-8");
setHeaderForResponse(res, "Content-Security-Policy", "default-src 'none'");
setHeaderForResponse(res, "X-Content-Type-Options", "nosniff");
let byteLength = Buffer.byteLength(document);
if (options && options.modifyResponseData) {
({
data: document,
byteLength
} = /** @type {{data: string, byteLength: number }} */
options.modifyResponseData(req, res, document, byteLength));
}
setHeaderForResponse(res, "Content-Length", byteLength);
res.end(document);
res.end(bufferOrStream);
}
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @typedef {Object} SendOptions send error options
* @property {Record<string, number | string | string[] | undefined>=} headers headers
* @property {import("../index").ModifyResponseData<Request, Response>=} modifyResponseData modify response data callback
* @property {import("../index").OutputFileSystem} outputFileSystem modify response data callback
*/
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {Request} req
* @param {Response} res
* @param {string} filename
* @param {import("../index").OutputFileSystem} outputFileSystem
* @param {number} start
* @param {number} end
* @param {() => Promise<void>} goNext
* @param {SendOptions<Request, Response>} options
* @returns {{ bufferOrStream: (Buffer | import("fs").ReadStream), byteLength: number }}
*/
async function send(req, res, filename, start, end, goNext, options) {
const isFsSupportsStream = typeof options.outputFileSystem.createReadStream === "function";
/** @type {string | Buffer | ReadStream} */
function createReadStreamOrReadFileSync(filename, outputFileSystem, start, end) {
/** @type {string | Buffer | import("fs").ReadStream} */
let bufferOrStream;
/** @type {number} */
let byteLength;
try {
if (isFsSupportsStream) {
bufferOrStream = /** @type {import("fs").createReadStream} */
options.outputFileSystem.createReadStream(filename, {
start,
end
});
// Handle files with zero bytes
byteLength = end === 0 ? 0 : end - start + 1;
} else {
bufferOrStream = /** @type {import("fs").readFileSync} */options.outputFileSystem.readFileSync(filename);
({
byteLength
} = bufferOrStream);
}
} catch (_ignoreError) {
await goNext();
return;
}
if (options.modifyResponseData) {
({
data: bufferOrStream,
byteLength
} = options.modifyResponseData(req, res, bufferOrStream, byteLength));
}
if (typeof ( /** @type {import("fs").ReadStream} */bufferOrStream.pipe) === "function") {
setHeaderForResponse(res, "Content-Length", byteLength);
if (req.method === "HEAD") {
res.end();
return;
}
/** @type {import("fs").ReadStream} */
bufferOrStream.pipe(res);
// Cleanup
const cleanup = () => {
destroyStream( /** @type {import("fs").ReadStream} */bufferOrStream, true);
};
// Response finished, cleanup
onFinishedStream(res, cleanup);
// error handling
/** @type {import("fs").ReadStream} */
bufferOrStream.on("error", error => {
// clean up stream early
cleanup();
// Handle Error
switch ( /** @type {NodeJS.ErrnoException} */error.code) {
case "ENAMETOOLONG":
case "ENOENT":
case "ENOTDIR":
sendError(req, res, 404, options);
break;
default:
sendError(req, res, 500, options);
break;
}
// Stream logic
const isFsSupportsStream = typeof outputFileSystem.createReadStream === "function";
if (isFsSupportsStream) {
bufferOrStream = /** @type {import("fs").createReadStream} */
outputFileSystem.createReadStream(filename, {
start,
end
});
return;
}
// Express API
if (typeof ( /** @type {Response & ExpectedResponse} */res.send) === "function") {
/** @type {Response & ExpectedResponse} */
res.send(bufferOrStream);
return;
}
// Only Node.js API used
res.setHeader("Content-Length", byteLength);
if (req.method === "HEAD") {
res.end();
// Handle files with zero bytes
byteLength = end === 0 ? 0 : end - start + 1;
} else {
res.end(bufferOrStream);
bufferOrStream = /** @type {import("fs").readFileSync} */
outputFileSystem.readFileSync(filename);
({
byteLength
} = bufferOrStream);
}
return {
bufferOrStream,
byteLength
};
}
module.exports = {
getHeaderNames,
getHeaderFromRequest,
getHeaderFromResponse,
setHeaderForResponse,
setStatusCode,
send,
sendError
pipe,
createReadStreamOrReadFileSync
};

@@ -9,2 +9,3 @@ "use strict";

const getPaths = require("./getPaths");
const memorize = require("./memorize");

@@ -14,36 +15,4 @@ /** @typedef {import("../index.js").IncomingMessage} IncomingMessage */

const cacheStore = new WeakMap();
/**
* @template T
* @param {Function} fn
* @param {{ cache?: Map<string, { data: T }> } | undefined} cache
* @param {(value: T) => T} callback
* @returns {any}
*/
const mem = (fn, {
cache = new Map()
} = {}, callback) => {
/**
* @param {any} arguments_
* @return {any}
*/
const memoized = (...arguments_) => {
const [key] = arguments_;
const cacheItem = cache.get(key);
if (cacheItem) {
return cacheItem.data;
}
let result = fn.apply(void 0, arguments_);
result = callback(result);
cache.set(key, {
data: result
});
return result;
};
cacheStore.set(memoized, cache);
return memoized;
};
// eslint-disable-next-line no-undefined
const memoizedParse = mem(parse, undefined, value => {
const memoizedParse = memorize(parse, undefined, value => {
if (value.pathname) {

@@ -77,2 +46,3 @@ // eslint-disable-next-line no-param-reassign

// TODO refactor me in the next major release, this function should return `{ filename, stats, error }`
// TODO fix redirect logic when `/` at the end, like https://github.com/pillarjs/send/blob/master/index.js#L586
/**

@@ -79,0 +49,0 @@ * @template {IncomingMessage} Request

{
"name": "webpack-dev-middleware",
"version": "7.1.1",
"version": "7.2.0",
"description": "A development middleware for webpack",

@@ -69,2 +69,4 @@ "license": "MIT",

"@commitlint/config-conventional": "^19.0.3",
"@fastify/express": "^2.3.0",
"@hapi/hapi": "^21.3.7",
"@types/connect": "^3.4.35",

@@ -89,5 +91,8 @@ "@types/express": "^4.17.13",

"express": "^4.17.1",
"fastify": "^4.26.2",
"file-loader": "^6.2.0",
"husky": "^9.0.10",
"jest": "^29.3.1",
"joi": "^17.12.2",
"koa": "^2.15.2",
"lint-staged": "^15.2.0",

@@ -94,0 +99,0 @@ "npm-run-all": "^4.1.5",

@@ -63,15 +63,16 @@ <div align="center">

| Name | Type | Default | Description |
| :---------------------------------------------: | :-----------------------: | :-------------------------------------------: | :------------------------------------------------------------------------------------------------------------------- |
| **[`methods`](#methods)** | `Array` | `[ 'GET', 'HEAD' ]` | Allows to pass the list of HTTP request methods accepted by the middleware |
| **[`headers`](#headers)** | `Array\|Object\|Function` | `undefined` | Allows to pass custom HTTP headers on each request. |
| **[`index`](#index)** | `Boolean\|String` | `index.html` | If `false` (but not `undefined`), the server will not respond to requests to the root URL. |
| **[`mimeTypes`](#mimetypes)** | `Object` | `undefined` | Allows to register custom mime types or extension mappings. |
| **[`mimeTypeDefault`](#mimetypedefault)** | `String` | `undefined` | Allows to register a default mime type when we can't determine the content type. |
| **[`publicPath`](#publicpath)** | `String` | `output.publicPath` (from a configuration) | The public path that the middleware is bound to. |
| **[`stats`](#stats)** | `Boolean\|String\|Object` | `stats` (from a configuration) | Stats options object or preset name. |
| **[`serverSideRender`](#serversiderender)** | `Boolean` | `undefined` | Instructs the module to enable or disable the server-side rendering mode. |
| **[`writeToDisk`](#writetodisk)** | `Boolean\|Function` | `false` | Instructs the module to write files to the configured location on disk as specified in your `webpack` configuration. |
| **[`outputFileSystem`](#outputfilesystem)** | `Object` | [`memfs`](https://github.com/streamich/memfs) | Set the default file system which will be used by webpack as primary destination of generated files. |
| **[`modifyResponseData`](#modifyresponsedata)** | `Function` | `undefined` | Allows to set up a callback to change the response data. |
| Name | Type | Default | Description |
| :---------------------------------------------: | :---------------------------: | :-------------------------------------------: | :--------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| **[`methods`](#methods)** | `Array` | `[ 'GET', 'HEAD' ]` | Allows to pass the list of HTTP request methods accepted by the middleware |
| **[`headers`](#headers)** | `Array\ | Object\ | Function` | `undefined` | Allows to pass custom HTTP headers on each request. |
| **[`index`](#index)** | `Boolean\ | String` | `index.html` | If `false` (but not `undefined`), the server will not respond to requests to the root URL. |
| **[`mimeTypes`](#mimetypes)** | `Object` | `undefined` | Allows to register custom mime types or extension mappings. |
| **[`mimeTypeDefault`](#mimetypedefault)** | `String` | `undefined` | Allows to register a default mime type when we can't determine the content type. |
| **[`etag`](#tag)** | `boolean\| "weak"\| "strong"` | `undefined` | Enable or disable etag generation. |
| **[`publicPath`](#publicpath)** | `String` | `output.publicPath` (from a configuration) | The public path that the middleware is bound to. |
| **[`stats`](#stats)** | `Boolean\ | String\ | Object` | `stats` (from a configuration) | Stats options object or preset name. |
| **[`serverSideRender`](#serversiderender)** | `Boolean` | `undefined` | Instructs the module to enable or disable the server-side rendering mode. |
| **[`writeToDisk`](#writetodisk)** | `Boolean\ | Function` | `false` | Instructs the module to write files to the configured location on disk as specified in your `webpack` configuration. |
| **[`outputFileSystem`](#outputfilesystem)** | `Object` | [`memfs`](https://github.com/streamich/memfs) | Set the default file system which will be used by webpack as primary destination of generated files. |
| **[`modifyResponseData`](#modifyresponsedata)** | `Function` | `undefined` | Allows to set up a callback to change the response data. |

@@ -175,2 +176,16 @@ The middleware accepts an `options` Object. The following is a property reference for the Object.

### etag
Type: `"weak" | "strong"`
Default: `undefined`
Enable or disable etag generation. Boolean value use
### lastModified
Type: `Boolean`
Default: `undefined`
Enable or disable `Last-Modified` header. Uses the file system's last modified value.
### publicPath

@@ -545,2 +560,94 @@

### Connect
```js
const connect = require("connect");
const http = require("http");
const webpack = require("webpack");
const webpackConfig = require("./webpack.config.js");
const devMiddleware = require("webpack-dev-middleware");
const compiler = webpack(webpackConfig);
const devMiddlewareOptions = {
/** Your webpack-dev-middleware-options */
};
const app = connect();
app.use(devMiddleware(compiler, devMiddlewareOptions));
http.createServer(app).listen(3000);
```
### Express
```js
const express = require("express");
const webpack = require("webpack");
const webpackConfig = require("./webpack.config.js");
const devMiddleware = require("webpack-dev-middleware");
const compiler = webpack(webpackConfig);
const devMiddlewareOptions = {
/** Your webpack-dev-middleware-options */
};
const app = express();
app.use(devMiddleware(compiler, devMiddlewareOptions));
app.listen(3000, () => console.log("Example app listening on port 3000!"));
```
### Koa
```js
const Koa = require("koa");
const webpack = require("webpack");
const webpackConfig = require("./test/fixtures/webpack.simple.config");
const middleware = require("./dist");
const compiler = webpack(webpackConfig);
const devMiddlewareOptions = {
/** Your webpack-dev-middleware-options */
};
const app = new Koa();
app.use(middleware.koaWrapper(compiler, devMiddlewareOptions));
app.listen(3000);
```
### Hapi
```js
const Hapi = require("@hapi/hapi");
const webpack = require("webpack");
const webpackConfig = require("./webpack.config.js");
const devMiddleware = require("webpack-dev-middleware");
const compiler = webpack(webpackConfig);
const devMiddlewareOptions = {};
(async () => {
const server = Hapi.server({ port: 3000, host: "localhost" });
await server.register({
plugin: devMiddleware.hapiPlugin(),
options: {
// The `compiler` option is required
compiler,
...devMiddlewareOptions,
},
});
await server.start();
console.log("Server running on %s", server.info.uri);
})();
process.on("unhandledRejection", (err) => {
console.log(err);
process.exit(1);
});
```
### Fastify

@@ -557,7 +664,9 @@

const compiler = webpack(webpackConfig);
const { publicPath } = webpackConfig.output;
const devMiddlewareOptions = {
/** Your webpack-dev-middleware-options */
};
(async () => {
await fastify.register(require("fastify-express"));
await fastify.use(devMiddleware(compiler, { publicPath }));
await fastify.register(require("@fastify/express"));
await fastify.use(devMiddleware(compiler, devMiddlewareOptions));
await fastify.listen(3000);

@@ -564,0 +673,0 @@ })();

@@ -41,12 +41,12 @@ /// <reference types="node" />

* @typedef {Object} ResponseData
* @property {string | Buffer | ReadStream} data
* @property {Buffer | ReadStream} data
* @property {number} byteLength
*/
/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @callback ModifyResponseData
* @param {RequestInternal} req
* @param {ResponseInternal} res
* @param {string | Buffer | ReadStream} data
* @param {Buffer | ReadStream} data
* @param {number} byteLength

@@ -56,4 +56,4 @@ * @return {ResponseData}

/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @typedef {Object} Context

@@ -70,4 +70,4 @@ * @property {boolean} state

/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @typedef {WithoutUndefined<Context<RequestInternal, ResponseInternal>, "watching">} FilledContext

@@ -77,9 +77,9 @@ */

/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @typedef {NormalizedHeaders | ((req: RequestInternal, res: ResponseInternal, context: Context<RequestInternal, ResponseInternal>) => void | undefined | NormalizedHeaders) | undefined} Headers
*/
/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal = IncomingMessage]
* @template {ServerResponse} [ResponseInternal = ServerResponse]
* @typedef {Object} Options

@@ -97,6 +97,8 @@ * @property {{[key: string]: string}} [mimeTypes]

* @property {ModifyResponseData<RequestInternal, ResponseInternal>} [modifyResponseData]
* @property {"weak" | "strong"} [etag]
* @property {boolean} [lastModified]
*/
/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @callback Middleware

@@ -138,4 +140,4 @@ * @param {RequestInternal} req

/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @typedef {Middleware<RequestInternal, ResponseInternal> & AdditionalMethods<RequestInternal, ResponseInternal>} API

@@ -154,4 +156,4 @@ */

/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @param {Compiler | MultiCompiler} compiler

@@ -162,4 +164,5 @@ * @param {Options<RequestInternal, ResponseInternal>} [options]

declare function wdm<
RequestInternal extends import("http").IncomingMessage,
ResponseInternal extends ServerResponse,
RequestInternal extends
import("http").IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
>(

@@ -171,2 +174,4 @@ compiler: Compiler | MultiCompiler,

export {
hapiWrapper,
koaWrapper,
Schema,

@@ -206,2 +211,5 @@ Compiler,

WithoutUndefined,
HapiPluginBase,
HapiPlugin,
HapiOptions,
};

@@ -212,6 +220,45 @@ }

type API<
RequestInternal extends import("http").IncomingMessage,
ResponseInternal extends ServerResponse,
RequestInternal extends
import("http").IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
> = Middleware<RequestInternal, ResponseInternal> &
AdditionalMethods<RequestInternal, ResponseInternal>;
/**
* @template S
* @template O
* @typedef {Object} HapiPluginBase
* @property {(server: S, options: O) => void | Promise<void>} register
*/
/**
* @template S
* @template O
* @typedef {HapiPluginBase<S, O> & { pkg: { name: string } }} HapiPlugin
*/
/**
* @typedef {Options & { compiler: Compiler | MultiCompiler }} HapiOptions
*/
/**
* @template HapiServer
* @template {HapiOptions} HapiOptionsInternal
* @returns {HapiPlugin<HapiServer, HapiOptionsInternal>}
*/
declare function hapiWrapper<
HapiServer,
HapiOptionsInternal extends HapiOptions,
>(): HapiPlugin<HapiServer, HapiOptionsInternal>;
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @param {Compiler | MultiCompiler} compiler
* @param {Options<RequestInternal, ResponseInternal>} [options]
* @returns {(ctx: any, next: Function) => Promise<void> | void}
*/
declare function koaWrapper<
RequestInternal extends
import("http").IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
>(
compiler: Compiler | MultiCompiler,
options?: Options<RequestInternal, ResponseInternal> | undefined,
): (ctx: any, next: Function) => Promise<void> | void;
type Schema = import("schema-utils/declarations/validate").Schema;

@@ -252,17 +299,19 @@ type Configuration = import("webpack").Configuration;

type ResponseData = {
data: string | Buffer | ReadStream;
data: Buffer | ReadStream;
byteLength: number;
};
type ModifyResponseData<
RequestInternal extends import("http").IncomingMessage,
ResponseInternal extends ServerResponse,
RequestInternal extends
import("http").IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
> = (
req: RequestInternal,
res: ResponseInternal,
data: string | Buffer | ReadStream,
data: Buffer | ReadStream,
byteLength: number,
) => ResponseData;
type Context<
RequestInternal extends import("http").IncomingMessage,
ResponseInternal extends ServerResponse,
RequestInternal extends
import("http").IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
> = {

@@ -279,4 +328,5 @@ state: boolean;

type FilledContext<
RequestInternal extends import("http").IncomingMessage,
ResponseInternal extends ServerResponse,
RequestInternal extends
import("http").IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
> = WithoutUndefined<Context<RequestInternal, ResponseInternal>, "watching">;

@@ -290,4 +340,5 @@ type NormalizedHeaders =

type Headers<
RequestInternal extends import("http").IncomingMessage,
ResponseInternal extends ServerResponse,
RequestInternal extends
import("http").IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
> =

@@ -302,4 +353,5 @@ | NormalizedHeaders

type Options<
RequestInternal extends import("http").IncomingMessage,
ResponseInternal extends ServerResponse,
RequestInternal extends
import("http").IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
> = {

@@ -323,6 +375,9 @@ mimeTypes?:

| undefined;
etag?: "strong" | "weak" | undefined;
lastModified?: boolean | undefined;
};
type Middleware<
RequestInternal extends import("http").IncomingMessage,
ResponseInternal extends ServerResponse,
RequestInternal extends
import("http").IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
> = (

@@ -355,1 +410,12 @@ req: RequestInternal,

};
type HapiPluginBase<S, O> = {
register: (server: S, options: O) => void | Promise<void>;
};
type HapiPlugin<S, O> = HapiPluginBase<S, O> & {
pkg: {
name: string;
};
};
type HapiOptions = Options & {
compiler: Compiler | MultiCompiler;
};

@@ -6,2 +6,9 @@ /// <reference types="node" />

* @template {ServerResponse} Response
* @typedef {Object} SendErrorOptions send error options
* @property {Record<string, number | string | string[] | undefined>=} headers headers
* @property {import("./index").ModifyResponseData<Request, Response>=} modifyResponseData modify response data callback
*/
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("./index.js").FilledContext<Request, Response>} context

@@ -17,4 +24,29 @@ * @return {import("./index.js").Middleware<Request, Response>}

declare namespace wrapper {
export { NextFunction, IncomingMessage, ServerResponse, NormalizedHeaders };
export {
SendErrorOptions,
NextFunction,
IncomingMessage,
ServerResponse,
NormalizedHeaders,
ReadStream,
};
}
/**
* send error options
*/
type SendErrorOptions<
Request extends import("http").IncomingMessage,
Response extends import("./index.js").ServerResponse,
> = {
/**
* headers
*/
headers?: Record<string, number | string | string[] | undefined> | undefined;
/**
* modify response data callback
*/
modifyResponseData?:
| import("./index").ModifyResponseData<Request, Response>
| undefined;
};
type NextFunction = import("./index.js").NextFunction;

@@ -24,1 +56,2 @@ type IncomingMessage = import("./index.js").IncomingMessage;

type NormalizedHeaders = import("./index.js").NormalizedHeaders;
type ReadStream = import("fs").ReadStream;
/// <reference types="node" />
export type IncomingMessage = import("../index.js").IncomingMessage;
export type ServerResponse = import("../index.js").ServerResponse;
export type ReadStream = import("fs").ReadStream;
export type ExpectedRequest = {
get: (name: string) => string | undefined;
};
export type ExpectedResponse = {
get: (name: string) => string | string[] | undefined;
set: (name: string, value: number | string | string[]) => void;
status: (status: number) => void;
send: (data: any) => void;
status?: ((status: number) => void) | undefined;
send?: ((data: any) => void) | undefined;
pipeInto?: ((data: any) => void) | undefined;
};
/**
* send error options
*/
export type SendOptions<
Request extends import("http").IncomingMessage,
Response extends import("../index.js").ServerResponse,
> = {
/**
* headers
*/
headers?: Record<string, number | string | string[] | undefined> | undefined;
/**
* modify response data callback
*/
modifyResponseData?:
| import("../index").ModifyResponseData<Request, Response>
| undefined;
/**
* modify response data callback
*/
outputFileSystem: import("../index").OutputFileSystem;
};
/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("../index.js").ServerResponse} ServerResponse */
/** @typedef {import("fs").ReadStream} ReadStream */
/**
* @typedef {Object} ExpectedRequest
* @property {(name: string) => string | undefined} get
*/
/**
* @typedef {Object} ExpectedResponse
* @property {(name: string) => string | string[] | undefined} get
* @property {(name: string, value: number | string | string[]) => void} set
* @property {(status: number) => void} status
* @property {(data: any) => void} send
* @property {(status: number) => void} [status]
* @property {(data: any) => void} [send]
* @property {(data: any) => void} [pipeInto]
*/
/**
* @template {ServerResponse} Response
* @template {ServerResponse & ExpectedResponse} Response
* @param {Response} res
* @returns {string[]}
* @param {number} code
*/
export function getHeaderNames<
Response extends import("../index.js").ServerResponse,
>(res: Response): string[];
export function setStatusCode<
Response extends import("http").ServerResponse<
import("http").IncomingMessage
> &
import("../index.js").ExtendedServerResponse &
ExpectedResponse,
>(res: Response, code: number): void;
/**
* @template {IncomingMessage} Request
* @param {Request} req
* @param {string} name
* @returns {string | string[] | undefined}
* @template {ServerResponse} Response
* @param {Response & ExpectedResponse} res
* @param {string | Buffer} bufferOrStream
*/
export function getHeaderFromRequest<
export function send<
Request extends import("http").IncomingMessage,
>(req: Request, name: string): string | string[] | undefined;
/**
* @template {ServerResponse} Response
* @param {Response} res
* @param {string} name
* @returns {number | string | string[] | undefined}
*/
export function getHeaderFromResponse<
Response extends import("../index.js").ServerResponse,
>(res: Response, name: string): number | string | string[] | undefined;
>(res: Response & ExpectedResponse, bufferOrStream: string | Buffer): void;
/**
* @template {ServerResponse} Response
* @param {Response} res
* @param {string} name
* @param {number | string | string[]} value
* @returns {void}
* @param {Response & ExpectedResponse} res
* @param {import("fs").ReadStream} bufferOrStream
*/
export function setHeaderForResponse<
Response extends import("../index.js").ServerResponse,
>(res: Response, name: string, value: number | string | string[]): void;
export function pipe<Response extends import("../index.js").ServerResponse>(
res: Response & ExpectedResponse,
bufferOrStream: import("fs").ReadStream,
): void;
/**
* @template {ServerResponse} Response
* @param {Response} res
* @param {number} code
*/
export function setStatusCode<
Response extends import("../index.js").ServerResponse,
>(res: Response, code: number): void;
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @typedef {Object} SendOptions send error options
* @property {Record<string, number | string | string[] | undefined>=} headers headers
* @property {import("../index").ModifyResponseData<Request, Response>=} modifyResponseData modify response data callback
* @property {import("../index").OutputFileSystem} outputFileSystem modify response data callback
*/
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {Request} req
* @param {Response} res
* @param {string} filename
* @param {import("../index").OutputFileSystem} outputFileSystem
* @param {number} start
* @param {number} end
* @param {() => Promise<void>} goNext
* @param {SendOptions<Request, Response>} options
* @returns {{ bufferOrStream: (Buffer | import("fs").ReadStream), byteLength: number }}
*/
export function send<
Request extends import("http").IncomingMessage,
Response extends import("../index.js").ServerResponse,
>(
req: Request,
res: Response,
export function createReadStreamOrReadFileSync(
filename: string,
outputFileSystem: import("../index").OutputFileSystem,
start: number,
end: number,
goNext: () => Promise<void>,
options: SendOptions<Request, Response>,
): Promise<void>;
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {Request} req response
* @param {Response} res response
* @param {number} status status
* @param {Partial<SendOptions<Request, Response>>=} options options
* @returns {void}
*/
export function sendError<
Request extends import("http").IncomingMessage,
Response extends import("../index.js").ServerResponse,
>(
req: Request,
res: Response,
status: number,
options?: Partial<SendOptions<Request, Response>> | undefined,
): void;
): {
bufferOrStream: Buffer | import("fs").ReadStream;
byteLength: number;
};
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc