@leanstacks/lambda-utils 0.1.0-alpha.3 → 0.1.0-alpha.4

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,47 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: ''
5
+ labels: bug
6
+ assignees: ''
7
+ ---
8
+
9
+ ## Describe the bug
10
+
11
+ _Provide a clear and concise description of what the bug is._
12
+
13
+ ## Steps to reproduce
14
+
15
+ Steps to reproduce the behavior:
16
+
17
+ 1. Go to '...'
18
+ 2. Click on '....'
19
+ 3. Scroll down to '....'
20
+ 4. See error
21
+
22
+ ## Expected behavior
23
+
24
+ _Provide a clear and concise description of what you expected to happen._
25
+
26
+ ## Screenshots
27
+
28
+ _If applicable, add screenshots to help explain your problem._
29
+
30
+ ## Environment
31
+
32
+ **Desktop (please complete the following information):**
33
+
34
+ - OS: [e.g. iOS]
35
+ - Browser [e.g. chrome, safari]
36
+ - Version [e.g. 22]
37
+
38
+ **Smartphone (please complete the following information):**
39
+
40
+ - Device: [e.g. iPhone6]
41
+ - OS: [e.g. iOS8.1]
42
+ - Browser [e.g. stock browser, safari]
43
+ - Version [e.g. 22]
44
+
45
+ ## Additional context
46
+
47
+ _Add any other context about the problem here._
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: Story
3
+ about: New feature or improvement request
4
+ title: ''
5
+ labels: enhancement
6
+ assignees: ''
7
+ ---
8
+
9
+ ## Describe the story
10
+
11
+ _Provide a clear description of the new feature or improvement to existing functionality._
12
+
13
+ ## Acceptance criteria
14
+
15
+ _Provide clear acceptance criteria to validate the story is complete._
16
+
17
+ [Gherkin syntax](https://cucumber.io/docs/gherkin/reference) example:
18
+
19
+ > Given the 'PERSONA' has 'DONE SOMETHING'
20
+ > When the 'PERSONA' does 'ONE THING'
21
+ > Then the 'PERSONA' must do 'ANOTHER THING'
22
+
23
+ ## Additional context
24
+
25
+ _Add any other context about the story here._
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: Task
3
+ about: A chore unrelated to features or problems
4
+ title: ''
5
+ labels: task
6
+ assignees: ''
7
+ ---
8
+
9
+ ## Describe the task
10
+
11
+ _Provide a clear description of the task._
12
+
13
+ ## Additional context
14
+
15
+ _Add any other context about the task here._
@@ -0,0 +1,39 @@
1
+ :loudspeaker: **Instructions**
2
+
3
+ - Begin with a **DRAFT** pull request.
4
+ - Follow _italicized instructions_ to add detail to assist the reviewers.
5
+ - After completing all checklist items, change the pull request to **READY**.
6
+
7
+ ---
8
+
9
+ ### :wrench: Change Summary
10
+
11
+ _Describe the changes included in this pull request. Link to the associated [GitHub](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) or Jira issue(s)._
12
+
13
+ - see #1234
14
+ - Added the [...]
15
+ - Updated the [...]
16
+ - Fixed the [...]
17
+
18
+ ### :memo: Checklist
19
+
20
+ _Pull request authors must complete the following tasks before marking the PR as ready to review._
21
+
22
+ - [ ] Complete a self-review of changes
23
+ - [ ] Unit tests have been created or updated
24
+ - [ ] The code is free of [new] lint errors and warnings
25
+ - [ ] Update project documentation as needed: README, /docs, JSDoc, etc.
26
+
27
+ ### :test_tube: Steps to Test
28
+
29
+ _Describe the process to test the changes in this pull request._
30
+
31
+ 1. Go to [...]
32
+ 2. Click on [...]
33
+ 3. Verify that [...]
34
+
35
+ ### :link: Additional Information
36
+
37
+ _Optionally, provide additional details, screenshots, or URLs that may assist the reviewer._
38
+
39
+ - [...]
package/README.md CHANGED
@@ -1,3 +1,180 @@
1
1
  # Lambda Utilities
2
2
 
3
- Lambda utilities
3
+ [![npm version](https://badge.fury.io/js/@leanstacks%2Flambda-utils.svg)](https://badge.fury.io/js/@leanstacks%2Flambda-utils)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ A comprehensive TypeScript utility library for AWS Lambda functions. Provides pre-configured logging, API response formatting, configuration validation, and AWS SDK clients—reducing boilerplate and promoting best practices.
7
+
8
+ ## Table of Contents
9
+
10
+ - [Installation](#installation)
11
+ - [Quick Start](#quick-start)
12
+ - [Features](#features)
13
+ - [Documentation](#documentation)
14
+ - [Contributing](#contributing)
15
+ - [License](#license)
16
+ - [Support](#support)
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @leanstacks/lambda-utils
22
+ ```
23
+
24
+ ### Requirements
25
+
26
+ - Node.js 24.x or higher
27
+ - TypeScript 5.0 or higher
28
+
29
+ ## Quick Start
30
+
31
+ ### Logging Example
32
+
33
+ ```typescript
34
+ import { Logger, withRequestTracking } from '@leanstacks/lambda-utils';
35
+
36
+ const logger = new Logger().instance;
37
+
38
+ export const handler = async (event: any, context: any) => {
39
+ withRequestTracking(event, context);
40
+
41
+ logger.info('Processing request');
42
+
43
+ // Your Lambda handler logic here
44
+
45
+ return { statusCode: 200, body: 'Success' };
46
+ };
47
+ ```
48
+
49
+ ### API Response Example
50
+
51
+ ```typescript
52
+ import { success, badRequest } from '@leanstacks/lambda-utils';
53
+
54
+ export const handler = async (event: any) => {
55
+ if (!event.body) {
56
+ return badRequest({ message: 'Body is required' });
57
+ }
58
+
59
+ // Process request
60
+
61
+ return success({ message: 'Request processed successfully' });
62
+ };
63
+ ```
64
+
65
+ ## Features
66
+
67
+ - **📝 Structured Logging** – Pino logger pre-configured for Lambda with automatic AWS request context enrichment
68
+ - **📤 API Response Helpers** – Standard response formatting for API Gateway with proper HTTP status codes
69
+ - **⚙️ Configuration Validation** – Environment variable validation with Zod schema support
70
+ - **🔌 AWS SDK Clients** – Pre-configured AWS SDK v3 clients for DynamoDB, Lambda, and more
71
+ - **🔒 Full TypeScript Support** – Complete type definitions and IDE autocomplete
72
+ - **⚡ Lambda Optimized** – Designed for performance in serverless environments
73
+
74
+ ## Documentation
75
+
76
+ Comprehensive guides and examples are available in the `docs` directory:
77
+
78
+ | Guide | Description |
79
+ | ------------------------------------------------------------ | ---------------------------------------------------------------------- |
80
+ | **[Logging Guide](./docs/LOGGING.md)** | Configure and use structured logging with automatic AWS Lambda context |
81
+ | **[API Gateway Responses](./docs/API_GATEWAY_RESPONSES.md)** | Format responses for API Gateway with standard HTTP patterns |
82
+ | **[Configuration](./docs/CONFIGURATION.md)** | Validate and manage environment variables with type safety |
83
+ | **[AWS Clients](./docs/CLIENTS.md)** | Use pre-configured AWS SDK v3 clients in your handlers |
84
+ | **[Getting Started](./docs/GETTING_STARTED.md)** | Setup and first steps guide |
85
+
86
+ ## Usage
87
+
88
+ ### Logging
89
+
90
+ The Logger utility provides structured logging configured specifically for AWS Lambda:
91
+
92
+ ```typescript
93
+ import { Logger } from '@leanstacks/lambda-utils';
94
+
95
+ const logger = new Logger({
96
+ level: 'info', // debug, info, warn, error
97
+ format: 'json', // json or text
98
+ }).instance;
99
+
100
+ logger.info({ message: 'User authenticated', userId: '12345' });
101
+ logger.error({ message: 'Operation failed', error: err.message });
102
+ ```
103
+
104
+ **→ See [Logging Guide](./docs/LOGGING.md) for detailed configuration and best practices**
105
+
106
+ ### API Responses
107
+
108
+ Generate properly formatted responses for API Gateway:
109
+
110
+ ```typescript
111
+ import { success, error, created, badRequest } from '@leanstacks/lambda-utils';
112
+
113
+ export const handler = async (event: any) => {
114
+ return success({
115
+ data: { id: '123', name: 'Example' },
116
+ });
117
+ };
118
+ ```
119
+
120
+ **→ See [API Gateway Responses](./docs/API_GATEWAY_RESPONSES.md) for all response types**
121
+
122
+ ### Configuration Validation
123
+
124
+ Validate your Lambda environment configuration:
125
+
126
+ ```typescript
127
+ import { validateConfig } from '@leanstacks/lambda-utils';
128
+ import { z } from 'zod';
129
+
130
+ const configSchema = z.object({
131
+ DATABASE_URL: z.string().url(),
132
+ LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']),
133
+ API_KEY: z.string(),
134
+ });
135
+
136
+ const config = validateConfig(configSchema);
137
+ ```
138
+
139
+ **→ See [Configuration](./docs/CONFIGURATION.md) for validation patterns**
140
+
141
+ ### AWS Clients
142
+
143
+ Use pre-configured AWS SDK v3 clients:
144
+
145
+ ```typescript
146
+ import { getDynamoDBClient, getLambdaClient } from '@leanstacks/lambda-utils';
147
+
148
+ const dynamoDB = getDynamoDBClient();
149
+ const lambda = getLambdaClient();
150
+
151
+ // Use clients for API calls
152
+ ```
153
+
154
+ **→ See [AWS Clients](./docs/CLIENTS.md) for available clients and examples**
155
+
156
+ ## Examples
157
+
158
+ Example Lambda functions using Lambda Utilities are available in the repository:
159
+
160
+ - API Gateway with logging and response formatting
161
+ - Configuration validation and DynamoDB integration
162
+ - Error handling and structured logging
163
+
164
+ ## Reporting Issues
165
+
166
+ If you encounter a bug or have a feature request, please report it on [GitHub Issues](https://github.com/leanstacks/lambda-utils/issues). Include as much detail as possible to help us investigate and resolve the issue quickly.
167
+
168
+ ## License
169
+
170
+ This project is licensed under the MIT License - see [LICENSE](./LICENSE) file for details.
171
+
172
+ ## Support
173
+
174
+ - **Issues & Questions:** [GitHub Issues](https://github.com/leanstacks/lambda-utils/issues)
175
+ - **Documentation:** [docs](./docs/README.md)
176
+ - **NPM Package:** [@leanstacks/lambda-utils](https://www.npmjs.com/package/@leanstacks/lambda-utils)
177
+
178
+ ## Changelog
179
+
180
+ See the project [releases](https://github.com/leanstacks/lambda-utils/releases) for version history and updates.
@@ -0,0 +1,333 @@
1
+ # Logging Guide
2
+
3
+ This guide explains how to use the Logger utility to implement structured logging in your AWS Lambda functions using TypeScript.
4
+
5
+ ## Overview
6
+
7
+ The Logger utility provides a thin wrapper around [Pino](https://getpino.io/) configured specifically for AWS Lambda. It automatically includes Lambda request context information in your logs and supports multiple output formats suitable for CloudWatch.
8
+
9
+ ## Installation
10
+
11
+ The Logger utility is included in the `@leanstacks/lambda-utils` package:
12
+
13
+ ```bash
14
+ npm install @leanstacks/lambda-utils
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ### Basic Usage
20
+
21
+ ```typescript
22
+ import { Logger } from '@leanstacks/lambda-utils';
23
+
24
+ const logger = new Logger().instance;
25
+
26
+ export const handler = async (event: any, context: any) => {
27
+ logger.info('[Handler] > Processing request');
28
+
29
+ // Your handler logic here
30
+
31
+ logger.info({ key: 'value' }, '[Handler] < Completed request');
32
+
33
+ return { statusCode: 200, body: 'Success' };
34
+ };
35
+ ```
36
+
37
+ ## Configuration
38
+
39
+ The Logger accepts a configuration object to customize its behavior:
40
+
41
+ ```typescript
42
+ import { Logger } from '@leanstacks/lambda-utils';
43
+
44
+ const logger = new Logger({
45
+ enabled: true, // Enable/disable logging (default: true)
46
+ level: 'info', // Minimum log level (default: 'info')
47
+ format: 'json', // Output format: 'json' or 'text' (default: 'json')
48
+ }).instance;
49
+ ```
50
+
51
+ ### Configuration Options
52
+
53
+ | Option | Type | Default | Description |
54
+ | --------- | ---------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------- |
55
+ | `enabled` | `boolean` | `true` | Whether logging is enabled. Set to `false` to disable all logging output. |
56
+ | `level` | `'debug' \| 'info' \| 'warn' \| 'error'` | `'info'` | Minimum log level to output. Messages below this level are filtered out. |
57
+ | `format` | `'json' \| 'text'` | `'json'` | Output format for log messages. Use `'json'` for structured logging or `'text'` for human-readable logs. |
58
+
59
+ ### Log Levels
60
+
61
+ Log levels are ordered by severity:
62
+
63
+ - **`debug`**: Detailed information for diagnosing problems (lowest severity)
64
+ - **`info`**: General informational messages about application flow
65
+ - **`warn`**: Warning messages for potentially harmful situations
66
+ - **`error`**: Error messages for serious problems (highest severity)
67
+
68
+ ## Logging Examples
69
+
70
+ ### Basic Logging
71
+
72
+ ```typescript
73
+ const logger = new Logger().instance;
74
+
75
+ logger.debug('Detailed diagnostic information');
76
+ logger.info('Application event or milestone');
77
+ logger.warn('Warning: something unexpected occurred');
78
+ logger.error('Error: operation failed');
79
+ ```
80
+
81
+ When the log message contains a simple string, pass the string as the only aregument to the logger function.
82
+
83
+ ### Structured Logging with Objects
84
+
85
+ ```typescript
86
+ const logger = new Logger().instance;
87
+
88
+ const userId = '12345';
89
+ const permissions = ['user:read', 'user:write'];
90
+
91
+ logger.info(
92
+ {
93
+ userId,
94
+ permissions,
95
+ },
96
+ 'User authenticated',
97
+ );
98
+ ```
99
+
100
+ When using structured logging, pass the context attributes object as the first parameter and the string log message as the second parameter. This allows the logger to properly format messages as either JSON or text.
101
+
102
+ ### Error Logging
103
+
104
+ ```typescript
105
+ const logger = new Logger().instance;
106
+
107
+ try {
108
+ // Your code here
109
+ } catch (error) {
110
+ logger.error(
111
+ {
112
+ error: error instanceof Error ? error.message : String(error),
113
+ stack: error instanceof Error ? error.stack : undefined,
114
+ },
115
+ 'Operation failed',
116
+ );
117
+ }
118
+ ```
119
+
120
+ ## Advanced Usage
121
+
122
+ ### Request Tracking Middleware
123
+
124
+ The `withRequestTracking` middleware automatically adds AWS Lambda context information to all log messages. This enriches your logs with request IDs, function names, and other Lambda metadata.
125
+
126
+ ```typescript
127
+ import { Logger, withRequestTracking } from '@leanstacks/lambda-utils';
128
+
129
+ const logger = new Logger().instance;
130
+
131
+ export const handler = async (event: any, context: any) => {
132
+ // Add Lambda context to all subsequent log messages
133
+ withRequestTracking(event, context);
134
+
135
+ logger.info('Request started');
136
+
137
+ // Your handler logic here
138
+
139
+ return { statusCode: 200 };
140
+ };
141
+ ```
142
+
143
+ ### Environment-Based Configuration
144
+
145
+ Configure logging based on your environment:
146
+
147
+ ```typescript
148
+ import { Logger } from '@leanstacks/lambda-utils';
149
+
150
+ const isProduction = process.env.NODE_ENV === 'production';
151
+ const isDevelopment = process.env.NODE_ENV === 'development';
152
+
153
+ const logger = new Logger({
154
+ level: isDevelopment ? 'debug' : 'info',
155
+ format: isProduction ? 'json' : 'text',
156
+ }).instance;
157
+ ```
158
+
159
+ ### Singleton Pattern for Reusable Logger
160
+
161
+ For best performance, create a single logger instance and reuse it throughout your application:
162
+
163
+ ```typescript
164
+ // logger.ts
165
+ import { Logger } from '@leanstacks/lambda-utils';
166
+
167
+ export const logger = new Logger({
168
+ level: (process.env.LOG_LEVEL as 'debug' | 'info' | 'warn' | 'error') || 'info',
169
+ format: (process.env.LOG_FORMAT as 'json' | 'text') || 'json',
170
+ }).instance;
171
+ ```
172
+
173
+ Then import it in your handlers:
174
+
175
+ ```typescript
176
+ // handler.ts
177
+ import { logger } from './logger';
178
+
179
+ export const handler = async (event: any) => {
180
+ logger.info({ message: 'Processing event', event });
181
+
182
+ // Your handler logic here
183
+
184
+ return { statusCode: 200 };
185
+ };
186
+ ```
187
+
188
+ ## Best Practices
189
+
190
+ ### 1. Use Structured Logging
191
+
192
+ Prefer objects over string concatenation:
193
+
194
+ ```typescript
195
+ // ✅ Good: Structured logging
196
+ logger.info(
197
+ {
198
+ userId: user.id,
199
+ },
200
+ 'User login',
201
+ );
202
+
203
+ // ❌ Avoid: String concatenation
204
+ logger.info(`User ${user.id} logged in at ${new Date().toISOString()}`);
205
+ ```
206
+
207
+ ### 2. Include Relevant Context
208
+
209
+ Include all relevant information that will help with debugging and monitoring:
210
+
211
+ ```typescript
212
+ logger.info(
213
+ {
214
+ orderId: order.id,
215
+ amount: order.total,
216
+ paymentMethod: order.paymentMethod,
217
+ duration: endTime - startTime,
218
+ },
219
+ 'Payment processed',
220
+ );
221
+ ```
222
+
223
+ ### 3. Use Appropriate Log Levels
224
+
225
+ Choose log levels that match the severity and importance of the event:
226
+
227
+ ```typescript
228
+ logger.debug('Cache hit for user profile'); // Development diagnostics
229
+ logger.info('User registered successfully'); // Normal operations
230
+ logger.warn('API rate limit approaching'); // Potential issues
231
+ logger.error('Database connection failed'); // Critical failures
232
+ ```
233
+
234
+ ### 4. Avoid Logging Sensitive Information
235
+
236
+ Never log passwords, API keys, tokens, or personally identifiable information (PII):
237
+
238
+ ```typescript
239
+ // ❌ Never do this
240
+ logger.info({ password: user.password });
241
+
242
+ // ✅ Log safe information
243
+ logger.info({ userId: user.id, email: user.email });
244
+ ```
245
+
246
+ ### 5. Performance Considerations
247
+
248
+ The logger is optimized for Lambda and uses lazy evaluation. Only use `debug` level logs in development:
249
+
250
+ ```typescript
251
+ // Disable debug logs in production for better performance
252
+ const logger = new Logger({
253
+ level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
254
+ }).instance;
255
+ ```
256
+
257
+ ## Output Formats
258
+
259
+ ### JSON Format (Default)
260
+
261
+ Best for production environments and log aggregation services like CloudWatch, Datadog, or Splunk:
262
+
263
+ ```json
264
+ {
265
+ "timestamp": "2025-12-18T13:42:40.502Z",
266
+ "level": "INFO",
267
+ "requestId": "req-abc-123",
268
+ "message": {
269
+ "awsRequestId": "req-def-456",
270
+ "x-correlation-trace-id": "Root=1-2a-28ab;Parent=1e6;Sampled=0;Lineage=1:bf3:0",
271
+ "x-correlation-id": "crl-abc-123",
272
+ "time": 1702900123456,
273
+ "pid": 1,
274
+ "hostname": "lambda-container",
275
+ "key": "value",
276
+ "msg": "User authenticated"
277
+ }
278
+ }
279
+ ```
280
+
281
+ ### Text Format
282
+
283
+ Best for local development and human-readable output:
284
+
285
+ ```
286
+ [2024-12-18T12:34:56.789Z] INFO: User authenticated userId=12345 requestId=req-abc-123
287
+ ```
288
+
289
+ ## Testing
290
+
291
+ When testing Lambda functions that use the logger, you can mock or configure the logger:
292
+
293
+ ```typescript
294
+ import { Logger } from '@leanstacks/lambda-utils';
295
+
296
+ describe('MyHandler', () => {
297
+ it('should log info message', () => {
298
+ const logger = new Logger({
299
+ enabled: true,
300
+ level: 'info',
301
+ }).instance;
302
+
303
+ const spyLog = jest.spyOn(logger, 'info');
304
+
305
+ // Your test code here
306
+
307
+ expect(spyLog).toHaveBeenCalledWith({
308
+ message: 'Expected message',
309
+ });
310
+ });
311
+ });
312
+ ```
313
+
314
+ ## Troubleshooting
315
+
316
+ ### Logs Not Appearing
317
+
318
+ 1. **Check if logging is enabled**: Verify `enabled: true` in configuration
319
+ 2. **Check log level**: Ensure the message log level meets the configured minimum level. Check the Lambda function [Logging configuration application log level](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs-log-level.html).
320
+ 3. **Check CloudWatch**: Logs appear in CloudWatch Logs under `/aws/lambda/[function-name]`
321
+
322
+ ### Performance Issues
323
+
324
+ 1. **Use appropriate log level**: Reduce logs in production by using `level: 'info'`
325
+ 2. **Limit object size**: Avoid logging very large objects that could impact performance
326
+ 3. **Use singleton pattern**: Create one logger instance and reuse it
327
+
328
+ ## Further reading
329
+
330
+ - [Pino Documentation](https://getpino.io/)
331
+ - [AWS Lambda Environment and Context](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html)
332
+ - [CloudWatch Logs Insights](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_QuerySyntax.html)
333
+ - [Back to the project documentation](README.md)
package/docs/README.md CHANGED
@@ -2,47 +2,26 @@
2
2
 
3
3
  Welcome to the Lambda Utilities documentation. This library provides a comprehensive set of utilities and helper functions to streamline the development of AWS Lambda functions using TypeScript.
4
4
 
5
- ## Table of Contents
5
+ ## Overview
6
6
 
7
- - [Getting Started](./GETTING_STARTED.md)
8
- - [Logging](./LOGGING.md)
9
- - [API Gateway Responses](./API_GATEWAY_RESPONSES.md)
10
- - [Configuration](./CONFIGURATION.md)
11
- - [Clients](./CLIENTS.md)
7
+ Lambda Utilities is a collection of pre-configured tools and helpers designed to reduce boilerplate code when developing AWS Lambda functions. It provides utilities for logging, API responses, configuration validation, and AWS SDK client management—all with full TypeScript support.
12
8
 
13
- ## Quick Start
9
+ ## Documentation
14
10
 
15
- Install the package:
16
-
17
- ```bash
18
- npm install @leanstacks/lambda-utils
19
- ```
20
-
21
- Use a utility in your Lambda function:
22
-
23
- ```typescript
24
- import { getLogger } from '@leanstacks/lambda-utils';
25
- import { success } from '@leanstacks/lambda-utils';
26
-
27
- const logger = getLogger();
28
-
29
- export const handler = async (event: any) => {
30
- logger.info({ message: 'Processing event', event });
31
-
32
- // Your handler logic here
33
-
34
- return success({ message: 'Success' });
35
- };
36
- ```
11
+ - **[Logging Guide](./LOGGING.md)** – Implement structured logging in your Lambda functions with Pino and automatic AWS context enrichment
12
+ - **[API Gateway Responses](./API_GATEWAY_RESPONSES.md)** – Format Lambda responses for API Gateway with standard HTTP status codes and headers
13
+ - **[Configuration](./CONFIGURATION.md)** – Validate environment variables and configuration with Zod type safety
14
+ - **[AWS Clients](./CLIENTS.md)** – Pre-configured AWS SDK v3 clients optimized for Lambda
15
+ - **[Getting Started](./GETTING_STARTED.md)** – Quick setup and installation instructions
37
16
 
38
17
  ## Features
39
18
 
40
- - **Logging:** Structured logging with Pino configured for Lambda
41
- - **API Responses:** Standard response formatting for API Gateway
42
- - **Configuration:** Environment variable validation with Zod
43
- - **AWS Clients:** Pre-configured AWS SDK v3 clients
44
- - **Type Safe:** Full TypeScript support with comprehensive types
19
+ - 📝 **Structured Logging** Pino logger pre-configured for Lambda with automatic request context
20
+ - 📤 **API Response Helpers** – Standard response formatting for API Gateway integration
21
+ - ⚙️ **Configuration Validation** – Environment variable validation with Zod schema support
22
+ - 🔌 **AWS Clients** Pre-configured AWS SDK v3 clients for common services
23
+ - 🔒 **Type Safe** Full TypeScript support with comprehensive type definitions
45
24
 
46
25
  ## Support
47
26
 
48
- For issues or questions, please visit the [GitHub repository](https://github.com/leanstacks/lambda-utils).
27
+ For issues or questions, visit the [GitHub repository](https://github.com/leanstacks/lambda-utils).
package/jest.config.ts CHANGED
@@ -3,13 +3,13 @@ import type { Config } from 'jest';
3
3
  const config: Config = {
4
4
  preset: 'ts-jest',
5
5
  testEnvironment: 'node',
6
- rootDir: 'src',
7
- testMatch: ['**/*.test.ts'],
6
+ testMatch: ['<rootDir>/src/**/*.test.ts'],
8
7
  moduleNameMapper: {
9
8
  '^@/(.*)$': '<rootDir>/$1',
10
9
  },
11
- collectCoverageFrom: ['**/*.ts', '!**/*.test.ts'],
12
- coveragePathIgnorePatterns: ['/node_modules/'],
10
+ coverageDirectory: 'coverage',
11
+ collectCoverageFrom: ['src/**/*.ts', '!src/**/*.test.ts', '!src/**/index.ts'],
12
+ coverageReporters: ['json', 'json-summary', 'lcov', 'text', 'clover'],
13
13
  };
14
14
 
15
15
  export default config;
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@leanstacks/lambda-utils",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.1.0-alpha.4",
4
4
  "description": "A collection of utilities and helper functions designed to streamline the development of AWS Lambda functions using TypeScript.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "rollup -c",
9
9
  "build:watch": "rollup -c -w",
10
- "clean": "rimraf dist",
11
- "test": "jest",
12
- "test:watch": "jest --watch",
13
- "test:coverage": "jest --coverage",
10
+ "clean": "rimraf coverage dist",
11
+ "format": "prettier --write \"src/**/*.ts\"",
12
+ "format:check": "prettier --check \"src/**/*.ts\"",
14
13
  "lint": "eslint src",
15
14
  "lint:fix": "eslint src --fix",
16
- "format": "prettier --write \"src/**/*.ts\"",
17
- "format:check": "prettier --check \"src/**/*.ts\""
15
+ "test": "jest",
16
+ "test:watch": "jest --watch",
17
+ "test:coverage": "jest --coverage"
18
18
  },
19
19
  "keywords": [
20
20
  "lambda",
@@ -44,7 +44,6 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "pino": "10.1.0",
47
- "pino-lambda": "4.4.1",
48
- "zod": "4.2.1"
47
+ "pino-lambda": "4.4.1"
49
48
  }
50
49
  }
@@ -0,0 +1,400 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import pino from 'pino';
3
+ import { CloudwatchLogFormatter, pinoLambdaDestination, StructuredLogFormatter } from 'pino-lambda';
4
+ import { Logger, withRequestTracking } from './logger';
5
+
6
+ // Mock pino-lambda module
7
+ jest.mock('pino-lambda');
8
+
9
+ // Mock pino module
10
+ jest.mock('pino');
11
+
12
+ describe('Logger', () => {
13
+ // Setup and cleanup
14
+ beforeEach(() => {
15
+ jest.clearAllMocks();
16
+ });
17
+
18
+ afterEach(() => {
19
+ jest.clearAllMocks();
20
+ });
21
+
22
+ describe('withRequestTracking', () => {
23
+ it('should be exported from logger module', () => {
24
+ // Arrange
25
+ // withRequestTracking is exported from logger.ts and is the result of calling lambdaRequestTracker()
26
+ // from pino-lambda. Jest mocks mean it will be the mocked value.
27
+
28
+ // Act & Assert
29
+ // We just verify that it was exported (defined by the import statement at the top)
30
+ // The actual functionality of lambdaRequestTracker is tested in pino-lambda
31
+ expect(typeof withRequestTracking === 'function' || withRequestTracking === undefined).toBe(true);
32
+ });
33
+ });
34
+
35
+ describe('constructor', () => {
36
+ it('should create Logger with default configuration', () => {
37
+ // Arrange
38
+ const mockLogger = { info: jest.fn() };
39
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
40
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
41
+
42
+ // Act
43
+ const logger = new Logger();
44
+
45
+ // Assert
46
+ expect(logger).toBeDefined();
47
+ });
48
+
49
+ it('should create Logger with custom enabled configuration', () => {
50
+ // Arrange
51
+ const mockLogger = { info: jest.fn() };
52
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
53
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
54
+
55
+ // Act
56
+ const logger = new Logger({ enabled: false });
57
+ const _instance = logger.instance;
58
+
59
+ // Assert
60
+ expect(pino).toHaveBeenCalledWith(
61
+ expect.objectContaining({
62
+ enabled: false,
63
+ }),
64
+ expect.anything(),
65
+ );
66
+ });
67
+
68
+ it('should create Logger with custom log level configuration', () => {
69
+ // Arrange
70
+ const mockLogger = { info: jest.fn() };
71
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
72
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
73
+
74
+ // Act
75
+ const logger = new Logger({ level: 'debug' });
76
+ const _instance = logger.instance;
77
+
78
+ // Assert
79
+ expect(pino).toHaveBeenCalledWith(
80
+ expect.objectContaining({
81
+ level: 'debug',
82
+ }),
83
+ expect.anything(),
84
+ );
85
+ });
86
+
87
+ it('should create Logger with custom format configuration (json)', () => {
88
+ // Arrange
89
+ const mockLogger = { info: jest.fn() };
90
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
91
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
92
+
93
+ // Act
94
+ const logger = new Logger({ format: 'json' });
95
+ const _instance = logger.instance;
96
+
97
+ // Assert
98
+ expect(StructuredLogFormatter).toHaveBeenCalled();
99
+ });
100
+
101
+ it('should create Logger with custom format configuration (text)', () => {
102
+ // Arrange
103
+ const mockLogger = { info: jest.fn() };
104
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
105
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
106
+
107
+ // Act
108
+ const logger = new Logger({ format: 'text' });
109
+ const _instance = logger.instance;
110
+
111
+ // Assert
112
+ expect(CloudwatchLogFormatter).toHaveBeenCalled();
113
+ });
114
+
115
+ it('should merge provided config with defaults', () => {
116
+ // Arrange
117
+ const mockLogger = { info: jest.fn() };
118
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
119
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
120
+
121
+ // Act
122
+ const logger = new Logger({ level: 'error' });
123
+ const _instance = logger.instance;
124
+
125
+ // Assert
126
+ expect(pino).toHaveBeenCalledWith(
127
+ expect.objectContaining({
128
+ enabled: true,
129
+ level: 'error',
130
+ }),
131
+ expect.anything(),
132
+ );
133
+ });
134
+ });
135
+
136
+ describe('instance getter', () => {
137
+ it('should return a Pino logger instance', () => {
138
+ // Arrange
139
+ const mockLogger = { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() };
140
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
141
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
142
+ const logger = new Logger();
143
+
144
+ // Act
145
+ const instance = logger.instance;
146
+
147
+ // Assert
148
+ expect(instance).toBe(mockLogger);
149
+ expect(instance).toHaveProperty('info');
150
+ expect(instance).toHaveProperty('warn');
151
+ expect(instance).toHaveProperty('error');
152
+ expect(instance).toHaveProperty('debug');
153
+ });
154
+
155
+ it('should create logger instance only once (lazy initialization)', () => {
156
+ // Arrange
157
+ const mockLogger = { info: jest.fn() };
158
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
159
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
160
+ const logger = new Logger();
161
+
162
+ // Act
163
+ const instance1 = logger.instance;
164
+ const instance2 = logger.instance;
165
+
166
+ // Assert
167
+ expect(instance1).toBe(instance2);
168
+ expect(pino).toHaveBeenCalledTimes(1);
169
+ });
170
+
171
+ it('should configure pino with enabled flag', () => {
172
+ // Arrange
173
+ const mockLogger = { info: jest.fn() };
174
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
175
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
176
+
177
+ // Act
178
+ const logger = new Logger({ enabled: false });
179
+ const _instance = logger.instance;
180
+
181
+ // Assert
182
+ expect(pino).toHaveBeenCalledWith(
183
+ expect.objectContaining({
184
+ enabled: false,
185
+ }),
186
+ expect.anything(),
187
+ );
188
+ });
189
+
190
+ it('should configure pino with log level', () => {
191
+ // Arrange
192
+ const mockLogger = { info: jest.fn() };
193
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
194
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
195
+
196
+ // Act
197
+ const logger = new Logger({ level: 'warn' });
198
+ const _instance = logger.instance;
199
+
200
+ // Assert
201
+ expect(pino).toHaveBeenCalledWith(
202
+ expect.objectContaining({
203
+ level: 'warn',
204
+ }),
205
+ expect.anything(),
206
+ );
207
+ });
208
+
209
+ it('should call pinoLambdaDestination with selected formatter', () => {
210
+ // Arrange
211
+ const mockLogger = { info: jest.fn() };
212
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
213
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
214
+
215
+ // Act
216
+ const logger = new Logger({ format: 'json' });
217
+ const _instance = logger.instance;
218
+
219
+ // Assert
220
+ expect(pinoLambdaDestination).toHaveBeenCalledWith(
221
+ expect.objectContaining({
222
+ formatter: expect.any(StructuredLogFormatter),
223
+ }),
224
+ );
225
+ });
226
+
227
+ it('should use StructuredLogFormatter when format is json', () => {
228
+ // Arrange
229
+ const mockLogger = { info: jest.fn() };
230
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
231
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
232
+
233
+ // Act
234
+ const logger = new Logger({ format: 'json' });
235
+ const _instance = logger.instance;
236
+
237
+ // Assert
238
+ expect(StructuredLogFormatter).toHaveBeenCalled();
239
+ });
240
+
241
+ it('should use CloudwatchLogFormatter when format is text', () => {
242
+ // Arrange
243
+ const mockLogger = { info: jest.fn() };
244
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
245
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
246
+
247
+ // Act
248
+ const logger = new Logger({ format: 'text' });
249
+ const _instance = logger.instance;
250
+
251
+ // Assert
252
+ expect(CloudwatchLogFormatter).toHaveBeenCalled();
253
+ });
254
+ });
255
+
256
+ describe('Logger configurations', () => {
257
+ it('should support all log levels', () => {
258
+ // Arrange
259
+ const mockLogger = { info: jest.fn() };
260
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
261
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
262
+ const levels: Array<'debug' | 'info' | 'warn' | 'error'> = ['debug', 'info', 'warn', 'error'];
263
+
264
+ // Act & Assert
265
+ levels.forEach((level) => {
266
+ jest.clearAllMocks();
267
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
268
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
269
+
270
+ const logger = new Logger({ level });
271
+ const _instance = logger.instance;
272
+
273
+ expect(pino).toHaveBeenCalledWith(
274
+ expect.objectContaining({
275
+ level,
276
+ }),
277
+ expect.anything(),
278
+ );
279
+ });
280
+ });
281
+
282
+ it('should support both json and text formats', () => {
283
+ // Arrange
284
+ const mockLogger = { info: jest.fn() };
285
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
286
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
287
+
288
+ // Act
289
+ const jsonLogger = new Logger({ format: 'json' });
290
+ const _jsonInstance = jsonLogger.instance;
291
+ const structuredFormatterCallCount = (StructuredLogFormatter as jest.Mock).mock.calls.length;
292
+
293
+ jest.clearAllMocks();
294
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
295
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
296
+
297
+ const textLogger = new Logger({ format: 'text' });
298
+ const _textInstance = textLogger.instance;
299
+
300
+ // Assert
301
+ expect(structuredFormatterCallCount).toBeGreaterThan(0);
302
+ expect(CloudwatchLogFormatter).toHaveBeenCalled();
303
+ });
304
+
305
+ it('should support enabled and disabled logging', () => {
306
+ // Arrange
307
+ const mockLogger = { info: jest.fn() };
308
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
309
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
310
+
311
+ // Act
312
+ const enabledLogger = new Logger({ enabled: true });
313
+ const _enabledInstance = enabledLogger.instance;
314
+ const firstCallArgs = jest.mocked(pino).mock.calls[0];
315
+
316
+ jest.clearAllMocks();
317
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
318
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
319
+
320
+ const disabledLogger = new Logger({ enabled: false });
321
+ const _disabledInstance = disabledLogger.instance;
322
+ const secondCallArgs = jest.mocked(pino).mock.calls[0];
323
+
324
+ // Assert
325
+ expect(firstCallArgs[0]).toEqual(expect.objectContaining({ enabled: true }));
326
+ expect(secondCallArgs[0]).toEqual(expect.objectContaining({ enabled: false }));
327
+ });
328
+ });
329
+
330
+ describe('integration scenarios', () => {
331
+ it('should create multiple logger instances with different configurations', () => {
332
+ // Arrange
333
+ const mockLogger1 = { info: jest.fn(), level: 'debug' };
334
+ const mockLogger2 = { info: jest.fn(), level: 'error' };
335
+ jest
336
+ .mocked(pino)
337
+ .mockReturnValueOnce(mockLogger1 as unknown as any)
338
+ .mockReturnValueOnce(mockLogger2 as unknown as any);
339
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
340
+
341
+ // Act
342
+ const debugLogger = new Logger({ level: 'debug', format: 'json' });
343
+ const errorLogger = new Logger({ level: 'error', format: 'text' });
344
+
345
+ const instance1 = debugLogger.instance;
346
+ const instance2 = errorLogger.instance;
347
+
348
+ // Assert
349
+ expect(instance1).toBe(mockLogger1);
350
+ expect(instance2).toBe(mockLogger2);
351
+ expect(pino).toHaveBeenCalledTimes(2);
352
+ });
353
+
354
+ it('should handle partial configuration overrides', () => {
355
+ // Arrange
356
+ const mockLogger = { info: jest.fn() };
357
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
358
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
359
+
360
+ // Act
361
+ const logger = new Logger({ level: 'warn' });
362
+ const _instance = logger.instance;
363
+
364
+ // Assert - should have custom level but default enabled and format
365
+ expect(pino).toHaveBeenCalledWith(
366
+ expect.objectContaining({
367
+ enabled: true,
368
+ level: 'warn',
369
+ }),
370
+ expect.anything(),
371
+ );
372
+ expect(StructuredLogFormatter).toHaveBeenCalled();
373
+ });
374
+
375
+ it('should handle full configuration override', () => {
376
+ // Arrange
377
+ const mockLogger = { info: jest.fn() };
378
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
379
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
380
+
381
+ // Act
382
+ const logger = new Logger({
383
+ enabled: false,
384
+ level: 'error',
385
+ format: 'text',
386
+ });
387
+ const _instance = logger.instance;
388
+
389
+ // Assert
390
+ expect(pino).toHaveBeenCalledWith(
391
+ expect.objectContaining({
392
+ enabled: false,
393
+ level: 'error',
394
+ }),
395
+ expect.anything(),
396
+ );
397
+ expect(CloudwatchLogFormatter).toHaveBeenCalled();
398
+ });
399
+ });
400
+ });
package/dist/index.d.ts DELETED
@@ -1 +0,0 @@
1
- export { Logger, LoggerConfig, withRequestTracking } from './logging/logger';
package/dist/index.js DELETED
@@ -1,78 +0,0 @@
1
- import pino from 'pino';
2
- import { lambdaRequestTracker, StructuredLogFormatter, CloudwatchLogFormatter, pinoLambdaDestination } from 'pino-lambda';
3
-
4
- /**
5
- * Logger middleware which adds AWS Lambda attributes to log messages.
6
- *
7
- * @example
8
- * ```typescript
9
- * import { withRequestTracking } from '@leanstacks/lambda-utils';
10
- *
11
- * export const handler = async (event, context) => {
12
- * withRequestTracking(event, context);
13
- *
14
- * // Your Lambda handler logic here
15
- * };
16
- * ```
17
- */
18
- const withRequestTracking = lambdaRequestTracker();
19
- /**
20
- * Logger class which provides a Pino logger instance with AWS Lambda attributes.
21
- *
22
- * @example
23
- * ```typescript
24
- * import { Logger } from '@leanstacks/lambda-utils';
25
- * const logger = new Logger().instance;
26
- *
27
- * logger.info('Hello, world!');
28
- * ```
29
- */
30
- class Logger {
31
- constructor(config) {
32
- this._loggerConfig = {
33
- enabled: true,
34
- level: 'info',
35
- format: 'json',
36
- };
37
- this._instance = null;
38
- /**
39
- * Creates a new, fully configured Pino logger instance.
40
- */
41
- this._createLogger = () => {
42
- const formatter = this._loggerConfig.format === 'json' ? new StructuredLogFormatter() : new CloudwatchLogFormatter();
43
- const lambdaDestination = pinoLambdaDestination({
44
- formatter,
45
- });
46
- return pino({
47
- enabled: this._loggerConfig.enabled,
48
- level: this._loggerConfig.level,
49
- }, lambdaDestination);
50
- };
51
- if (config) {
52
- this._loggerConfig = {
53
- enabled: config.enabled ?? true,
54
- level: config.level ?? 'info',
55
- format: config.format ?? 'json',
56
- };
57
- }
58
- }
59
- /**
60
- * Get the logger instance.
61
- *
62
- * @example
63
- * ```typescript
64
- * import { Logger } from '@leanstacks/lambda-utils';
65
- * const logger = new Logger().instance;
66
- *
67
- * logger.info('Hello, world!');
68
- * ```
69
- */
70
- get instance() {
71
- if (this._instance === null) {
72
- this._instance = this._createLogger();
73
- }
74
- return this._instance;
75
- }
76
- }
77
-
78
- export { Logger, withRequestTracking };
@@ -1,59 +0,0 @@
1
- import pino from 'pino';
2
- /**
3
- * Logger middleware which adds AWS Lambda attributes to log messages.
4
- *
5
- * @example
6
- * ```typescript
7
- * import { withRequestTracking } from '@leanstacks/lambda-utils';
8
- *
9
- * export const handler = async (event, context) => {
10
- * withRequestTracking(event, context);
11
- *
12
- * // Your Lambda handler logic here
13
- * };
14
- * ```
15
- */
16
- export declare const withRequestTracking: (event: import("pino-lambda").LambdaEvent, context: import("pino-lambda").LambdaContext) => void;
17
- /**
18
- * Configuration options for the Logger
19
- */
20
- export interface LoggerConfig {
21
- /** Whether logging is enabled */
22
- enabled?: boolean;
23
- /** Minimum log level (e.g., 'debug', 'info', 'warn', 'error') */
24
- level?: 'debug' | 'info' | 'warn' | 'error';
25
- /** Output format: 'json' for StructuredLogFormatter, 'text' for CloudwatchLogFormatter */
26
- format?: 'json' | 'text';
27
- }
28
- /**
29
- * Logger class which provides a Pino logger instance with AWS Lambda attributes.
30
- *
31
- * @example
32
- * ```typescript
33
- * import { Logger } from '@leanstacks/lambda-utils';
34
- * const logger = new Logger().instance;
35
- *
36
- * logger.info('Hello, world!');
37
- * ```
38
- */
39
- export declare class Logger {
40
- private _loggerConfig;
41
- private _instance;
42
- constructor(config?: LoggerConfig);
43
- /**
44
- * Creates a new, fully configured Pino logger instance.
45
- */
46
- private _createLogger;
47
- /**
48
- * Get the logger instance.
49
- *
50
- * @example
51
- * ```typescript
52
- * import { Logger } from '@leanstacks/lambda-utils';
53
- * const logger = new Logger().instance;
54
- *
55
- * logger.info('Hello, world!');
56
- * ```
57
- */
58
- get instance(): pino.Logger;
59
- }