# Engine API

Engine is DevBlanket's serverless edge runtime for executing user-uploaded JavaScript lambdas in response to HTTP requests.

> **First time here?** Read the [DevBlanket API Overview](https://devblanket.com) first. It explains authentication, how to get your org ID from Door's `/api/me` endpoint, and the conventions shared across all DevBlanket services.

## Quick Start

### 1. Write Your Lambda

Create an ES module with a default export function:

```javascript
// hello.js
export default async function handler(ctx) {
  const name = ctx.params.name || "World";
  return Response.json({ message: `Hello, ${name}!` });
}
```

### 2. Upload to Uploads

```bash
curl -X POST "https://uploads.devblanket.com/api/orgs/${ORG_ID}/files" \
  -H "Authorization: Bearer ${EXTRA_KEY}" \
  -F "file=@hello.js" \
  -F "isPublic=true"
```

Save the returned `file.id` (e.g., `file_abc123`).

### 3. Register Your Domain

```bash
curl -X POST "https://engine.devblanket.com/api/orgs/${ORG_ID}/domains" \
  -H "Authorization: Bearer ${EXTRA_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"hostname": "api.mycompany.com"}'
```

Save the returned `domain.id` (e.g., `dom_xyz789`).

### 4. Create a Route

```bash
curl -X POST "https://engine.devblanket.com/api/orgs/${ORG_ID}/domains/${DOMAIN_ID}/routes" \
  -H "Authorization: Bearer ${EXTRA_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "GET",
    "pattern": "/hello/:name",
    "lambdaFileId": "file_abc123"
  }'
```

### 5. Point DNS to Engine

Add a CNAME record:

```
api.mycompany.com  CNAME  engine.devblanket.com
```

### 6. Test It

```bash
curl https://api.mycompany.com/hello/Fernando
# {"message":"Hello, Fernando!"}
```

## Authentication

Use your Extra Key as a Bearer token:

```
Authorization: Bearer <your-extra-key>
```

## Endpoints

All operations are scoped to an organization. Get your `orgId` from Door's `/api/me` endpoint.

### Domain Management

```
GET    /api/orgs/:orgId/domains              # List domains
POST   /api/orgs/:orgId/domains              # Register domain
GET    /api/orgs/:orgId/domains/:domainId    # Get domain
DELETE /api/orgs/:orgId/domains/:domainId    # Delete domain
```

#### Register Domain

```json
POST /api/orgs/:orgId/domains
{
  "hostname": "api.mycompany.com"
}
```

### Route Management

```
GET    /api/orgs/:orgId/domains/:domainId/routes              # List routes
POST   /api/orgs/:orgId/domains/:domainId/routes              # Create route
GET    /api/orgs/:orgId/domains/:domainId/routes/:routeId     # Get route
PUT    /api/orgs/:orgId/domains/:domainId/routes/:routeId     # Update route
DELETE /api/orgs/:orgId/domains/:domainId/routes/:routeId     # Delete route
```

#### Create Route

```json
POST /api/orgs/:orgId/domains/:domainId/routes
{
  "method": "GET",
  "pattern": "/users/:id",
  "lambdaFileId": "file_abc123",
  "config": {
    "timeout": 30000,
    "env": { "FEATURE_FLAG": "enabled" }
  }
}
```

### Route Pattern Syntax

- `/users` - Exact match
- `/users/:id` - Named parameter (available as `ctx.params.id`)
- `/api/*` - Wildcard (matches any suffix)
- `/files/:path*` - Named wildcard (captures rest of path)

## Lambda Interface

Lambdas are ES modules with a default export function:

```javascript
export default async function handler(ctx) {
  // ctx.request  - Request object (Fetch API)
  // ctx.params   - Route parameters { id: "123" }
  // ctx.query    - URL search params as object
  // ctx.env      - Environment variables from route config
  // ctx.org      - Organization { id, name, slug }
  // ctx.base     - Pre-authenticated Base client
  // ctx.uploads  - Pre-authenticated Uploads client
  // ctx.door     - Pre-authenticated Door client

  const { id } = ctx.params;
  const result = await ctx.base.query("users", { limit: 1 });

  return Response.json(result);
}
```

### Service Clients

Lambdas have pre-authenticated clients for DevBlanket services:

**ctx.base** - Message queue operations:
- `publish(message)` - Publish to matching queues
- `query(queue, opts)` - Query messages
- `ack(queue, seq)` - Acknowledge message
- `nack(queue, seq, opts)` - Reject message

**ctx.uploads** - File storage:
- `listFiles(opts)` - List files
- `getFile(fileId)` - Download file
- `uploadFile(blob, opts)` - Upload file
- `deleteFile(fileId)` - Delete file

**ctx.door** - User/org info:
- `me()` - Get current user/org
- `getOrganization()` - Get org details
- `listMembers()` - List org members

## Updating Lambdas

When you update your code:

1. Upload the new version to Uploads
2. Update the route to point to the new file ID:

```bash
curl -X PUT "https://engine.devblanket.com/api/orgs/${ORG_ID}/domains/${DOMAIN_ID}/routes/${ROUTE_ID}" \
  -H "Authorization: Bearer ${EXTRA_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"lambdaFileId": "file_newversion789"}'
```

Engine appends `?v={updatedAt}` to the import URL, busting Deno's module cache automatically.

### Dependency Versioning

For transitive dependencies (imports within your lambda), you're responsible for cache busting. Version your imports:

```javascript
// Version your dependencies to bust cache on updates
import { helper } from "https://uploads.devblanket.com/files/xyz?v=2";
```

## OpenAPI Schema

Full API specification available at:

```
GET /openapi.json
```

## Error Format

```json
{
  "error": "Error message",
  "requestId": "abc12345"
}
```
