@felloh-org/lambda-wrapper
v1.11.219
Published
Lambda wrapper for all Felloh Serverless Projects
Readme
Lambda Wrapper
A shared library for Felloh serverless projects that provides core functionality including dependency injection, logging, request handling, database entities, and response models for AWS Lambda functions.
Installation
npm install @felloh-org/lambda-wrapper
# or
yarn add @felloh-org/lambda-wrapperQuick Start
import { LambdaWrapper, DEFINITIONS, ResponseModel } from '@felloh-org/lambda-wrapper';
const configuration = {
SERVICE_NAME: 'my-service',
};
export const handler = LambdaWrapper(configuration, async (di, request) => {
const logger = di.get(DEFINITIONS.LOGGER);
const warehouse = di.get(DEFINITIONS.WAREHOUSE);
logger.info('Processing request');
// Your handler logic here
return new ResponseModel({ message: 'Success' }, 200).generate();
});Lambda Wrapper
The core wrapper function takes a configuration object and a handler function, returning a Lambda-compatible function with built-in dependency injection, error handling, logging, and metrics.
import { LambdaWrapper } from '@felloh-org/lambda-wrapper';
const handler = LambdaWrapper(configuration, async (di, request, callback) => {
// di - DependencyInjection container with all services
// request - RequestService instance with parsed request data
// callback - Lambda callback (for non-async handlers)
});Handler Patterns
Async handler (recommended):
export const handler = LambdaWrapper(config, async (di, request) => {
const response = new ResponseModel({ result: 'ok' }, 200);
return response.generate();
});Callback handler:
export const handler = LambdaWrapper(config, (di, request, callback) => {
const response = new ResponseModel({ result: 'ok' }, 200);
callback(null, response.generate());
});Throw errors as responses:
export const handler = LambdaWrapper(config, async (di, request) => {
const params = request.getAll();
if (!params.email) {
const error = new ResponseModel({}, 422);
error.addError({
title: 'Validation Error',
message: 'Email is required',
});
throw error; // Returns a 422 response, not a 500
}
// continue processing...
});Error Handling
The wrapper automatically catches errors and returns appropriate responses:
- ResponseModel thrown - returns the response as-is (e.g. 400, 404, 422)
- LambdaTermination thrown - returns a response using the error's
codeproperty - Unhandled errors - returns a 500 response and logs the error
- Database size limit errors - returns a 413 response
5xx errors are logged automatically; 4xx errors are not.
Warm-up Support
The wrapper handles serverless-plugin-warmup events automatically. When a warmup event is detected, the handler returns immediately without executing your code.
Dependency Injection
Every handler receives a DependencyInjection container as its first argument. The container provides access to all built-in services and any custom services you register.
Built-in Services
| Service | Definition | Description |
|---------|------------|-------------|
| Logger | DEFINITIONS.LOGGER | Structured logging with Winston and Baselime |
| Request | DEFINITIONS.REQUEST | Parsed HTTP request data |
| Warehouse | DEFINITIONS.WAREHOUSE | TypeORM database connection manager |
| Authentication | DEFINITIONS.AUTHENTICATION | User role and organisation checks |
| User | DEFINITIONS.USER | Current user from Cognito JWT claims |
| EventBridge | DEFINITIONS.EVENT_BRIDGE | AWS EventBridge event publishing |
| Secrets | DEFINITIONS.SECRETS | AWS Secrets Manager with caching |
| HTTP | DEFINITIONS.HTTP | Axios-based HTTP client |
| Webhook | DEFINITIONS.WEBHOOK | HMAC-signed webhook dispatch |
| AuditLogger | DEFINITIONS.AUDIT_LOGGER | Audit event creation via EventBridge |
| PaymentInitiationObject | DEFINITIONS.PAYMENT_INITIATION_OBJECT | Payment link and ecommerce lookup |
Custom Services
Create custom services by extending DependencyAwareClass:
import { DependencyAwareClass, DEFINITIONS } from '@felloh-org/lambda-wrapper';
class PaymentProcessor extends DependencyAwareClass {
async processPayment(transactionId) {
const logger = this.getContainer().get(DEFINITIONS.LOGGER);
const warehouse = this.getContainer().get(DEFINITIONS.WAREHOUSE);
const connection = await warehouse.connect();
const repo = connection.getRepository(TransactionEntity);
const transaction = await repo.findOne({ where: { id: transactionId } });
logger.info(`Processing payment ${transactionId}`);
return transaction;
}
}Register custom services in the configuration:
const configuration = {
DEPENDENCIES: {
PAYMENT_PROCESSOR: PaymentProcessor,
},
};
export const handler = LambdaWrapper(configuration, async (di, request) => {
const processor = di.get('PAYMENT_PROCESSOR');
await processor.processPayment('txn_123');
});Container API
// Get a service
const logger = di.get(DEFINITIONS.LOGGER);
// Get the raw Lambda event
const event = di.getEvent();
// Get the Lambda context
const context = di.getContext();
// Get configuration values
const config = di.getConfiguration(); // full config
const dbHost = di.getConfiguration('DB_HOST'); // specific key, returns null if missing
// Check if running in serverless-offline
if (di.isOffline) {
// local development mode
}Services
RequestService
Parses and provides access to all HTTP request data.
const request = di.get(DEFINITIONS.REQUEST);
// Get a single parameter (checks query params for GET, body for POST)
const email = request.get('email');
const page = request.get('page', '1'); // with default value
// Get all parameters
const allParams = request.getAll();
// Force reading from query string regardless of HTTP method
const queryParams = request.getAll('GET');
// Path parameters
const id = request.getPathParameter('id');
const allPath = request.getPathParameter(); // all path params
// Headers (case-insensitive)
const contentType = request.getHeader('Content-Type');
const allHeaders = request.getAllHeaders();
// Authorization
const token = request.getAuthorizationToken(); // extracts Bearer token
// Client info
const ip = request.getIp();
const browser = request.getUserBrowserAndDevice(); // parsed user-agent
// AWS event records (SQS, SNS, etc.)
const record = request.getAWSRecords();Content type support:
The request service automatically parses the body based on the Content-Type header:
application/json- JSON parsingapplication/x-www-form-urlencoded- form data parsingtext/xml- XML parsing via xml2jsmultipart/form-data- file upload parsing with Buffer support
Request validation:
const constraints = {
email: { presence: true, email: true },
amount: { presence: true, numericality: { greaterThan: 0 } },
start_date: { datetime: { dateOnly: true } },
};
await request.validateAgainstConstraints(constraints);
// Resolves on success, rejects with a 422 ResponseModel on failureValidation uses validate.js with datetime support via moment.
LoggerService
Structured logging with Winston and Baselime integration. Pretty-prints output in serverless-offline mode.
const logger = di.get(DEFINITIONS.LOGGER);
// Standard logging
logger.info('Payment processed successfully');
logger.error(new Error('Connection failed'));
logger.warning(error); // routes to error() or info() based on LOGGER_SOFT_WARNING env
// Labels (lightweight markers for tracing)
logger.label('payment-initiated');
logger.label('silent-label', true); // silent = not logged to console
// Metrics
logger.metric('response_time', 250);
logger.metric('ip_address', '10.0.0.1', true); // silent = not logged to console
// Object inspection
logger.object('Processing payload', { id: 1, amount: 500 });
logger.object('Failed response', errorObj, 'error'); // supports 'info', 'warning', 'error'Axios errors are automatically trimmed to config, message, response.status, and response.data - stripping verbose request/header data from logs.
WarehouseService
Manages TypeORM database connections with connection caching.
const warehouse = di.get(DEFINITIONS.WAREHOUSE);
// Get a connection (cached by default)
const connection = await warehouse.connect();
// Force a new connection (bypass cache)
const fresh = await warehouse.connect(null, false);
// Connect to a specific database
const analytics = await warehouse.connect('analytics_db');
// Health check
await warehouse.status();Connection types:
Set via the WAREHOUSE_TYPE environment variable:
postgres- direct PostgreSQL connection usingWAREHOUSE_HOST,WAREHOUSE_USERNAME,WAREHOUSE_PASSWORD,WAREHOUSE_DATABASEpostgres-ssm- credentials fetched from AWS Secrets Manager usingDB_CREDS_ARN
AuthenticationService
Checks user roles and organisation access. Lazy-initialises a database connection on first use.
const auth = di.get(DEFINITIONS.AUTHENTICATION);
await auth.init();
// Check organisation access and roles in one call
// Throws a 401 ResponseModel if checks fail
await auth.checkUserRoles('org-123', ['admin', 'finance']);
// Check organisation access only
await auth.checkUserRoles('org-123');
// Check roles only
await auth.checkUserRoles(null, ['admin']);
// Individual checks
const roles = await auth.fetchUserRoles(); // ['admin', 'viewer']
const hasAdmin = await auth.hasRole('admin'); // true/false
const orgs = await auth.fetchUserOrganisations(user); // includes descendantsHTTPService
Axios-based HTTP client with configurable timeout.
const http = di.get(DEFINITIONS.HTTP);
// Default timeout is 10 seconds
http.setDefaultTimeout(30000);
const response = await http.request({
method: 'POST',
url: 'https://api.example.com/payments',
headers: { Authorization: 'Bearer token' },
data: { amount: 1000 },
});SecretsService
Fetches and caches secrets from AWS Secrets Manager.
const secrets = di.get(DEFINITIONS.SECRETS);
// Fetched once, then cached for the Lambda execution
const creds = await secrets.get('arn:aws:secretsmanager:eu-west-1:123:secret:db-creds');
// { username: 'admin', password: '...', host: '...', ... }WebhookService
Dispatches HMAC-SHA256 signed webhooks.
const webhook = di.get(DEFINITIONS.WEBHOOK);
await webhook.send(
'https://example.com/webhook',
{ event: 'payment.completed', transaction_id: 'txn_123' },
'webhook-signing-secret'
);
// POST with X-Signature header containing HMAC-SHA256 hex digestAuditLoggerService
Creates audit trail events via EventBridge.
const auditLogger = di.get(DEFINITIONS.AUDIT_LOGGER);
await auditLogger.createEvent('payment.refunded', 'entity-123', 'org-456');Response Model
All Lambda responses use a standardised format with CORS headers.
import { ResponseModel } from '@felloh-org/lambda-wrapper';
// Basic response
const response = new ResponseModel({ users: [] }, 200);
return response.generate();
// Building a response
const response = new ResponseModel();
response.setData({ id: 1, name: 'Test' });
response.setCode(201);
response.setMetaVariable('pagination', { page: 1, total: 10 });
response.setBodyVariable('warnings', ['Deprecated field used']);
return response.generate();
// Adding errors
const error = new ResponseModel({}, 422);
error.addError({
title: 'Validation Error',
message: 'Email is not valid',
documentation_url: 'https://developers.felloh.com/errors#error-responses',
type: 'validation',
code: 'validation.email',
});
throw error; // or return error.generate()
// Static shorthand
return ResponseModel.generate({ result: 'ok' }, 200);Generated response format:
{
"statusCode": 200,
"headers": {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true
},
"body": "{\"data\":{},\"errors\":[],\"meta\":{\"code\":200,\"reason\":\"OK\",\"message\":\"The request was successful\",\"request_id\":\"uuid-v4\"}}"
}Supported status codes:
| Code | Reason | |------|--------| | 200 | OK | | 201 | Created | | 204 | No Content | | 400 | Bad Request | | 401 | Unauthorized | | 403 | Forbidden | | 404 | Not Found | | 406 | Not Acceptable | | 410 | Gone | | 413 | Request Entity Too Large | | 422 | Unprocessable Entity | | 429 | Too Many Requests | | 500 | Internal Server Error | | 502 | Bad Gateway | | 503 | Service Unavailable | | 504 | Gateway Timeout |
Status Model
Used for health checks and service status reporting.
import { StatusModel, STATUS_TYPES } from '@felloh-org/lambda-wrapper';
const status = new StatusModel('database', STATUS_TYPES.OK);
// STATUS_TYPES: OK, ACCEPTABLE_FAILURE, APPLICATION_FAILURELambda Termination
A custom error class for terminating Lambda execution with a specific status code and consumer-facing message.
import { LambdaTermination } from '@felloh-org/lambda-wrapper';
// Terminates with a 503 response
throw new LambdaTermination(
'Payment provider timeout after 30s', // internal (logged)
503, // status code
'Service temporarily unavailable', // body (returned to consumer)
'Provider X timed out' // details
);EventBridge Events
Publish typed events to AWS EventBridge.
import {
LambdaWrapper,
DEFINITIONS,
TransactionCompleteEvent,
TransactionCompleteEmailEvent,
UserRegistrationEvent,
CSVEmailEvent,
RefundRequestEmailEvent,
BaseEvent,
} from '@felloh-org/lambda-wrapper';
export const handler = LambdaWrapper(config, async (di) => {
const eventBridge = di.get(DEFINITIONS.EVENT_BRIDGE);
// Use a pre-built event
const event = new TransactionCompleteEvent();
event.setDetailParam('transaction_id', 'txn_123');
await eventBridge.put(event);
// Or build a custom event
const custom = new BaseEvent('order.shipped');
custom.setDetailParam('order_id', 'ord_456');
custom.setDetail({ order_id: 'ord_456', carrier: 'DHL' });
await eventBridge.put(custom);
});Available event types:
| Event | Description |
|-------|-------------|
| BaseEvent | Generic event, set your own detail type |
| TransactionCompleteEvent | Transaction completed |
| TransactionCompleteEmailEvent | Transaction receipt email |
| UserRegistrationEvent | New user registration |
| UserPasswordChangedEvent | Password changed |
| UserPasswordResetEvent | Password reset requested |
| SandboxRegistrationEvent | Sandbox account registration |
| CSVEmailEvent | CSV export email |
| POSCRequestEmailEvent | POSC request email |
| RefundRequestEmailEvent | Refund request notification |
Webhook Models
Format transaction and refund data for webhook payloads.
import { TransactionWebhookModel, RefundWebhookModel } from '@felloh-org/lambda-wrapper';
// Transaction webhook
const payload = new TransactionWebhookModel(transaction, 'Payment received');
const data = payload.get();
// { transaction: { id, narrative }, amount, booking, status, currency, surcharge, ... }Database (TypeORM)
Entities
The library includes TypeORM entities organised by domain:
| Domain | Description |
|--------|-------------|
| payment | Transactions, refunds, chargebacks, payment links, ecommerce, providers, sessions |
| user | Users, organisations, roles, features, webhooks, billing, partners, portals |
| bank | Accounts, ledgers, settlements, disbursals, adjustments, beneficiaries |
| agent-data | Bookings, components, suppliers, package types |
| acquirer | BIN data, batches, adjustments, band rates, transaction settlements |
| aisp | Open banking transactions, GoCardless accounts/agreements/requisitions |
| nuapay | Nuapay accounts, balances, beneficiaries, transactions |
| nuvei | Nuvei batches, merchants, transactions, metadata |
| trust-payments | Trust Payments batches, merchants, transactions, chargebacks, fees |
| total-processing | Total Processing transactions and metadata |
| planet | Planet transactions, metadata, batches |
| saltedge | Saltedge accounts, connections, customers, transactions, leads |
| basis-theory | Tokenisation tokens |
| go-cardless | GoCardless organisation configuration |
| marketing | Marketing contacts and ESUs |
| reference | Countries |
| playbook | Playbooks, requests, secrets, versions |
Migrations
Run migrations against the database:
# Create database schemas
yarn orm:schema:create
# Run pending migrations
yarn orm:migration:run
# Generate a new migration
yarn orm:migration:generate <name>
# Revert last migration
yarn orm:revertMigrations require the following environment variables:
| Variable | Description |
|----------|-------------|
| WAREHOUSE_TYPE | postgres or postgres-ssm |
| WAREHOUSE_HOST | Database host (for postgres type) |
| WAREHOUSE_USERNAME | Database username (for postgres type) |
| WAREHOUSE_PASSWORD | Database password (for postgres type) |
| WAREHOUSE_DATABASE | Database name |
| WAREHOUSE_SCHEMA | Database schema |
| DB_CREDS_ARN | Secrets Manager ARN (for postgres-ssm type) |
Database Schemas
The database is organised into the following schemas: user, payment, bank, agent_data, nuapay, trust_payments, total_processing, token, nuvei, reference, saltedge, acquirer, operations, basis_theory, aisp, go_cardless, planet, marketing, amex, crm, shield.
Utilities
PromisifiedDelay
Provides randomised delays for retry logic, with configurable latency profiles.
import { PromisifiedDelay } from '@felloh-org/lambda-wrapper';
// High latency profile (2-20s delays, weighted towards shorter)
const delay = new PromisifiedDelay(true);
// Standard latency profile (2-5s delays)
const delay = new PromisifiedDelay(false);
// Use in retry loop
for (let attempt = 0; attempt < 3; attempt++) {
try {
return await makeRequest();
} catch (error) {
await delay.get();
}
}Development
Commands
# Build for production (webpack)
yarn build
# Build for TypeORM migrations (babel)
yarn build:orm
# Run linter
yarn lint
# Run tests with coverage
yarn test
# Run dependency vulnerability audit
yarn audit:check
# Run database migrations
yarn orm:migration:run
# Generate a new migration
yarn orm:migration:generate <name>
# Revert last migration
yarn orm:revert
# Create database schemas
yarn orm:schema:createEnvironment Variables
| Variable | Description |
|----------|-------------|
| SERVICE_NAME | Service identifier for logging and events |
| EVENT_BRIDGE_ARN | AWS EventBridge bus ARN |
| REGION | AWS region |
| WAREHOUSE_TYPE | Database connection type (postgres or postgres-ssm) |
| WAREHOUSE_HOST | Database host |
| WAREHOUSE_USERNAME | Database username |
| WAREHOUSE_PASSWORD | Database password |
| WAREHOUSE_DATABASE | Database name |
| WAREHOUSE_SCHEMA | Database schema |
| DB_CREDS_ARN | Secrets Manager ARN for database credentials |
| DB_LOGGING | Set to true to enable TypeORM query logging |
| LOGGER_SOFT_WARNING | Set to true to downgrade warnings to info level |
| IS_OFFLINE | Set by serverless-offline for local development |
Project Structure
src/
├── action/ # Action handlers (e.g. unmatched route)
├── config/ # Dependency definitions
├── dependency-injection/ # DI container and base class
├── entity/ # TypeORM entities by domain
├── enums/ # Shared enumerations
├── event/ # EventBridge event classes
├── migration/ # Database migrations by schema
├── model/ # Response, status, and webhook models
├── service/ # Core services (logger, request, warehouse, auth, etc.)
├── util/ # Utilities (LambdaTermination, PromisifiedDelay)
└── wrapper/ # Lambda wrapper implementationCI/CD
The project uses Concourse CI with the following pipeline:
- lint-and-test - runs lint, tests, and dependency audit in parallel
- migrations.staging - creates schemas and runs migrations on staging
- migrations.production - promotes to production after staging succeeds
- migrations.sandbox - promotes to sandbox after staging succeeds
Testing
Tests are colocated with source files (e.g. src/service/logger.test.js alongside src/service/logger.js).
yarn testLicense
MIT
