@leanstacks/lambda-utils 0.1.0-alpha.5 → 0.1.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/package.json CHANGED
@@ -1,10 +1,31 @@
1
1
  {
2
2
  "name": "@leanstacks/lambda-utils",
3
- "version": "0.1.0-alpha.5",
3
+ "version": "0.1.0",
4
4
  "description": "A collection of utilities and helper functions designed to streamline the development of AWS Lambda functions using TypeScript.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
7
7
  "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "docs"
11
+ ],
12
+ "keywords": [
13
+ "lambda",
14
+ "aws",
15
+ "utilities",
16
+ "nodejs",
17
+ "typescript"
18
+ ],
19
+ "author": "Matthew Warman <leanstacker@gmail.com> (https://leanstacks.com)",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/leanstacks/lambda-utils.git"
24
+ },
25
+ "homepage": "https://github.com/leanstacks/lambda-utils#readme",
26
+ "bugs": {
27
+ "url": "https://github.com/leanstacks/lambda-utils/issues"
28
+ },
8
29
  "scripts": {
9
30
  "build": "rollup -c",
10
31
  "build:watch": "rollup -c -w",
@@ -18,15 +39,6 @@
18
39
  "test:watch": "jest --watch",
19
40
  "test:coverage": "jest --coverage"
20
41
  },
21
- "keywords": [
22
- "lambda",
23
- "aws",
24
- "utilities",
25
- "nodejs",
26
- "typescript"
27
- ],
28
- "author": "Matthew Warman <leanstacker@gmail.com> (https://leanstacks.com)",
29
- "license": "MIT",
30
42
  "devDependencies": {
31
43
  "@eslint/js": "9.39.2",
32
44
  "@rollup/plugin-commonjs": "29.0.0",
package/.editorconfig DELETED
@@ -1,12 +0,0 @@
1
- # This file is used to define coding styles and formatting rules for the project.
2
- # For more information, see https://editorconfig.org/
3
- root=true
4
-
5
- [*]
6
- charset=utf-8
7
- end_of_line=lf
8
- indent_style=space
9
- indent_size=2
10
- insert_final_newline=true
11
- max_line_length=120
12
- trim_trailing_whitespace=true
@@ -1,47 +0,0 @@
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._
@@ -1,25 +0,0 @@
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._
@@ -1,15 +0,0 @@
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._
@@ -1,39 +0,0 @@
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
- - [...]
@@ -1,101 +0,0 @@
1
- # Lambda Utilities Project Instructions
2
-
3
- This project contains a set of utilities and helper functions designed to streamline the development of AWS Lambda functions using TypeScript. The utilities cover common tasks such as input validation, response formatting, error handling, and logging. The project is packaged and published as an npm package for easy integration into other Lambda projects.
4
-
5
- ---
6
-
7
- ## Technology Stack
8
-
9
- Each pattern project uses the following technology stack:
10
-
11
- - **Language:** TypeScript
12
- - **Platform:** AWS Lambda
13
- - **Runtime:** Node.js 24.x
14
- - **AWS SDK:** v3 (modular packages)
15
- - **Testing:** Jest
16
- - **Linting/Formatting:** ESLint + Prettier
17
- - **Validation:** Zod
18
- - **Logging:** Pino + Pino-Lambda
19
- - **Package Manager:** npm
20
- - **Package Bundler:** rollup
21
-
22
- ---
23
-
24
- ## Pattern Project Structure
25
-
26
- Each pattern project follows a consistent directory and file structure to promote maintainability and scalability. Below is an example structure:
27
-
28
- ```
29
- /docs # Project documentation
30
- README.md # Documentation table of contents
31
-
32
- /src
33
- /logging
34
- logger.ts # Logger utility using Pino
35
- logger.test.ts # Unit tests for logger
36
- /clients
37
- dynamodb-client.ts # AWS SDK client for DynamoDB
38
- dynamodb-client.test.ts # Unit tests for DynamoDB client
39
- lambda-client.ts # AWS SDK client for Lambda
40
- lambda-client.test.ts # Unit tests for Lambda client
41
- /validation
42
- config.ts # Configuration validation with Zod
43
- config.test.ts # Unit tests for config
44
- validator.ts # Generic validator helpers
45
- validator.test.ts # Unit tests for validator
46
- /responses
47
- apigateway-response.ts # Standard API response helpers
48
- apigateway-response.test.ts # Unit tests for API response helpers
49
- .editorconfig # Editor config
50
- .gitignore # Git ignore rules
51
- .nvmrc # Node version manager config
52
- .prettierrc # Prettier config
53
- eslint.config.mjs # ESLint config
54
- jest.config.ts # App Jest config
55
- jest.setup.ts # App Jest setup
56
- package.json # App NPM package config
57
- README.md # Project README
58
- tsconfig.json # Project TypeScript config
59
- ```
60
-
61
- ---
62
-
63
- ## Source Code Guidelines
64
-
65
- Each pattern project follows best practices for source code organization, naming conventions, and coding standards. Below are the key guidelines:
66
-
67
- - Use **TypeScript** for all source and infrastructure code.
68
- - Use arrow functions for defining functions.
69
- - Use path aliases for cleaner imports (e.g., `@utils`, `@models`).
70
- - Organize import statements: external packages first, then internal modules.
71
- - Use async/await for asynchronous operations.
72
- - Document functions and modules with JSDoc comments.
73
-
74
- ### Source Code Commands & Scripts
75
-
76
- - Use `npm run build` to compile TypeScript.
77
- - Use `npm run test` to run tests.
78
- - Use `npm run test:coverage` to run tests with coverage report.
79
- - Use `npm run lint` to run ESLint.
80
- - Use `npm run lint:fix` to fix ESLint issues.
81
- - Use `npm run format` to run Prettier to format code.
82
- - Use `npm run format:check` to check code formatting with Prettier.
83
-
84
- ---
85
-
86
- ## Unit Testing Guidelines
87
-
88
- Each pattern project includes comprehensive unit tests for both application and infrastructure code. Below are the key guidelines for writing unit tests:
89
-
90
- - Use the **Jest** testing framework.
91
- - Place test files next to the source file, with `.test.ts` suffix.
92
- - Use `describe` and `it` blocks for organization.
93
- - Use `beforeEach` for setup and `afterEach` for cleanup.
94
- - Use `expect` assertions for results.
95
- - Mock dependencies to isolate the component under test.
96
- - Mock external calls (e.g., AWS SDK, databases).
97
- - Structure your tests using the Arrange-Act-Assert pattern:
98
- - **Arrange:** Set up the test environment, including any necessary mocks and test data.
99
- - **Act:** Execute the function or service being tested.
100
- - **Assert:** Verify that the results are as expected.
101
- - Add comments to separate these sections for clarity.
@@ -1,43 +0,0 @@
1
- name: Continuous Integration
2
-
3
- on:
4
- pull_request:
5
- branches:
6
- - main
7
- workflow_dispatch:
8
-
9
- concurrency:
10
- group: ${{ github.workflow }}-${{ github.ref }}
11
- cancel-in-progress: true
12
-
13
- jobs:
14
- ci:
15
- name: Build, Lint, and Test
16
-
17
- runs-on: ubuntu-latest
18
- timeout-minutes: 5
19
-
20
- steps:
21
- - name: Checkout repository
22
- uses: actions/checkout@v5
23
-
24
- - name: Setup Node.js
25
- uses: actions/setup-node@v5
26
- with:
27
- node-version-file: '.nvmrc'
28
- cache: 'npm'
29
-
30
- - name: Install dependencies
31
- run: npm ci
32
-
33
- - name: Lint code
34
- run: npm run lint
35
-
36
- - name: Check code formatting
37
- run: npm run format:check
38
-
39
- - name: Build
40
- run: npm run build
41
-
42
- - name: Run tests with coverage
43
- run: npm run test:coverage
@@ -1,51 +0,0 @@
1
- name: Publish
2
-
3
- on:
4
- release:
5
- types:
6
- - published
7
- workflow_dispatch:
8
-
9
- concurrency:
10
- group: ${{ github.workflow }}
11
- cancel-in-progress: false
12
-
13
- permissions:
14
- id-token: write
15
- contents: read
16
-
17
- jobs:
18
- build:
19
- name: Build and Publish
20
-
21
- runs-on: ubuntu-latest
22
- timeout-minutes: 10
23
-
24
- steps:
25
- - name: Checkout repository
26
- uses: actions/checkout@v5
27
-
28
- - name: Setup Node.js
29
- uses: actions/setup-node@v5
30
- with:
31
- node-version-file: '.nvmrc'
32
- cache: 'npm'
33
- registry-url: 'https://registry.npmjs.org'
34
-
35
- - name: Install dependencies
36
- run: npm ci
37
-
38
- - name: Lint code
39
- run: npm run lint
40
-
41
- - name: Check code formatting
42
- run: npm run format:check
43
-
44
- - name: Run tests
45
- run: npm run test
46
-
47
- - name: Build
48
- run: npm run build
49
-
50
- - name: Publish
51
- run: npm publish
package/.husky/pre-commit DELETED
@@ -1,3 +0,0 @@
1
- npm test
2
- npm run format:check
3
- npm run lint
package/.nvmrc DELETED
@@ -1 +0,0 @@
1
- 24.11.1
package/.prettierrc DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "plugins": [],
3
- "printWidth": 120,
4
- "semi": true,
5
- "singleQuote": true,
6
- "tabWidth": 2,
7
- "trailingComma": "all"
8
- }
package/eslint.config.mjs DELETED
@@ -1,44 +0,0 @@
1
- import js from '@eslint/js';
2
- import tsPlugin from '@typescript-eslint/eslint-plugin';
3
- import tsParser from '@typescript-eslint/parser';
4
-
5
- export default [
6
- {
7
- ignores: ['dist', 'node_modules'],
8
- },
9
- {
10
- files: ['src/**/*.ts'],
11
- languageOptions: {
12
- parser: tsParser,
13
- parserOptions: {
14
- ecmaVersion: 2020,
15
- sourceType: 'module',
16
- },
17
- globals: {
18
- console: 'readonly',
19
- process: 'readonly',
20
- describe: 'readonly',
21
- it: 'readonly',
22
- expect: 'readonly',
23
- beforeEach: 'readonly',
24
- afterEach: 'readonly',
25
- jest: 'readonly',
26
- },
27
- },
28
- plugins: {
29
- '@typescript-eslint': tsPlugin,
30
- },
31
- rules: {
32
- ...js.configs.recommended.rules,
33
- ...tsPlugin.configs.recommended.rules,
34
- '@typescript-eslint/no-unused-vars': [
35
- 'error',
36
- {
37
- argsIgnorePattern: '^_',
38
- varsIgnorePattern: '^_',
39
- },
40
- ],
41
- '@typescript-eslint/no-explicit-any': 'warn',
42
- },
43
- },
44
- ];
package/jest.config.ts DELETED
@@ -1,15 +0,0 @@
1
- import type { Config } from 'jest';
2
-
3
- const config: Config = {
4
- preset: 'ts-jest',
5
- testEnvironment: 'node',
6
- testMatch: ['<rootDir>/src/**/*.test.ts'],
7
- moduleNameMapper: {
8
- '^@/(.*)$': '<rootDir>/$1',
9
- },
10
- coverageDirectory: 'coverage',
11
- collectCoverageFrom: ['src/**/*.ts', '!src/**/*.test.ts', '!src/**/index.ts'],
12
- coverageReporters: ['json', 'json-summary', 'lcov', 'text', 'clover'],
13
- };
14
-
15
- export default config;
package/jest.setup.ts DELETED
@@ -1,9 +0,0 @@
1
- // Jest setup file for global test configuration
2
- // Add any global test setup here
3
-
4
- // Mock pino-lambda for tests
5
- jest.mock('pino-lambda', () => {
6
- return {
7
- target: 'pino-lambda',
8
- };
9
- });
package/rollup.config.js DELETED
@@ -1,34 +0,0 @@
1
- import commonjs from '@rollup/plugin-commonjs';
2
- import { nodeResolve } from '@rollup/plugin-node-resolve';
3
- import terser from '@rollup/plugin-terser';
4
- import peerDepsExternal from 'rollup-plugin-peer-deps-external';
5
- import typescript from 'rollup-plugin-typescript2';
6
-
7
- import packageJson from './package.json' with { type: 'json' };
8
-
9
- export default {
10
- input: 'src/index.ts',
11
- output: [
12
- {
13
- file: packageJson.main,
14
- format: 'cjs',
15
- sourcemap: true,
16
- },
17
- {
18
- file: packageJson.module,
19
- format: 'esm',
20
- sourcemap: true,
21
- },
22
- ],
23
- plugins: [
24
- peerDepsExternal(),
25
- nodeResolve(),
26
- commonjs(),
27
- typescript({
28
- tsconfig: 'tsconfig.json',
29
- clean: true,
30
- }),
31
- terser(),
32
- ],
33
- external: [...Object.keys(packageJson.dependencies || {})],
34
- };
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export { Logger, LoggerConfig, withRequestTracking } from './logging/logger';
@@ -1,400 +0,0 @@
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
- });
@@ -1,104 +0,0 @@
1
- import pino from 'pino';
2
- import {
3
- CloudwatchLogFormatter,
4
- lambdaRequestTracker,
5
- pinoLambdaDestination,
6
- StructuredLogFormatter,
7
- } from 'pino-lambda';
8
-
9
- /**
10
- * Logger middleware which adds AWS Lambda attributes to log messages.
11
- *
12
- * @example
13
- * ```typescript
14
- * import { withRequestTracking } from '@leanstacks/lambda-utils';
15
- *
16
- * export const handler = async (event, context) => {
17
- * withRequestTracking(event, context);
18
- *
19
- * // Your Lambda handler logic here
20
- * };
21
- * ```
22
- */
23
- export const withRequestTracking = lambdaRequestTracker();
24
-
25
- /**
26
- * Configuration options for the Logger
27
- */
28
- export interface LoggerConfig {
29
- /** Whether logging is enabled */
30
- enabled?: boolean;
31
- /** Minimum log level (e.g., 'debug', 'info', 'warn', 'error') */
32
- level?: 'debug' | 'info' | 'warn' | 'error';
33
- /** Output format: 'json' for StructuredLogFormatter, 'text' for CloudwatchLogFormatter */
34
- format?: 'json' | 'text';
35
- }
36
-
37
- /**
38
- * Logger class which provides a Pino logger instance with AWS Lambda attributes.
39
- *
40
- * @example
41
- * ```typescript
42
- * import { Logger } from '@leanstacks/lambda-utils';
43
- * const logger = new Logger().instance;
44
- *
45
- * logger.info('Hello, world!');
46
- * ```
47
- */
48
- export class Logger {
49
- private _loggerConfig: LoggerConfig = {
50
- enabled: true,
51
- level: 'info',
52
- format: 'json',
53
- };
54
-
55
- private _instance: pino.Logger | null = null;
56
-
57
- constructor(config?: LoggerConfig) {
58
- if (config) {
59
- this._loggerConfig = {
60
- enabled: config.enabled ?? true,
61
- level: config.level ?? 'info',
62
- format: config.format ?? 'json',
63
- };
64
- }
65
- }
66
-
67
- /**
68
- * Creates a new, fully configured Pino logger instance.
69
- */
70
- private _createLogger = (): pino.Logger => {
71
- const formatter =
72
- this._loggerConfig.format === 'json' ? new StructuredLogFormatter() : new CloudwatchLogFormatter();
73
-
74
- const lambdaDestination = pinoLambdaDestination({
75
- formatter,
76
- });
77
-
78
- return pino(
79
- {
80
- enabled: this._loggerConfig.enabled,
81
- level: this._loggerConfig.level,
82
- },
83
- lambdaDestination,
84
- );
85
- };
86
-
87
- /**
88
- * Get the logger instance.
89
- *
90
- * @example
91
- * ```typescript
92
- * import { Logger } from '@leanstacks/lambda-utils';
93
- * const logger = new Logger().instance;
94
- *
95
- * logger.info('Hello, world!');
96
- * ```
97
- */
98
- get instance(): pino.Logger {
99
- if (this._instance === null) {
100
- this._instance = this._createLogger();
101
- }
102
- return this._instance;
103
- }
104
- }
package/tsconfig.json DELETED
@@ -1,22 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "ESNext",
5
- "lib": ["ES2020"],
6
- "declaration": true,
7
- "outDir": "./dist",
8
- "rootDir": "./src",
9
- "strict": true,
10
- "esModuleInterop": true,
11
- "skipLibCheck": true,
12
- "forceConsistentCasingInFileNames": true,
13
- "resolveJsonModule": true,
14
- "moduleResolution": "node",
15
- "baseUrl": "./src",
16
- "paths": {
17
- "@/*": ["./*"]
18
- }
19
- },
20
- "include": ["src/**/*"],
21
- "exclude": ["node_modules", "dist", "**/*.test.ts"]
22
- }