@leanstacks/lambda-utils 0.2.0 → 0.3.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ import { DynamoDBClient, DynamoDBClientConfig } from '@aws-sdk/client-dynamodb';
2
+ import { DynamoDBDocumentClient, marshallOptions as MarshallOptions, unmarshallOptions as UnmarshallOptions } from '@aws-sdk/lib-dynamodb';
3
+ /**
4
+ * Initializes both the DynamoDB client and DynamoDB Document client with the provided configuration.
5
+ * If the clients are already initialized, this will replace them with new instances.
6
+ *
7
+ * @param config - DynamoDB client configuration
8
+ * @param marshallOptions - Options for marshalling JavaScript objects to DynamoDB AttributeValues
9
+ * @param unmarshallOptions - Options for unmarshalling DynamoDB AttributeValues to JavaScript objects
10
+ * @returns An object containing both the base client and document client
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * // Initialize with default configuration
15
+ * initializeDynamoDBClients();
16
+ *
17
+ * // Initialize with custom configuration
18
+ * initializeDynamoDBClients(
19
+ * { region: 'us-east-1' },
20
+ * { removeUndefinedValues: true },
21
+ * { wrapNumbers: false }
22
+ * );
23
+ * ```
24
+ */
25
+ export declare const initializeDynamoDBClients: (config?: DynamoDBClientConfig, marshallOptions?: MarshallOptions, unmarshallOptions?: UnmarshallOptions) => {
26
+ client: DynamoDBClient;
27
+ documentClient: DynamoDBDocumentClient;
28
+ };
29
+ /**
30
+ * Returns the singleton DynamoDB client instance.
31
+ * Throws an error if the client has not been initialized.
32
+ *
33
+ * @returns The DynamoDB client instance
34
+ * @throws Error if the client has not been initialized
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const client = getDynamoDBClient();
39
+ * ```
40
+ */
41
+ export declare const getDynamoDBClient: () => DynamoDBClient;
42
+ /**
43
+ * Returns the singleton DynamoDB Document client instance.
44
+ * Throws an error if the client has not been initialized.
45
+ *
46
+ * @returns The DynamoDB Document client instance
47
+ * @throws Error if the client has not been initialized
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const docClient = getDynamoDBDocumentClient();
52
+ * ```
53
+ */
54
+ export declare const getDynamoDBDocumentClient: () => DynamoDBDocumentClient;
55
+ /**
56
+ * Resets both DynamoDB client instances.
57
+ * Useful for testing or when you need to reinitialize the clients with different configurations.
58
+ */
59
+ export declare const resetDynamoDBClients: () => void;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { Logger, LoggerConfig, withRequestTracking } from './logging/logger';
2
2
  export { createResponse, ok, created, noContent, badRequest, notFound, internalServerError, httpHeaders, Headers, } from './utils/apigateway-response';
3
+ export { getDynamoDBClient, getDynamoDBDocumentClient, initializeDynamoDBClients, resetDynamoDBClients, } from './clients/dynamodb-client';
package/dist/index.esm.js CHANGED
@@ -1,2 +1,2 @@
1
- import e from"pino";import{lambdaRequestTracker as n,StructuredLogFormatter as o,CloudwatchLogFormatter as t,pinoLambdaDestination as s}from"pino-lambda";const r=n();class i{constructor(n){this._loggerConfig={enabled:!0,level:"info",format:"json"},this._instance=null,this._createLogger=()=>{const n="json"===this._loggerConfig.format?new o:new t,r=s({formatter:n});return e({enabled:this._loggerConfig.enabled,level:this._loggerConfig.level},r)},n&&(this._loggerConfig={enabled:n.enabled??!0,level:n.level??"info",format:n.format??"json"})}get instance(){return null===this._instance&&(this._instance=this._createLogger()),this._instance}}const l={contentType:e=>({"Content-Type":e}),json:{"Content-Type":"application/json"},cors:(e="*")=>({"Access-Control-Allow-Origin":e})},a=(e,n,o={})=>({statusCode:e,headers:{...o},body:JSON.stringify(n)}),g=(e,n={})=>a(200,e,n),c=(e,n={})=>a(201,e,n),f=(e={})=>a(204,{},e),m=(e="Bad Request",n={})=>a(400,{message:e},n),h=(e="Not Found",n={})=>a(404,{message:e},n),d=(e="Internal Server Error",n={})=>a(500,{message:e},n);export{i as Logger,m as badRequest,a as createResponse,c as created,l as httpHeaders,d as internalServerError,f as noContent,h as notFound,g as ok,r as withRequestTracking};
1
+ import n from"pino";import{lambdaRequestTracker as e,StructuredLogFormatter as t,CloudwatchLogFormatter as o,pinoLambdaDestination as i}from"pino-lambda";import{DynamoDBClient as l}from"@aws-sdk/client-dynamodb";import{DynamoDBDocumentClient as r}from"@aws-sdk/lib-dynamodb";const s=e();class a{constructor(e){this._loggerConfig={enabled:!0,level:"info",format:"json"},this._instance=null,this._createLogger=()=>{const e="json"===this._loggerConfig.format?new t:new o,l=i({formatter:e});return n({enabled:this._loggerConfig.enabled,level:this._loggerConfig.level},l)},e&&(this._loggerConfig={enabled:e.enabled??!0,level:e.level??"info",format:e.format??"json"})}get instance(){return null===this._instance&&(this._instance=this._createLogger()),this._instance}}const m={contentType:n=>({"Content-Type":n}),json:{"Content-Type":"application/json"},cors:(n="*")=>({"Access-Control-Allow-Origin":n})},c=(n,e,t={})=>({statusCode:n,headers:{...t},body:JSON.stringify(e)}),g=(n,e={})=>c(200,n,e),f=(n,e={})=>c(201,n,e),d=(n={})=>c(204,{},n),u=(n="Bad Request",e={})=>c(400,{message:n},e),h=(n="Not Found",e={})=>c(404,{message:n},e),p=(n="Internal Server Error",e={})=>c(500,{message:n},e);let C=null,y=null;const _=(n,e,t)=>{C=new l(n||{});const o={marshallOptions:e||{},unmarshallOptions:t||{}};return y=r.from(C,o),{client:C,documentClient:y}},b=()=>{if(!C)throw new Error("DynamoDB client not initialized. Call initializeDynamoDBClients() first.");return C},w=()=>{if(!y)throw new Error("DynamoDB Document client not initialized. Call initializeDynamoDBClients() first.");return y},D=()=>{C=null,y=null};export{a as Logger,u as badRequest,c as createResponse,f as created,b as getDynamoDBClient,w as getDynamoDBDocumentClient,m as httpHeaders,_ as initializeDynamoDBClients,p as internalServerError,d as noContent,h as notFound,g as ok,D as resetDynamoDBClients,s as withRequestTracking};
2
2
  //# sourceMappingURL=index.esm.js.map
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var e=require("pino"),t=require("pino-lambda");const o=t.lambdaRequestTracker();const r=(e,t,o={})=>({statusCode:e,headers:{...o},body:JSON.stringify(t)});exports.Logger=class{constructor(o){this._loggerConfig={enabled:!0,level:"info",format:"json"},this._instance=null,this._createLogger=()=>{const o="json"===this._loggerConfig.format?new t.StructuredLogFormatter:new t.CloudwatchLogFormatter,r=t.pinoLambdaDestination({formatter:o});return e({enabled:this._loggerConfig.enabled,level:this._loggerConfig.level},r)},o&&(this._loggerConfig={enabled:o.enabled??!0,level:o.level??"info",format:o.format??"json"})}get instance(){return null===this._instance&&(this._instance=this._createLogger()),this._instance}},exports.badRequest=(e="Bad Request",t={})=>r(400,{message:e},t),exports.createResponse=r,exports.created=(e,t={})=>r(201,e,t),exports.httpHeaders={contentType:e=>({"Content-Type":e}),json:{"Content-Type":"application/json"},cors:(e="*")=>({"Access-Control-Allow-Origin":e})},exports.internalServerError=(e="Internal Server Error",t={})=>r(500,{message:e},t),exports.noContent=(e={})=>r(204,{},e),exports.notFound=(e="Not Found",t={})=>r(404,{message:e},t),exports.ok=(e,t={})=>r(200,e,t),exports.withRequestTracking=o;
1
+ "use strict";var e=require("pino"),t=require("pino-lambda"),n=require("@aws-sdk/client-dynamodb"),o=require("@aws-sdk/lib-dynamodb");const r=t.lambdaRequestTracker();const i=(e,t,n={})=>({statusCode:e,headers:{...n},body:JSON.stringify(t)});let s=null,a=null;exports.Logger=class{constructor(n){this._loggerConfig={enabled:!0,level:"info",format:"json"},this._instance=null,this._createLogger=()=>{const n="json"===this._loggerConfig.format?new t.StructuredLogFormatter:new t.CloudwatchLogFormatter,o=t.pinoLambdaDestination({formatter:n});return e({enabled:this._loggerConfig.enabled,level:this._loggerConfig.level},o)},n&&(this._loggerConfig={enabled:n.enabled??!0,level:n.level??"info",format:n.format??"json"})}get instance(){return null===this._instance&&(this._instance=this._createLogger()),this._instance}},exports.badRequest=(e="Bad Request",t={})=>i(400,{message:e},t),exports.createResponse=i,exports.created=(e,t={})=>i(201,e,t),exports.getDynamoDBClient=()=>{if(!s)throw new Error("DynamoDB client not initialized. Call initializeDynamoDBClients() first.");return s},exports.getDynamoDBDocumentClient=()=>{if(!a)throw new Error("DynamoDB Document client not initialized. Call initializeDynamoDBClients() first.");return a},exports.httpHeaders={contentType:e=>({"Content-Type":e}),json:{"Content-Type":"application/json"},cors:(e="*")=>({"Access-Control-Allow-Origin":e})},exports.initializeDynamoDBClients=(e,t,r)=>{s=new n.DynamoDBClient(e||{});const i={marshallOptions:t||{},unmarshallOptions:r||{}};return a=o.DynamoDBDocumentClient.from(s,i),{client:s,documentClient:a}},exports.internalServerError=(e="Internal Server Error",t={})=>i(500,{message:e},t),exports.noContent=(e={})=>i(204,{},e),exports.notFound=(e="Not Found",t={})=>i(404,{message:e},t),exports.ok=(e,t={})=>i(200,e,t),exports.resetDynamoDBClients=()=>{s=null,a=null},exports.withRequestTracking=r;
2
2
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,214 @@
1
+ # DynamoDB Client Utilities
2
+
3
+ The DynamoDB client utilities provide reusable singleton instances of `DynamoDBClient` and `DynamoDBDocumentClient` for use in AWS Lambda functions. These utilities enable you to configure clients once and reuse them across invocations, following AWS best practices for Lambda performance optimization.
4
+
5
+ ## Overview
6
+
7
+ The utility exports the following functions:
8
+
9
+ - `initializeDynamoDBClients()` - Initialize both DynamoDB clients with optional configuration
10
+ - `getDynamoDBClient()` - Get the singleton DynamoDB client instance
11
+ - `getDynamoDBDocumentClient()` - Get the singleton DynamoDB Document client instance
12
+ - `resetDynamoDBClients()` - Reset both client instances (useful for testing)
13
+
14
+ ## Usage
15
+
16
+ ### Basic Usage
17
+
18
+ ```typescript
19
+ import { initializeDynamoDBClients, getDynamoDBDocumentClient } from '@leanstacks/lambda-utils';
20
+ import { PutCommand } from '@aws-sdk/lib-dynamodb';
21
+
22
+ // Initialize both clients (typically done once outside the handler)
23
+ initializeDynamoDBClients({ region: 'us-east-1' });
24
+
25
+ export const handler = async (event: any) => {
26
+ // Get the document client (most common use case)
27
+ const docClient = getDynamoDBDocumentClient();
28
+
29
+ // Use the document client
30
+ await docClient.send(
31
+ new PutCommand({
32
+ TableName: 'MyTable',
33
+ Item: { id: '123', name: 'Example' },
34
+ }),
35
+ );
36
+ };
37
+ ```
38
+
39
+ ### Using the Base DynamoDB Client
40
+
41
+ ````typescript
42
+ import { initializeDynamoDBClients, getDynamoDBClient } from '@leanstacks/lambda-utils';
43
+ import { PutItemCommand } from '@aws-sdk/client-dynamodb';
44
+
45
+ // Initialize both clients
46
+ initializeDynamoDBClients({ region: 'us-east-1' });
47
+
48
+ export const handler = async (event: any) => {
49
+ // Get the base client for advanced use cases
50
+ const client = getDynamoDBClient();
51
+
52
+ // Use the base client
53
+ await client.send(new PutItemCommand({
54
+ TableName: 'MyTable',
55
+ Item: { id: { S: '123' }, name: { S: 'Example' } }
56
+ }));
57
+
58
+ ### Advanced Configuration
59
+
60
+ #### Custom DynamoDB Client Configuration
61
+
62
+ ```typescript
63
+ import { initializeDynamoDBClients } from '@leanstacks/lambda-utils';
64
+
65
+ initializeDynamoDBClients({
66
+ region: 'us-west-2',
67
+ endpoint: 'http://localhost:8000', // For local development
68
+ credentials: {
69
+ accessKeyId: 'local',
70
+ secretAccessKey: 'local',
71
+ },
72
+ });
73
+ ````
74
+
75
+ #### Custom Marshall/Unmarshall Options
76
+
77
+ ```typescript
78
+ import { initializeDynamoDBClients } from '@leanstacks/lambda-utils';
79
+
80
+ initializeDynamoDBClients(
81
+ { region: 'us-east-1' },
82
+ {
83
+ // Marshall options
84
+ removeUndefinedValues: true,
85
+ convertEmptyValues: false,
86
+ convertClassInstanceToMap: true,
87
+ },
88
+ {
89
+ // Unmarshall options
90
+ wrapNumbers: false,
91
+ },
92
+ );
93
+ ```
94
+
95
+ ### Lambda Handler Pattern
96
+
97
+ ```typescript
98
+ import { initializeDynamoDBClients, getDynamoDBDocumentClient } from '@leanstacks/lambda-utils';
99
+ import { GetCommand } from '@aws-sdk/lib-dynamodb';
100
+
101
+ // Initialize clients outside the handler (runs once per cold start)
102
+ initializeDynamoDBClients({ region: process.env.AWS_REGION }, { removeUndefinedValues: true });
103
+
104
+ // Handler function
105
+ export const handler = async (event: any) => {
106
+ const docClient = getDynamoDBDocumentClient();
107
+
108
+ const result = await docClient.send(
109
+ new GetCommand({
110
+ TableName: process.env.TABLE_NAME,
111
+ Key: { id: event.id },
112
+ }),
113
+ );
114
+
115
+ return result.Item;
116
+ };
117
+ ```
118
+
119
+ ## API Reference
120
+
121
+ ### `initializeDynamoDBClients(config?, marshallOptions?, unmarshallOptions?): { client: DynamoDBClient; documentClient: DynamoDBDocumentClient }`
122
+
123
+ Initializes both the DynamoDB client and DynamoDB Document client with the provided configuration.
124
+
125
+ **Parameters:**
126
+
127
+ - `config` (optional) - DynamoDB client configuration object
128
+ - `marshallOptions` (optional) - Options for marshalling JavaScript objects to DynamoDB AttributeValues
129
+ - `unmarshallOptions` (optional) - Options for unmarshalling DynamoDB AttributeValues to JavaScript objects
130
+
131
+ **Returns:**
132
+
133
+ - An object containing both `client` (DynamoDBClient) and `documentClient` (DynamoDBDocumentClient)
134
+
135
+ **Notes:**
136
+
137
+ - Creates both clients in a single call
138
+ - If called multiple times, it will replace the existing client instances
139
+ - If no config is provided, uses default AWS SDK configuration
140
+ - Most users will only need the `documentClient` from the return value or via `getDynamoDBDocumentClient()`
141
+
142
+ ### `getDynamoDBClient(): DynamoDBClient`
143
+
144
+ Returns the singleton DynamoDB client instance.
145
+
146
+ **Returns:**
147
+
148
+ - The `DynamoDBClient` instance
149
+
150
+ **Throws:**
151
+
152
+ - Error if the client has not been initialized
153
+
154
+ ### `getDynamoDBDocumentClient(): DynamoDBDocumentClient`
155
+
156
+ Returns the singleton DynamoDB Document client instance.
157
+
158
+ **Returns:**
159
+
160
+ - The `DynamoDBDocumentClient` instance
161
+
162
+ **Throws:**
163
+
164
+ - Error if the document client has not been initialized
165
+
166
+ ### `resetDynamoDBClients(): void`
167
+
168
+ Resets both DynamoDB client instances to null.
169
+
170
+ **Notes:**
171
+
172
+ - Primarily useful for testing scenarios where you need to reinitialize clients with different configurations
173
+ - After calling this, you must reinitialize the clients before using the getter functions
174
+
175
+ ## Best Practices
176
+
177
+ 1. **Initialize Outside the Handler**: Always initialize clients outside your Lambda handler function to reuse the instance across invocations.
178
+
179
+ 2. **Use Environment Variables**: Configure clients using environment variables for flexibility across environments.
180
+
181
+ 3. **Error Handling**: Always wrap client operations in try-catch blocks to handle errors gracefully.
182
+
183
+ 4. **Testing**: Use `resetDynamoDBClients()` in test setup/teardown to ensure clean test isolation.
184
+
185
+ ## Testing Example
186
+
187
+ ```typescript
188
+ import { initializeDynamoDBClients, getDynamoDBDocumentClient, resetDynamoDBClients } from '@leanstacks/lambda-utils';
189
+
190
+ describe('MyLambdaHandler', () => {
191
+ beforeEach(() => {
192
+ resetDynamoDBClients();
193
+ initializeDynamoDBClients(
194
+ { region: 'us-east-1', endpoint: 'http://localhost:8000' },
195
+ { removeUndefinedValues: true },
196
+ );
197
+ });
198
+
199
+ afterEach(() => {
200
+ resetDynamoDBClients();
201
+ });
202
+
203
+ it('should retrieve item from DynamoDB', async () => {
204
+ // Your test code here
205
+ });
206
+ });
207
+ ```
208
+
209
+ ## Related Resources
210
+
211
+ - **[AWS SDK for JavaScript v3 - DynamoDB Client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/)**
212
+ - **[AWS SDK for JavaScript v3 - DynamoDB Document Client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-lib-dynamodb/Class/DynamoDBDocumentClient/)**
213
+ - **[AWS Lambda Best Practices](https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html)**
214
+ - **[Back to the project documentation](README.md)**
package/docs/README.md CHANGED
@@ -10,8 +10,8 @@ Lambda Utilities is a collection of pre-configured tools and helpers designed to
10
10
 
11
11
  - **[Logging Guide](./LOGGING.md)** – Implement structured logging in your Lambda functions with Pino and automatic AWS context enrichment
12
12
  - **[API Gateway Responses](./API_GATEWAY_RESPONSES.md)** – Format Lambda responses for API Gateway with standard HTTP status codes and headers
13
+ - **[DynamoDB Client](./DYNAMODB_CLIENT.md)** – Reusable singleton DynamoDB client instances with custom configuration
13
14
  - **[Configuration](./CONFIGURATION.md)** – Validate environment variables and configuration with Zod type safety
14
- - **[AWS Clients](./CLIENTS.md)** – Pre-configured AWS SDK v3 clients optimized for Lambda
15
15
  - **[Getting Started](./GETTING_STARTED.md)** – Quick setup and installation instructions
16
16
 
17
17
  ## Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leanstacks/lambda-utils",
3
- "version": "0.2.0",
3
+ "version": "0.3.0-alpha.1",
4
4
  "description": "A collection of utilities and helper functions designed to streamline the development of AWS Lambda functions using TypeScript.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -63,7 +63,9 @@
63
63
  "typescript": "5.9.3"
64
64
  },
65
65
  "dependencies": {
66
- "pino": "10.1.0",
67
- "pino-lambda": "4.4.1"
66
+ "@aws-sdk/client-dynamodb": "^3.955.0",
67
+ "@aws-sdk/lib-dynamodb": "^3.955.0",
68
+ "pino": "^10.1.0",
69
+ "pino-lambda": "^4.4.1"
68
70
  }
69
71
  }