@leanstacks/lambda-utils 0.3.0 → 0.4.0
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.
- package/README.md +85 -4
- package/dist/clients/lambda-client.d.ts +78 -0
- package/dist/clients/sns-client.d.ts +75 -0
- package/dist/clients/sqs-client.d.ts +74 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.esm.js +1 -1
- package/dist/index.js +1 -1
- package/docs/LAMBDA_CLIENT.md +292 -0
- package/docs/README.md +4 -1
- package/docs/SNS_CLIENT.md +363 -0
- package/docs/SQS_CLIENT.md +352 -0
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ export const handler = async (event: APIGatewayProxyEvent) => {
|
|
|
94
94
|
- **📝 Structured Logging** – Pino logger pre-configured for Lambda with automatic AWS request context enrichment
|
|
95
95
|
- **📤 API Response Helpers** – Standard response formatting for API Gateway with proper HTTP status codes
|
|
96
96
|
- **⚙️ Configuration Validation** – Environment variable validation with Zod schema support
|
|
97
|
-
- **🔌 AWS SDK Clients** – Pre-configured AWS SDK v3 clients including DynamoDB with
|
|
97
|
+
- **🔌 AWS SDK Clients** – Pre-configured AWS SDK v3 clients including DynamoDB, Lambda, SNS, and SQS with singleton patterns
|
|
98
98
|
- **🔒 Full TypeScript Support** – Complete type definitions and IDE autocomplete
|
|
99
99
|
- **⚡ Lambda Optimized** – Designed for performance in serverless environments
|
|
100
100
|
|
|
@@ -107,7 +107,10 @@ Comprehensive guides and examples are available in the `docs` directory:
|
|
|
107
107
|
| **[Configuration Guide](./docs/CONFIGURATION.md)** | Validate environment variables with Zod schemas and type safety |
|
|
108
108
|
| **[Logging Guide](./docs/LOGGING.md)** | Configure and use structured logging with automatic AWS Lambda context |
|
|
109
109
|
| **[API Gateway Responses](./docs/API_GATEWAY_RESPONSES.md)** | Format responses for API Gateway with standard HTTP patterns |
|
|
110
|
-
| **[DynamoDB Client](./docs/DYNAMODB_CLIENT.md)** | Use pre-configured
|
|
110
|
+
| **[DynamoDB Client](./docs/DYNAMODB_CLIENT.md)** | Use pre-configured DynamoDB clients with singleton pattern |
|
|
111
|
+
| **[Lambda Client](./docs/LAMBDA_CLIENT.md)** | Invoke other Lambda functions synchronously or asynchronously |
|
|
112
|
+
| **[SNS Client](./docs/SNS_CLIENT.md)** | Publish messages to SNS topics with message attributes |
|
|
113
|
+
| **[SQS Client](./docs/SQS_CLIENT.md)** | Send messages to SQS queues with message attributes |
|
|
111
114
|
|
|
112
115
|
## Usage
|
|
113
116
|
|
|
@@ -195,11 +198,89 @@ export const handler = async (event: any, context: any) => {
|
|
|
195
198
|
|
|
196
199
|
**→ See [DynamoDB Client Guide](./docs/DYNAMODB_CLIENT.md) for detailed configuration and examples**
|
|
197
200
|
|
|
198
|
-
|
|
201
|
+
#### Lambda Client
|
|
202
|
+
|
|
203
|
+
Invoke other Lambda functions synchronously or asynchronously:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { invokeLambdaSync, invokeLambdaAsync } from '@leanstacks/lambda-utils';
|
|
207
|
+
|
|
208
|
+
export const handler = async (event: any) => {
|
|
209
|
+
// Synchronous invocation - wait for response
|
|
210
|
+
const response = await invokeLambdaSync('my-function-name', {
|
|
211
|
+
key: 'value',
|
|
212
|
+
data: { nested: true },
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Asynchronous invocation - fire and forget
|
|
216
|
+
await invokeLambdaAsync('my-async-function', {
|
|
217
|
+
eventType: 'process',
|
|
218
|
+
data: [1, 2, 3],
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return { statusCode: 200, body: JSON.stringify(response) };
|
|
222
|
+
};
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**→ See [Lambda Client Guide](./docs/LAMBDA_CLIENT.md) for detailed configuration and examples**
|
|
226
|
+
|
|
227
|
+
#### SNS Client
|
|
228
|
+
|
|
229
|
+
Publish messages to SNS topics with optional message attributes:
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { publishToTopic, SNSMessageAttributes } from '@leanstacks/lambda-utils';
|
|
233
|
+
|
|
234
|
+
export const handler = async (event: any) => {
|
|
235
|
+
const attributes: SNSMessageAttributes = {
|
|
236
|
+
priority: {
|
|
237
|
+
DataType: 'String',
|
|
238
|
+
StringValue: 'high',
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const messageId = await publishToTopic(
|
|
243
|
+
'arn:aws:sns:us-east-1:123456789012:MyTopic',
|
|
244
|
+
{ orderId: '12345', status: 'completed' },
|
|
245
|
+
attributes,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
return { statusCode: 200, body: JSON.stringify({ messageId }) };
|
|
249
|
+
};
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**→ See [SNS Client Guide](./docs/SNS_CLIENT.md) for detailed configuration and examples**
|
|
253
|
+
|
|
254
|
+
#### SQS Client
|
|
255
|
+
|
|
256
|
+
Send messages to SQS queues with optional message attributes:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { sendToQueue, SQSMessageAttributes } from '@leanstacks/lambda-utils';
|
|
260
|
+
|
|
261
|
+
export const handler = async (event: any) => {
|
|
262
|
+
const attributes: SQSMessageAttributes = {
|
|
263
|
+
priority: {
|
|
264
|
+
DataType: 'String',
|
|
265
|
+
StringValue: 'high',
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const messageId = await sendToQueue(
|
|
270
|
+
'https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue',
|
|
271
|
+
{ orderId: '12345', status: 'completed' },
|
|
272
|
+
attributes,
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
return { statusCode: 200, body: JSON.stringify({ messageId }) };
|
|
276
|
+
};
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**→ See [SQS Client Guide](./docs/SQS_CLIENT.md) for detailed configuration and examples**
|
|
199
280
|
|
|
200
281
|
## Examples
|
|
201
282
|
|
|
202
|
-
|
|
283
|
+
To see an example Lambda microservice using the Lambda Utilities, see the LeanStacks [`lambda-starter`](https://github.com/leanstacks/lambda-starter) :octocat: GitHub repository.
|
|
203
284
|
|
|
204
285
|
- API Gateway with logging and response formatting
|
|
205
286
|
- Configuration validation and DynamoDB integration
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { LambdaClient, LambdaClientConfig } from '@aws-sdk/client-lambda';
|
|
2
|
+
/**
|
|
3
|
+
* Initializes the Lambda client with the provided configuration.
|
|
4
|
+
* If the client is already initialized, this will replace it with a new instance.
|
|
5
|
+
*
|
|
6
|
+
* @param config - Lambda client configuration
|
|
7
|
+
* @returns The Lambda client instance
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // Initialize with default configuration
|
|
12
|
+
* initializeLambdaClient();
|
|
13
|
+
*
|
|
14
|
+
* // Initialize with custom configuration
|
|
15
|
+
* initializeLambdaClient({ region: 'us-east-1' });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare const initializeLambdaClient: (config?: LambdaClientConfig) => LambdaClient;
|
|
19
|
+
/**
|
|
20
|
+
* Returns the singleton Lambda client instance.
|
|
21
|
+
* If the client has not been initialized, creates one with default configuration.
|
|
22
|
+
*
|
|
23
|
+
* @returns The Lambda client instance
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const client = getLambdaClient();
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare const getLambdaClient: () => LambdaClient;
|
|
31
|
+
/**
|
|
32
|
+
* Resets the Lambda client instance.
|
|
33
|
+
* Useful for testing or when you need to reinitialize the client with a different configuration.
|
|
34
|
+
*/
|
|
35
|
+
export declare const resetLambdaClient: () => void;
|
|
36
|
+
/**
|
|
37
|
+
* Invokes a Lambda function synchronously (RequestResponse).
|
|
38
|
+
* The function waits for the response and returns the payload.
|
|
39
|
+
*
|
|
40
|
+
* @param functionName - The name or ARN of the Lambda function to invoke
|
|
41
|
+
* @param payload - The JSON payload to pass to the Lambda function
|
|
42
|
+
* @returns Promise that resolves to the response payload from the Lambda function
|
|
43
|
+
* @throws Error if the Lambda invocation fails or returns a function error
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* interface MyResponse {
|
|
48
|
+
* result: string;
|
|
49
|
+
* statusCode: number;
|
|
50
|
+
* }
|
|
51
|
+
*
|
|
52
|
+
* const response = await invokeLambdaSync<MyResponse>(
|
|
53
|
+
* 'my-function-name',
|
|
54
|
+
* { key: 'value', data: { nested: true } }
|
|
55
|
+
* );
|
|
56
|
+
* console.log(response.result);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare const invokeLambdaSync: <T = unknown>(functionName: string, payload: unknown) => Promise<T>;
|
|
60
|
+
/**
|
|
61
|
+
* Invokes a Lambda function asynchronously (Event).
|
|
62
|
+
* The function returns immediately without waiting for the Lambda execution to complete.
|
|
63
|
+
*
|
|
64
|
+
* @param functionName - The name or ARN of the Lambda function to invoke
|
|
65
|
+
* @param payload - The JSON payload to pass to the Lambda function
|
|
66
|
+
* @returns Promise that resolves when the invocation request is accepted
|
|
67
|
+
* @throws Error if the Lambda invocation request fails
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* // Fire and forget invocation
|
|
72
|
+
* await invokeLambdaAsync(
|
|
73
|
+
* 'my-async-function',
|
|
74
|
+
* { eventType: 'process', data: [1, 2, 3] }
|
|
75
|
+
* );
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export declare const invokeLambdaAsync: (functionName: string, payload: unknown) => Promise<void>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns';
|
|
2
|
+
/**
|
|
3
|
+
* Interface for SNS message attributes.
|
|
4
|
+
* Supports the AWS SNS data types: String, String.Array, Number, and Binary.
|
|
5
|
+
* Note: String.Array is the only array type supported by AWS SNS.
|
|
6
|
+
*/
|
|
7
|
+
export interface SNSMessageAttributes {
|
|
8
|
+
[key: string]: {
|
|
9
|
+
DataType: 'String' | 'String.Array' | 'Number' | 'Binary';
|
|
10
|
+
StringValue?: string;
|
|
11
|
+
BinaryValue?: Uint8Array;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Initializes the SNS client with the provided configuration.
|
|
16
|
+
* If the client is already initialized, this will replace it with a new instance.
|
|
17
|
+
*
|
|
18
|
+
* @param config - SNS client configuration
|
|
19
|
+
* @returns The SNS client instance
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Initialize with default configuration
|
|
24
|
+
* initializeSNSClient();
|
|
25
|
+
*
|
|
26
|
+
* // Initialize with custom configuration
|
|
27
|
+
* initializeSNSClient({ region: 'us-east-1' });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare const initializeSNSClient: (config?: SNSClientConfig) => SNSClient;
|
|
31
|
+
/**
|
|
32
|
+
* Returns the singleton SNS client instance.
|
|
33
|
+
* If the client has not been initialized, creates one with default configuration.
|
|
34
|
+
*
|
|
35
|
+
* @returns The SNS client instance
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const client = getSNSClient();
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare const getSNSClient: () => SNSClient;
|
|
43
|
+
/**
|
|
44
|
+
* Resets the SNS client instance.
|
|
45
|
+
* Useful for testing or when you need to reinitialize the client with a different configuration.
|
|
46
|
+
*/
|
|
47
|
+
export declare const resetSNSClient: () => void;
|
|
48
|
+
/**
|
|
49
|
+
* Publishes a message to an SNS topic.
|
|
50
|
+
*
|
|
51
|
+
* @param topicArn - The ARN of the SNS topic to publish to
|
|
52
|
+
* @param message - The message content (will be converted to JSON string)
|
|
53
|
+
* @param attributes - Optional message attributes for filtering
|
|
54
|
+
* @returns Promise that resolves to the message ID
|
|
55
|
+
* @throws Error if the SNS publish operation fails
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const messageId = await publishToTopic(
|
|
60
|
+
* 'arn:aws:sns:us-east-1:123456789012:MyTopic',
|
|
61
|
+
* { orderId: '12345', status: 'completed' },
|
|
62
|
+
* {
|
|
63
|
+
* priority: {
|
|
64
|
+
* DataType: 'String',
|
|
65
|
+
* StringValue: 'high'
|
|
66
|
+
* },
|
|
67
|
+
* categories: {
|
|
68
|
+
* DataType: 'String.Array',
|
|
69
|
+
* StringValue: JSON.stringify(['urgent', 'vip', 'customer-request'])
|
|
70
|
+
* }
|
|
71
|
+
* }
|
|
72
|
+
* );
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export declare const publishToTopic: (topicArn: string, message: Record<string, unknown>, attributes?: SNSMessageAttributes) => Promise<string>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs';
|
|
2
|
+
/**
|
|
3
|
+
* Interface for SQS message attributes.
|
|
4
|
+
* Supports the AWS SQS data types: String, Number, and Binary.
|
|
5
|
+
*/
|
|
6
|
+
export interface SQSMessageAttributes {
|
|
7
|
+
[key: string]: {
|
|
8
|
+
DataType: 'String' | 'Number' | 'Binary';
|
|
9
|
+
StringValue?: string;
|
|
10
|
+
BinaryValue?: Uint8Array;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Initializes the SQS client with the provided configuration.
|
|
15
|
+
* If the client is already initialized, this will replace it with a new instance.
|
|
16
|
+
*
|
|
17
|
+
* @param config - SQS client configuration
|
|
18
|
+
* @returns The SQS client instance
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* // Initialize with default configuration
|
|
23
|
+
* initializeSQSClient();
|
|
24
|
+
*
|
|
25
|
+
* // Initialize with custom configuration
|
|
26
|
+
* initializeSQSClient({ region: 'us-east-1' });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare const initializeSQSClient: (config?: SQSClientConfig) => SQSClient;
|
|
30
|
+
/**
|
|
31
|
+
* Returns the singleton SQS client instance.
|
|
32
|
+
* If the client has not been initialized, creates one with default configuration.
|
|
33
|
+
*
|
|
34
|
+
* @returns The SQS client instance
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const client = getSQSClient();
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare const getSQSClient: () => SQSClient;
|
|
42
|
+
/**
|
|
43
|
+
* Resets the SQS client instance.
|
|
44
|
+
* Useful for testing or when you need to reinitialize the client with a different configuration.
|
|
45
|
+
*/
|
|
46
|
+
export declare const resetSQSClient: () => void;
|
|
47
|
+
/**
|
|
48
|
+
* Sends a message to an SQS queue.
|
|
49
|
+
*
|
|
50
|
+
* @param queueUrl - The URL of the SQS queue to send to
|
|
51
|
+
* @param message - The message content (will be converted to JSON string)
|
|
52
|
+
* @param attributes - Optional message attributes for filtering
|
|
53
|
+
* @returns Promise that resolves to the message ID
|
|
54
|
+
* @throws Error if the SQS send operation fails
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const messageId = await sendToQueue(
|
|
59
|
+
* 'https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue',
|
|
60
|
+
* { orderId: '12345', status: 'completed' },
|
|
61
|
+
* {
|
|
62
|
+
* priority: {
|
|
63
|
+
* DataType: 'String',
|
|
64
|
+
* StringValue: 'high'
|
|
65
|
+
* },
|
|
66
|
+
* attempts: {
|
|
67
|
+
* DataType: 'Number',
|
|
68
|
+
* StringValue: '1'
|
|
69
|
+
* }
|
|
70
|
+
* }
|
|
71
|
+
* );
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare const sendToQueue: (queueUrl: string, message: Record<string, unknown>, attributes?: SQSMessageAttributes) => Promise<string>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
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
3
|
export { getDynamoDBClient, getDynamoDBDocumentClient, initializeDynamoDBClients, resetDynamoDBClients, } from './clients/dynamodb-client';
|
|
4
|
+
export { getSNSClient, initializeSNSClient, SNSMessageAttributes, publishToTopic, resetSNSClient, } from './clients/sns-client';
|
|
5
|
+
export { getLambdaClient, initializeLambdaClient, invokeLambdaAsync, invokeLambdaSync, resetLambdaClient, } from './clients/lambda-client';
|
|
6
|
+
export { getSQSClient, initializeSQSClient, SQSMessageAttributes, sendToQueue, resetSQSClient, } from './clients/sqs-client';
|
|
4
7
|
export { createConfigManager, ConfigManager } from './validation/config';
|
package/dist/index.esm.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import n from"pino";import{lambdaRequestTracker as e,StructuredLogFormatter as t,CloudwatchLogFormatter as o,pinoLambdaDestination as r}from"pino-lambda";import{DynamoDBClient as
|
|
1
|
+
import n from"pino";import{lambdaRequestTracker as e,StructuredLogFormatter as t,CloudwatchLogFormatter as o,pinoLambdaDestination as r}from"pino-lambda";import{DynamoDBClient as s}from"@aws-sdk/client-dynamodb";import{DynamoDBDocumentClient as i}from"@aws-sdk/lib-dynamodb";import{SNSClient as a,PublishCommand as l}from"@aws-sdk/client-sns";import{LambdaClient as c,InvokeCommand as u}from"@aws-sdk/client-lambda";import{SQSClient as d,SendMessageCommand as m}from"@aws-sdk/client-sqs";import{z as f}from"zod";const g=e();class w{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,s=r({formatter:e});return n({enabled:this._loggerConfig.enabled,level:this._loggerConfig.level},s)},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 p={contentType:n=>({"Content-Type":n}),json:{"Content-Type":"application/json"},cors:(n="*")=>({"Access-Control-Allow-Origin":n})},y=(n,e,t={})=>({statusCode:n,headers:{...t},body:JSON.stringify(e)}),h=(n,e={})=>y(200,n,e),b=(n,e={})=>y(201,n,e),C=(n={})=>y(204,{},n),E=(n="Bad Request",e={})=>y(400,{message:n},e),v=(n="Not Found",e={})=>y(404,{message:n},e),_=(n="Internal Server Error",e={})=>y(500,{message:n},e);let D=null,N=null;const O=(n,e,t)=>{D=new s(n||{});const o={marshallOptions:e||{},unmarshallOptions:t||{}};return N=i.from(D,o),{client:D,documentClient:N}},j=()=>{if(!D)throw new Error("DynamoDB client not initialized. Call initializeDynamoDBClients() first.");return D},F=()=>{if(!N)throw new Error("DynamoDB Document client not initialized. Call initializeDynamoDBClients() first.");return N},S=()=>{D=null,N=null};let T=null;const B=n=>(T=new a(n||{}),T),J=()=>(T||(T=new a({})),T),M=()=>{T=null},k=async(n,e,t)=>{const o=J(),r=new l({TopicArn:n,Message:JSON.stringify(e),MessageAttributes:t});return(await o.send(r)).MessageId??""};let z=null;const A=n=>(z=new c(n||{}),z),I=()=>(z||(z=new c({})),z),$=()=>{z=null},L=async(n,e)=>{const t=I(),o=new u({FunctionName:n,InvocationType:"RequestResponse",Payload:JSON.stringify(e)}),r=await t.send(o);if(r.FunctionError)throw new Error(`Lambda function error: ${r.FunctionError}`);return r.Payload?JSON.parse((new TextDecoder).decode(r.Payload)):null},P=async(n,e)=>{const t=I(),o=new u({FunctionName:n,InvocationType:"Event",Payload:JSON.stringify(e)}),r=await t.send(o);if(r.FunctionError)throw new Error(`Lambda function error: ${r.FunctionError}`)};let q=null;const R=n=>(q=new d(n||{}),q),x=()=>(q||(q=new d({})),q),Q=()=>{q=null},U=async(n,e,t)=>{const o=x(),r=new m({QueueUrl:n,MessageBody:JSON.stringify(e),MessageAttributes:t});return(await o.send(r)).MessageId??""},Z=n=>{let e=null;const t=()=>{try{return n.parse(process.env)}catch(n){if(n instanceof f.ZodError){const e=n.issues.map(n=>`${n.path.join(".")}: ${n.message}`).join(", ");throw new Error(`Configuration validation failed: ${e}`)}throw n}};return{get:()=>(e||(e=t()),e),refresh:()=>(e=t(),e)}};export{w as Logger,E as badRequest,Z as createConfigManager,y as createResponse,b as created,j as getDynamoDBClient,F as getDynamoDBDocumentClient,I as getLambdaClient,J as getSNSClient,x as getSQSClient,p as httpHeaders,O as initializeDynamoDBClients,A as initializeLambdaClient,B as initializeSNSClient,R as initializeSQSClient,_ as internalServerError,P as invokeLambdaAsync,L as invokeLambdaSync,C as noContent,v as notFound,h as ok,k as publishToTopic,S as resetDynamoDBClients,$ as resetLambdaClient,M as resetSNSClient,Q as resetSQSClient,U as sendToQueue,g 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"),
|
|
1
|
+
"use strict";var e=require("pino"),n=require("pino-lambda"),t=require("@aws-sdk/client-dynamodb"),r=require("@aws-sdk/lib-dynamodb"),o=require("@aws-sdk/client-sns"),s=require("@aws-sdk/client-lambda"),i=require("@aws-sdk/client-sqs"),a=require("zod");const l=n.lambdaRequestTracker();const c=(e,n,t={})=>({statusCode:e,headers:{...t},body:JSON.stringify(n)});let u=null,d=null;let m=null;const g=()=>(m||(m=new o.SNSClient({})),m);let p=null;const C=()=>(p||(p=new s.LambdaClient({})),p);let w=null;const y=()=>(w||(w=new i.SQSClient({})),w);exports.Logger=class{constructor(t){this._loggerConfig={enabled:!0,level:"info",format:"json"},this._instance=null,this._createLogger=()=>{const t="json"===this._loggerConfig.format?new n.StructuredLogFormatter:new n.CloudwatchLogFormatter,r=n.pinoLambdaDestination({formatter:t});return e({enabled:this._loggerConfig.enabled,level:this._loggerConfig.level},r)},t&&(this._loggerConfig={enabled:t.enabled??!0,level:t.level??"info",format:t.format??"json"})}get instance(){return null===this._instance&&(this._instance=this._createLogger()),this._instance}},exports.badRequest=(e="Bad Request",n={})=>c(400,{message:e},n),exports.createConfigManager=e=>{let n=null;const t=()=>{try{return e.parse(process.env)}catch(e){if(e instanceof a.z.ZodError){const n=e.issues.map(e=>`${e.path.join(".")}: ${e.message}`).join(", ");throw new Error(`Configuration validation failed: ${n}`)}throw e}};return{get:()=>(n||(n=t()),n),refresh:()=>(n=t(),n)}},exports.createResponse=c,exports.created=(e,n={})=>c(201,e,n),exports.getDynamoDBClient=()=>{if(!u)throw new Error("DynamoDB client not initialized. Call initializeDynamoDBClients() first.");return u},exports.getDynamoDBDocumentClient=()=>{if(!d)throw new Error("DynamoDB Document client not initialized. Call initializeDynamoDBClients() first.");return d},exports.getLambdaClient=C,exports.getSNSClient=g,exports.getSQSClient=y,exports.httpHeaders={contentType:e=>({"Content-Type":e}),json:{"Content-Type":"application/json"},cors:(e="*")=>({"Access-Control-Allow-Origin":e})},exports.initializeDynamoDBClients=(e,n,o)=>{u=new t.DynamoDBClient(e||{});const s={marshallOptions:n||{},unmarshallOptions:o||{}};return d=r.DynamoDBDocumentClient.from(u,s),{client:u,documentClient:d}},exports.initializeLambdaClient=e=>(p=new s.LambdaClient(e||{}),p),exports.initializeSNSClient=e=>(m=new o.SNSClient(e||{}),m),exports.initializeSQSClient=e=>(w=new i.SQSClient(e||{}),w),exports.internalServerError=(e="Internal Server Error",n={})=>c(500,{message:e},n),exports.invokeLambdaAsync=async(e,n)=>{const t=C(),r=new s.InvokeCommand({FunctionName:e,InvocationType:"Event",Payload:JSON.stringify(n)}),o=await t.send(r);if(o.FunctionError)throw new Error(`Lambda function error: ${o.FunctionError}`)},exports.invokeLambdaSync=async(e,n)=>{const t=C(),r=new s.InvokeCommand({FunctionName:e,InvocationType:"RequestResponse",Payload:JSON.stringify(n)}),o=await t.send(r);if(o.FunctionError)throw new Error(`Lambda function error: ${o.FunctionError}`);return o.Payload?JSON.parse((new TextDecoder).decode(o.Payload)):null},exports.noContent=(e={})=>c(204,{},e),exports.notFound=(e="Not Found",n={})=>c(404,{message:e},n),exports.ok=(e,n={})=>c(200,e,n),exports.publishToTopic=async(e,n,t)=>{const r=g(),s=new o.PublishCommand({TopicArn:e,Message:JSON.stringify(n),MessageAttributes:t});return(await r.send(s)).MessageId??""},exports.resetDynamoDBClients=()=>{u=null,d=null},exports.resetLambdaClient=()=>{p=null},exports.resetSNSClient=()=>{m=null},exports.resetSQSClient=()=>{w=null},exports.sendToQueue=async(e,n,t)=>{const r=y(),o=new i.SendMessageCommand({QueueUrl:e,MessageBody:JSON.stringify(n),MessageAttributes:t});return(await r.send(o)).MessageId??""},exports.withRequestTracking=l;
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# Lambda Client Utilities
|
|
2
|
+
|
|
3
|
+
The Lambda client utilities provide a reusable singleton instance of `LambdaClient` for use in AWS Lambda functions. These utilities enable you to configure the client once and reuse it across invocations, following AWS best practices for Lambda performance optimization.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The utility exports the following functions:
|
|
8
|
+
|
|
9
|
+
- `initializeLambdaClient()` - Initialize the Lambda client with optional configuration
|
|
10
|
+
- `getLambdaClient()` - Get the singleton Lambda client instance
|
|
11
|
+
- `invokeLambdaSync()` - Invoke a Lambda function synchronously (RequestResponse)
|
|
12
|
+
- `invokeLambdaAsync()` - Invoke a Lambda function asynchronously (Event)
|
|
13
|
+
- `resetLambdaClient()` - Reset the client instance (useful for testing)
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Synchronous Invocation (RequestResponse)
|
|
18
|
+
|
|
19
|
+
Use synchronous invocation when you need to wait for the Lambda function to complete and return a response:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { invokeLambdaSync } from '@leanstacks/lambda-utils';
|
|
23
|
+
|
|
24
|
+
export const handler = async (event: any) => {
|
|
25
|
+
// Invoke a Lambda function and wait for the response
|
|
26
|
+
const response = await invokeLambdaSync('my-function-name', {
|
|
27
|
+
key: 'value',
|
|
28
|
+
data: { nested: true },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
statusCode: 200,
|
|
33
|
+
body: JSON.stringify(response),
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Synchronous Invocation with Typed Response
|
|
39
|
+
|
|
40
|
+
For better type safety, you can specify the expected response type:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { invokeLambdaSync } from '@leanstacks/lambda-utils';
|
|
44
|
+
|
|
45
|
+
interface MyFunctionResponse {
|
|
46
|
+
result: string;
|
|
47
|
+
statusCode: number;
|
|
48
|
+
data: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const handler = async (event: any) => {
|
|
52
|
+
const response = await invokeLambdaSync<MyFunctionResponse>('my-function-name', {
|
|
53
|
+
operation: 'getData',
|
|
54
|
+
id: '12345',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
console.log(`Function returned: ${response.result}`);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
statusCode: response.statusCode,
|
|
61
|
+
body: JSON.stringify(response.data),
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Asynchronous Invocation (Event)
|
|
67
|
+
|
|
68
|
+
Use asynchronous invocation for fire-and-forget scenarios where you don't need to wait for the Lambda function to complete:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { invokeLambdaAsync } from '@leanstacks/lambda-utils';
|
|
72
|
+
|
|
73
|
+
export const handler = async (event: any) => {
|
|
74
|
+
// Invoke a Lambda function asynchronously (fire and forget)
|
|
75
|
+
await invokeLambdaAsync('my-async-function', {
|
|
76
|
+
eventType: 'process',
|
|
77
|
+
data: [1, 2, 3],
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Returns immediately without waiting for the function to complete
|
|
81
|
+
return {
|
|
82
|
+
statusCode: 202,
|
|
83
|
+
body: JSON.stringify({ message: 'Processing initiated' }),
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Using Function ARN
|
|
89
|
+
|
|
90
|
+
You can invoke Lambda functions by name or ARN:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { invokeLambdaSync } from '@leanstacks/lambda-utils';
|
|
94
|
+
|
|
95
|
+
export const handler = async (event: any) => {
|
|
96
|
+
// Using function ARN
|
|
97
|
+
const response = await invokeLambdaSync('arn:aws:lambda:us-east-1:123456789012:function:my-function', {
|
|
98
|
+
key: 'value',
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
statusCode: 200,
|
|
103
|
+
body: JSON.stringify(response),
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Using the Lambda Client Directly
|
|
109
|
+
|
|
110
|
+
For advanced use cases, you can access the underlying Lambda client:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { getLambdaClient } from '@leanstacks/lambda-utils';
|
|
114
|
+
import { ListFunctionsCommand } from '@aws-sdk/client-lambda';
|
|
115
|
+
|
|
116
|
+
export const handler = async (event: any) => {
|
|
117
|
+
const client = getLambdaClient();
|
|
118
|
+
|
|
119
|
+
const response = await client.send(new ListFunctionsCommand({}));
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
statusCode: 200,
|
|
123
|
+
body: JSON.stringify(response.Functions),
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Custom Configuration
|
|
129
|
+
|
|
130
|
+
Initialize the Lambda client with custom configuration at the start of your Lambda handler:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { initializeLambdaClient, invokeLambdaSync } from '@leanstacks/lambda-utils';
|
|
134
|
+
|
|
135
|
+
export const handler = async (event: any) => {
|
|
136
|
+
// Initialize with custom configuration (only needs to be done once)
|
|
137
|
+
initializeLambdaClient({
|
|
138
|
+
region: 'us-west-2',
|
|
139
|
+
maxAttempts: 3,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const response = await invokeLambdaSync('my-function', { key: 'value' });
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
statusCode: 200,
|
|
146
|
+
body: JSON.stringify(response),
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## API Reference
|
|
152
|
+
|
|
153
|
+
### initializeLambdaClient(config?)
|
|
154
|
+
|
|
155
|
+
Initializes the Lambda client with the provided configuration. If the client is already initialized, this will replace it with a new instance.
|
|
156
|
+
|
|
157
|
+
**Parameters:**
|
|
158
|
+
|
|
159
|
+
- `config` (optional): `LambdaClientConfig` - AWS SDK Lambda client configuration
|
|
160
|
+
|
|
161
|
+
**Returns:** `LambdaClient` - The Lambda client instance
|
|
162
|
+
|
|
163
|
+
### getLambdaClient()
|
|
164
|
+
|
|
165
|
+
Returns the singleton Lambda client instance. If the client has not been initialized, creates one with default configuration.
|
|
166
|
+
|
|
167
|
+
**Returns:** `LambdaClient` - The Lambda client instance
|
|
168
|
+
|
|
169
|
+
### invokeLambdaSync<T>(functionName, payload)
|
|
170
|
+
|
|
171
|
+
Invokes a Lambda function synchronously (RequestResponse). The function waits for the response and returns the payload.
|
|
172
|
+
|
|
173
|
+
**Parameters:**
|
|
174
|
+
|
|
175
|
+
- `functionName`: `string` - The name or ARN of the Lambda function to invoke
|
|
176
|
+
- `payload`: `unknown` - The JSON payload to pass to the Lambda function
|
|
177
|
+
|
|
178
|
+
**Returns:** `Promise<T>` - Promise that resolves to the response payload from the Lambda function
|
|
179
|
+
|
|
180
|
+
**Throws:** `Error` - If the Lambda invocation fails or returns a function error
|
|
181
|
+
|
|
182
|
+
### invokeLambdaAsync(functionName, payload)
|
|
183
|
+
|
|
184
|
+
Invokes a Lambda function asynchronously (Event). The function returns immediately without waiting for the Lambda execution to complete.
|
|
185
|
+
|
|
186
|
+
**Parameters:**
|
|
187
|
+
|
|
188
|
+
- `functionName`: `string` - The name or ARN of the Lambda function to invoke
|
|
189
|
+
- `payload`: `unknown` - The JSON payload to pass to the Lambda function
|
|
190
|
+
|
|
191
|
+
**Returns:** `Promise<void>` - Promise that resolves when the invocation request is accepted
|
|
192
|
+
|
|
193
|
+
**Throws:** `Error` - If the Lambda invocation request fails
|
|
194
|
+
|
|
195
|
+
### resetLambdaClient()
|
|
196
|
+
|
|
197
|
+
Resets the Lambda client instance. Useful for testing or when you need to reinitialize the client with a different configuration.
|
|
198
|
+
|
|
199
|
+
**Returns:** `void`
|
|
200
|
+
|
|
201
|
+
## Error Handling
|
|
202
|
+
|
|
203
|
+
Both `invokeLambdaSync` and `invokeLambdaAsync` throw errors in the following cases:
|
|
204
|
+
|
|
205
|
+
1. **Function Errors**: When the invoked Lambda function returns an error (e.g., unhandled exceptions)
|
|
206
|
+
2. **AWS SDK Errors**: When the AWS SDK encounters an error (e.g., network issues, permission errors)
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { invokeLambdaSync } from '@leanstacks/lambda-utils';
|
|
210
|
+
|
|
211
|
+
export const handler = async (event: any) => {
|
|
212
|
+
try {
|
|
213
|
+
const response = await invokeLambdaSync('my-function', { key: 'value' });
|
|
214
|
+
return {
|
|
215
|
+
statusCode: 200,
|
|
216
|
+
body: JSON.stringify(response),
|
|
217
|
+
};
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('Lambda invocation failed:', error);
|
|
220
|
+
return {
|
|
221
|
+
statusCode: 500,
|
|
222
|
+
body: JSON.stringify({ error: 'Failed to invoke Lambda function' }),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Testing
|
|
229
|
+
|
|
230
|
+
The utility provides a `resetLambdaClient()` function for testing purposes:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import { resetLambdaClient, initializeLambdaClient } from '@leanstacks/lambda-utils';
|
|
234
|
+
import { mockClient } from 'aws-sdk-client-mock';
|
|
235
|
+
import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda';
|
|
236
|
+
|
|
237
|
+
describe('My Lambda Tests', () => {
|
|
238
|
+
const lambdaClientMock = mockClient(LambdaClient);
|
|
239
|
+
|
|
240
|
+
beforeEach(() => {
|
|
241
|
+
resetLambdaClient();
|
|
242
|
+
lambdaClientMock.reset();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should invoke Lambda function successfully', async () => {
|
|
246
|
+
// Mock the Lambda client response
|
|
247
|
+
const responsePayload = { result: 'success' };
|
|
248
|
+
const encoder = new TextEncoder();
|
|
249
|
+
const responseBytes = encoder.encode(JSON.stringify(responsePayload));
|
|
250
|
+
|
|
251
|
+
lambdaClientMock.on(InvokeCommand).resolves({
|
|
252
|
+
StatusCode: 200,
|
|
253
|
+
Payload: responseBytes,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Test your code that uses invokeLambdaSync or invokeLambdaAsync
|
|
257
|
+
// ...
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Best Practices
|
|
263
|
+
|
|
264
|
+
1. **Singleton Pattern**: The Lambda client is created once and reused across invocations, improving performance by reducing initialization overhead.
|
|
265
|
+
|
|
266
|
+
2. **Error Handling**: Always wrap Lambda invocations in try-catch blocks to handle potential errors gracefully.
|
|
267
|
+
|
|
268
|
+
3. **Type Safety**: Use TypeScript generics to specify the expected response type for synchronous invocations:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
const response = await invokeLambdaSync<MyResponseType>('my-function', payload);
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
4. **Async vs Sync**: Choose the appropriate invocation type:
|
|
275
|
+
- Use `invokeLambdaSync` when you need to wait for and process the response
|
|
276
|
+
- Use `invokeLambdaAsync` for fire-and-forget scenarios to improve performance
|
|
277
|
+
|
|
278
|
+
5. **IAM Permissions**: Ensure your Lambda function has the necessary IAM permissions to invoke other Lambda functions:
|
|
279
|
+
```json
|
|
280
|
+
{
|
|
281
|
+
"Effect": "Allow",
|
|
282
|
+
"Action": ["lambda:InvokeFunction"],
|
|
283
|
+
"Resource": "arn:aws:lambda:region:account-id:function:function-name"
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## See Also
|
|
288
|
+
|
|
289
|
+
- [AWS SDK for JavaScript - Lambda Client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-lambda/)
|
|
290
|
+
- [AWS Lambda Developer Guide - Invoking Functions](https://docs.aws.amazon.com/lambda/latest/dg/lambda-invocation.html)
|
|
291
|
+
- [DynamoDB Client Documentation](./DYNAMODB_CLIENT.md)
|
|
292
|
+
- [SNS Client Documentation](./SNS_CLIENT.md)
|