mantle.config.ts Reference
Project configuration for Mantle, declared via defineConfig() from @j0nathan-ll0yd/core.
// mantle.config.ts
import { defineConfig } from "@j0nathan-ll0yd/core";
export default defineConfig({
name: "my-api",
});The CLI reads this file at build and deploy time. It does not affect Lambda runtime behavior.
Top-Level Options
| Option | Type | Required | Description |
|---|---|---|---|
name | string | yes | Project name. Used as prefix for Terraform resource naming. |
database | object | — | Database provider config. |
auth | object | — | Auth provider config. |
features | object | — | Feature flags for observability and resilience. |
backend | object | — | Remote Terraform state backend (S3). |
allowedStages | array | — | Stages mantle deploy may target; any other --stage is hard-refused. |
customVariables | array | — | Additional Terraform variables appended to variables.tf. |
storage | array | — | S3 storage buckets to provision. |
eventbridge | object | — | EventBridge custom bus configuration. |
cors | object | — | CORS policy for API Gateway responses. |
queues | array | — | SQS queues to provision. |
dynamodb | array | — | DynamoDB tables to provision. |
websocket | object | — | WebSocket API Gateway configuration. |
observability | object | — | ADOT and metrics configuration. |
secrets | object | — | SOPS-encrypted secrets configuration. |
sns | object | — | SNS topics and platform applications. |
cloudfront | object | — | CloudFront distribution fronting API Gateway. |
authorizer | object | — | API Gateway custom authorizer cache settings. |
layers | array | — | Custom Lambda layers. |
containerRegistry | object | — | ECR container registry configuration (imageRetentionCount only). |
ci | object | — | CI pipeline settings for mantle ci and workflow generation. |
openapi | object | — | Extra schema sources for mantle generate openapi. |
awsSdkAppId | string | — | AWS SDK user-agent application identifier. |
Database
database: {
provider: "aurora-dsql";
}| Option | Type | Description |
|---|---|---|
provider | 'aurora-dsql' | 'aurora-serverless-v2' | 'neon' | Database provider. |
Backend (Terraform State)
backend: {
s3: {
bucket: 'my-tfstate',
key: 'infra.tfstate',
region: 'us-east-1',
}
}| Option | Type | Description |
|---|---|---|
s3.bucket | string | S3 bucket name. |
s3.key | string | State file key. |
s3.region | string | Bucket region. |
s3.encrypt | boolean | Enable server-side encryption. |
s3.dynamodbTable | string | DynamoDB table name for state locking. Deprecated — prefer useLockfile. |
s3.workspaceKeyPrefix | string | Key prefix for Terraform workspaces. |
s3.useLockfile | boolean | S3-native state locking via conditional writes (OpenTofu 1.10+). Preferred over DynamoDB; defaults to true for new projects. |
Allowed Stages (Deploy Guard)
allowedStages: ["staging"];When set, mantle deploy --stage <stage> hard-errors for any stage not in the list — at stage-validation time, before any build, generate, or tofu invocation, so --dry-run is refused too and --yes does not bypass it. Unset means all stages are allowed. Use with a single shared state backend to prevent a wrong-stage deploy from renaming (destroy + recreate) every resource in the live stack.
Related: an apply whose initialized state lists zero addresses (empty local file or empty remote key — e.g. a stale checkout pointing at a pre-rename backend key) is refused when the account already has {stage}--prefixed Lambdas (override with --allow-empty-state for a genuine first bootstrap).
EventBridge
eventbridge: {
bus: 'MyBus',
sqsTargets: [
{
detailType: 'OrderCreated',
queue: 'OrderQueue',
}
]
}| Option | Type | Description |
|---|---|---|
bus | string | Custom event bus name. |
sqsTargets | array | EventBridge rules that route events to SQS queues. |
sqsTargets[].detailType | string | EventBridge detail-type to match. |
sqsTargets[].queue | string | Queue name (must match an entry in queues). |
sqsTargets[].inputTransformer | object | Reshape event before delivery. Has inputPaths and inputTemplate. |
Storage (S3)
storage: [{ name: "assets", cloudfront: true, intelligentTiering: true }];| Option | Type | Default | Description |
|---|---|---|---|
name | string | — | Bucket logical name. |
bucketNameOverride | string | — | Full bucket name as Terraform expression, bypasses name prefix. |
cloudfront | boolean | false | Provision a CloudFront distribution. |
cloudfrontPriceClass | string | — | CloudFront price class. |
corsOrigins | string[] | — | CORS allowed origins for the bucket. |
corsMethods | string[] | — | CORS allowed methods. |
intelligentTiering | boolean | false | Enable S3 Intelligent-Tiering. |
versioning | boolean | false | Enable object versioning. |
assets | string[] | — | Static asset keys to upload. Source path: static/{key} at project root. |
Queues (SQS)
queues: [{ name: "OrderQueue", visibilityTimeoutSeconds: 300 }];| Option | Type | Default | Description |
|---|---|---|---|
name | string | — | Queue name. Must match the queue option in defineSqsHandler. |
delaySeconds | number | 0 | Delivery delay in seconds. |
maxMessageSize | number | 262144 | Max message size in bytes. |
retentionSeconds | number | 345600 | Message retention in seconds (4 days). |
visibilityTimeoutSeconds | number | 30 | Visibility timeout in seconds. |
enableDlqAlarm | boolean | true | Enable CloudWatch alarm for DLQ message depth. |
DynamoDB
dynamodb: [
{
name: "idempotency",
hashKey: "id",
attributes: [{ name: "id", type: "S" }],
ttlAttribute: "expiration",
},
];| Option | Type | Default | Description |
|---|---|---|---|
name | string | — | Table logical name. |
tableNameOverride | string | — | Full table name as Terraform expression. |
hashKey | string | — | Partition key attribute name. |
rangeKey | string | — | Sort key attribute name. |
attributes | array | — | Attribute definitions. Each has name and type (S, N, B). |
billingMode | string | PAY_PER_REQUEST | PAY_PER_REQUEST or PROVISIONED. |
ttlAttribute | string | — | Attribute name for TTL-based expiry. |
pointInTimeRecovery | boolean | false | Enable PITR. |
globalSecondaryIndexes | array | — | GSI definitions. Each has name, hashKey, rangeKey?, projectionType?. |
WebSocket
websocket: {
routeSelectionExpression: '$request.body.action',
stageName: 'live',
}| Option | Type | Default | Description |
|---|---|---|---|
routeSelectionExpression | string | $request.body.action | Route selection expression. |
stageName | string | live | API Gateway stage name. |
throttlingBurstLimit | number | 100 | Burst limit. |
throttlingRateLimit | number | 50 | Rate limit (requests/second). |
Observability
observability: {
adot: true,
metricsNamespace: 'MyApp',
}| Option | Type | Default | Description |
|---|---|---|---|
adot | boolean | false | Attach AWS Distro for OpenTelemetry (ADOT) layer to all Lambdas. |
metricsNamespace | string | — | CloudWatch metrics namespace. Injected as METRICS_NAMESPACE env var. |
commonEnv | Record<string, string> | — | Additional env vars injected into all Lambdas via locals.tf. |
CORS
cors: {
origins: ['https://app.example.com'],
methods: ['GET', 'POST'],
maxAge: 86400,
}| Option | Type | Default | Description |
|---|---|---|---|
origins | string[] | — | Allowed origins. Empty = no CORS headers (secure default). |
methods | string[] | ['GET','POST','PUT','DELETE','PATCH','OPTIONS'] | Allowed HTTP methods. |
headers | string[] | ['Content-Type','Authorization'] | Allowed request headers. |
maxAge | number | 86400 | Preflight cache max-age in seconds. |
Secrets (SOPS)
secrets: {
provider: 'sops',
filePattern: 'secrets.{env}.enc.yaml',
}| Option | Type | Default | Description |
|---|---|---|---|
provider | 'sops' | — | Secrets provider. Only sops is supported. |
filePattern | string | secrets.{env}.enc.yaml | Encrypted file pattern. {env} is replaced with staging or prod. |
Per-Lambda secrets are mapped in defineLambda({ secrets: { ENV_VAR: 'sops.key.path' } }).
SNS
sns: {
topics: [{ name: 'notifications' }],
platformApplications: [
{
name: 'my-app',
platform: 'APNS_SANDBOX',
credentialSecret: 'apns.privateKey',
principalSecret: 'apns.certificate',
}
],
}| Option | Type | Description |
|---|---|---|
topics[].name | string | Topic name. Wired as env var. |
platformApplications[].name | string | Application name. |
platformApplications[].platform | 'APNS' | 'APNS_SANDBOX' | 'GCM' | Push platform. |
platformApplications[].credentialSecret | string | SOPS key path for the platform credential. |
platformApplications[].principalSecret | string | SOPS key path for the platform principal. |
platformApplications[].resourceName | string | Override for the Terraform resource identifier. |
CloudFront (API Distribution)
cloudfront: {
apiDistribution: {
geoRestriction: { type: 'whitelist', locations: ['US', 'CA'] },
forwardedHeaders: ['Authorization', 'X-API-Key'],
cacheTtl: { default: 0, min: 0, max: 0 },
}
}| Option | Type | Description |
|---|---|---|
apiDistribution.geoRestriction | object | type: whitelist or blacklist. locations: ISO 3166-1 alpha-2 codes. |
apiDistribution.forwardedHeaders | string[] | Headers forwarded to the origin. |
apiDistribution.cacheTtl | object | Cache TTL settings: default, min, max in seconds. |
Custom Lambda Layers
layers: [
{
name: "ffmpeg",
path: "layers/ffmpeg",
compatibleArchitectures: ["x86_64"],
description: "ffmpeg binary",
},
];| Option | Type | Default | Description |
|---|---|---|---|
name | string | — | Layer name. Referenced in defineLambda({ layers: ['local.ffmpeg_layer_arn'] }). |
path | string | — | Path to layer source directory, relative to project root. |
compatibleArchitectures | string[] | ['arm64'] | arm64 and/or x86_64. |
compatibleRuntimes | string[] | — | Compatible runtimes (e.g. ['nodejs24.x']). Omit for binary-only layers. |
description | string | — | Human-readable description. |
Custom Terraform Variables
customVariables: [
{
name: "api_quota_limit",
type: "number",
description: "Daily API quota",
default: "10000",
},
];| Option | Type | Description |
|---|---|---|
name | string | Variable name. |
type | string | Terraform type expression: string, number, bool, list(string), etc. |
description | string | Variable description. |
default | string | Default value as Terraform expression string. Omit for required variables. |
sensitive | boolean | Mark as sensitive (redacted in plan output, and redacted in deploy's binding warnings). |
validation | object | condition: Terraform expression. errorMessage: shown on violation. |
valueFrom | string | Project-relative module whose default export returns the value. See below. |
valueFrom — let the project compute a variable's value
valueFrom names a module inside the project. Its default export is a function returning the value as a string. mantle deploy invokes it and passes the result as -var, exactly as it passes the container image URIs and the deployment provenance facts. Nothing is exported into the environment as TF_VAR_.
customVariables: [
{
name: "llm_spec_contract",
type: "string",
description: "Spec contract the composer fail-closes without",
valueFrom: "scripts/llm-provenance-contract.mjs",
validation: { regex: "^package:[0-9.]+/rule:[0-9]+$" },
},
];// scripts/llm-provenance-contract.mjs
export default function computeSpecContract() {
return `package:${version}/rule:${ruleCount}`;
}Behavior:
- Binding is by variable name. The variable may be declared by this
customVariablesentry or by a Lambda'sdefineLambda({env}); either waydeploybinds it.defineLambdastays the single place a Lambda says what it reads. When both declare the same name, thevalueFromentry supplies the generatedvariableblock, so itsdescriptionandvalidationreachvariables.tfinstead of the machine-derived description. - Resolution runs before any side effect — before the build, the ECR push, and the apply.
- The provider may be sync or async; the result is awaited. There is no timeout: a provider that never settles hangs the deploy before anything is written.
- The returned string is trimmed, matching how an explicit
TF_VAR_binding is read. validationis enforced twice: by the CLI against the resolved value before the deploy proceeds, and by tofu at plan time through the generatedvalidationblock. A raw HCLconditionis enforced by tofu only — the CLI cannot evaluate HCL.- An explicit
TF_VAR_<name>wins, with the same precedence the provenance facts use: a blank value counts as unset, and a divergence between the explicit value and the provider's is reported. A CI job already binding the value keeps working unchanged.
deploy refuses, with a named message and before any side effect, when: the module is missing, fails to bundle or import, exports no callable default, throws, returns a non-string, returns a blank string, or returns a value failing the declared validation. It also refuses the declaration itself when name is a framework-owned variable (environment, api_bearer_token, log_level, the provenance facts, image_uri_*, and the rest), when two entries claim one name, or when valueFrom is absolute or points outside the project — each of those would bind a value the project did not intend, or bind a different value on CI than on a laptop.
Authorizer
authorizer: {
cacheTtl: 0;
}| Option | Type | Default | Description |
|---|---|---|---|
cacheTtl | number | 300 | Token cache TTL in seconds. 0 disables caching and identity source validation. |
CI
Consumed by mantle ci and by GitHub Actions workflow generation.
ci: {
deploy: true,
customSteps: [
{ name: "contract-check", command: "pnpm run check:contract", phase: "Validate" },
],
}| Option | Type | Default | Description |
|---|---|---|---|
deploy | boolean | — | Run deployment after CI passes. |
customSteps | array | — | Instance-specific steps run alongside the built-in pipeline. |
Each entry in customSteps is { name, command, phase }. A custom step is treated as optional for fail-fast ordering, but it still fails the pipeline when it runs and exits non-zero.
OpenAPI
Consumed by mantle generate openapi.
openapi: {
additionalSchemas: [
{ source: "#types/notification-schemas", prefix: "Notifications." },
],
}| Option | Type | Description |
|---|---|---|
additionalSchemas | array | Extra Zod schema files to emit as standalone components in the generated spec. |
Each entry is { source, prefix }: source is a TypeScript path alias to the schema file, prefix is prepended to every generated component name from that file.
AWS SDK Application ID
awsSdkAppId: "my-api";| Option | Type | Description |
|---|---|---|
awsSdkAppId | string | Emitted as AWS_SDK_UA_APP_ID on every Lambda. |
Sets the AWS SDK user-agent application identifier, which makes per-application CloudTrail and Cost Explorer filtering possible. Maximum 50 characters; allowed characters are A-Z a-z 0-9 ! $ % & ' * + - . ^ _ | ~. When undefined, no AWS_SDK_UA_APP_ID variable is emitted.
Extended CLI Config
For CI, hooks, and conventions settings beyond the ci block above, use defineToolingConfig from @j0nathan-ll0yd/cli/config instead of defineConfig. See the CLI reference for details.