-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* SLSCMN-3 config docs * SLSCMN-3 config docs * SLSCMN-3 config docs * SLSCMN-3 config docs * SLSCMN-3 config docs * SLSCMN-3 config docs * SLSCMN-3 docs * SLSCMN-3 docs * SLSCMN-3 middy docs * SLSCMN-3 docs * SLSCMN-3 dynamo docs * SLSCMN-3 error docs * SLSCMN-3 error docs * SLSCMN-3 readme * SLSCMN-3 config service tests * SLSCMN-3 exclude jest source in rollup config * SLSCMN-3 move middleware tests * SLSCMN-3 organize tests * SLSCMN-3 middyfy tests * SLSCMN-3 jsdoc * SLSCMN-3 dynamo service structure * SLSCMN-3 reshape dynamo service * SLSCMN-3 dynamo service tests
- Loading branch information
Showing
32 changed files
with
1,403 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,25 @@ | ||
# @leanstacks/serverless-common | ||
|
||
A suite of common components used to compose serverless application components for the LeanStacks organization. | ||
|
||
## License | ||
|
||
[MIT License](./LICENSE) | ||
|
||
## Requirements | ||
|
||
This library requires the following: | ||
|
||
- Node >= 18.17.x | ||
|
||
## Install | ||
|
||
To install this library, issue the following command in your AWS Serverless project: | ||
|
||
``` | ||
npm install @leanstacks/serverless-common | ||
``` | ||
|
||
## Documentation | ||
|
||
For more detailed information regarding specific components see the [documentation](/docs/DOCS.md). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
:house: [Home](/README.md) | ||
|
||
# @leanstacks/serverless-common Documentation | ||
|
||
The documentation is organized into sections by module. | ||
|
||
## Table of Contents | ||
|
||
1. [Handlers](/docs/utils/MIDDYFY.md) | ||
1. [Configuration](/docs/services/CONFIG.md) | ||
1. [DynamoDB Client](/docs/services/DYNAMO.md) | ||
1. [Errors](/docs/errors/ERRORS.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
:house: [Home](/README.md) | :books: [Docs](../DOCS.md) | ||
|
||
# Errors | ||
|
||
This document describes how to use the family of `Error` classes within the `@leanstacks/serverless-common` package. | ||
|
||
## How it works | ||
|
||
The `@leanstacks/serverless-common` package provides a family of error classes which may be utilized within AWS Lambda functions to ensure that a consistent response is returned when exception scenarios occur. | ||
|
||
## Using the errors | ||
|
||
There are two base error classes: `ServiceError` and `HttpError`. Both extend `Error` and, therefore, both have `name` and `message` properties. Additional properties are included to provide more information. | ||
|
||
### `ServiceError` | ||
|
||
A `ServiceError` may be used in any type of AWS Lambda function. The error has the following properties: | ||
|
||
- `name` -- The string "ServiceError". | ||
- `message` -- A string message describing the error. | ||
- `statusCode` -- Optional. A numeric [HTTP response status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) | ||
- `code` -- Optional. A numeric value to convey further meaning to the API client regarding the cause of the error | ||
|
||
A `ServiceError` may be constructed with either a string or an Error as the first parameter. The optional second parameter is the numeric `code`. And, the optional third parameter is the `statusCode`. | ||
|
||
_Example: Throw a `ServiceError` with a message._ | ||
|
||
```ts | ||
new ServiceError('Object not found in S3 bucket'); | ||
``` | ||
|
||
_Example: Throw a `ServiceError` with an error and code._ | ||
|
||
```ts | ||
try { | ||
... | ||
} catch (err) { | ||
throw new ServiceError(err, 1001); | ||
} | ||
``` | ||
|
||
### `HttpError` | ||
|
||
A `HttpError` and, more often, it's sub-classes are used in API Gateway Lambda functions. The error has the following properties: | ||
|
||
- `name` -- The string "HttpError". | ||
- `message` -- A string message describing the error. | ||
- `statusCode` -- A numeric [HTTP response status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) | ||
|
||
A `HttpError` may be constructed with either a string or an Error as the first parameter and a status code as the second parameter. | ||
|
||
_Example: Throw a `HttpError` with a message and status code._ | ||
|
||
```ts | ||
new HttpError('Not found', 404); | ||
``` | ||
|
||
_Example: Throw a `HttpError` with an error and status code._ | ||
|
||
```ts | ||
try { | ||
... | ||
} catch (err) { | ||
throw new HttpError(err, 500); | ||
} | ||
``` | ||
|
||
However, it is more common to use one of the `HttpError` sub-classes: | ||
|
||
- `BadRequestError` | ||
- `ForbiddenError` | ||
- `NotFoundError` | ||
|
||
### `BadRequestError` | ||
|
||
A `BadRequestError` extends `HttpError`. The error `name` is "BadRequestError" and the `statusCode` is 400. | ||
|
||
The `BadRequestError` constructor accepts an optional string `message` parameter. When provided, this value is set to the error `message` property, otherwise, the default HTTP status message for HTTP 400 is used. | ||
|
||
_Example: Throw a `BadRequestError` with the default message._ | ||
|
||
```ts | ||
new BadRequestError(); | ||
``` | ||
|
||
_Example: Throw a `BadRequestError` with a custom message._ | ||
|
||
```ts | ||
try { | ||
... | ||
} catch (err) { | ||
throw new BadRequestError(err.message); | ||
} | ||
``` | ||
|
||
### `ForbiddenError` | ||
|
||
A `ForbiddenError` extends `HttpError`. The error `name` is "ForbiddenError" and the `statusCode` is 403. | ||
|
||
The `ForbiddenError` constructor accepts an optional string `message` parameter. When provided, this value is set to the error `message` property, otherwise, the default HTTP status message for HTTP 403 is used. | ||
|
||
_Example: Throw a `ForbiddenError` with the default message._ | ||
|
||
```ts | ||
new ForbiddenError(); | ||
``` | ||
|
||
_Example: Throw a `ForbiddenError` with a custom message._ | ||
|
||
```ts | ||
try { | ||
... | ||
} catch (err) { | ||
throw new ForbiddenError(err.message); | ||
} | ||
``` | ||
|
||
### `NotFoundError` | ||
|
||
A `NotFoundError` extends `HttpError`. The error `name` is "NotFoundError" and the `statusCode` is 404. | ||
|
||
The `NotFoundError` constructor accepts an optional string `message` parameter. When provided, this value is set to the error `message` property, otherwise, the default HTTP status message for HTTP 404 is used. | ||
|
||
_Example: Throw a `NotFoundError` with the default message._ | ||
|
||
```ts | ||
new NotFoundError(); | ||
``` | ||
|
||
_Example: Throw a `NotFoundError` with a custom message._ | ||
|
||
```ts | ||
try { | ||
... | ||
} catch (err) { | ||
throw new NotFoundError(err.message); | ||
} | ||
``` | ||
|
||
## Example API client response | ||
|
||
When any of the error family of classes is thrown from an API Gateway Lambda function, the `http-error-handler` middleware intercepts the error and, using the information within the error, creates an informative response for the API client. The response body payload is similar to the example below. | ||
|
||
```json | ||
{ | ||
"name": "NotFoundError", | ||
"message": "The requested resource could not be found but may be available in the future. Subsequent requests by the client are permissible.", | ||
"code": 404, | ||
"statusCode": 404 | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
:house: [Home](/README.md) | :books: [Docs](../DOCS.md) | ||
|
||
# Configuration | ||
|
||
This document describes how to configure a serverless application component leveraging | ||
shared components from the `serverless-common` package. | ||
|
||
## How it works | ||
|
||
AWS provides configuration to Lambda functions through the `process.env` object, similar to | ||
any Node.js application. The `serverless-common` package provides the means for serverless | ||
components to validate and access a typed configuration object. | ||
|
||
AWS supplies a default set of attributes to every Lambda function, e.g. `AWS_REGION`, and | ||
you may define additional custom attributes for your functions. | ||
|
||
## Using `LambdaConfig`, the default configuration | ||
|
||
When a serverless component does not declare any additional environment variables, but | ||
needs access to the base configuration supplied to all Lambda functions, the | ||
`serverless-common` package provides the `lambdaConfigValues` object of type `LambdaConfig`. | ||
The example below illustrates how to use `lambdaConfigValues`. | ||
|
||
Simply import `lambdaConfigValues` into any module which requires access to the configuration. | ||
This ready to use object is of type [`LambdaConfig`](/src/services/config.service.ts). | ||
|
||
```ts | ||
// some-component.ts | ||
import { lambdaConfigValues as config } from '@leanstacks/serverless-common'; | ||
|
||
console.log(`The region is ${config.AWS_REGON}`); | ||
``` | ||
|
||
## Extending `LambdaConfig` with custom configuration attributes | ||
|
||
When a Lambda function has custom configuration attributes, simply extend the `LambdaConfig` type | ||
and [Joi](https://joi.dev/) validation schema. The example below illustrates how to extend `LambdaConfig`. | ||
|
||
Create a configuration module in your serverless project. Import the `LambdaConfig` type, the | ||
`lambdaConfigSchema` Joi schema for that type, and the `validateConfig` utility function. | ||
|
||
```ts | ||
// my-config.ts | ||
import { LambdaConfig, lambdaConfigSchema, validateConfig } from '@leanstacks/serverless-common'; | ||
|
||
// extend LambdaConfig | ||
type MyConfig = LambdaConfig & { | ||
TABLE_NAME: string; | ||
QUEUE_NAME: string; | ||
EXPIRES_IN_DAYS: number; | ||
}; | ||
|
||
// extend lambdaConfigSchema | ||
const myConfigSchema = lambdaConfigSchema.keys({ | ||
TABLE_NAME: Joi.string().required(), | ||
QUEUE_NAME: Joi.string().required(), | ||
EXPIRES_IN_DAYS: Joi.number().default(30), | ||
}); | ||
|
||
// validate and process custom configuration | ||
const config: MyConfig = validateConfig<MyConfig>(myConfigSchema); | ||
export default config; | ||
``` | ||
|
||
Now the exported `config` object of type `MyConfig` may be used in any other module within the | ||
serverless component. For example... | ||
|
||
```ts | ||
// some-component.ts | ||
import config from 'my-config'; | ||
|
||
console.log(`The table name is ${config.TABLE_NAME}`); | ||
``` | ||
|
||
## Performance considerations | ||
|
||
The configuration attributes are validated by Joi. To ensure that performance is not negatively | ||
impacted, this validation occurs just once, when the configuration module | ||
is first loaded by the module loader. When modules subsequently import the configuration | ||
object, the pre-processed object is loaded without executing the validation process again. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
:house: [Home](/README.md) | :books: [Docs](../DOCS.md) | ||
|
||
# DynamoDB Client Service | ||
|
||
This document describes how to use the DynamoDB Client service component, `DynamoService`, to perform actions on the items within a table. | ||
|
||
## How it works | ||
|
||
The AWS SDK provides components which facilitate operations on DynamoDB tables. These components must be configured and instantiated before use. This boilerplate setup logic is encapsulated within the `DynamoService` component. | ||
|
||
## Using `DynamoService` | ||
|
||
The `DynamoService` wraps the `DynamoDBClient` and `DynamoDBDocumentClient` from the AWS SDK, exposing the functions which you need to interact with DynamoDB tables. Those functions include: | ||
|
||
- batchWrite | ||
- delete | ||
- get | ||
- put | ||
- query | ||
- scan | ||
- update | ||
|
||
`DynamoService` leverages the family of `CommandInput` types from the AWS SDK for the function parameters and return types. For example, the signature of the `get` function is: | ||
|
||
```ts | ||
get(input: GetCommandInput): Promise<GetCommandOutput> | ||
``` | ||
|
||
For more information about the CommandInput and CommandOutput objects see the [`@aws-sdk/lib-dynamodb`](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-lib-dynamodb/) official documentation. | ||
|
||
In the example below, the `find` function accepts an identifier and uses `DynamoService.get` to search the table for the item. | ||
|
||
First, we get the `DynamoService` instance. `DynamoService` is a singleton to improve performance across invocations of the Lambda function. | ||
|
||
```ts | ||
const dynamoService: DynamoService = DynamoService.getInstance(); | ||
``` | ||
|
||
Then the service is used to fetch an item from the table by the table key. | ||
|
||
```ts | ||
async find(taskListId: string): Promise<TaskList | null> { | ||
const output = await this.dynamoService.get({ | ||
TableName: config.TASK_LIST_TABLE, | ||
Key: { | ||
taskListId, | ||
}, | ||
}); | ||
|
||
if (output.Item) { | ||
return output.Item as TaskList; | ||
} else { | ||
// not found | ||
return null; | ||
} | ||
} | ||
``` | ||
|
||
Notice the shape of the input object to the `get` function is the `GetCommandInput` from the AWS SDK. Furthermore, the shape of the returned object is the `GetCommandOutput`. The same pattern applies to the other functions of `DynamoService`. | ||
|
||
## Performance considerations | ||
|
||
The Dynamo components are constructed and configured once, when the dynamo module is first loaded by the module loader. Furthermore, the components are preserved between function invocations. When other modules subsequently import the DynamoService, the component is loaded without constructing the client components again. |
Oops, something went wrong.