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-wrapper
Quick 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.
Warmup 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 System
Every handler receives a DependencyInjection container as its first argument. The container provides access to 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 |
| Tracing | DEFINITIONS.TRACING | OpenTelemetry spans and attributes, exported to Honeycomb |
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 failure
Validation 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.
TracingService (Honeycomb / OpenTelemetry)
Every wrapped invocation is traced with OpenTelemetry and exported to Honeycomb over OTLP/HTTP. Tracing is off unless HONEYCOMB_API_KEY is set - without it the OpenTelemetry API hands out non-recording spans, so services can call these helpers unconditionally.
What you get per invocation, with no code changes in the service:
- one root span named after the trigger (
POST /organisations/{id}, the EventBridgedetail-type,aws:sqs <function>), carryingfaas.invocation_id,faas.coldstart,faas.trigger, the HTTP method/route/path/client IP/user agent,http.response.status_code, andfelloh.request_idfrom theResponseModelbody; - a child span for every
HTTPService.request()call (HTTP POST, withserver.address,url.pathand the response status; the query string is never recorded); - every
logger.error()recorded as an exception event on the active span. A handler that logs an error and still returns 2xx keeps an errored trace, so "errors that did not fail the request" stay queryable; - 5xx responses and thrown errors mark the span as errored with
error.type; - an explicit flush before the handler returns (Lambda freezes the process immediately afterwards), bounded by
HONEYCOMB_FLUSH_TIMEOUT_MS.
const tracing = di.get(DEFINITIONS.TRACING);
// Add business context to the invocation span - the fields you will actually query by
tracing.setAttributes({ organisation_id: organisation.id, transaction_id: transaction.id });
// Wrap a unit of work in a child span
const score = await tracing.withSpan('risk.score', async (span) => {
span.setAttribute('model', 'laya');
return scorer.score(narrative);
});
tracing.isEnabled(); // true when spans are being exported
tracing.getTracer(); // raw OpenTelemetry tracer for anything else
Error detail: enough on the span to locate the fault
When an invocation fails (thrown error, 4xx/5xx response, or an error logged mid-request), the invocation span carries flat, queryable fields so a person or an AI can narrow the problem down from Honeycomb alone:
| Field | What it tells you |
|---|---|
faas.handler, code.function | The Lambda handler export (handler.signup) and the wrapped function's name (Signup), i.e. the file to open |
aws.log.group.names, aws.log.stream.names, aws.xray.trace_id, faas.invocation_id | Exactly where the CloudWatch logs for this invocation are |
error.type, error.message, error.stack, error.code, error.cause | The thrown error, stack capped at 4 KB |
db.query.text, db.query.parameters, db.response.status_code, db.error.detail, db.error.position, db.error.constraint, db.error.table, db.error.hint | For TypeORM/Postgres failures: the exact statement, its (redacted) parameters and the SQLSTATE detail |
felloh.upstream.method, felloh.upstream.url, felloh.upstream.status_code, felloh.upstream.response_body | For failed calls to providers or other services, what was called and what came back |
felloh.response.errors | The errors[] array the API returned |
enduser.id, felloh.request.path_parameters, felloh.request.query, felloh.request.body | Who called and with what. The body is redacted (passwords, tokens, card numbers, IBANs, sort codes and similar keys are replaced) and capped at 4 KB |
faas.remaining_time_ms, faas.timeout_imminent | Remaining budget at the end; a span closed by the timeout watchdog carries faas.timeout_imminent = true |
service.version | The deployed commit when the pipeline provides GIT_SHA, COMMIT_SHA or SERVICE_VERSION |
Honeycomb dataset definitions
Honeycomb's Home, service map and error rate features bind to fields chosen under Dataset settings → Definitions. The wrapper emits a field for each one it can; pick these when Honeycomb asks:
| Definition | Field the wrapper sends | Notes |
|---|---|---|
| Error | error | A short Type: message string on every failed span: a 5xx response, a throw with no HTTP outcome, an error logged via logger.error(), or the timeout watchdog. Absent on healthy spans and on 4xx responses, including 404s and 422s that handlers produce by throwing a Response; those keep span status unset and still carry felloh.response.errors. |
| HTTP Status Code | http.response.status_code | Current OpenTelemetry name; Honeycomb's default guess is the older http.status_code, so select it explicitly. |
| Route | http.route | The API Gateway resource, e.g. /organisations/{id}. |
| User | enduser.id | The Cognito user id from the authorizer, when the request was authenticated. |
| Name, Service Name, Span Duration, Trace/Span/Parent IDs | name, service.name, duration_ms, trace.trace_id, trace.span_id, trace.parent_id | Set by Honeycomb's OTLP ingest; nothing to choose. |
| Metadata: Annotation Type | meta.annotation_type | span_event for the exception events we emit, link for span links. Filled by OTLP ingest once such data has arrived. |
| Metadata: Link Span ID / Link Trace ID | trace.link.span_id, trace.link.trace_id | Filled by OTLP ingest; the wrapper does not create links today. |
| Logs: Severity | severity | Present on the wrapper's exception events, always error. |
| Logs: Message | message | Present on the wrapper's exception events: the logged context plus the error message. The standard exception.message is sent as well. |
The wrapper sends traces only, so the two Logs definitions bind to span events rather than to a separate log stream.
Two behaviours worth knowing:
- Timeout watchdog. A Lambda that hits its timeout is killed before any code runs, so it would leave no trace. Shortly before the deadline (remaining time minus the flush timeout and a small margin) the wrapper closes the invocation span as an error, marks it
faas.timeout_imminent, and exports it. If the handler does finish afterwards the span is left as it is. - Request body capture follows
HONEYCOMB_CAPTURE_REQUEST:errors(default) attaches the redacted body only to failed invocations,alwaysattaches it to every span,offnever sends it. Path parameters and the query string are always recorded (redacted).
Configuration (set per service in serverless.yml provider.environment, values from the pipeline):
| Variable | Default | Purpose |
|---|---|---|
HONEYCOMB_API_KEY | unset (tracing off) | Honeycomb ingest key. Setting it enables tracing. |
HONEYCOMB_API_ENDPOINT | https://api.honeycomb.io | Use https://api.eu1.honeycomb.io for EU teams. |
OTEL_SERVICE_NAME | SERVICE_NAME, then the function name | Honeycomb creates one dataset per service name. |
HONEYCOMB_DATASET | unset | Honeycomb Classic only; environment-based teams leave it unset. |
HONEYCOMB_SAMPLE_RATE | 1 | Keep 1 in N traces at the root. Spans carry SampleRate so Honeycomb re-weights counts. Head sampling cannot see errors, so keep this at 1 until volume forces otherwise. |
HONEYCOMB_FLUSH_TIMEOUT_MS | 2000 | Upper bound on the export wait added to each invocation. |
HONEYCOMB_CAPTURE_REQUEST | errors | When to attach the redacted request body: errors, always or off. |
GIT_SHA / COMMIT_SHA / SERVICE_VERSION | Lambda function version | Reported as service.version, so a trace names the deployed commit. Set it from the pipeline. |
Resource attributes come from the Lambda environment: service.name, service.version, deployment.environment.name (STAGE), cloud.region, faas.name, faas.version, faas.max_memory.
Notes:
- The provider lives for the life of the container; warm invocations reuse it.
Tracing.reset()tears it down (tests). - The exporter is a plain HTTPS call to Honeycomb, so it adds a few hundred milliseconds to the cold start and roughly the flush round trip to each invocation. Telemetry failures are logged and never fail a request.
- If a Lambda layer has already registered a global tracer provider, it wins; the wrapper's provider then only flushes an empty buffer.
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 descendants
HTTPService
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 digest
AuditLoggerService
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_FAILURE
Lambda 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:revert
Migrations 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 in 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:create
Environment 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 implementation
CI/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 test
License
MIT