laconia-core
Advanced tools
Comparing version 0.1.0 to 0.2.0
{ | ||
"name": "laconia-core", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "Micro AWS Lambda framework", | ||
@@ -18,5 +18,5 @@ "keywords": [ | ||
"files": [ | ||
"lib" | ||
"src" | ||
], | ||
"main": "lib/index.js", | ||
"main": "src/index.js", | ||
"repository": { | ||
@@ -26,17 +26,14 @@ "type": "git", | ||
}, | ||
"scripts": { | ||
"compile": "babel src -d lib", | ||
"prepublish": "npm run compile", | ||
"compile:watch": "npm run compile -- --watch" | ||
}, | ||
"dependencies": { | ||
"lodash.isfunction": "^3.0.9", | ||
"lodash.isplainobject": "^4.0.6" | ||
}, | ||
"devDependencies": { | ||
"laconia-test-helper": "^0.1.0" | ||
"laconia-test-helper": "^0.2.0" | ||
}, | ||
"peerDependencies": { | ||
"aws-sdk": "^2.0.1" | ||
}, | ||
"engines": { | ||
"node": ">=6.10" | ||
"node": ">=8.10" | ||
} | ||
} |
209
README.md
@@ -9,2 +9,17 @@ # laconia-core | ||
An AWS Lambda handler function is a single entry point for both injecting dependencies | ||
and function execution. In non-serverless development, you can and will normally | ||
only focus on the latter. This brings a unique challenge to AWS Lambda development | ||
as it is very difficult to test a handler function when it is responsible for doing | ||
both the object creations and the application run. laconia-core is a simple dependency | ||
injection framework for your Lambda code, hence solving this problem for you. | ||
Laconia explicitly splits the responsibility of the object creations and Lambda function execution. | ||
Laconia also provides a simple way for you to execute your Lambda function | ||
so that your unit tests will not execute the code that instantiates your Lambda dependencies. | ||
## FAQ | ||
Check out [FAQ](https://github.com/ceilfors/laconia#faq) | ||
## Usage | ||
@@ -24,28 +39,107 @@ | ||
Create a new js file and the following capabilities will be made | ||
available for you: | ||
To fully understand how Laconia's Dependency Injection works, let's have a look into an | ||
example below. _This is not a running code as there are a lot of code that have been trimmed down, | ||
full example can be found in the acceptance test: [src](packages/laconia-acceptance-test/src/place-order.js) | ||
and [unit test](packages/laconia-acceptance-test/test/place-order.spec.js)_. | ||
Lambda handler code: | ||
```js | ||
const { laconia, recurse invoke } = require('laconia-core') | ||
// Objects creation, a function that returns an object | ||
const instances = ({ env }) => ({ | ||
orderRepository: new DynamoDbOrderRepository(env.ORDER_TABLE_NAME), | ||
idGenerator: new UuidIdGenerator() | ||
}); | ||
// Handler function, which do not have any object instantiations | ||
module.exports.handler = laconia( | ||
// Instances made available via destructuring | ||
async ({ event, orderRepository, idGenerator }) => { | ||
await orderRepository.save(order); | ||
} | ||
).register(instances); | ||
``` | ||
The `laconia` function will be the main entry of your Lambda execution. It wraps the | ||
original Lambda signature so that you won't forget to call AWS | ||
Lambda's `callback` anymore. | ||
Unit test code: | ||
To respond to your Lambda caller, you can do the following: | ||
```js | ||
const handler = require("../src/place-order").handler; | ||
* return object: This will used for calling Lambda `callback` | ||
* return Promise: The promise will be resolved/rejected, and `callback` will called appropriately | ||
* throw error: The error object will be used for calling Lambda `callback` | ||
* return `recurse(payload)` function: Recurse the currently running Lambda | ||
* -_in progress_- return `send(statusCode, data)` function: Return an API Gateway Lambda Proxy Integration response | ||
* -_in progress_- return `sendError(statusCode, error)` function: Return an API Gateway Lambda Proxy Integration response | ||
// Creates a mock Laconia context | ||
beforeEach(() => { | ||
lc = { | ||
orderRepository: { | ||
save: jest.fn().mockReturnValue(Promise.resolve()) | ||
} | ||
}; | ||
}); | ||
// Runs handler function without worrying about the objects creation | ||
it("should store order to order table", async () => { | ||
await handler.run(lc); | ||
expect(lc.orderRepository.save).toBeCalledWith( | ||
expect.objectContaining(order) | ||
); | ||
}); | ||
``` | ||
Note that as you have seen so far, Laconia is not aiming to become a comprehensive | ||
DI framework hence the need of you handle the instantiation of all of the objects by yourself. | ||
It should theoretically be possible to integrate Laconia to other more comprehensive | ||
NodeJS DI framework but it has not been tested. | ||
### LaconiaContext | ||
Laconia provides a one stop location to get all of the information you need for your Lambda | ||
function execution via `LaconiaContext`. In a nutshell, LaconiaContext is just an object that | ||
contains all of those information by using object property keys, hence you can destructure it | ||
to get just the information you need. | ||
#### AWS Event and Context | ||
When Laconia is adopted, the handler function signature will change. Without Laconia, | ||
your handler signature would be `event, context, callback`. With Laconia, your handler | ||
signature would be `laconiaContext`. The `event` and `context` are always available in LaconiaContext. | ||
`callback` is not made available as this should not be necessary anymore when you are | ||
using Node 8, just `return` the value that you want to return to the caller inside the handler function. | ||
Example: | ||
```js | ||
const { laconia } = require('laconia-core') | ||
laconia(({ event, context }) => true); | ||
``` | ||
module.exports.handler = laconia(() => 'hello') | ||
#### Laconia helpers | ||
LaconiaContext contains some helpers that you can use by default. They are accessible | ||
via the following keys: | ||
* `invoke` | ||
* `recurse` | ||
The details of these helpers are covered in the different section of this documentation. | ||
Example: | ||
```js | ||
laconia(({ invoke, recurse }) => true); | ||
``` | ||
#### Environment Variables | ||
It is very common to set environment variables for your Lambda functions. | ||
This is normally accessed via `process.env`. Unit testing a Lambda function that | ||
uses `process.env` is awkward, as you have to modify the `process` global variable and remember | ||
to remove your change so that it doesn't affect other test. | ||
For better unit testability, LaconiaContext contains the environment variables | ||
with key `env`. | ||
Example: | ||
```js | ||
laconia(({ env }) => true); | ||
``` | ||
### API | ||
@@ -63,8 +157,47 @@ | ||
// Simple return value | ||
laconia(() => 'value') | ||
laconia(() => "value"); | ||
// Return a promise and 'value' will be returned to the Lambda caller | ||
laconia(() => Promise.resolve('value')) | ||
laconia(() => Promise.resolve("value")); | ||
``` | ||
#### `register(instanceFn)` | ||
Registers objects into LaconiaContext. Objects registered here will be made | ||
available in the Lambda function execution. | ||
* `instanceFn(laconiaContext)` | ||
* This `Function` is called when your Lambda is invoked | ||
* An object which contains the instances to be registered must be returned | ||
Example: | ||
```js | ||
// Register an object with key 'service' | ||
laconia(({ service }) => service.call()).register(() => ({ | ||
service: new SomeService() | ||
})); | ||
``` | ||
#### `run(laconiaContext)` | ||
Runs Lambda handler function with the specified `laconiaContext` and bypasses | ||
the LaconiaContext registration step. This function should only be used in a | ||
unit test. | ||
* `laconiaContext` | ||
* A plain object that should represent the LaconiaContext to be used in the function execution | ||
```js | ||
// Runs a handler function with a LaconiaContext that contains a mock service object. | ||
// `SomeService` will not be instantiated | ||
laconia(({ service }) => service.call()) | ||
.register(() => ({ | ||
service: new SomeService() | ||
})) | ||
.run({ | ||
service: jest.mock() | ||
}); | ||
``` | ||
## Lambda Invocation | ||
@@ -78,9 +211,14 @@ | ||
An `invoke` function is injected to LaconiaContext by default, or you can | ||
import it manually. | ||
```js | ||
const { invoke } = require('laconia-core') | ||
const { laconia } = require("laconia-core"); | ||
// Waits for Lambda response before continuing | ||
await invoke('function-name').requestResponse({ foo: 'bar' }) | ||
// Invokes a Lambda and not wait for it to return | ||
await invoke('function-name').fireAndForget({ foo: 'bar' }) | ||
module.exports.handler = laconia(async ({ invoke }) => { | ||
// Waits for Lambda response before continuing | ||
await invoke("function-name").requestResponse({ foo: "bar" }); | ||
// Invokes a Lambda and not wait for it to return | ||
await invoke("function-name").fireAndForget({ foo: "bar" }); | ||
}); | ||
``` | ||
@@ -103,5 +241,5 @@ | ||
// Customise AWS.Lambda instantiation | ||
invoke('name', { | ||
lambda: new AWS.Lambda({ apiVersion: '2015-03-31' }) | ||
}) | ||
invoke("name", { | ||
lambda: new AWS.Lambda({ apiVersion: "2015-03-31" }) | ||
}); | ||
``` | ||
@@ -119,3 +257,3 @@ | ||
```js | ||
invoke('fn').requestResponse({ foo: 'bar' }) | ||
invoke("fn").requestResponse({ foo: "bar" }); | ||
``` | ||
@@ -133,3 +271,3 @@ | ||
```js | ||
invoke('fn').fireAndForget({ foo: 'bar' }) | ||
invoke("fn").fireAndForget({ foo: "bar" }); | ||
``` | ||
@@ -139,12 +277,13 @@ | ||
To be used together with `laconia` function to recurse the currently running Lambda. | ||
An instantiated `recurse` function is injected to LaconiaContext by default, or | ||
you can import it manually from laconia-core. | ||
```js | ||
const { laconia, recurse } = require('laconia-core') | ||
const { laconia } = require("laconia-core"); | ||
module.exports.handler = laconia(({ event }) => { | ||
module.exports.handler = laconia(({ event, recurse }) => { | ||
if (event.input !== 3) { | ||
return recurse({ input: event.input + 1 }) | ||
return recurse({ input: event.input + 1 }); | ||
} | ||
}) | ||
}); | ||
``` | ||
@@ -163,7 +302,7 @@ | ||
```js | ||
laconia(({ event }, { recurse }) => { | ||
laconia(({ event, recurse }) => { | ||
if (event.input !== 3) { | ||
return recurse({ input: event.input + 1 }) | ||
return recurse({ input: event.input + 1 }); | ||
} | ||
}) | ||
}); | ||
``` |
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
13007
8
134
300
1
1
+ Addedavailable-typed-arrays@1.0.7(transitive)
+ Addedaws-sdk@2.1692.0(transitive)
+ Addedbase64-js@1.5.1(transitive)
+ Addedbuffer@4.9.2(transitive)
+ Addedcall-bind@1.0.8(transitive)
+ Addedcall-bind-apply-helpers@1.0.1(transitive)
+ Addedcall-bound@1.0.3(transitive)
+ Addeddefine-data-property@1.1.4(transitive)
+ Addeddunder-proto@1.0.1(transitive)
+ Addedes-define-property@1.0.1(transitive)
+ Addedes-errors@1.3.0(transitive)
+ Addedes-object-atoms@1.1.1(transitive)
+ Addedevents@1.1.1(transitive)
+ Addedfor-each@0.3.4(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedget-intrinsic@1.2.7(transitive)
+ Addedget-proto@1.0.1(transitive)
+ Addedgopd@1.2.0(transitive)
+ Addedhas-property-descriptors@1.0.2(transitive)
+ Addedhas-symbols@1.1.0(transitive)
+ Addedhas-tostringtag@1.0.2(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedieee754@1.1.13(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedis-arguments@1.2.0(transitive)
+ Addedis-callable@1.2.7(transitive)
+ Addedis-generator-function@1.1.0(transitive)
+ Addedis-regex@1.2.1(transitive)
+ Addedis-typed-array@1.1.15(transitive)
+ Addedisarray@1.0.0(transitive)
+ Addedjmespath@0.16.0(transitive)
+ Addedmath-intrinsics@1.1.0(transitive)
+ Addedpossible-typed-array-names@1.0.0(transitive)
+ Addedpunycode@1.3.2(transitive)
+ Addedquerystring@0.2.0(transitive)
+ Addedsafe-regex-test@1.1.0(transitive)
+ Addedsax@1.2.1(transitive)
+ Addedset-function-length@1.2.2(transitive)
+ Addedurl@0.10.3(transitive)
+ Addedutil@0.12.5(transitive)
+ Addeduuid@8.0.0(transitive)
+ Addedwhich-typed-array@1.1.18(transitive)
+ Addedxml2js@0.6.2(transitive)
+ Addedxmlbuilder@11.0.1(transitive)
- Removedlodash.isfunction@^3.0.9
- Removedlodash.isfunction@3.0.9(transitive)