@leanstacks/lambda-utils 0.3.0-alpha.4 → 0.4.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,348 @@
1
+ # Configuration Guide
2
+
3
+ This guide explains how to use the `createConfigManager` utility to validate and manage environment variables in your Lambda functions with full TypeScript type safety.
4
+
5
+ ## Overview
6
+
7
+ The configuration utility provides:
8
+
9
+ - **Schema-based validation** using Zod for environment variables
10
+ - **Type-safe access** to your configuration with full TypeScript support
11
+ - **Caching** of validated configuration for performance
12
+ - **Flexible defaults** for optional environment variables
13
+ - **Clear error messages** when validation fails
14
+
15
+ ## Quick Start
16
+
17
+ ### Define Your Schema
18
+
19
+ Create a Zod schema that describes your environment variables:
20
+
21
+ ```typescript
22
+ import { z } from 'zod';
23
+
24
+ const configSchema = z.object({
25
+ // Required variables
26
+ TABLE_NAME: z.string().min(1, 'TABLE_NAME is required'),
27
+
28
+ // Optional with defaults
29
+ AWS_REGION: z.string().default('us-east-1'),
30
+ DEBUG_MODE: z
31
+ .enum(['true', 'false'])
32
+ .default('false')
33
+ .transform((val) => val === 'true'),
34
+ });
35
+
36
+ // Infer the TypeScript type from your schema
37
+ type Config = z.infer<typeof configSchema>;
38
+ ```
39
+
40
+ ### Create and Use ConfigManager
41
+
42
+ ```typescript
43
+ import { createConfigManager } from '@leanstacks/lambda-utils';
44
+
45
+ // Create the manager
46
+ const configManager = createConfigManager(configSchema);
47
+
48
+ // Get validated config (cached after first call)
49
+ const config = configManager.get();
50
+
51
+ // Use your configuration
52
+ console.log(config.TABLE_NAME); // Type-safe access
53
+ console.log(config.AWS_REGION); // Automatically defaults to 'us-east-1'
54
+ console.log(config.DEBUG_MODE); // Typed as boolean
55
+ ```
56
+
57
+ ## Complete Example
58
+
59
+ Here's a realistic Lambda function configuration:
60
+
61
+ ```typescript
62
+ import { z } from 'zod';
63
+ import { createConfigManager } from '@leanstacks/lambda-utils';
64
+
65
+ /**
66
+ * Schema for validating environment variables
67
+ */
68
+ const envSchema = z.object({
69
+ // Required variables
70
+ TASKS_TABLE: z.string().min(1, 'TASKS_TABLE environment variable is required'),
71
+
72
+ // Optional variables with defaults
73
+ AWS_REGION: z.string().default('us-east-1'),
74
+
75
+ // Logging configuration
76
+ LOGGING_ENABLED: z
77
+ .enum(['true', 'false'] as const)
78
+ .default('true')
79
+ .transform((val) => val === 'true'),
80
+ LOGGING_LEVEL: z.enum(['debug', 'info', 'warn', 'error'] as const).default('debug'),
81
+ LOGGING_FORMAT: z.enum(['text', 'json'] as const).default('json'),
82
+
83
+ // CORS configuration
84
+ CORS_ALLOW_ORIGIN: z.string().default('*'),
85
+ });
86
+
87
+ /**
88
+ * Type representing our validated config
89
+ */
90
+ export type Config = z.infer<typeof envSchema>;
91
+
92
+ /**
93
+ * Configuration manager instance
94
+ */
95
+ const configManager = createConfigManager(envSchema);
96
+
97
+ /**
98
+ * Validated configuration object. Singleton.
99
+ */
100
+ export const config = configManager.get();
101
+
102
+ /**
103
+ * Refresh configuration (useful in tests)
104
+ */
105
+ export const refreshConfig = () => configManager.refresh();
106
+ ```
107
+
108
+ Then use it in your handler:
109
+
110
+ ```typescript
111
+ import { config } from './config';
112
+ import { Logger } from '@leanstacks/lambda-utils';
113
+
114
+ const logger = new Logger({
115
+ level: config.LOGGING_LEVEL,
116
+ format: config.LOGGING_FORMAT,
117
+ }).instance;
118
+
119
+ export const handler = async (event: any) => {
120
+ logger.info({
121
+ message: 'Processing request',
122
+ table: config.TASKS_TABLE,
123
+ region: config.AWS_REGION,
124
+ });
125
+
126
+ // Your handler logic here
127
+ };
128
+ ```
129
+
130
+ ## API Reference
131
+
132
+ ### `createConfigManager<T>(schema: T): ConfigManager<z.infer<T>>`
133
+
134
+ Creates a configuration manager instance.
135
+
136
+ **Parameters:**
137
+
138
+ - `schema` - A Zod schema defining your environment variables
139
+
140
+ **Returns:** A `ConfigManager` instance with two methods
141
+
142
+ ### `ConfigManager.get(): T`
143
+
144
+ Gets the validated configuration (cached after the first call).
145
+
146
+ **Throws:** `Error` if validation fails
147
+
148
+ **Returns:** The validated configuration object
149
+
150
+ ```typescript
151
+ const config = configManager.get();
152
+ // First call: validates and caches
153
+ // Subsequent calls: returns cached value
154
+ ```
155
+
156
+ ### `ConfigManager.refresh(): T`
157
+
158
+ Refreshes the configuration by re-validating environment variables against the schema.
159
+
160
+ **Throws:** `Error` if validation fails
161
+
162
+ **Returns:** The newly validated configuration object
163
+
164
+ Use this in tests when you need to change environment variables:
165
+
166
+ ```typescript
167
+ beforeEach(() => {
168
+ process.env.TABLE_NAME = 'test-table';
169
+ configManager.refresh(); // Re-validate with new values
170
+ });
171
+ ```
172
+
173
+ ## Best Practices
174
+
175
+ ### 1. Separate Configuration Module
176
+
177
+ Create a dedicated configuration module for your Lambda function:
178
+
179
+ ```typescript
180
+ // src/config.ts
181
+ import { z } from 'zod';
182
+ import { createConfigManager } from '@leanstacks/lambda-utils';
183
+
184
+ const schema = z.object({
185
+ TABLE_NAME: z.string().min(1),
186
+ AWS_REGION: z.string().default('us-east-1'),
187
+ });
188
+
189
+ export type Config = z.infer<typeof schema>;
190
+
191
+ const configManager = createConfigManager(schema);
192
+
193
+ export const config = configManager.get();
194
+ export const refresh = () => configManager.refresh();
195
+ ```
196
+
197
+ ### 2. Validate Early
198
+
199
+ Call `config.get()` during handler initialization to validate configuration before processing requests:
200
+
201
+ ```typescript
202
+ export const handler = async (event: any, context: any) => {
203
+ // Validation happens here, fails fast if config is invalid
204
+ const config = configManager.get();
205
+
206
+ // Handler logic with validated config
207
+ };
208
+ ```
209
+
210
+ ### 3. Use Enums for Known Values
211
+
212
+ Use `z.enum()` for configuration options with limited valid values:
213
+
214
+ ```typescript
215
+ const schema = z.object({
216
+ LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
217
+ ENVIRONMENT: z.enum(['dev', 'staging', 'prod']),
218
+ });
219
+
220
+ // TypeScript autocomplete for config.LOG_LEVEL
221
+ ```
222
+
223
+ ### 4. Transform String to Boolean
224
+
225
+ Since environment variables are always strings, use `transform()` to convert them:
226
+
227
+ ```typescript
228
+ const schema = z.object({
229
+ ENABLE_FEATURE: z
230
+ .enum(['true', 'false'])
231
+ .default('false')
232
+ .transform((val) => val === 'true'),
233
+ });
234
+
235
+ // config.ENABLE_FEATURE is now a boolean
236
+ if (config.ENABLE_FEATURE) {
237
+ // Feature is enabled
238
+ }
239
+ ```
240
+
241
+ ### 5. Provide Helpful Error Messages
242
+
243
+ Use Zod's second parameter to provide context-specific error messages:
244
+
245
+ ```typescript
246
+ const schema = z.object({
247
+ DATABASE_URL: z.string().url('DATABASE_URL must be a valid URL'),
248
+ API_KEY: z.string().min(32, 'API_KEY must be at least 32 characters'),
249
+ });
250
+ ```
251
+
252
+ ### 6. Test Configuration Validation
253
+
254
+ Test that your schema properly validates configuration:
255
+
256
+ ```typescript
257
+ import { config, refresh } from './config';
258
+
259
+ describe('Configuration', () => {
260
+ it('should load default values', () => {
261
+ delete process.env.AWS_REGION;
262
+ refresh();
263
+ expect(config.AWS_REGION).toBe('us-east-1');
264
+ });
265
+
266
+ it('should validate required variables', () => {
267
+ delete process.env.TABLE_NAME;
268
+ expect(() => refresh()).toThrow();
269
+ });
270
+
271
+ it('should parse boolean values', () => {
272
+ process.env.DEBUG_MODE = 'true';
273
+ refresh();
274
+ expect(config.DEBUG_MODE).toBe(true);
275
+ });
276
+ });
277
+ ```
278
+
279
+ ## Common Patterns
280
+
281
+ ### Database Configuration
282
+
283
+ ```typescript
284
+ const schema = z.object({
285
+ DATABASE_URL: z.string().url(),
286
+ DATABASE_POOL_SIZE: z
287
+ .string()
288
+ .default('10')
289
+ .transform((val) => parseInt(val, 10)),
290
+ DATABASE_TIMEOUT: z
291
+ .string()
292
+ .default('5000')
293
+ .transform((val) => parseInt(val, 10)),
294
+ });
295
+ ```
296
+
297
+ ### Feature Flags
298
+
299
+ ```typescript
300
+ const schema = z.object({
301
+ FEATURE_NEW_UI: z
302
+ .enum(['true', 'false'])
303
+ .default('false')
304
+ .transform((val) => val === 'true'),
305
+ FEATURE_BETA_API: z
306
+ .enum(['true', 'false'])
307
+ .default('false')
308
+ .transform((val) => val === 'true'),
309
+ });
310
+ ```
311
+
312
+ ### Multi-Environment Setup
313
+
314
+ ```typescript
315
+ const schema = z.object({
316
+ ENVIRONMENT: z.enum(['development', 'staging', 'production']),
317
+ LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
318
+ DEBUG_MODE: z
319
+ .enum(['true', 'false'])
320
+ .refine(
321
+ (val) => (val === 'true' ? process.env.ENVIRONMENT === 'development' : true),
322
+ 'DEBUG_MODE can only be true in development',
323
+ )
324
+ .transform((val) => val === 'true'),
325
+ });
326
+ ```
327
+
328
+ ## Error Handling
329
+
330
+ Configuration validation errors include detailed information about what failed:
331
+
332
+ ```typescript
333
+ try {
334
+ const config = configManager.get();
335
+ } catch (error) {
336
+ if (error instanceof Error) {
337
+ console.error(error.message);
338
+ // Output: "Configuration validation failed: TABLE_NAME: String must contain at least 1 character"
339
+ }
340
+ }
341
+ ```
342
+
343
+ Lambda will automatically fail fast if configuration is invalid, which is the desired behavior for Lambda functions.
344
+
345
+ ## Related Documentation
346
+
347
+ - **[Zod Documentation](https://zod.dev/)** – Learn more about schema validation with Zod
348
+ - **[Back to the project documentation](README.md)**
@@ -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)
package/docs/README.md CHANGED
@@ -8,18 +8,20 @@ Lambda Utilities is a collection of pre-configured tools and helpers designed to
8
8
 
9
9
  ## Documentation
10
10
 
11
+ - **[Configuration Guide](./CONFIGURATION.md)** – Validate environment variables with Zod schemas and type-safe configuration management
11
12
  - **[Logging Guide](./LOGGING.md)** – Implement structured logging in your Lambda functions with Pino and automatic AWS context enrichment
12
13
  - **[API Gateway Responses](./API_GATEWAY_RESPONSES.md)** – Format Lambda responses for API Gateway with standard HTTP status codes and headers
13
14
  - **[DynamoDB Client](./DYNAMODB_CLIENT.md)** – Reusable singleton DynamoDB client instances with custom configuration
14
- - **[Configuration](./CONFIGURATION.md)** – Validate environment variables and configuration with Zod type safety
15
- - **[Getting Started](./GETTING_STARTED.md)** – Quick setup and installation instructions
15
+ - **[Lambda Client](./LAMBDA_CLIENT.md)** – Reusable singleton Lambda client for invoking other Lambda functions
16
+ - **[SNS Client](./SNS_CLIENT.md)** – Reusable singleton SNS client for publishing messages to topics with message attributes
17
+ - **[SQS Client](./SQS_CLIENT.md)** – Reusable singleton SQS client for sending messages to queues with message attributes
16
18
 
17
19
  ## Features
18
20
 
19
21
  - 📝 **Structured Logging** – Pino logger pre-configured for Lambda with automatic request context
20
22
  - 📤 **API Response Helpers** – Standard response formatting for API Gateway integration
21
23
  - ⚙️ **Configuration Validation** – Environment variable validation with Zod schema support
22
- - 🔌 **AWS Clients** – Pre-configured AWS SDK v3 clients for common services
24
+ - 🔌 **AWS Clients** – Pre-configured AWS SDK v3 clients for DynamoDB, SNS, SQS, and Lambda
23
25
  - 🔒 **Type Safe** – Full TypeScript support with comprehensive type definitions
24
26
 
25
27
  ## Support