Skip to content

Infrastructure as Code

Define event stores and subscriptions in TypeScript, deploy with one command

DeltaBase supports Infrastructure as Code (IaC) through TypeScript configuration files. Define your event stores and subscriptions declaratively, and deploy them with a single command.


Create a deltabase.config.ts file in your project root:

import type { InfrastructureConfig as DeltaBaseConfig } from '@delta-base/server';
const config: DeltaBaseConfig = {
eventStores: [
{
name: 'my-service-events',
description: 'Events for my service',
subscriptions: [
{
id: 'users-webhook',
eventFilter: 'user.*',
subscriberType: 'webhook',
webhook: {
url: 'https://my-service.com/api/projections/users/events',
headers: {
Authorization: 'Bearer my-secret-token',
},
},
},
],
},
],
};
export default config;
Terminal window
pnpx @delta-base/cli deploy --api-key your-api-key

That’s it. Your event store and subscriptions are now live.


interface EventStoreConfig {
// Required: unique name (3-63 chars, lowercase alphanumeric + dash/underscore)
name: string;
// Optional: human-readable description
description?: string;
// Optional: storage and retention settings
settings?: {
retentionPeriodDays?: number; // How long to keep events
maxStreamSizeBytes?: number; // Maximum size per stream
};
// Optional: webhook subscriptions
subscriptions?: SubscriptionConfig[];
}
interface SubscriptionConfig {
// Required: unique identifier for this subscription
id: string;
// Required: which events to deliver (supports wildcards)
eventFilter: string | string[];
// Required: delivery mechanism
subscriberType: 'webhook';
// Required for webhook type
webhook?: {
url: string; // Endpoint URL
headers?: Record<string, string>; // Custom headers
timeoutMs?: number; // Request timeout
retryPolicy?: {
maxAttempts: number; // Total retry attempts
backoffMinutes: number; // Wait between retries
};
};
// Optional: where to start reading events
initialPosition?: 'latest' | 'earliest';
}

Here’s a complete configuration for a service with multiple projections:

deltabase.config.ts
import type { InfrastructureConfig as DeltaBaseConfig } from '@delta-base/server';
// Use environment variables for sensitive values
const SERVICE_URL = process.env.SERVICE_URL || 'http://localhost:8787';
const EVENT_STORE_NAME = process.env.DELTABASE_EVENT_STORE_NAME || 'my-service-dev';
const PROJECTION_TOKEN = process.env.PROJECTION_AUTH_TOKEN || 'dev-token';
const config: DeltaBaseConfig = {
eventStores: [
{
name: EVENT_STORE_NAME,
description: 'Authentication and user management service',
settings: {
retentionPeriodDays: 365,
maxStreamSizeBytes: 1073741824, // 1GB
},
subscriptions: [
// Users projection
{
id: 'users-projection',
eventFilter: ['user.created', 'user.updated', 'user.deleted'],
subscriberType: 'webhook',
webhook: {
url: `${SERVICE_URL}/api/projections/users/events`,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${PROJECTION_TOKEN}`,
},
retryPolicy: {
maxAttempts: 5,
backoffMinutes: 2,
},
},
},
// Organizations projection
{
id: 'organizations-projection',
eventFilter: [
'organization.created',
'organization.updated',
'organization.deleted',
],
subscriberType: 'webhook',
webhook: {
url: `${SERVICE_URL}/api/projections/organizations/events`,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${PROJECTION_TOKEN}`,
},
retryPolicy: {
maxAttempts: 5,
backoffMinutes: 2,
},
},
},
// Cross-aggregate projection (users + organizations + memberships)
{
id: 'user-organizations-projection',
eventFilter: [
'user.created',
'user.updated',
'user.deleted',
'organization.created',
'organization.updated',
'organization.deleted',
'membership.created',
'membership.updated',
'membership.deleted',
],
subscriberType: 'webhook',
webhook: {
url: `${SERVICE_URL}/api/projections/user-organizations/events`,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${PROJECTION_TOKEN}`,
},
retryPolicy: {
maxAttempts: 5,
backoffMinutes: 2,
},
},
},
],
},
],
};
export default config;

Terminal window
# Deploy using default config file (deltabase.config.ts)
pnpx @delta-base/cli deploy --api-key your-api-key
# Deploy using custom config file
pnpx @delta-base/cli deploy --config ./config/production.ts --api-key your-api-key
# Deploy to custom API URL
pnpx @delta-base/cli deploy --api-url https://api.your-domain.com --api-key your-api-key

Preview changes without applying them:

Terminal window
pnpx @delta-base/cli deploy --dry-run --api-key your-api-key

Enable detailed logging:

Terminal window
pnpx @delta-base/cli deploy --verbose --api-key your-api-key

Configure the CLI with environment variables instead of flags:

Terminal window
# Set API key
export DELTABASE_API_KEY=your-api-key
# Set API URL (optional, defaults to production)
export DELTABASE_API_URL=https://api.delta-base.com
# Deploy without flags
pnpx @delta-base/cli deploy

Create a .env file:

Terminal window
DELTABASE_API_KEY=your-api-key
DELTABASE_API_URL=https://api.delta-base.com
# Your service configuration
SERVICE_URL=https://my-service.example.com
PROJECTION_AUTH_TOKEN=super-secret-token
DELTABASE_EVENT_STORE_NAME=my-service-production

Load it before deployment:

Terminal window
source .env && pnpx @delta-base/cli deploy

Use the same config file with different environment variables:

deltabase.config.ts
const SERVICE_URL = process.env.SERVICE_URL;
const EVENT_STORE_NAME = process.env.DELTABASE_EVENT_STORE_NAME;
const config: DeltaBaseConfig = {
eventStores: [
{
name: EVENT_STORE_NAME, // Different per environment
subscriptions: [
{
id: 'users-projection',
webhook: {
url: `${SERVICE_URL}/api/projections/users/events`,
// ...
},
},
],
},
],
};

Deploy with different env files:

Terminal window
# Development
source .env.development && pnpx @delta-base/cli deploy
# Staging
source .env.staging && pnpx @delta-base/cli deploy
# Production
source .env.production && pnpx @delta-base/cli deploy

Create environment-specific config files:

config/
├── deltabase.development.ts
├── deltabase.staging.ts
└── deltabase.production.ts

Deploy the appropriate one:

Terminal window
pnpx @delta-base/cli deploy --config ./config/deltabase.production.ts

The deploy command performs reconciliation - it compares your config to the current state and makes the minimum necessary changes.

ScenarioBehavior
Event store in config but doesn’t existCreate new event store
Event store exists with different settingsUpdate settings
Event store exists and matches configNo change
ScenarioBehavior
Subscription in config but doesn’t existCreate new subscription
Subscription exists with different configUpdate subscription
Subscription exists but not in configDelete subscription
Subscription matches configNo change

Warning: Subscriptions not in your config file will be deleted. This ensures your config file is the single source of truth.


name: Deploy Infrastructure
on:
push:
branches: [main]
paths:
- 'deltabase.config.ts'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Deploy DeltaBase Infrastructure
run: pnpx @delta-base/cli deploy
env:
DELTABASE_API_KEY: ${{ secrets.DELTABASE_API_KEY }}
SERVICE_URL: ${{ vars.SERVICE_URL }}
PROJECTION_AUTH_TOKEN: ${{ secrets.PROJECTION_AUTH_TOKEN }}
DELTABASE_EVENT_STORE_NAME: ${{ vars.EVENT_STORE_NAME }}
deploy-infrastructure:
stage: deploy
image: node:20
script:
- npx @delta-base/cli deploy
only:
- main
- tags
variables:
DELTABASE_API_KEY: $DELTABASE_API_KEY
SERVICE_URL: $SERVICE_URL

For more control, use a manual workflow:

name: Deploy Infrastructure (Manual)
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy'
required: true
type: choice
options:
- development
- staging
- production
dry_run:
description: 'Dry run (preview only)'
required: false
type: boolean
default: false
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Deploy (Dry Run)
if: ${{ inputs.dry_run }}
run: pnpx @delta-base/cli deploy --dry-run --verbose
env:
DELTABASE_API_KEY: ${{ secrets.DELTABASE_API_KEY }}
- name: Deploy
if: ${{ !inputs.dry_run }}
run: pnpx @delta-base/cli deploy
env:
DELTABASE_API_KEY: ${{ secrets.DELTABASE_API_KEY }}

Treat deltabase.config.ts like any other infrastructure code:

Terminal window
git add deltabase.config.ts
git commit -m "Add users projection subscription"

Never commit tokens or secrets:

// Good
headers: {
Authorization: `Bearer ${process.env.PROJECTION_AUTH_TOKEN}`,
}
// Bad - don't do this
headers: {
Authorization: 'Bearer my-actual-secret-token',
}

Preview changes before applying:

Terminal window
pnpx @delta-base/cli deploy --dry-run

IDs should describe what the subscription does:

// Good
{ id: 'users-projection' }
{ id: 'order-notifications-webhook' }
{ id: 'analytics-events-export' }
// Bad
{ id: 'sub1' }
{ id: 'webhook' }

For large projects, split your config:

config/subscriptions/users.ts
export const usersSubscription = {
id: 'users-projection',
eventFilter: ['user.created', 'user.updated', 'user.deleted'],
// ...
};
// config/subscriptions/orders.ts
export const ordersSubscription = {
id: 'orders-projection',
eventFilter: ['order.created', 'order.shipped'],
// ...
};
// deltabase.config.ts
import { usersSubscription } from './config/subscriptions/users';
import { ordersSubscription } from './config/subscriptions/orders';
const config: DeltaBaseConfig = {
eventStores: [
{
name: 'my-service',
subscriptions: [usersSubscription, ordersSubscription],
},
],
};

Error: Could not find deltabase.config.ts

Solution: Ensure the file exists in your project root, or use --config to specify the path.

Error: Invalid API key

Solution:

  • Check your API key is correct
  • Ensure it’s set via --api-key flag or DELTABASE_API_KEY environment variable
  • Verify the key has the necessary permissions
Error: Event store name must be 3-63 characters

Solution: Event store names must:

  • Be 3-63 characters long
  • Use only lowercase letters, numbers, dashes, and underscores
  • Start with a letter or number

The subscription was created but events aren’t being delivered.

Solution:

  • Verify the webhook URL is publicly accessible
  • Check your server is running and accepting requests
  • Verify the PROJECTION_AUTH_TOKEN is correct on both sides