@lbu/server
Advanced tools
Comparing version 0.0.6 to 0.0.7
19
index.js
@@ -1,17 +0,10 @@ | ||
const { getApp } = require("./src/app"); | ||
const { | ||
createBodyParsers, | ||
getBodyParser, | ||
getMultipartBodyParser, | ||
} = require("./src/middleware"); | ||
const compose = require("koa-compose"); | ||
import compose from "koa-compose"; | ||
module.exports = { | ||
getApp, | ||
export { compose }; | ||
export { getApp } from "./src/app.js"; | ||
export { | ||
createBodyParsers, | ||
getBodyParser, | ||
getMultipartBodyParser, | ||
compose, | ||
}; | ||
AppError, | ||
} from "./src/middleware/index.js"; |
{ | ||
"name": "@lbu/server", | ||
"version": "0.0.6", | ||
"version": "0.0.7", | ||
"description": "Koa server and common middleware", | ||
"main": "index.js", | ||
"type": "module", | ||
"keywords": [ | ||
@@ -16,9 +17,7 @@ "lightbase", | ||
"dependencies": { | ||
"@lbu/insight": "^0.0.6", | ||
"@lbu/stdlib": "^0.0.6", | ||
"@lbu/insight": "^0.0.7", | ||
"@lbu/stdlib": "^0.0.7", | ||
"koa": "2.11.0", | ||
"koa-body": "4.1.1", | ||
"koa-compose": "4.1.0", | ||
"koa-helmet": "5.2.0", | ||
"koa2-cors": "2.0.6" | ||
"koa-compose": "4.1.0" | ||
}, | ||
@@ -39,3 +38,3 @@ "author": { | ||
}, | ||
"gitHead": "a7b150d9265f449127dc812d4bcef7fb803d6d63" | ||
"gitHead": "4c2199aeb1052bab67b3b1355bd648086736abf7" | ||
} |
@@ -27,16 +27,15 @@ # @lbu/server | ||
- @lbu/cli: Project template, and simple script runner | ||
- @lbu/code-gen: Flexible code generators. Supports generating validators, | ||
router, SQL and more | ||
- @lbu/code-gen: Flexible code generators. Supports generating router, validator | ||
- @lbu/insight: Opinionated logger | ||
- @lbu/server: Wrap around Koa and some useful middleware | ||
- @lbu/stdlib: Growing library with features like uuid generation and background | ||
jobs | ||
- @lbu/stdlib: Growing library of various common utilities like uuid & a basic | ||
templating system | ||
## Roadmap | ||
- [ ] @lbu/store: Common abstraction for FileSystem, Redis, Memory support for | ||
queues, KV store etc | ||
- [ ] @lbu/code-gen: More plugins | ||
- [ ] @lbu/code-gen: OpenAPI importer | ||
- [ ] @lbu/features: Feature flag implementation based on @lbu/store & support | ||
for code-gen | ||
- [ ] @lbu/code-gen: Postgres query generator | ||
## Docs | ||
@@ -43,0 +42,0 @@ |
@@ -1,9 +0,9 @@ | ||
const Koa = require("koa"); | ||
const { | ||
import Koa from "koa"; | ||
import { | ||
defaultHeaders, | ||
errorHandler, | ||
healthHandler, | ||
logMiddleware, | ||
notFoundHandler, | ||
logMiddleware, | ||
defaultHeaders, | ||
} = require("./middleware"); | ||
} from "./middleware/index.js"; | ||
@@ -13,13 +13,17 @@ /** | ||
* @param {Object=} opts | ||
* @param {boolean} [opts.proxy=false] | ||
* @param {boolean} [opts.disableHeaders=false] | ||
* @param {boolean} [opts.enableHealthRoute=true] | ||
* @param {KoaErrorHandler=} opts.onError | ||
* @param {boolean} [opts.proxy] | ||
* @param {boolean} [opts.disableHeaders] | ||
* @param {boolean} [opts.disableHealthRoute] | ||
* @param {ErrorHandlerOptions} [opts.errorOptions] | ||
* @param {Object} opts.headers Argument for defaultHeaders middleware | ||
* @param {CorsOptions} opts.headers.cors Argument for defaultHeaders middleware | ||
*/ | ||
const getApp = (opts = {}) => { | ||
export const getApp = (opts = {}) => { | ||
const app = new Koa(); | ||
app.proxy = opts.proxy === true; | ||
app.proxy = | ||
opts.proxy === undefined | ||
? process.env.NODE_ENV === "production" | ||
: opts.proxy; | ||
if (opts.enableHealthRoute !== false) { | ||
if (opts.disableHealthRoute !== true) { | ||
app.use(healthHandler()); | ||
@@ -29,6 +33,6 @@ } | ||
app.use(logMiddleware()); | ||
app.use(errorHandler(opts.onError)); | ||
app.use(errorHandler(opts.errorOptions || {})); | ||
app.use(notFoundHandler()); | ||
if (!opts.disableHeaders) { | ||
if (opts.disableHeaders !== true) { | ||
app.use(defaultHeaders(opts.headers)); | ||
@@ -39,5 +43,1 @@ } | ||
}; | ||
module.exports = { | ||
getApp, | ||
}; |
@@ -1,3 +0,3 @@ | ||
const koaBody = require("koa-body"); | ||
const { merge } = require("@lbu/stdlib"); | ||
import { merge } from "@lbu/stdlib"; | ||
import koaBody from "koa-body"; | ||
@@ -12,3 +12,3 @@ let memoizeBodyParser; | ||
*/ | ||
const createBodyParsers = (opts = {}) => { | ||
export const createBodyParsers = (opts = {}) => { | ||
const multiPartOpts = merge({}, opts); | ||
@@ -28,3 +28,3 @@ | ||
*/ | ||
const getBodyParser = () => { | ||
export const getBodyParser = () => { | ||
if (memoizeBodyParser === undefined) { | ||
@@ -45,3 +45,3 @@ throw new Error( | ||
*/ | ||
const getMultipartBodyParser = () => { | ||
export const getMultipartBodyParser = () => { | ||
if (memoizeMultipartBodyParser === undefined) { | ||
@@ -55,7 +55,1 @@ throw new Error( | ||
}; | ||
module.exports = { | ||
createBodyParsers, | ||
getBodyParser, | ||
getMultipartBodyParser, | ||
}; |
@@ -0,3 +1,82 @@ | ||
import { isNil } from "@lbu/stdlib"; | ||
/** | ||
* @callback KoaErrorHandler | ||
* AppErrors represent errors, that should immediately stop the request and return a | ||
* status and other meta data directly | ||
*/ | ||
export class AppError extends Error { | ||
/** | ||
* Create a new AppError | ||
* @param {string} key | ||
* @param {number} status | ||
* @param {Object} [info={}] | ||
* @param {Error} [originalError] | ||
*/ | ||
constructor(key, status, info, originalError) { | ||
super(); | ||
this.key = key; | ||
this.status = status; | ||
this.info = info || {}; | ||
this.originalError = originalError; | ||
Object.setPrototypeOf(this, AppError.prototype); | ||
if (isNil(key) || isNil(status)) { | ||
return AppError.serverError( | ||
{ | ||
appErrorConstruct: { | ||
key, | ||
status, | ||
}, | ||
}, | ||
this, | ||
); | ||
} | ||
} | ||
/** | ||
* Throw a new 404 not found error | ||
* @param {Object} [info={}] | ||
* @param {Error} [error] | ||
* @return {AppError} | ||
*/ | ||
static notFound(info = {}, error = undefined) { | ||
return new AppError("error.server.notFound", 404, info, error); | ||
} | ||
/** | ||
* Throw a new 405 Not implemented error | ||
* @param {Object} [info={}] | ||
* @param {Error} [error] | ||
* @return {AppError} | ||
*/ | ||
static notImplemented(info = {}, error = undefined) { | ||
return new AppError("error.server.notImplemented", 405, info, error); | ||
} | ||
/** | ||
* Throw a new 500 internal server error | ||
* @param {Object} [info={}] | ||
* @param {Error} [error] | ||
* @return {AppError} | ||
*/ | ||
static serverError(info = {}, error = undefined) { | ||
return new AppError("error.server.internal", 500, info, error); | ||
} | ||
/** | ||
* Throw a new 400 validation error | ||
* @param {string} key | ||
* @param {Object} [info={}] | ||
* @param {Error} [error] | ||
* @return {AppError} | ||
*/ | ||
static validationError(key, info = {}, error = undefined) { | ||
return new AppError(key, 400, info, error); | ||
} | ||
} | ||
/** | ||
* @callback CustomErrorHandler | ||
* @param ctx Koa Context | ||
@@ -8,34 +87,84 @@ * @param {Error} err | ||
const { NotFoundError } = require("./notFound"); | ||
/** | ||
* @callback AppErrorHandler | ||
* @param ctx Koa Context | ||
* @param {string} key | ||
* @param {Object} info | ||
* @returns {Object} The any data extracted from the key | ||
*/ | ||
/** | ||
* @type CustomErrorHandler | ||
* Default onError handler that doesn't handle anything | ||
*/ | ||
const defaultOnError = () => false; | ||
/** | ||
* @type AppErrorHandler | ||
* Default onAppError handler that builds a simple object with key, message and info. | ||
*/ | ||
const defaultOnAppError = (ctx, key, info) => ({ key, message: key, info }); | ||
/** | ||
* @typedef {Object} ErrorHandlerOptions | ||
* @property {AppErrorHandler} [onAppError] Called to set the initial body when the | ||
* error is an AppError | ||
* @property {CustomErrorHandler} [onError] Called before all others to let the user | ||
* handle their own errors | ||
* @property {boolean} [leakError] Useful on development and staging environments to | ||
* just dump the error to the consumer | ||
*/ | ||
/** | ||
* Handle any upstream errors | ||
* @param {KoaErrorHandler} onError | ||
* @param {ErrorHandlerOptions} opts | ||
* @returns {function(...[*]=)} | ||
*/ | ||
const errorHandler = onError => async (ctx, next) => { | ||
try { | ||
await next(); | ||
} catch (error) { | ||
if (onError && onError(ctx, error)) { | ||
return; | ||
} | ||
export const errorHandler = ({ onAppError, onError, leakError }) => { | ||
onAppError = onAppError || defaultOnAppError; | ||
onError = onError || defaultOnError; | ||
leakError = leakError === true; | ||
if (error instanceof NotFoundError) { | ||
ctx.body = { | ||
message: "Not found", | ||
}; | ||
} else { | ||
ctx.log.error({ | ||
error, | ||
return async (ctx, next) => { | ||
try { | ||
await next(); | ||
} catch (error) { | ||
if (onError(ctx, error)) { | ||
return; | ||
} | ||
let err = error; | ||
let log = ctx.log.info; | ||
if (!(error instanceof AppError)) { | ||
log = ctx.log.error; | ||
err = new AppError("error.server.internal", 500, {}, error); | ||
} | ||
ctx.status = err.status; | ||
ctx.body = onAppError(ctx, err.key, err.info); | ||
let originalError = undefined; | ||
if (err.originalError) { | ||
originalError = { | ||
name: err.originalError.name, | ||
message: err.originalError.message, | ||
stack: err.originalError.stack.split("\n"), | ||
}; | ||
} | ||
log({ | ||
type: "API_ERROR", | ||
status: err.status, | ||
key: err.key, | ||
info: err.info, | ||
originalError, | ||
}); | ||
ctx.status = 500; | ||
ctx.body = { | ||
message: "Internal server error", | ||
}; | ||
if (!isNil(err.originalError) && leakError) { | ||
ctx.body.info = ctx.body.info || {}; | ||
ctx.body.info._error = originalError; | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
module.exports = { | ||
errorHandler, | ||
}; |
@@ -1,20 +0,22 @@ | ||
const koaHelmet = require("koa-helmet"); | ||
const cors = require("koa2-cors"); | ||
import { cors } from "./cors.js"; | ||
const noop = () => {}; | ||
/** | ||
* TODO: Replace with custom code calling Helmet, and custom cors implementation | ||
* @param {Object} [opts=] | ||
* @param {Object=} opts.helmet Helmet configuration see koa-helmet | ||
* @param {Object=} opts.cors Cors configuration see koa2-cors | ||
* @param {Object} [opts] | ||
* @param {CorsOptions} opts.cors Cors configuration see koa2-cors | ||
*/ | ||
const defaultHeaders = (opts = {}) => { | ||
const helmetExec = koaHelmet(opts.helmet); | ||
export const defaultHeaders = (opts = {}) => { | ||
// Excerpt from default helmet headers | ||
// When serving static files, some extra headers should be added | ||
// See: https://helmetjs.github.io/docs/ | ||
const standardHeaders = { | ||
"X-DNS-Prefetch-Control": "off", | ||
"Strict-Transport-Security": "max-age=5184000; includeSubDomains", // 60 day default | ||
}; | ||
const corsOptions = opts.cors || {}; | ||
corsOptions.returnNext = false; | ||
const corsExec = cors(opts.cors); | ||
return async (ctx, next) => { | ||
// At the moment, both do not rely on next, so just use a noop | ||
await helmetExec(ctx, noop); | ||
await corsExec(ctx, noop); | ||
return (ctx, next) => { | ||
ctx.set(standardHeaders); | ||
corsExec(ctx); | ||
@@ -24,5 +26,1 @@ return next(); | ||
}; | ||
module.exports = { | ||
defaultHeaders, | ||
}; |
/** | ||
* Middleware that immediately returns on ANY /_health | ||
*/ | ||
const healthHandler = () => (ctx, next) => { | ||
export const healthHandler = () => (ctx, next) => { | ||
if (ctx.path === "/_health") { | ||
@@ -11,5 +11,1 @@ ctx.body = ""; | ||
}; | ||
module.exports = { | ||
healthHandler, | ||
}; |
@@ -1,22 +0,10 @@ | ||
const { | ||
export { | ||
createBodyParsers, | ||
getBodyParser, | ||
getMultipartBodyParser, | ||
} = require("./body"); | ||
const { errorHandler } = require("./error"); | ||
const { defaultHeaders } = require("./headers"); | ||
const { healthHandler } = require("./health"); | ||
const { logMiddleware } = require("./log"); | ||
const { NotFoundError, notFoundHandler } = require("./notFound"); | ||
module.exports = { | ||
createBodyParsers, | ||
errorHandler, | ||
defaultHeaders, | ||
getBodyParser, | ||
getMultipartBodyParser, | ||
healthHandler, | ||
logMiddleware, | ||
NotFoundError, | ||
notFoundHandler, | ||
}; | ||
} from "./body.js"; | ||
export { AppError, errorHandler } from "./error.js"; | ||
export { defaultHeaders } from "./headers.js"; | ||
export { healthHandler } from "./health.js"; | ||
export { logMiddleware } from "./log.js"; | ||
export { notFoundHandler } from "./notFound.js"; |
@@ -1,4 +0,4 @@ | ||
const { newLogger } = require("@lbu/insight"); | ||
const { isNil, uuid } = require("@lbu/stdlib"); | ||
const { Transform } = require("stream"); | ||
import { newLogger } from "@lbu/insight"; | ||
import { isNil, uuid } from "@lbu/stdlib"; | ||
import { Transform } from "stream"; | ||
@@ -70,3 +70,3 @@ /** | ||
*/ | ||
const logMiddleware = () => async (ctx, next) => { | ||
export const logMiddleware = () => async (ctx, next) => { | ||
const startTime = process.hrtime.bigint(); | ||
@@ -103,5 +103,1 @@ | ||
}; | ||
module.exports = { | ||
logMiddleware, | ||
}; |
@@ -1,26 +0,12 @@ | ||
/** | ||
* Error to be used when a path is not found | ||
*/ | ||
class NotFoundError extends Error { | ||
constructor() { | ||
super(); | ||
import { AppError } from "./error.js"; | ||
Object.setPrototypeOf(this, NotFoundError.prototype); | ||
} | ||
} | ||
/** | ||
* Middleware that sets a 404 and throws a NotFoundError | ||
*/ | ||
const notFoundHandler = () => async (ctx, next) => { | ||
export const notFoundHandler = () => async (ctx, next) => { | ||
await next(); | ||
ctx.status = ctx.status || 404; | ||
if (ctx.status === 404) { | ||
throw new NotFoundError(); | ||
throw AppError.notFound(); | ||
} | ||
}; | ||
module.exports = { | ||
NotFoundError, | ||
notFoundHandler, | ||
}; |
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
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
18375
5
13
485
Yes
47
1
1
+ Added@lbu/insight@0.0.7(transitive)
+ Added@lbu/stdlib@0.0.7(transitive)
+ Addeddotenv@8.2.0(transitive)
- Removedkoa-helmet@5.2.0
- Removedkoa2-cors@2.0.6
- Removed@lbu/insight@0.0.6(transitive)
- Removed@lbu/stdlib@0.0.6(transitive)
- Removedbowser@2.9.0(transitive)
- Removedcamelize@1.0.0(transitive)
- Removedcontent-security-policy-builder@2.1.0(transitive)
- Removeddasherize@2.0.0(transitive)
- Removeddont-sniff-mimetype@1.1.0(transitive)
- Removedfeature-policy@0.3.0(transitive)
- Removedhelmet@3.23.3(transitive)
- Removedhelmet-crossdomain@0.4.0(transitive)
- Removedhelmet-csp@2.10.0(transitive)
- Removedhide-powered-by@1.1.0(transitive)
- Removedhpkp@2.0.0(transitive)
- Removedhsts@2.2.0(transitive)
- Removedkoa-helmet@5.2.0(transitive)
- Removedkoa2-cors@2.0.6(transitive)
- Removednocache@2.1.0(transitive)
- Removedreferrer-policy@1.2.0(transitive)
- Removedx-xss-protection@1.3.0(transitive)
Updated@lbu/insight@^0.0.7
Updated@lbu/stdlib@^0.0.7