@tinyhttp/app
Advanced tools
Comparing version 0.2.9 to 0.2.10
# @tinyhttp/app | ||
## 0.2.10 | ||
### Patch Changes | ||
- FINALLY FIX THAT BUG WHERE 404 COULDN'T BE FOUND | ||
## 0.2.9 | ||
@@ -4,0 +10,0 @@ |
@@ -1,1 +0,534 @@ | ||
import{STATUS_CODES as e,METHODS as t,createServer as r}from"http";import n from"regexparam";import{parse as s}from"url";import o from"range-parser";import a from"proxy-addr";import i from"es-accepts";import d from"es-fresh";import{sign as h}from"@tinyhttp/cookie-signature";import{lookup as l}from"es-mime-types";import{serialize as p}from"@tinyhttp/cookie";import{format as u,parse as c}from"es-content-type";import f from"@tinyhttp/etag";const compileTrust=e=>"function"==typeof e?e:!0===e?function(){return!0}:"number"==typeof e?(t,r)=>{if(e)return r<e}:("string"==typeof e&&(e=e.split(/ *, */)),a.compile(e||[])),getQueryParams=(e="/")=>s(e,!0).query,getURLParams=(e="/",t="/")=>((e,t)=>{let r=0,n={},s=t.pattern.exec(e);for(;r<t.keys.length;)n[t.keys[r]]=(null==s?void 0:s[++r])||null;return n})(e,n(t)),getRouteFromApp=(e,t)=>e.middleware.find(e=>e.handler.name===t.name),getProtocol=e=>{const t=e.connection.encrypted?"https":"http";if(!compileTrust(e.connection.remoteAddress))return t;const r=e.headers["X-Forwarded-Proto"]||t,n=r.indexOf(",");return-1!==n?r.substring(0,n).trim():r.trim()},getRequestHeader=e=>t=>{const r=t.toLowerCase();switch(r){case"referer":case"referrer":return e.headers.referrer||e.headers.referer;default:return e.headers[r]}},setRequestHeader=e=>(t,r)=>e.headers[t.toLowerCase()]=r,getRangeFromHeader=e=>(t,r)=>{const n=e.get("Range");if(n)return o(t,n,r)},checkIfXMLHttpRequest=e=>"XMLHttpRequest"===e.headers["X-Requested-With"],getHostname=e=>{let t=e.get("X-Forwarded-Host");if(t&&compileTrust(e.connection.remoteAddress)||(t=e.get("Host")),!t)return;const r="["===t[0]?t.indexOf("]")+1:0,n=t.indexOf(":",r);return-1!==n?t.substring(0,n):t},getIP=e=>a(e,compileTrust),getFreshOrStale=(e,t)=>{const r=e.method,n=t.statusCode;if("GET"!==r&&"HEAD"!==r)return!1;if(n>=200&&n<300||304===n){const r={etag:t.get("ETag"),"last-modified":t.get("Last-Modified")};return d(e.headers,r)}return!1},getAccepts=e=>(...t)=>new i(e).types(t),onErrorHandler=t=>(r,n)=>{const s=n.statusCode=t.code||t.status||500;"string"==typeof t||Buffer.isBuffer(t)?n.end(t):n.end(t.message||e[s])},pushMiddleware=e=>({path:t,handler:r,method:n,handlers:s,type:o})=>{const a=(({path:e,handler:t,method:r})=>({method:r,handler:t||e,path:"string"==typeof e?e:"/"}))({path:t,handler:r,method:n,type:o}),i=s.map(e=>({handler:e}));for(const t of[a,...i])e.push({...t,type:o})};class Router{get(e,t,...r){return pushMiddleware(this.middleware)({path:e,handler:t,handlers:r,method:"GET",type:"route"}),this}post(e,t,...r){return pushMiddleware(this.middleware)({path:e,handler:t,handlers:r,method:"POST",type:"route"}),this}put(e,t,...r){return pushMiddleware(this.middleware)({path:e,handler:t,handlers:r,method:"PUT",type:"route"}),this}patch(e,t,...r){return pushMiddleware(this.middleware)({path:e,handler:t,handlers:r,method:"PATCH",type:"route"}),this}head(e,t,...r){return pushMiddleware(this.middleware)({path:e,handler:t,handlers:r,method:"HEAD",type:"route"}),this}delete(e,t,...r){return pushMiddleware(this.middleware)({path:e,handler:t,handlers:r,method:"DELETE",type:"route"}),this}options(e,t,...r){return pushMiddleware(this.middleware)({path:e,handler:t,handlers:r,method:"OPTIONS",type:"route"}),this}all(e,r,...n){for(const s of t)pushMiddleware(this.middleware)({path:e,handler:r,method:s,handlers:n,type:"route"});return this}use(e,t,...r){return pushMiddleware(this.middleware)({path:e,handler:"string"==typeof e?t:e,handlers:r,type:"mw"}),this}}const createETag=(e,t)=>{const r=Buffer.isBuffer(e)?e:Buffer.from(e,t);return f(r,{weak:!0})};const json=(e,t)=>(e,...r)=>(t.setHeader("Content-Type","application/json"),"object"==typeof e&&null!=e?t.end(JSON.stringify(e,null,2),...r):"string"==typeof e&&t.end(e,...r),t),send=(e,t)=>r=>{let n=r;if("object"==typeof r&&null!==r)n=JSON.stringify(r,null,2);else if("string"==typeof r){const e=t.getHeader("Content-Type");"string"==typeof e&&t.setHeader("Content-Type",function setCharset(e,t){const r=c(e);return r.parameters.charset=t,u(r)}(e,"utf-8"))}let s;return!t.getHeader("etag")&&(s=createETag(n,"utf8"))&&t.setHeader("etag",s),204!==t.statusCode&&304!==t.statusCode||(t.removeHeader("Content-Type"),t.removeHeader("Content-Length"),t.removeHeader("Transfer-Encoding"),n=""),"HEAD"===e.method&&t.end(""),"object"==typeof r?null===r?t.end(""):Buffer.isBuffer(r)?t.getHeader("Content-Type")||t.setHeader("content-type","application/octet-stream"):json(0,t)(n,"utf8"):t.end(n,"utf8"),t},status=(e,t)=>e=>(t.statusCode=e,t),setCookie=(e,t)=>(r,n,s)=>{const o=e.secret,a=s.signed;if(a&&!o)throw new Error('cookieParser("secret") required for signed cookies');let i="object"==typeof n?"j:"+JSON.stringify(n):String(n);return a&&(i="s:"+h(i,o)),s.maxAge&&(s.expires=new Date(Date.now()+s.maxAge),s.maxAge/=1e3),null==s.path&&(s.path="/"),t.setHeader("Set-Cookie",p(r,String(i),s)),t},clearCookie=(e,t)=>(r,n)=>{const s=Object.assign({},{expires:new Date(1),path:"/"},n);return setCookie(e,t)(r,"",s)},m=/;\s*charset\s*=/,setHeader=(e,t)=>(e,r)=>{if("string"==typeof e){let n=Array.isArray(r)?r.map(String):String(r);if("content-type"===e.toLowerCase()){if(Array.isArray(n))throw new TypeError("Content-Type cannot be set to an Array");if(!m.test(n)){const e=l(n.split(";")[0]);e&&(n+="; charset="+e.toLowerCase())}}t.setHeader(e,n)}else for(const r in e)t.setHeader(r,e[r]);return t},setLocationHeader=(e,t)=>r=>{let n=r;return"back"===r&&(n=e.get("Referrer")||"/"),t.setHeader("Location",encodeURIComponent(n)),t},getResponseHeader=(e,t)=>e=>t.getHeader(e),setLinksHeader=(e,t)=>e=>{let r=t.get("Link")||"";return r&&(r+=", "),t.set("Link",r+Object.keys(e).map(t=>"<"+e[t]+'>; rel="'+t+'"').join(", "))},sendStatus=(t,r)=>t=>{const n=e[t]||String(t);return r.statusCode=t,r.set("Content-Type","text/plain"),r.send(n)},extendMiddleware=({networkExtensions:e,freshnessTesting:t})=>(r,n)=>{if(n.get=getResponseHeader(0,n),e){const e=getProtocol(r),t="https"===e;r.protocol=e,r.secure=t,r.connection=Object.assign(r.socket,{encrypted:t}),r.hostname=getHostname(r)}r.query=getQueryParams(r.url),t&&(r.fresh=getFreshOrStale(r,n),r.stale=!r.fresh),r.get=getRequestHeader(r),r.set=setRequestHeader(r),r.range=getRangeFromHeader(r),r.accepts=getAccepts(r),r.xhr=checkIfXMLHttpRequest(r),n.header=n.set=setHeader(0,n),n.send=send(r,n),n.json=json(0,n),n.status=status(0,n),n.sendStatus=sendStatus(0,n),n.location=setLocationHeader(r,n),n.links=setLinksHeader(0,n),n.cookie=setCookie(r,n),n.clearCookie=clearCookie(r,n)},applyHandler=e=>async(t,r,n)=>{"AsyncFunction"===e[Symbol.toStringTag]?await e(t,r,n):e(t,r,n)};class App extends Router{constructor(e={}){super(),this.locals=Object.create(null),this.middleware=[],this.onError=(null==e?void 0:e.onError)||onErrorHandler,this.noMatchHandler=(null==e?void 0:e.noMatchHandler)||this.onError({code:404}),this.settings=e.settings||{}}async handler(e,t){const r=this.middleware;extendMiddleware(this.settings)(e,t);const s={handler:this.noMatchHandler,type:"mw",path:"/"};r.includes(s)||r.push(s);let o=0;const a=r.length-1,next=r=>{r?this.onError(r)(e,t):loop()},handle=e=>async(t,r,s)=>{const{path:o,method:a,handler:i,type:d}=e;if("route"===d){if(t.method===a){const e=t.url.lastIndexOf("?"),a=t.url.slice(0,-1===e?t.url.length:e);n(o).pattern.test(a)?(t.params=getURLParams(t.url,o),t.route=getRouteFromApp(this,i),r.statusCode=200,applyHandler(i)(t,r,s)):loop()}}else t.url.startsWith(o)?applyHandler(i)(t,r,s):loop()};1===r.length&&handle(r[0])(e,t);const loop=()=>{t.writableEnded||o<a&&handle(r[o++])(e,t,next)};loop()}listen(e,t,n="localhost",s){return r((e,t)=>{this.handler(e,t)}).listen(e,n,s,t)}}export{App,Router,applyHandler,checkIfXMLHttpRequest,clearCookie,extendMiddleware,getAccepts,getFreshOrStale,getHostname,getIP,getProtocol,getQueryParams,getRangeFromHeader,getRequestHeader,getResponseHeader,getRouteFromApp,getURLParams,json,send,sendStatus,setCookie,setHeader,setLinksHeader,setLocationHeader,setRequestHeader,status}; | ||
import { STATUS_CODES, METHODS, createServer } from 'http'; | ||
import rg from 'regexparam'; | ||
import { parse } from 'url'; | ||
import parseRange from 'range-parser'; | ||
import proxyAddr from 'proxy-addr'; | ||
import Accepts from 'es-accepts'; | ||
import fresh from 'es-fresh'; | ||
import { sign } from '@tinyhttp/cookie-signature'; | ||
import { lookup } from 'es-mime-types'; | ||
import { serialize } from '@tinyhttp/cookie'; | ||
import { format, parse as parse$1 } from 'es-content-type'; | ||
import etag from '@tinyhttp/etag'; | ||
const compileTrust = (val) => { | ||
if (typeof val === 'function') | ||
return val; | ||
if (val === true) { | ||
// Support plain true/false | ||
return function () { | ||
return true; | ||
}; | ||
} | ||
if (typeof val === 'number') { | ||
// Support trusting hop count | ||
return (_, i) => { | ||
if (val) { | ||
return i < val; | ||
} | ||
}; | ||
} | ||
if (typeof val === 'string') { | ||
// Support comma-separated values | ||
val = val.split(/ *, */); | ||
} | ||
return proxyAddr.compile(val || []); | ||
}; | ||
const rgExec = (path, result) => { | ||
let i = 0, out = {}; | ||
let matches = result.pattern.exec(path); | ||
while (i < result.keys.length) { | ||
out[result.keys[i]] = (matches === null || matches === void 0 ? void 0 : matches[++i]) || null; | ||
} | ||
return out; | ||
}; | ||
const getQueryParams = (url = '/') => { | ||
return parse(url, true).query; | ||
}; | ||
const getURLParams = (reqUrl = '/', url = '/') => { | ||
return rgExec(reqUrl, rg(url)); | ||
}; | ||
const getRouteFromApp = (app, handler) => { | ||
return app.middleware.find((h) => h.handler.name === handler.name); | ||
}; | ||
const getProtocol = (req) => { | ||
const proto = req.connection.encrypted ? 'https' : 'http'; | ||
if (!compileTrust(req.connection.remoteAddress)) { | ||
return proto; | ||
} | ||
const header = req.headers['X-Forwarded-Proto'] || proto; | ||
const index = header.indexOf(','); | ||
return index !== -1 ? header.substring(0, index).trim() : header.trim(); | ||
}; | ||
const getRequestHeader = (req) => (header) => { | ||
const lc = header.toLowerCase(); | ||
switch (lc) { | ||
case 'referer': | ||
case 'referrer': | ||
return req.headers.referrer || req.headers.referer; | ||
default: | ||
return req.headers[lc]; | ||
} | ||
}; | ||
const setRequestHeader = (req) => (field, value) => { | ||
return (req.headers[field.toLowerCase()] = value); | ||
}; | ||
const getRangeFromHeader = (req) => (size, options) => { | ||
const range = req.get('Range'); | ||
if (!range) | ||
return; | ||
return parseRange(size, range, options); | ||
}; | ||
const checkIfXMLHttpRequest = (req) => { | ||
if (req.headers['X-Requested-With'] === 'XMLHttpRequest') { | ||
return true; | ||
} | ||
else { | ||
return false; | ||
} | ||
}; | ||
const getHostname = (req) => { | ||
let host = req.get('X-Forwarded-Host'); | ||
if (!host || !compileTrust(req.connection.remoteAddress)) { | ||
host = req.get('Host'); | ||
} | ||
if (!host) | ||
return; | ||
// IPv6 literal support | ||
const offset = host[0] === '[' ? host.indexOf(']') + 1 : 0; | ||
const index = host.indexOf(':', offset); | ||
return index !== -1 ? host.substring(0, index) : host; | ||
}; | ||
const getIP = (req) => { | ||
return proxyAddr(req, compileTrust); | ||
}; | ||
// export const getRequestIs = (types: string | string[], ...args: string[]) => (req: Request) => { | ||
// let arr = types | ||
// if (!Array.isArray(types)) { | ||
// arr = new Array(args.length) | ||
// for (let i = 0; i < arr.length; i++) { | ||
// arr[i] = args[i] | ||
// } | ||
// } | ||
// } | ||
const getFreshOrStale = (req, res) => { | ||
const method = req.method; | ||
const status = res.statusCode; | ||
// GET or HEAD for weak freshness validation only | ||
if (method !== 'GET' && method !== 'HEAD') | ||
return false; | ||
// 2xx or 304 as per rfc2616 14.26 | ||
if ((status >= 200 && status < 300) || 304 === status) { | ||
const resHeaders = { | ||
etag: res.get('ETag'), | ||
'last-modified': res.get('Last-Modified'), | ||
}; | ||
return fresh(req.headers, resHeaders); | ||
} | ||
return false; | ||
}; | ||
const getAccepts = (req) => (...types) => { | ||
return new Accepts(req).types(types); | ||
}; | ||
const onErrorHandler = (err, _req, res) => { | ||
const code = (res.statusCode = err.code || err.status || 500); | ||
if (typeof err === 'string' || Buffer.isBuffer(err)) | ||
res.end(err); | ||
else | ||
res.end(err.message || STATUS_CODES[code]); | ||
}; | ||
const isAsync = (fn) => fn[Symbol.toStringTag] === 'AsyncFunction'; | ||
const createMiddlewareFromRoute = ({ path, handler, method, }) => ({ | ||
method, | ||
handler: handler || path, | ||
path: typeof path === 'string' ? path : '/', | ||
}); | ||
const pushMiddleware = (mw) => ({ path, handler, method, handlers, type, }) => { | ||
const m = createMiddlewareFromRoute({ path, handler, method, type }); | ||
const waresFromHandlers = handlers.map((handler) => ({ | ||
handler, | ||
})); | ||
for (const mdw of [m, ...waresFromHandlers]) { | ||
mw.push({ ...mdw, type }); | ||
} | ||
}; | ||
class Router { | ||
get(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
handlers, | ||
method: 'GET', | ||
type: 'route', | ||
}); | ||
return this; | ||
} | ||
post(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
handlers, | ||
method: 'POST', | ||
type: 'route', | ||
}); | ||
return this; | ||
} | ||
put(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
handlers, | ||
method: 'PUT', | ||
type: 'route', | ||
}); | ||
return this; | ||
} | ||
patch(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
handlers, | ||
method: 'PATCH', | ||
type: 'route', | ||
}); | ||
return this; | ||
} | ||
head(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
handlers, | ||
method: 'HEAD', | ||
type: 'route', | ||
}); | ||
return this; | ||
} | ||
delete(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
handlers, | ||
method: 'DELETE', | ||
type: 'route', | ||
}); | ||
return this; | ||
} | ||
options(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
handlers, | ||
method: 'OPTIONS', | ||
type: 'route', | ||
}); | ||
return this; | ||
} | ||
all(path, handler, ...handlers) { | ||
for (const method of METHODS) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
method, | ||
handlers, | ||
type: 'route', | ||
}); | ||
} | ||
return this; | ||
} | ||
use(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler: typeof path === 'string' ? handler : path, | ||
handlers, | ||
type: 'mw', | ||
}); | ||
return this; | ||
} | ||
} | ||
const createETag = (body, encoding) => { | ||
const buf = !Buffer.isBuffer(body) ? Buffer.from(body, encoding) : body; | ||
return etag(buf, { weak: true }); | ||
}; | ||
function setCharset(type, charset) { | ||
const parsed = parse$1(type); | ||
parsed.parameters.charset = charset; | ||
return format(parsed); | ||
} | ||
const json = (_req, res) => (body, ...args) => { | ||
res.setHeader('Content-Type', 'application/json'); | ||
if (typeof body === 'object' && body != null) { | ||
res.end(JSON.stringify(body, null, 2), ...args); | ||
} | ||
else if (typeof body === 'string') { | ||
res.end(body, ...args); | ||
} | ||
return res; | ||
}; | ||
const send = (req, res) => (body) => { | ||
let bodyToSend = body; | ||
// in case of object - turn it to json | ||
if (typeof body === 'object' && body !== null) { | ||
bodyToSend = JSON.stringify(body, null, 2); | ||
} | ||
else { | ||
if (typeof body === 'string') { | ||
// reflect this in content-type | ||
const type = res.getHeader('Content-Type'); | ||
if (typeof type === 'string') { | ||
res.setHeader('Content-Type', setCharset(type, 'utf-8')); | ||
} | ||
} | ||
} | ||
// Set encoding | ||
const encoding = 'utf8'; | ||
// populate ETag | ||
let etag; | ||
if (!res.getHeader('etag') && (etag = createETag(bodyToSend, encoding))) { | ||
res.setHeader('etag', etag); | ||
} | ||
// strip irrelevant headers | ||
if (res.statusCode === 204 || res.statusCode === 304) { | ||
res.removeHeader('Content-Type'); | ||
res.removeHeader('Content-Length'); | ||
res.removeHeader('Transfer-Encoding'); | ||
bodyToSend = ''; | ||
} | ||
if (req.method === 'HEAD') { | ||
res.end(''); | ||
} | ||
if (typeof body === 'object') { | ||
if (body === null) { | ||
res.end(''); | ||
} | ||
else if (Buffer.isBuffer(body)) { | ||
if (!res.getHeader('Content-Type')) { | ||
res.setHeader('content-type', 'application/octet-stream'); | ||
} | ||
} | ||
else { | ||
json(req, res)(bodyToSend, encoding) | ||
; | ||
} | ||
} | ||
else { | ||
{ | ||
// respond with encoding | ||
res.end(bodyToSend, encoding); | ||
} | ||
} | ||
return res; | ||
}; | ||
const status = (_req, res) => (status) => { | ||
res.statusCode = status; | ||
return res; | ||
}; | ||
const setCookie = (req, res) => (name, value, options) => { | ||
const secret = req.secret; | ||
const signed = options.signed; | ||
if (signed && !secret) { | ||
throw new Error('cookieParser("secret") required for signed cookies'); | ||
} | ||
let val = typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value); | ||
if (signed) { | ||
val = 's:' + sign(val, secret); | ||
} | ||
if (options.maxAge) { | ||
options.expires = new Date(Date.now() + options.maxAge); | ||
options.maxAge /= 1000; | ||
} | ||
if (options.path == null) { | ||
options.path = '/'; | ||
} | ||
res.setHeader('Set-Cookie', serialize(name, String(val), options)); | ||
return res; | ||
}; | ||
const clearCookie = (req, res) => (name, options) => { | ||
const opts = Object.assign({}, { expires: new Date(1), path: '/' }, options); | ||
return setCookie(req, res)(name, '', opts); | ||
}; | ||
const charsetRegExp = /;\s*charset\s*=/; | ||
const setHeader = (_req, res) => (field, val) => { | ||
if (typeof field === 'string') { | ||
let value = Array.isArray(val) ? val.map(String) : String(val); | ||
// add charset to content-type | ||
if (field.toLowerCase() === 'content-type') { | ||
if (Array.isArray(value)) { | ||
throw new TypeError('Content-Type cannot be set to an Array'); | ||
} | ||
if (!charsetRegExp.test(value)) { | ||
const charset = lookup(value.split(';')[0]); | ||
if (charset) | ||
value += '; charset=' + charset.toLowerCase(); | ||
} | ||
} | ||
res.setHeader(field, value); | ||
} | ||
else { | ||
for (const key in field) { | ||
res.setHeader(key, field[key]); | ||
} | ||
} | ||
return res; | ||
}; | ||
const setLocationHeader = (req, res) => (url) => { | ||
let loc = url; | ||
// "back" is an alias for the referrer | ||
if (url === 'back') { | ||
loc = req.get('Referrer') || '/'; | ||
} | ||
// set location | ||
res.setHeader('Location', encodeURIComponent(loc)); | ||
return res; | ||
}; | ||
const getResponseHeader = (_req, res) => (field) => { | ||
return res.getHeader(field); | ||
}; | ||
const setLinksHeader = (_req, res) => (links) => { | ||
let link = res.get('Link') || ''; | ||
if (link) | ||
link += ', '; | ||
return res.set('Link', link + | ||
Object.keys(links) | ||
.map((rel) => '<' + links[rel] + '>; rel="' + rel + '"') | ||
.join(', ')); | ||
}; | ||
const sendStatus = (_req, res) => (statusCode) => { | ||
const body = STATUS_CODES[statusCode] || String(statusCode); | ||
res.statusCode = statusCode; | ||
res.set('Content-Type', 'text/plain'); | ||
return res.send(body); | ||
}; | ||
const extendMiddleware = ({ networkExtensions, freshnessTesting, }) => (req, res) => { | ||
/// Define extensions | ||
res.get = getResponseHeader(req, res); | ||
/* | ||
Request extensions | ||
*/ | ||
if (networkExtensions) { | ||
const proto = getProtocol(req); | ||
const secure = proto === 'https'; | ||
req.protocol = proto; | ||
req.secure = secure; | ||
req.connection = Object.assign(req.socket, { | ||
encrypted: secure, | ||
}); | ||
req.hostname = getHostname(req); | ||
} | ||
req.query = getQueryParams(req.url); | ||
if (freshnessTesting) { | ||
req.fresh = getFreshOrStale(req, res); | ||
req.stale = !req.fresh; | ||
} | ||
req.get = getRequestHeader(req); | ||
req.set = setRequestHeader(req); | ||
req.range = getRangeFromHeader(req); | ||
req.accepts = getAccepts(req); | ||
req.xhr = checkIfXMLHttpRequest(req); | ||
/* | ||
Response extensions | ||
*/ | ||
res.header = res.set = setHeader(req, res); | ||
res.send = send(req, res); | ||
res.json = json(req, res); | ||
res.status = status(req, res); | ||
res.sendStatus = sendStatus(req, res); | ||
res.location = setLocationHeader(req, res); | ||
res.links = setLinksHeader(req, res); | ||
res.cookie = setCookie(req, res); | ||
res.clearCookie = clearCookie(req, res); | ||
}; | ||
const applyHandler = (h) => async (req, res, next) => { | ||
if (isAsync(h)) { | ||
await h(req, res, next); | ||
} | ||
else { | ||
h(req, res, next); | ||
} | ||
}; | ||
class App extends Router { | ||
constructor(options = {}) { | ||
super(); | ||
this.locals = Object.create(null); | ||
this.middleware = []; | ||
this.onError = (options === null || options === void 0 ? void 0 : options.onError) || onErrorHandler; | ||
this.noMatchHandler = | ||
(options === null || options === void 0 ? void 0 : options.noMatchHandler) || this.onError.bind(null, { code: 404 }); | ||
this.settings = options.settings || {}; | ||
} | ||
async handler(req, res) { | ||
const mw = this.middleware; | ||
extendMiddleware(this.settings)(req, res); | ||
const noMatchMW = { | ||
handler: this.noMatchHandler, | ||
type: 'mw', | ||
path: '/', | ||
}; | ||
if (!mw.includes(noMatchMW)) | ||
mw.push(noMatchMW); | ||
let idx = 0; | ||
const len = mw.length - 1; | ||
const next = (err) => { | ||
if (err) { | ||
this.onError(err, req, res); | ||
} | ||
else { | ||
loop(); | ||
} | ||
}; | ||
const handle = (mw) => async (req, res, next) => { | ||
const { path, method, handler, type } = mw; | ||
if (type === 'route') { | ||
if (req.method === method) { | ||
// strip query parameters for req.params | ||
const queryParamStart = req.url.lastIndexOf('?'); | ||
const reqUrlWithoutParams = req.url.slice(0, queryParamStart === -1 ? req.url.length : queryParamStart); | ||
if (rg(path).pattern.test(reqUrlWithoutParams)) { | ||
req.params = getURLParams(req.url, path); | ||
req.route = getRouteFromApp(this, handler); | ||
// route found, send Success 200 | ||
res.statusCode = 200; | ||
applyHandler(handler)(req, res, next); | ||
} | ||
else { | ||
loop(); | ||
} | ||
} | ||
} | ||
else { | ||
if (req.url.startsWith(path)) { | ||
applyHandler(handler)(req, res, next); | ||
} | ||
else { | ||
loop(); | ||
} | ||
} | ||
}; | ||
// If there was only one middleware + 404 handler | ||
if (mw.length === 2) | ||
handle(mw[1])(req, res); | ||
const loop = () => { | ||
if (!res.writableEnded) { | ||
if (idx < len) { | ||
handle(mw[idx++])(req, res, next); | ||
} | ||
} | ||
}; | ||
loop(); | ||
} | ||
listen(port, cb, host = 'localhost', backlog) { | ||
const server = createServer((req, res) => { | ||
this.handler(req, res); | ||
}); | ||
return server.listen(port, host, backlog, cb); | ||
} | ||
} | ||
export { App, Router, applyHandler, checkIfXMLHttpRequest, clearCookie, extendMiddleware, getAccepts, getFreshOrStale, getHostname, getIP, getProtocol, getQueryParams, getRangeFromHeader, getRequestHeader, getResponseHeader, getRouteFromApp, getURLParams, json, send, sendStatus, setCookie, setHeader, setLinksHeader, setLocationHeader, setRequestHeader, status }; |
@@ -1,2 +0,2 @@ | ||
import { Handler } from './router'; | ||
export declare const onErrorHandler: (err: any) => Handler; | ||
import { ErrorHandler } from './router'; | ||
export declare const onErrorHandler: ErrorHandler; |
@@ -7,3 +7,3 @@ import { Request } from './request'; | ||
export declare type Handler = AsyncHandler | SyncHandler; | ||
export declare type ErrorHandler = (err: any) => (req: Request, res: Response) => void; | ||
export declare type ErrorHandler = (err: any, req: Request, res: Response) => void; | ||
declare type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'DELETE' | string; | ||
@@ -10,0 +10,0 @@ declare type MiddlewareType = 'mw' | 'route'; |
{ | ||
"name": "@tinyhttp/app", | ||
"version": "0.2.9", | ||
"version": "0.2.10", | ||
"description": "tinyhttp core with App, Request, Response and Router", | ||
@@ -5,0 +5,0 @@ "type": "module", |
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
Network access
Supply chain riskThis module accesses the network.
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
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
50383
1229
1
3