@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 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 document client support
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 AWS SDK v3 clients in your handlers |
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
- Additional AWS Clients are coming soon.
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
- Example Lambda functions using Lambda Utilities are available in the repository:
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 i}from"@aws-sdk/client-dynamodb";import{DynamoDBDocumentClient as s}from"@aws-sdk/lib-dynamodb";import{z as l}from"zod";const a=e();class c{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,i=r({formatter:e});return n({enabled:this._loggerConfig.enabled,level:this._loggerConfig.level},i)},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})},f=(n,e,t={})=>({statusCode:n,headers:{...t},body:JSON.stringify(e)}),g=(n,e={})=>f(200,n,e),d=(n,e={})=>f(201,n,e),u=(n={})=>f(204,{},n),h=(n="Bad Request",e={})=>f(400,{message:n},e),p=(n="Not Found",e={})=>f(404,{message:n},e),C=(n="Internal Server Error",e={})=>f(500,{message:n},e);let w=null,y=null;const _=(n,e,t)=>{w=new i(n||{});const o={marshallOptions:e||{},unmarshallOptions:t||{}};return y=s.from(w,o),{client:w,documentClient:y}},b=()=>{if(!w)throw new Error("DynamoDB client not initialized. Call initializeDynamoDBClients() first.");return w},D=()=>{if(!y)throw new Error("DynamoDB Document client not initialized. Call initializeDynamoDBClients() first.");return y},v=()=>{w=null,y=null},j=n=>{let e=null;const t=()=>{try{return n.parse(process.env)}catch(n){if(n instanceof l.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{c as Logger,h as badRequest,j as createConfigManager,f as createResponse,d as created,b as getDynamoDBClient,D as getDynamoDBDocumentClient,m as httpHeaders,_ as initializeDynamoDBClients,C as internalServerError,u as noContent,p as notFound,g as ok,v as resetDynamoDBClients,a as withRequestTracking};
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"),t=require("pino-lambda"),n=require("@aws-sdk/client-dynamodb"),r=require("@aws-sdk/lib-dynamodb"),o=require("zod");const i=t.lambdaRequestTracker();const s=(e,t,n={})=>({statusCode:e,headers:{...n},body:JSON.stringify(t)});let a=null,l=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,r=t.pinoLambdaDestination({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}},exports.badRequest=(e="Bad Request",t={})=>s(400,{message:e},t),exports.createConfigManager=e=>{let t=null;const n=()=>{try{return e.parse(process.env)}catch(e){if(e instanceof o.z.ZodError){const t=e.issues.map(e=>`${e.path.join(".")}: ${e.message}`).join(", ");throw new Error(`Configuration validation failed: ${t}`)}throw e}};return{get:()=>(t||(t=n()),t),refresh:()=>(t=n(),t)}},exports.createResponse=s,exports.created=(e,t={})=>s(201,e,t),exports.getDynamoDBClient=()=>{if(!a)throw new Error("DynamoDB client not initialized. Call initializeDynamoDBClients() first.");return a},exports.getDynamoDBDocumentClient=()=>{if(!l)throw new Error("DynamoDB Document client not initialized. Call initializeDynamoDBClients() first.");return l},exports.httpHeaders={contentType:e=>({"Content-Type":e}),json:{"Content-Type":"application/json"},cors:(e="*")=>({"Access-Control-Allow-Origin":e})},exports.initializeDynamoDBClients=(e,t,o)=>{a=new n.DynamoDBClient(e||{});const i={marshallOptions:t||{},unmarshallOptions:o||{}};return l=r.DynamoDBDocumentClient.from(a,i),{client:a,documentClient:l}},exports.internalServerError=(e="Internal Server Error",t={})=>s(500,{message:e},t),exports.noContent=(e={})=>s(204,{},e),exports.notFound=(e="Not Found",t={})=>s(404,{message:e},t),exports.ok=(e,t={})=>s(200,e,t),exports.resetDynamoDBClients=()=>{a=null,l=null},exports.withRequestTracking=i;
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)