diff --git a/docs/middlewares.md b/docs/middlewares.md index bd5a925a6..1eac48f4e 100644 --- a/docs/middlewares.md +++ b/docs/middlewares.md @@ -4,7 +4,6 @@ - [cache](#cache) - [cors](#cors) -- [dbManager](#dbManager) - [doNotWaitForEmptyEventLoop](#donotwaitforemptyeventloop) - [functionShield](#functionshield) - [httpContentNegotiation](#httpcontentnegotiation) @@ -135,85 +134,6 @@ handler({}, {}, (_, response) => { }) ``` -## [dbManager](/src/middlewares/dbManager.js) - -dbManager provides seamless connection with database of your choice. By default it uses knex.js but you can use any tool that you want. - -After initialization your database connection is accessible under: -```javascript -middy((event, context) => { - const { db } = context; -}); -``` - -Mind that if you use knex you will also need driver of your choice ([check docs](http://knexjs.org/#Installation-node)), for PostgreSQL that would be: -``` -yarn add pg -// or -npm install pg -``` - -### Options - -- `config`: configuration object passed as is to client (knex.js by default), for more details check [knex documentation](http://knexjs.org/#Installation-client) -- `client` (optional): client that you want to use when connecting to database of your choice. By default knex.js is used but as long as your client is run as `client(config)` or you create wrapper to conform, you can use other tools. Due to node6 support in middy, knex is capped at version `0.17.3`. If you wish to use newer features, provide your own knex client here. -- `secretsPath` (optional): if for any reason you want to pass credentials using context, pass path to secrets laying in context object - good example is combining this middleware with [ssm](#ssm) -- `removeSecrets` (optional): By default is true. Works only in combination with `secretsPath`. Removes sensitive data from context once client is initialized. -- `forceNewConnection` (optional): Creates new connection on every run and destroys it after. Database client needs to have `destroy` function in order to properly clean up connections. - -### Sample usage - -Minimal configuration - -```javascript -const handler = middy(async (event, context) => { - const { db } = context; - const records = await db.select('*').from('my_table'); - console.log(records); -}); - -handler.use(dbManager({ - config: { - client: 'pg', - connection: { - host: '127.0.0.1', - user: 'your_database_user', - password: 'your_database_password', - database: 'myapp_test' - } - }, -})); -``` - -Credentials as secrets object - -```javascript -const handler = middy(async (event, context) => { - const { db } = context; - const records = await db.select('*').from('my_table'); - console.log(records); -}); - -handler.use(secretsManager({ - secrets: { - [secretsField]: 'my_db_credentials' // { user: 'your_database_user', password: 'your_database_password' } - }, - throwOnFailedCall: true -})); - -handler.use(dbManager({ - config: { - client: 'pg', - connection: { - host : '127.0.0.1', - database : 'myapp_test' - } - }, - secretsPath: secretsField -})); -``` - - ## [doNotWaitForEmptyEventLoop](/src/middlewares/doNotWaitForEmptyEventLoop.js) Sets `context.callbackWaitsForEmptyEventLoop` property to `false`. diff --git a/middlewares.d.ts b/middlewares.d.ts index e5092d194..14c830e50 100644 --- a/middlewares.d.ts +++ b/middlewares.d.ts @@ -1,7 +1,6 @@ import { SSM } from 'aws-sdk' import { Options as AjvOptions } from 'ajv' import { HttpError } from 'http-errors' -import Knex from 'knex' import middy from './' interface ICorsOptions { @@ -116,17 +115,8 @@ interface IHTTPSecurityHeadersOptions { xssFilter?: Object } -interface IDbManagerOptions { - client?: Knex | Function, - config: Knex.Config | Object, - forceNewConnection?: boolean, - secretsPath?: string, - removeSecrets?: boolean -} - declare const cache: middy.Middleware; declare const cors: middy.Middleware; -declare const dbManager: middy.Middleware; declare const doNotWaitForEmptyEventLoop: middy.Middleware; declare const httpContentNegotiation: middy.Middleware; declare const httpErrorHandler: middy.Middleware; diff --git a/package.json b/package.json index 3dae2c8a6..b52c8bb84 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,6 @@ "content-type": "^1.0.4", "http-errors": "^1.7.1", "json-mask": "^0.3.8", - "knex": "^0.17.6", "negotiator": "^0.6.1", "once": "^1.4.0", "qs": "^6.6.0", diff --git a/src/middlewares/__tests__/dbManager.js b/src/middlewares/__tests__/dbManager.js deleted file mode 100644 index 7867d2e94..000000000 --- a/src/middlewares/__tests__/dbManager.js +++ /dev/null @@ -1,277 +0,0 @@ -jest.mock('knex') - -const knex = require('knex') - -const middy = require('../../middy') -const dbManager = require('../dbManager') - -describe('💾 Database manager', () => { - let destroyFn - let clientMock - beforeEach(() => { - destroyFn = jest.fn() - clientMock = jest.fn(() => ({ - destroy: destroyFn - })) - }) - - afterEach(() => { - clientMock.mockReset() - destroyFn.mockReset() - }) - - test('it should create db instance with default config', (done) => { - knex.mockReturnValue(clientMock()) - const handler = middy((event, context, cb) => { - expect(context.db).toEqual(clientMock()) // compare invocations, not functions - return cb(null, event.body) // propagates the body as a response - }) - - handler.use(dbManager({ - config: {} - })) - - // invokes the handler - const event = { - body: JSON.stringify({ foo: 'bar' }) - } - handler(event, {}, (err, body) => { - expect(err).toEqual(null) - expect(body).toEqual('{"foo":"bar"}') - expect(destroyFn).toHaveBeenCalledTimes(0) - done() - }) - }) - - test('it should destroy instance if forceNewConnection flag provided', (done) => { - knex.mockReturnValue(clientMock()) - const handler = middy((event, context, cb) => { - expect(context.db).toEqual(clientMock()) // compare invocations, not functions - return cb(null, event.body) // propagates the body as a response - }) - - handler.use(dbManager({ - config: {}, - forceNewConnection: true - })) - - // invokes the handler - const event = { - body: JSON.stringify({ foo: 'bar' }) - } - handler(event, {}, (err, body) => { - expect(err).toEqual(null) - expect(body).toEqual('{"foo":"bar"}') - expect(destroyFn).toHaveBeenCalledTimes(1) - done() - }) - }) - - test('it should throw if no config provided', (done) => { - const returnedValue = jest.fn(clientMock) - knex.mockReturnValue(returnedValue) - const handler = middy((event, context, cb) => { - expect(context.db).toEqual(returnedValue) - return cb(null, event.body) // propagates the body as a response - }) - - handler.use(dbManager({ - forceNewConnection: true - })) - - // invokes the handler - const event = { - body: JSON.stringify({ foo: 'bar' }) - } - handler(event, {}, (err) => { - expect(err).toBeTruthy() - expect(err.message).toEqual('Config is required in dbManager') - done() - }) - }) - - test('it should initialize custom client', (done) => { - const newClient = jest.fn().mockReturnValue(clientMock) - const config = { - connection: { - user: '1234', - password: '56678' - } - } - const handler = middy((event, context, cb) => { - expect(context.db.toString()).toEqual(clientMock.toString()) // compare invocations, not functions - expect(newClient).toHaveBeenCalledTimes(1) - expect(newClient).toHaveBeenCalledWith(config) - return cb(null, event.body) // propagates the body as a response - }) - - handler.use(dbManager({ - client: newClient, - config, - forceNewConnection: true - })) - - // invokes the handler - const event = { - body: JSON.stringify({ foo: 'bar' }) - } - handler(event, {}, (err, body) => { - expect(err).toEqual(null) - expect(body).toEqual('{"foo":"bar"}') - done() - }) - }) - - test('it should grab connection details from context', (done) => { - const newClient = jest.fn().mockReturnValue(clientMock) - const secretsPath = 'secret_location' - const connection = { - user: '1234', - password: '56678' - } - const config = { - connection: { - host: '127.0.0.1' - } - } - const handler = middy((event, context, cb) => { - expect(context.db.toString()).toEqual(clientMock.toString()) // compare invocations, not functions - expect(newClient).toHaveBeenCalledTimes(1) - Object.assign(config.connection, { connection }) // add details that are supposed be in context - expect(newClient).toHaveBeenCalledWith(config) - return cb(null, event.body) // propagates the body as a response - }) - - handler.use({ - before: (handler, next) => { - handler.context[secretsPath] = connection - next() - } - }) - - handler.use(dbManager({ - client: newClient, - config, - secretsPath, - forceNewConnection: true - })) - - handler.use({ - after: (handler, next) => { - expect(handler.context[secretsPath]).toBeUndefined() - next() - } - }) - - // invokes the handler - const event = { - body: JSON.stringify({ foo: 'bar' }) - } - handler(event, {}, (err, body) => { - expect(err).toEqual(null) - expect(body).toEqual('{"foo":"bar"}') - done() - }) - }) - - test('it should grab connection details from context even if connection object doesn\'t exist', (done) => { - const newClient = jest.fn().mockReturnValue(clientMock) - const secretsPath = 'secret_location' - const connection = { - host: '127.0.0.1', - user: '1234', - password: '56678' - } - const config = { - } - const handler = middy((event, context, cb) => { - expect(context.db.toString()).toEqual(clientMock.toString()) // compare invocations, not functions - expect(newClient).toHaveBeenCalledTimes(1) - Object.assign(config.connection, { connection }) // add details that are supposed be in context - expect(newClient).toHaveBeenCalledWith(config) - return cb(null, event.body) // propagates the body as a response - }) - - handler.use({ - before: (handler, next) => { - handler.context[secretsPath] = connection - next() - } - }) - - handler.use(dbManager({ - client: newClient, - config, - secretsPath, - forceNewConnection: true - })) - - handler.use({ - after: (handler, next) => { - expect(handler.context[secretsPath]).toBeUndefined() - next() - } - }) - - // invokes the handler - const event = { - body: JSON.stringify({ foo: 'bar' }) - } - handler(event, {}, (err, body) => { - expect(err).toEqual(null) - expect(body).toEqual('{"foo":"bar"}') - done() - }) - }) - - test('it should grab connection details and not delete details from context if removeSecrets = false', (done) => { - const newClient = jest.fn().mockReturnValue(clientMock) - const secretsPath = 'secret_location' - const connection = { - host: '127.0.0.1', - user: '1234', - password: '56678' - } - const config = { - } - const handler = middy((event, context, cb) => { - expect(context.db.toString()).toEqual(clientMock.toString()) // compare invocations, not functions - expect(newClient).toHaveBeenCalledTimes(1) - Object.assign(config.connection, { connection }) // add details that are supposed be in context - expect(newClient).toHaveBeenCalledWith(config) - return cb(null, event.body) // propagates the body as a response - }) - - handler.use({ - before: (handler, next) => { - handler.context[secretsPath] = connection - next() - } - }) - - handler.use(dbManager({ - client: newClient, - config, - secretsPath, - removeSecrets: false, - forceNewConnection: true - })) - - handler.use({ - after: (handler, next) => { - expect(handler.context[secretsPath]).toEqual(connection) - next() - } - }) - - // invokes the handler - const event = { - body: JSON.stringify({ foo: 'bar' }) - } - handler(event, {}, (err, body) => { - expect(err).toEqual(null) - expect(body).toEqual('{"foo":"bar"}') - done() - }) - }) -}) diff --git a/src/middlewares/dbManager.js b/src/middlewares/dbManager.js deleted file mode 100644 index f208736c2..000000000 --- a/src/middlewares/dbManager.js +++ /dev/null @@ -1,54 +0,0 @@ - -const knex = require('knex') - -let dbInstance - -module.exports = (opts) => { - const defaults = { - client: knex, - config: null, - forceNewConnection: false, - secretsPath: null, // provide path where credentials lay in context - removeSecrets: true - } - - const options = Object.assign({}, defaults, opts) - - function cleanup (handler, next) { - if (options.forceNewConnection && (dbInstance && typeof dbInstance.destroy === 'function')) { - dbInstance.destroy((err) => next(err || handler.error)) - } - next(handler.error) - } - - return { - before: (handler, next) => { - const { - client, - config, - forceNewConnection, - secretsPath, - removeSecrets - } = options - - if (!config) { - throw new Error('Config is required in dbManager') - } - if (!dbInstance || forceNewConnection) { - if (secretsPath) { - config.connection = Object.assign({}, config.connection || {}, handler.context[secretsPath]) - } - dbInstance = client(config) - } - - Object.assign(handler.context, { db: dbInstance }) - if (secretsPath && removeSecrets) { - delete handler.context[secretsPath] - } - return next() - }, - - after: cleanup, - onError: cleanup - } -} diff --git a/src/middlewares/index.js b/src/middlewares/index.js index 19d212ba3..cf49010f9 100644 --- a/src/middlewares/index.js +++ b/src/middlewares/index.js @@ -1,7 +1,6 @@ module.exports = { cache: require('./cache'), cors: require('./cors'), - dbManager: require('./dbManager'), doNotWaitForEmptyEventLoop: require('./doNotWaitForEmptyEventLoop'), httpContentNegotiation: require('./httpContentNegotiation'), httpErrorHandler: require('./httpErrorHandler'),