# @worktif/runtime

[![npm version](https://img.shields.io/npm/v/@worktif/runtime.svg)](https://www.npmjs.com/package/@worktif/runtime)
![Node Support](https://img.shields.io/badge/node-%3E%3D18.0.0-43853d)
![TypeScript](https://img.shields.io/badge/TypeScript-5.8-3178c6)
![License](https://img.shields.io/badge/license-Elastic--2.0-blue.svg)

Type-safe AWS Lambda microservices framework with CDK infrastructure-as-code. Define Lambda handlers with dependency injection, deploy with a single command.

```typescript
// Define a Lambda handler with type-safe DI
type UserTies = { userService: UsersService };

export const getUser: LambdaDefinition<UserTies> = {
  ties: { userService: UsersService },
  handler: async (event) => {
    const user = await event.ties.userService.getUser(event.pathParameters.id);
    return { statusCode: 200, body: JSON.stringify(user) };
  },
  http: { method: 'GET', path: '/users/{id}' }
};
```

---

## Why Runtime?

| Traditional Approach | With Runtime |
|---------------------|--------------|
| Manual Lambda wiring | Type-safe DI via `event.ties` |
| Write CloudFormation/Terraform | CDK constructs auto-provision |
| Separate infra and app repos | Unified codebase |
| Runtime DI resolution overhead | Pre-built DI at deploy time |

---

## Features

- **Type-Safe Lambda DI** — Access services via `event.ties.serviceName` with full TypeScript support
- **Declarative Microservices** — Group Lambda handlers by domain with shared DI containers
- **CDK Infrastructure** — Two-stack architecture: `RuntimeInfraStack` (S3, DynamoDB, IAM) + `RuntimeWebStack` (Lambda, API Gateway)
- **Cold Start Optimization** — `init` functions execute once per Lambda environment, results cached in `event.snapshot`
- **Multiple Auth Types** — JWT, IAM, Cognito, Custom Lambda authorizers
- **API Gateway v1 & v2** — HTTP API (default) or REST API with full feature support
- **React SSR** — Optional server-side rendering with streaming and S3 caching

---

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
  - [LambdaDefinition](#lambdadefinition)
  - [MicroserviceDefinition](#microservicedefinition)
  - [Ties Classes](#ties-classes)
  - [Init Functions](#init-functions)
- [CDK Infrastructure](#cdk-infrastructure)
- [HTTP Integration](#http-integration)
- [CLI Reference](#cli-reference)
- [React SSR (Optional)](#react-ssr-optional)
- [Package Exports](#package-exports)
- [License](#license)

---

## Installation

```bash
npm install @worktif/runtime
# or
yarn add @worktif/runtime
```

**Prerequisites:**
- Node.js ≥ 18
- AWS CLI configured
- AWS CDK CLI: `npm install -g aws-cdk`

---

## Quick Start

### 1. Define a Service

```typescript
// src/services/users.service.ts
export class UsersService {
  async getUser(id: string) {
    // Your business logic
    return { id, name: 'John Doe', email: 'john@example.com' };
  }

  async createUser(data: { email: string; name: string }) {
    return { id: crypto.randomUUID(), ...data };
  }
}
```

### 2. Create a Ties Class

```typescript
// src/ties/users.ties.ts
import { PureContainer } from '@worktif/utils';
import { UsersService } from '../services/users.service';

export class UsersTies {
  static register(container: PureContainer<string>) {
    container.tie('userService', UsersService, []);
  }
}
```

### 3. Define Lambda Handlers

```typescript
// src/handlers/users.handlers.ts
import { LambdaDefinition } from '@worktif/runtime';
import { UsersService } from '../services/users.service';

type UserTies = { userService: UsersService };

export const getUser: LambdaDefinition<UserTies> = {
  id: 'get',
  ties: { userService: UsersService },
  handler: async (event) => {
    const user = await event.ties.userService.getUser(event.pathParameters.id);
    return { statusCode: 200, body: JSON.stringify(user) };
  },
  http: { method: 'GET', path: '/users/{id}' }
};

export const createUser: LambdaDefinition<UserTies> = {
  id: 'create',
  ties: { userService: UsersService },
  handler: async (event) => {
    const data = JSON.parse(event.body || '{}');
    const user = await event.ties.userService.createUser(data);
    return { statusCode: 201, body: JSON.stringify(user) };
  },
  http: { method: 'POST', path: '/users' }
};
```

### 4. Create CDK Stacks

```typescript
// cdk/bin/app.ts
import * as cdk from 'aws-cdk-lib';
import { RuntimeInfraStack, RuntimeWebStack } from '@worktif/runtime/infra';
import { UsersTies } from '../src/ties/users.ties';
import { getUser, createUser } from '../src/handlers/users.handlers';

const app = new cdk.App();
const stage = 'dev';
const serviceName = 'my-api';

// Infrastructure stack (S3, DynamoDB, IAM)
const infraStack = new RuntimeInfraStack(app, `${serviceName}-infra-${stage}`, {
  stage,
  serviceName,
  env: { region: 'us-east-1' }
});

// Runtime stack (Lambda, API Gateway)
new RuntimeWebStack(app, `${serviceName}-runtime-${stage}`, {
  stage,
  serviceName,
  infraStack,
  register: {
    users: {
      ties: [UsersTies],
      lambdas: [getUser, createUser]
    }
  }
});

app.synth();
```

### 5. Deploy

```bash
npx runtime build
cdk deploy --all
```

---

## Core Concepts

### LambdaDefinition

Declarative Lambda handler with typed dependency injection:

```typescript
import { LambdaDefinition } from '@worktif/runtime';

type MyTies = {
  paymentService: PaymentsService;
  configService: ConfigService;
};

type MySnapshot = {
  dbConnection: Connection;
};

export const processPayment: LambdaDefinition<MyTies, MySnapshot> = {
  // Unique ID within microservice (optional, auto-generated)
  id: 'process',

  // Dependencies — pass class constructors, receive instances in handler
  ties: {
    paymentService: PaymentsService,
    configService: ConfigService
  },

  // Cold start initialization (optional)
  init: async (ties) => {
    const dbConnection = await ties.configService.createDbConnection();
    return { dbConnection };
  },

  // Handler with typed event.ties and event.snapshot
  handler: async (event, context) => {
    // event.ties.paymentService — fully typed instance
    // event.snapshot.dbConnection — from init function
    const result = await event.ties.paymentService.charge(event.body);
    return { statusCode: 200, body: JSON.stringify(result) };
  },

  // HTTP integration (optional)
  http: {
    method: 'POST',
    path: '/payments/process',
    auth: { type: 'jwt', issuer: 'https://auth.example.com' }
  },

  // Lambda config overrides (optional)
  config: {
    memorySize: 1024,
    timeout: 30,
    environment: { PAYMENT_MODE: 'live' }
  }
};
```

**Type Safety:**
- `ties` property accepts class constructors
- `event.ties` provides instantiated objects with full IntelliSense
- `event.snapshot` typed from `init` return type

### MicroserviceDefinition

Group related Lambda handlers with shared DI:

```typescript
import { MicroserviceDefinition } from '@worktif/runtime';

type SharedSnapshot = { config: AppConfig };

const paymentsService: MicroserviceDefinition<SharedSnapshot> = {
  // DI configuration classes
  ties: [PaymentsTies, BillingTies],

  // Microservice-level init (runs before lambda-level init)
  init: async (ties) => {
    const config = await ties.configService.loadConfig();
    return { config };
  },

  // Lambda endpoints
  lambdas: [chargeHandler, refundHandler, webhookHandler]
};
```

**Architecture:**
- Each microservice = one DI container = one Lambda Layer
- Lambda handlers share dependencies within microservice
- Microservice `init` runs first, then lambda `init`, snapshots merged

### Ties Classes

Configure dependency injection using `PureContainer`:

```typescript
import { PureContainer } from '@worktif/utils';

export class PaymentsTies {
  static register(container: PureContainer<string>) {
    // Simple registration
    container.tie('paymentService', PaymentsService, []);

    // With dependencies
    container.tie('billingService', BillingService, ['paymentService']);

    // Singleton (default)
    container.tieSingleton('configService', ConfigService, []);

    // Constant value
    container.tieConst('apiKey', process.env.API_KEY);
  }
}
```

### Init Functions

Cold start optimization — execute once per Lambda environment:

```typescript
export const handler: LambdaDefinition<MyTies, MySnapshot> = {
  ties: { dbService: DatabaseService },

  // Runs once on cold start, result cached
  init: async (ties) => {
    const connection = await ties.dbService.connect();
    return { connection };
  },

  handler: async (event) => {
    // event.snapshot.connection — reused across warm invocations
    const result = await event.snapshot.connection.query('SELECT * FROM users');
    return { statusCode: 200, body: JSON.stringify(result) };
  }
};
```

**Execution Flow:**
```
Cold Start:
  1. Lambda environment created
  2. Microservice init executes (if defined)
  3. Lambda init executes (if defined)
  4. Snapshots merged (lambda takes precedence)
  5. Handler invoked with event.snapshot

Warm Invocation:
  1. Cached snapshot retrieved
  2. Handler invoked (no init execution)
```

---

## CDK Infrastructure

### Two-Stack Architecture

```typescript
import { RuntimeInfraStack, RuntimeWebStack } from '@worktif/runtime/infra';

// Stack 1: Long-lived infrastructure
const infraStack = new RuntimeInfraStack(app, 'MyApp-Infra-dev', {
  stage: 'dev',
  serviceName: 'my-app',
  enableSeo: true,  // Optional: DynamoDB for SEO metadata
});

// Stack 2: Application runtime
new RuntimeWebStack(app, 'MyApp-Runtime-dev', {
  stage: 'dev',
  serviceName: 'my-app',
  infraStack,
  register: { /* microservices */ },

  // API Gateway mode
  apiMode: 'httpApi',  // 'httpApi' (v2) | 'restApi' (v1) | 'lambdaUrl'

  // Lambda defaults
  lambdaMemorySize: 512,
  lambdaTimeout: 30,
  runtime: lambda.Runtime.NODEJS_22_X,

  // Global environment variables
  environment: {
    LOG_LEVEL: 'info'
  }
});
```

### RuntimeInfraStack Resources

- **S3 Bucket** — Static assets, SSR cache
- **IAM Role** — Lambda execution role with least privilege
- **DynamoDB Table** — SEO metadata (when `enableSeo: true`)
- **CloudFormation Outputs** — Cross-stack references

### RuntimeWebStack Resources

- **Lambda Functions** — One per `LambdaDefinition`
- **Lambda Layers** — One per microservice (contains DI container)
- **API Gateway** — HTTP API (v2) or REST API (v1)
- **IAM Permissions** — Auto-granted to Lambda role

### Declarative Infrastructure API

Create additional resources with framework conventions:

```typescript
// In RuntimeInfraStack context
const { construct: usersTable, entity } = infraStack.createTable('users', {
  partitionKey: { name: 'userId', type: dynamodb.AttributeType.STRING },
  billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
}, {
  autoGrant: true,      // Grant Lambda role access
  grantType: 'readWrite',
  enableStream: true,
  enablePitr: true,     // Point-in-time recovery
});

// SQS Queue
const { construct: ordersQueue } = infraStack.createQueue('orders', {}, {
  enableDlq: true,
  maxReceiveCount: 5,
});

// S3 Bucket
const { construct: assetsBucket } = infraStack.createBucket('assets', {
  versioned: true,
});
```

---

## HTTP Integration

### Authentication Types

```typescript
// No authentication
http: { method: 'GET', path: '/health', auth: 'none' }

// JWT (any OIDC provider)
http: {
  method: 'GET',
  path: '/users',
  auth: {
    type: 'jwt',
    issuer: 'https://auth.example.com',
    audience: ['api.example.com']
  }
}

// Cognito
http: {
  method: 'GET',
  path: '/profile',
  auth: {
    type: 'cognito',
    userPoolId: 'us-east-1_xxxxx',
    region: 'us-east-1',
    userPoolClientIds: ['client-id']
  }
}

// IAM (service-to-service)
http: { method: 'POST', path: '/internal', auth: { type: 'iam' } }

// Custom Lambda authorizer
http: {
  method: 'POST',
  path: '/admin',
  auth: {
    type: 'custom',
    authorizerLambda: myAuthFunction,
    enableSimpleResponses: true
  }
}
```

### CORS Configuration

```typescript
http: {
  method: 'POST',
  path: '/api/data',
  cors: true,
  corsConfig: {
    allowOrigins: ['https://myapp.com'],
    allowMethods: ['GET', 'POST'],
    allowHeaders: ['Content-Type', 'Authorization'],
    allowCredentials: true,
    maxAge: 3600
  }
}
```

### REST API vs HTTP API

```typescript
// HTTP API (v2) — default, lower latency, lower cost
new RuntimeWebStack(app, 'MyApp', {
  apiMode: 'httpApi',
  // ...
});

// REST API (v1) — API keys, usage plans, WAF, caching
new RuntimeWebStack(app, 'MyApp', {
  apiMode: 'restApi',
  // ...
});
```

---

## CLI Reference

```bash
# Development
npx runtime dev                    # Start dev server (port 3000)
npx runtime dev --port 8080        # Custom port

# Building
npx runtime build                  # Build all targets
npx runtime build --target lambda  # Lambda handlers only
npx runtime build --target infra   # CDK infrastructure only

# Deployment
npx runtime deploy --stage dev
npx runtime deploy --stage prod

# Resource Management
npx runtime stacks list            # List CloudFormation stacks
npx runtime stacks list --outputs  # With stack outputs
npx runtime destroy --stage dev    # Destroy stacks
npx runtime cache-clear --stage dev # Invalidate CloudFront

# SEO (optional)
npx runtime seo init               # Initialize SEO config
npx runtime seo sync --stage dev   # Sync to DynamoDB

# Diagnostics
npx runtime doctor                 # Environment checks
npx runtime info                   # System information
```

---

## React SSR (Optional)

Runtime supports server-side rendering as an optional feature:

```typescript
import { runtime, runtimeRouter, RuntimeHeadManager } from '@worktif/runtime';

runtime({
  // Microservices (primary)
  register: {
    users: { ties: [UsersTies], lambdas: [getUser, createUser] }
  },

  // React SSR (optional)
  app: ({ router }) => <App router={router} />,
  router: runtimeRouter({ router: routes }),

  config: { serviceName: 'my-app', stage: 'dev' }
});
```

**SSR Features:**
- `renderToPipeableStream()` for streaming HTML
- S3 caching with ETag-based invalidation
- `RuntimeHeadManager` for SEO metadata
- `runtimeAsync` data loaders (parallel execution)

See [React SSR Documentation](docs/integration/react-setup.md) for details.

---

## Package Exports

| Export | Path | Description |
|--------|------|-------------|
| Main | `@worktif/runtime` | Core types, React components |
| Infra | `@worktif/runtime/infra` | CDK constructs |
| Lambda | `@worktif/runtime/lambda` | Pre-built Lambda bundle |
| CLI | `@worktif/runtime/cli.js` | Command-line interface |
| Bin | `@worktif/runtime/bin` | Build utilities |

```typescript
// Types and React components
import { runtime, runtimeRouter, ErrorBoundary } from '@worktif/runtime';
import type { LambdaDefinition, MicroserviceDefinition } from '@worktif/runtime';

// CDK constructs
import { RuntimeInfraStack, RuntimeWebStack } from '@worktif/runtime/infra';
```

---

## Environment Variables

| Variable | Description | Context |
|----------|-------------|---------|
| `RUNTIME_ENV_STACK_NAME` | Service name | Lambda |
| `RUNTIME_ENV_STAGE` | Deployment stage | Lambda |
| `LAMBDA_ID` | Lambda identifier | Lambda |
| `STATIC_S3_BUCKET_NAME` | S3 bucket name | Lambda |

---

## Documentation

- [Installation Guide](docs/getting-started/installation.md)
- [Architecture Overview](docs/architecture/overview.md)
- [Lambda Layer Architecture](docs/architecture/lambda-layer.md)
- [CLI Commands](docs/cli/commands.md)
- [CDK Usage](docs/cdk/usage.md)
- [React SSR Setup](docs/integration/react-setup.md)
- [API Reference](docs/reference/api.md)

---

## License

[Elastic License 2.0](LICENSE)

**Maintainer:** Raman Marozau — [raman@worktif.com](mailto:raman@worktif.com)
