@leanstacks/lambda-utils 0.3.0-alpha.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.js +1 -1
- package/dist/validation/config.d.ts +45 -0
- package/docs/CONFIGURATION.md +348 -0
- package/docs/README.md +1 -2
- package/package.json +4 -4
- package/dist/validation/zod-validator.d.ts +0 -9
package/README.md
CHANGED
|
@@ -46,6 +46,33 @@ export const handler = async (event: any, context: any) => {
|
|
|
46
46
|
};
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
### Configuration Example
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { z } from 'zod';
|
|
53
|
+
import { createConfigManager } from '@leanstacks/lambda-utils';
|
|
54
|
+
|
|
55
|
+
// Define your configuration schema
|
|
56
|
+
const configSchema = z.object({
|
|
57
|
+
TABLE_NAME: z.string().min(1),
|
|
58
|
+
AWS_REGION: z.string().default('us-east-1'),
|
|
59
|
+
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
type Config = z.infer<typeof configSchema>;
|
|
63
|
+
|
|
64
|
+
const configManager = createConfigManager(configSchema);
|
|
65
|
+
const config = configManager.get();
|
|
66
|
+
|
|
67
|
+
export const handler = async (event: any) => {
|
|
68
|
+
console.log(`Using table: ${config.TABLE_NAME}`);
|
|
69
|
+
|
|
70
|
+
// Your Lambda handler logic here
|
|
71
|
+
|
|
72
|
+
return { statusCode: 200, body: 'Success' };
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
49
76
|
### API Response Example
|
|
50
77
|
|
|
51
78
|
```typescript
|
|
@@ -77,12 +104,35 @@ Comprehensive guides and examples are available in the `docs` directory:
|
|
|
77
104
|
|
|
78
105
|
| Guide | Description |
|
|
79
106
|
| ------------------------------------------------------------ | ---------------------------------------------------------------------- |
|
|
107
|
+
| **[Configuration Guide](./docs/CONFIGURATION.md)** | Validate environment variables with Zod schemas and type safety |
|
|
80
108
|
| **[Logging Guide](./docs/LOGGING.md)** | Configure and use structured logging with automatic AWS Lambda context |
|
|
81
109
|
| **[API Gateway Responses](./docs/API_GATEWAY_RESPONSES.md)** | Format responses for API Gateway with standard HTTP patterns |
|
|
82
|
-
| **[
|
|
110
|
+
| **[DynamoDB Client](./docs/DYNAMODB_CLIENT.md)** | Use pre-configured AWS SDK v3 clients in your handlers |
|
|
83
111
|
|
|
84
112
|
## Usage
|
|
85
113
|
|
|
114
|
+
### Configuration
|
|
115
|
+
|
|
116
|
+
Validate and manage environment variables with type safety:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { z } from 'zod';
|
|
120
|
+
import { createConfigManager } from '@leanstacks/lambda-utils';
|
|
121
|
+
|
|
122
|
+
const configManager = createConfigManager(
|
|
123
|
+
z.object({
|
|
124
|
+
TABLE_NAME: z.string().min(1),
|
|
125
|
+
AWS_REGION: z.string().default('us-east-1'),
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const config = configManager.get();
|
|
130
|
+
// TypeScript infers type from schema
|
|
131
|
+
// Validation errors thrown immediately
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**→ See [Configuration Guide](./docs/CONFIGURATION.md) for detailed validation patterns and best practices**
|
|
135
|
+
|
|
86
136
|
### Logging
|
|
87
137
|
|
|
88
138
|
The Logger utility provides structured logging configured specifically for AWS Lambda:
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { Logger, LoggerConfig, withRequestTracking } from './logging/logger';
|
|
2
2
|
export { createResponse, ok, created, noContent, badRequest, notFound, internalServerError, httpHeaders, Headers, } from './utils/apigateway-response';
|
|
3
3
|
export { getDynamoDBClient, getDynamoDBDocumentClient, initializeDynamoDBClients, resetDynamoDBClients, } from './clients/dynamodb-client';
|
|
4
|
-
export {
|
|
4
|
+
export { createConfigManager, ConfigManager } from './validation/config';
|
package/dist/index.esm.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import n from"pino";import{lambdaRequestTracker as e,StructuredLogFormatter as
|
|
1
|
+
import n from"pino";import{lambdaRequestTracker as e,StructuredLogFormatter as t,CloudwatchLogFormatter as o,pinoLambdaDestination as r}from"pino-lambda";import{DynamoDBClient as i}from"@aws-sdk/client-dynamodb";import{DynamoDBDocumentClient as s}from"@aws-sdk/lib-dynamodb";import{z as l}from"zod";const a=e();class c{constructor(e){this._loggerConfig={enabled:!0,level:"info",format:"json"},this._instance=null,this._createLogger=()=>{const e="json"===this._loggerConfig.format?new t:new o,i=r({formatter:e});return n({enabled:this._loggerConfig.enabled,level:this._loggerConfig.level},i)},e&&(this._loggerConfig={enabled:e.enabled??!0,level:e.level??"info",format:e.format??"json"})}get instance(){return null===this._instance&&(this._instance=this._createLogger()),this._instance}}const m={contentType:n=>({"Content-Type":n}),json:{"Content-Type":"application/json"},cors:(n="*")=>({"Access-Control-Allow-Origin":n})},f=(n,e,t={})=>({statusCode:n,headers:{...t},body:JSON.stringify(e)}),g=(n,e={})=>f(200,n,e),d=(n,e={})=>f(201,n,e),u=(n={})=>f(204,{},n),h=(n="Bad Request",e={})=>f(400,{message:n},e),p=(n="Not Found",e={})=>f(404,{message:n},e),C=(n="Internal Server Error",e={})=>f(500,{message:n},e);let w=null,y=null;const _=(n,e,t)=>{w=new i(n||{});const o={marshallOptions:e||{},unmarshallOptions:t||{}};return y=s.from(w,o),{client:w,documentClient:y}},b=()=>{if(!w)throw new Error("DynamoDB client not initialized. Call initializeDynamoDBClients() first.");return w},D=()=>{if(!y)throw new Error("DynamoDB Document client not initialized. Call initializeDynamoDBClients() first.");return y},v=()=>{w=null,y=null},j=n=>{let e=null;const t=()=>{try{return n.parse(process.env)}catch(n){if(n instanceof l.ZodError){const e=n.issues.map(n=>`${n.path.join(".")}: ${n.message}`).join(", ");throw new Error(`Configuration validation failed: ${e}`)}throw n}};return{get:()=>(e||(e=t()),e),refresh:()=>(e=t(),e)}};export{c as Logger,h as badRequest,j as createConfigManager,f as createResponse,d as created,b as getDynamoDBClient,D as getDynamoDBDocumentClient,m as httpHeaders,_ as initializeDynamoDBClients,C as internalServerError,u as noContent,p as notFound,g as ok,v as resetDynamoDBClients,a as withRequestTracking};
|
|
2
2
|
//# sourceMappingURL=index.esm.js.map
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var e=require("pino"),t=require("pino-lambda"),n=require("@aws-sdk/client-dynamodb"),r=require("@aws-sdk/lib-dynamodb"),o=require("zod");const i=t.lambdaRequestTracker();const s=(e,t,n={})=>({statusCode:e,headers:{...n},body:JSON.stringify(t)});let a=null,l=null;exports.Logger=class{constructor(n){this._loggerConfig={enabled:!0,level:"info",format:"json"},this._instance=null,this._createLogger=()=>{const n="json"===this._loggerConfig.format?new t.StructuredLogFormatter:new t.CloudwatchLogFormatter,r=t.pinoLambdaDestination({formatter:n});return e({enabled:this._loggerConfig.enabled,level:this._loggerConfig.level},r)},n&&(this._loggerConfig={enabled:n.enabled??!0,level:n.level??"info",format:n.format??"json"})}get instance(){return null===this._instance&&(this._instance=this._createLogger()),this._instance}},exports.badRequest=(e="Bad Request",t={})=>s(400,{message:e},t),exports.createResponse=s,exports.created=(e,t={})=>s(201,e,t),exports.getDynamoDBClient=()=>{if(!a)throw new Error("DynamoDB client not initialized. Call initializeDynamoDBClients() first.");return a},exports.getDynamoDBDocumentClient=()=>{if(!l)throw new Error("DynamoDB Document client not initialized. Call initializeDynamoDBClients() first.");return l},exports.httpHeaders={contentType:e=>({"Content-Type":e}),json:{"Content-Type":"application/json"},cors:(e="*")=>({"Access-Control-Allow-Origin":e})},exports.initializeDynamoDBClients=(e,t,o)=>{a=new n.DynamoDBClient(e||{});const i={marshallOptions:t||{},unmarshallOptions:o||{}};return l=r.DynamoDBDocumentClient.from(a,i),{client:a,documentClient:l}},exports.internalServerError=(e="Internal Server Error",t={})=>s(500,{message:e},t),exports.noContent=(e={})=>s(204,{},e),exports.notFound=(e="Not Found",t={})=>s(404,{message:e},t),exports.ok=(e,t={})=>s(200,e,t),exports.resetDynamoDBClients=()=>{a=null,l=null},exports.
|
|
1
|
+
"use strict";var e=require("pino"),t=require("pino-lambda"),n=require("@aws-sdk/client-dynamodb"),r=require("@aws-sdk/lib-dynamodb"),o=require("zod");const i=t.lambdaRequestTracker();const s=(e,t,n={})=>({statusCode:e,headers:{...n},body:JSON.stringify(t)});let a=null,l=null;exports.Logger=class{constructor(n){this._loggerConfig={enabled:!0,level:"info",format:"json"},this._instance=null,this._createLogger=()=>{const n="json"===this._loggerConfig.format?new t.StructuredLogFormatter:new t.CloudwatchLogFormatter,r=t.pinoLambdaDestination({formatter:n});return e({enabled:this._loggerConfig.enabled,level:this._loggerConfig.level},r)},n&&(this._loggerConfig={enabled:n.enabled??!0,level:n.level??"info",format:n.format??"json"})}get instance(){return null===this._instance&&(this._instance=this._createLogger()),this._instance}},exports.badRequest=(e="Bad Request",t={})=>s(400,{message:e},t),exports.createConfigManager=e=>{let t=null;const n=()=>{try{return e.parse(process.env)}catch(e){if(e instanceof o.z.ZodError){const t=e.issues.map(e=>`${e.path.join(".")}: ${e.message}`).join(", ");throw new Error(`Configuration validation failed: ${t}`)}throw e}};return{get:()=>(t||(t=n()),t),refresh:()=>(t=n(),t)}},exports.createResponse=s,exports.created=(e,t={})=>s(201,e,t),exports.getDynamoDBClient=()=>{if(!a)throw new Error("DynamoDB client not initialized. Call initializeDynamoDBClients() first.");return a},exports.getDynamoDBDocumentClient=()=>{if(!l)throw new Error("DynamoDB Document client not initialized. Call initializeDynamoDBClients() first.");return l},exports.httpHeaders={contentType:e=>({"Content-Type":e}),json:{"Content-Type":"application/json"},cors:(e="*")=>({"Access-Control-Allow-Origin":e})},exports.initializeDynamoDBClients=(e,t,o)=>{a=new n.DynamoDBClient(e||{});const i={marshallOptions:t||{},unmarshallOptions:o||{}};return l=r.DynamoDBDocumentClient.from(a,i),{client:a,documentClient:l}},exports.internalServerError=(e="Internal Server Error",t={})=>s(500,{message:e},t),exports.noContent=(e={})=>s(204,{},e),exports.notFound=(e="Not Found",t={})=>s(404,{message:e},t),exports.ok=(e,t={})=>s(200,e,t),exports.resetDynamoDBClients=()=>{a=null,l=null},exports.withRequestTracking=i;
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Interface for a configuration manager instance
|
|
4
|
+
*/
|
|
5
|
+
export interface ConfigManager<T> {
|
|
6
|
+
/**
|
|
7
|
+
* Get the validated configuration (cached after first call)
|
|
8
|
+
* @throws {Error} if validation fails
|
|
9
|
+
* @returns The validated configuration object
|
|
10
|
+
*/
|
|
11
|
+
get: () => T;
|
|
12
|
+
/**
|
|
13
|
+
* Refresh the configuration by re-validating environment variables
|
|
14
|
+
* Useful in tests when environment variables are changed
|
|
15
|
+
* @throws {Error} if validation fails
|
|
16
|
+
* @returns The newly validated configuration object
|
|
17
|
+
*/
|
|
18
|
+
refresh: () => T;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Creates a reusable configuration manager for any Lambda function
|
|
22
|
+
*
|
|
23
|
+
* @template T - The configuration type inferred from the provided Zod schema
|
|
24
|
+
* @param schema - A Zod schema that defines the structure and validation rules for environment variables
|
|
25
|
+
* @returns A ConfigManager instance with get() and refresh() methods
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* // Define your schema
|
|
30
|
+
* const configSchema = z.object({
|
|
31
|
+
* TABLE_NAME: z.string().min(1),
|
|
32
|
+
* AWS_REGION: z.string().default('us-east-1'),
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* // Create config manager
|
|
36
|
+
* const configManager = createConfigManager(configSchema);
|
|
37
|
+
*
|
|
38
|
+
* // Access configuration (cached on first call)
|
|
39
|
+
* const config = configManager.get();
|
|
40
|
+
*
|
|
41
|
+
* // Type your config
|
|
42
|
+
* type Config = z.infer<typeof configSchema>;
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare const createConfigManager: <T extends z.ZodSchema>(schema: T) => ConfigManager<z.infer<T>>;
|
|
@@ -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)**
|
package/docs/README.md
CHANGED
|
@@ -8,11 +8,10 @@ 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
|
|
16
15
|
|
|
17
16
|
## Features
|
|
18
17
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leanstacks/lambda-utils",
|
|
3
|
-
"version": "0.3.0
|
|
3
|
+
"version": "0.3.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",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"jest": "30.2.0",
|
|
56
56
|
"prettier": "3.7.4",
|
|
57
57
|
"rimraf": "6.1.2",
|
|
58
|
-
"rollup": "4.
|
|
58
|
+
"rollup": "4.54.0",
|
|
59
59
|
"rollup-plugin-peer-deps-external": "2.2.4",
|
|
60
60
|
"rollup-plugin-typescript2": "0.36.0",
|
|
61
61
|
"ts-jest": "29.4.6",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"typescript": "5.9.3"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@aws-sdk/client-dynamodb": "3.
|
|
67
|
-
"@aws-sdk/lib-dynamodb": "3.
|
|
66
|
+
"@aws-sdk/client-dynamodb": "3.956.0",
|
|
67
|
+
"@aws-sdk/lib-dynamodb": "3.956.0",
|
|
68
68
|
"pino": "10.1.0",
|
|
69
69
|
"pino-lambda": "4.4.1",
|
|
70
70
|
"zod": "4.2.1"
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
/**
|
|
3
|
-
* Validates the given data against the provided Zod schema.
|
|
4
|
-
* @param schema The Zod schema to validate against.
|
|
5
|
-
* @param data The data to be validated.
|
|
6
|
-
* @returns The validated data if it passes the schema validation.
|
|
7
|
-
* @throws {Error} If the validation fails or an unknown error occurs.
|
|
8
|
-
*/
|
|
9
|
-
export declare const validate: (schema: z.ZodSchema, data: unknown) => unknown;
|