# Using Emulate in Code — The Programmatic API

> How `createEmulator()` in api.ts lets test suites spin up a single service, get its URL, call `reset()` between tests, and `close()` when done — without touching the CLI at all.

- Repository: vercel-labs/emulate
- GitHub: https://github.com/vercel-labs/emulate
- Human wiki: https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d
- Complete Markdown: https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/llms-full.txt

## Source Files

- `packages/emulate/src/api.ts`
- `packages/emulate/src/__tests__/api.test.ts`
- `packages/@emulators/core/src/__tests__/store.test.ts`
- `packages/@emulators/core/src/__tests__/auth.test.ts`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [packages/emulate/src/api.ts](packages/emulate/src/api.ts)
- [packages/emulate/src/__tests__/api.test.ts](packages/emulate/src/__tests__/api.test.ts)
- [packages/emulate/src/registry.ts](packages/emulate/src/registry.ts)
- [packages/emulate/src/base-url.ts](packages/emulate/src/base-url.ts)
- [packages/@emulators/core/src/store.ts](packages/@emulators/core/src/store.ts)
- [packages/@emulators/core/src/__tests__/store.test.ts](packages/@emulators/core/src/__tests__/store.test.ts)
- [packages/@emulators/core/src/__tests__/auth.test.ts](packages/@emulators/core/src/__tests__/auth.test.ts)
</details>

# Using Emulate in Code — The Programmatic API

This page explains how to use `createEmulator()` — the TypeScript function that starts a local fake service and hands back a URL your code can call — entirely from within a test suite or script, no CLI required.

The programmatic API is useful when you want tight integration with a test runner: spin up an emulator in `beforeAll`, call `reset()` between test cases to wipe state, and call `close()` in `afterAll` to free the port. Each emulator runs as a real HTTP server so your code under test does not need any modification — it just points at a different base URL.

---

## The `Emulator` Interface

`createEmulator()` returns a promise that resolves to an object with three members:

| Member | Type | What it does |
|---|---|---|
| `url` | `string` | The base URL of the running HTTP server (e.g. `http://localhost:4000`) |
| `reset()` | `() => void` | Clears all in-memory state and re-runs the seed, so each test starts clean |
| `close()` | `() => Promise<void>` | Stops the HTTP server and releases the port |

Sources: [packages/emulate/src/api.ts:19-23](packages/emulate/src/api.ts)

---

## `EmulatorOptions` — What You Pass In

```typescript
// packages/emulate/src/api.ts
export interface EmulatorOptions {
  service: ServiceName;  // which service to emulate
  port?: number;         // default: 4000
  seed?: SeedConfig;     // optional initial data
  baseUrl?: string;      // override the advertised URL
}
```

**`service`** is a string key from the service registry. Valid values are:

> `"vercel"` · `"github"` · `"google"` · `"slack"` · `"apple"` · `"microsoft"` · `"okta"` · `"aws"` · `"resend"` · `"stripe"` · `"mongoatlas"` · `"clerk"`

Sources: [packages/emulate/src/registry.ts:17-31](packages/emulate/src/registry.ts)

Passing an unknown service name throws immediately:

```
Error: Unknown service: unknown-svc
```

Sources: [packages/emulate/src/api.ts:29-31](packages/emulate/src/api.ts), [packages/emulate/src/__tests__/api.test.ts:87-90](packages/emulate/src/__tests__/api.test.ts)

---

## Minimal Setup

```typescript
import { createEmulator } from "emulate";

const github = await createEmulator({ service: "github", port: 14000 });

// github.url === "http://localhost:14000"
const res = await fetch(`${github.url}/user`, {
  headers: { Authorization: "token test_token_admin" },
});
const { login } = await res.json(); // "admin"

await github.close();
```

Sources: [packages/emulate/src/__tests__/api.test.ts:5-18](packages/emulate/src/__tests__/api.test.ts)

The default token `test_token_admin` is created automatically when no `seed.tokens` is provided. It maps to the `"admin"` user with broad scopes.

Sources: [packages/emulate/src/api.ts:41-43](packages/emulate/src/api.ts)

---

## Typical Test-Suite Pattern

```typescript
import { describe, it, beforeAll, afterAll, beforeEach } from "vitest";
import { createEmulator, type Emulator } from "emulate";

describe("GitHub integration", () => {
  let github: Emulator;

  beforeAll(async () => {
    github = await createEmulator({
      service: "github",
      port: 14020,
      seed: {
        tokens: { my_token: { login: "alice", scopes: ["repo"] } },
        github: { users: [{ login: "alice" }] },
      },
    });
  });

  afterAll(() => github.close());

  beforeEach(() => github.reset()); // wipe state before each test

  it("creates a repo", async () => {
    const res = await fetch(`${github.url}/user/repos`, {
      method: "POST",
      headers: {
        Authorization: "token my_token",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ name: "my-repo" }),
    });
    expect(res.status).toBe(201);
  });

  it("starts with zero repos after reset", async () => {
    const res = await fetch(`${github.url}/user/repos`, {
      headers: { Authorization: "token my_token" },
    });
    const repos = await res.json();
    expect(repos).toHaveLength(0); // reset() ran before this test
  });
});
```

---

## How `reset()` Works

`reset()` is synchronous. It does two things in order:

1. Calls `store.reset()` — clears every in-memory collection and all key-value data.
2. Calls `seed()` again — re-runs the plugin's built-in seed plus any `seedFromConfig` you provided in `EmulatorOptions.seed`.

```typescript
// packages/emulate/src/api.ts
reset() {
  store.reset();
  seed();
},
```

Sources: [packages/emulate/src/api.ts:73-76](packages/emulate/src/api.ts)

At the `Store` level, `reset()` calls `clear()` on every `Collection` (wiping items, indexes, and the auto-increment counter) and clears all `setData` values:

```typescript
// packages/@emulators/core/src/store.ts
reset(): void {
  for (const collection of this.collections.values()) {
    collection.clear();
  }
  this._data.clear();
}
```

Sources: [packages/@emulators/core/src/store.ts:251-256](packages/@emulators/core/src/store.ts)

This is verified by the test suite: create a resource, call `reset()`, list the same resource — it is gone:

```typescript
// packages/emulate/src/__tests__/api.test.ts:32-58
github.reset();
const repos = await listRepos(); // length === 0
```

Sources: [packages/emulate/src/__tests__/api.test.ts:48-57](packages/emulate/src/__tests__/api.test.ts)

---

## Seeding Initial Data

The `seed` option accepts a `SeedConfig` object. It has two independent sub-shapes:

### Token seeds

```typescript
seed: {
  tokens: {
    "my_token": { login: "alice", scopes: ["repo", "user"] },
    "other_token": { login: "bob" },
  }
}
```

Each key becomes a valid `Authorization: token <key>` credential. Token IDs are assigned starting at 100 and auto-incremented. If you provide no `tokens`, the default `test_token_admin` → `admin` mapping is used.

Sources: [packages/emulate/src/api.ts:35-43](packages/emulate/src/api.ts)

### Service-specific seeds

Put service data under the service name key. The shape is service-specific and forwarded to that service's `seedFromConfig` function:

```typescript
seed: {
  github: {
    users: [{ login: "alice" }],
    repos: [{ owner: "alice", name: "my-repo" }],
  }
}
```

Sources: [packages/emulate/src/api.ts:44-66](packages/emulate/src/api.ts)

---

## Running Multiple Services in Parallel

Each `createEmulator()` call is independent. You can start multiple services simultaneously by assigning them different ports:

```typescript
const [github, vercel] = await Promise.all([
  createEmulator({ service: "github", port: 14010 }),
  createEmulator({ service: "vercel", port: 14011 }),
]);

// github.url === "http://localhost:14010"
// vercel.url === "http://localhost:14011"

await Promise.all([github.close(), vercel.close()]);
```

Sources: [packages/emulate/src/__tests__/api.test.ts:20-29](packages/emulate/src/__tests__/api.test.ts)

---

## How the Base URL Is Resolved

`emulator.url` is not always `http://localhost:<port>`. The value is determined by a five-level fallback chain:

```
1. seed[service].baseUrl   (per-service, from SeedConfig)
2. options.baseUrl         (explicit programmatic override)
3. EMULATE_BASE_URL env var (supports {service} interpolation)
4. PORTLESS_URL env var    (supports {service} interpolation)
5. http://localhost:<port> (default)
```

The `{service}` placeholder in env vars is replaced at runtime, so `EMULATE_BASE_URL=https://{service}.test.internal` becomes `https://github.test.internal` when starting the GitHub emulator.

Sources: [packages/emulate/src/base-url.ts:9-32](packages/emulate/src/base-url.ts)

This means CI environments that route traffic differently (e.g., a Docker network with named hosts) can set `EMULATE_BASE_URL` globally without any code changes.

---

## Authentication Behavior

The emulator's HTTP server validates the `Authorization` header against the token map built from your `seed.tokens`. The auth middleware sets an `authUser` context value on every matched request:

```typescript
// resolution order: known token → fallbackUser (if configured) → unauthenticated
authMiddleware(tokenMap, undefined, fallbackUser)
```

Sources: [packages/@emulators/core/src/__tests__/auth.test.ts:28-43](packages/@emulators/core/src/__tests__/auth.test.ts)

The `fallbackUser` is the service's `defaultFallback()` value — for GitHub it defaults to `{ login: "admin", scopes: ["repo", "user", "admin:org", "admin:repo_hook"] }`. This means **any** unrecognized token authenticates as that user by default, making it easy to test without managing tokens explicitly.

Sources: [packages/emulate/src/registry.ts:86-89](packages/emulate/src/registry.ts)

Some services support a **strict mode** that disables this fallback:

```typescript
seed: { slack: { strict_scopes: true } }
```

With `strict_scopes: true`, requests with unknown tokens are rejected with a `missing_scope` error instead of succeeding as a fallback user.

Sources: [packages/emulate/src/__tests__/api.test.ts:61-85](packages/emulate/src/__tests__/api.test.ts)

---

## Lifecycle Diagram

```text
createEmulator(options)
        │
        ├─ lookup service in SERVICE_REGISTRY
        ├─ dynamic import of service plugin (lazy)
        ├─ build token map from seed.tokens (or use defaults)
        ├─ resolve baseUrl (5-level fallback)
        ├─ createServer(plugin, { port, baseUrl, tokens, ... })
        ├─ call seed() → plugin.seed() + seedFromConfig()
        └─ serve({ fetch, port })  ←─ HTTP server starts
                │
                ▼
        { url, reset(), close() }
                │
   ┌────────────┼────────────┐
   │            │            │
reset()      fetch()      close()
   │            │            │
store.reset()  HTTP         httpServer.close()
seed()        handler        (Promise)
```

---

## API Reference Summary

| Option | Type | Default | Notes |
|---|---|---|---|
| `service` | `ServiceName` | required | One of 12 named services |
| `port` | `number` | `4000` | TCP port to bind |
| `seed.tokens` | `Record<string, {login, scopes?}>` | `test_token_admin→admin` | Auth tokens to pre-register |
| `seed[service]` | `Record<string, unknown>` | none | Service-specific seed data |
| `baseUrl` | `string` | `http://localhost:<port>` | Overrides URL resolution |

| Method | Sync? | Effect |
|---|---|---|
| `reset()` | sync | Clears all collections and re-runs seed |
| `close()` | async | Stops HTTP server, resolves when port is free |

Sources: [packages/emulate/src/api.ts:7-86](packages/emulate/src/api.ts)

---

## Closing Summary

`createEmulator()` in `api.ts` is a thin orchestration layer: it dynamically loads the right service plugin, builds an auth token map, resolves the base URL, creates an in-memory HTTP server via `@emulators/core`, runs the seed, and returns a `{ url, reset, close }` handle. The `reset()` method's two-step contract — clear the store, re-run the seed — is what makes between-test isolation reliable and deterministic. Multiple services can coexist as independent processes on different ports, and CI base-URL overrides are handled through environment variables without touching test code.

Sources: [packages/emulate/src/api.ts:25-86](packages/emulate/src/api.ts)
