# Emulate — Plain-Language Wiki

> vercel-labs/emulate runs real, stateful local copies of external APIs (GitHub, Google, Slack, Stripe, and more) so your app can work offline, in CI, and in sandboxes without ever calling the real internet. Think of it as a recording studio that plays back exactly what each cloud service would say.

## Context Links

- [Agent index](https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/llms.txt)
- [Human interactive wiki](https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d)
- [GitHub repository](https://github.com/vercel-labs/emulate)

## Repository Metadata

- Repository: vercel-labs/emulate

- Generated: 2026-05-24T18:22:50.807Z
- Updated: 2026-05-24T18:24:12.243Z
- Runtime: Claude Code
- Format: Explain Like I'm 5
- Pages: 8

## Page Index

- 01. [Explain It Simply — What Emulate Does](https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/01-explain-it-simply-what-emulate-does.md) - Plain-language explanation of the problem emulate solves, the one analogy to hold in your head, and the three ideas you must not forget before reading anything else.
- 02. [Telling Emulate What World to Create — Seed Config](https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/02-telling-emulate-what-world-to-create-seed-config.md) - How the YAML/JSON seed file pre-populates users, repos, OAuth clients, messages, and tokens before any test runs, and why this makes tests deterministic without touching the real internet.
- 03. [The Engine Under the Hood — @emulators/core](https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/03-the-engine-under-the-hood-emulators-core.md) - What the shared core package actually provides: the in-memory Store (collections of entities), the Hono HTTP server, auth middleware, OAuth helpers, webhook dispatcher, and pagination — the pieces every service plugin relies on.
- 04. [The Registry — How Each Service Gets Wired In](https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/04-the-registry-how-each-service-gets-wired-in.md) - How registry.ts acts as the master switchboard: each ServiceEntry knows how to lazy-load its plugin package, what endpoints it covers, what default auth fallback to use, and what starter config to generate with `emulate init`.
- 05. [Using Emulate in Code — The Programmatic API](https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/05-using-emulate-in-code-the-programmatic-api.md) - 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.
- 06. [OAuth & Identity Services — Google, Apple, Microsoft, Okta, Clerk](https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/06-oauth-identity-services-google-apple-microsoft-okta-clerk.md) - The five emulators that pretend to be identity providers: what OAuth/OIDC flows each one covers, how the auto-login fallback bypasses the browser consent screen in CI, and what each package lives in.
- 07. [Platform & Infrastructure Services — GitHub, Vercel, Slack, AWS, Stripe, Resend, MongoDB Atlas](https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/07-platform-infrastructure-services-github-vercel-slack-aws-stripe-resend-mongodb-atlas.md) - The seven emulators covering developer platforms and cloud infrastructure: what REST surface area each one reproduces, how webhooks are dispatched in-process, and what the examples directory shows about wiring them into a Next.js app.
- 08. [The One Map to Keep — Core Ideas Recapped](https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/08-the-one-map-to-keep-core-ideas-recapped.md) - A single-page recap: the core idea in one sentence, the three moving parts every developer must know (Store, ServicePlugin, registry entry), the analogy that holds, important caveats about what emulate is NOT, and where to go next.

## Source File Index

- `emulate.config.example.yaml`
- `examples/oauth/README.md`
- `examples/stripe-checkout/README.md`
- `packages/@emulators/apple/src`
- `packages/@emulators/aws/src`
- `packages/@emulators/clerk/src`
- `packages/@emulators/core/src/__tests__/auth.test.ts`
- `packages/@emulators/core/src/__tests__/store.test.ts`
- `packages/@emulators/core/src/index.ts`
- `packages/@emulators/core/src/middleware/auth.ts`
- `packages/@emulators/core/src/persistence.ts`
- `packages/@emulators/core/src/plugin.ts`
- `packages/@emulators/core/src/server.ts`
- `packages/@emulators/github/src`
- `packages/@emulators/google/src`
- `packages/@emulators/microsoft/src`
- `packages/@emulators/mongoatlas/src`
- `packages/@emulators/okta/src`
- `packages/@emulators/resend/src`
- `packages/@emulators/slack/src`
- `packages/@emulators/stripe/src`
- `packages/@emulators/vercel/src`
- `packages/emulate/package.json`
- `packages/emulate/src/__tests__/api.test.ts`
- `packages/emulate/src/api.ts`
- `packages/emulate/src/base-url.ts`
- `packages/emulate/src/commands/init.ts`
- `packages/emulate/src/commands/list.ts`
- `packages/emulate/src/commands/start.ts`
- `packages/emulate/src/portless.ts`
- `packages/emulate/src/registry.ts`
- `README.md`
- `skills/apple/SKILL.md`
- `skills/google/SKILL.md`

---

## 01. Explain It Simply — What Emulate Does

> Plain-language explanation of the problem emulate solves, the one analogy to hold in your head, and the three ideas you must not forget before reading anything else.

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/01-explain-it-simply-what-emulate-does.md
- Generated: 2026-05-24T18:12:50.095Z

### Source Files

- `README.md`
- `packages/emulate/src/commands/start.ts`
- `packages/emulate/package.json`
- `emulate.config.example.yaml`

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

- [README.md](README.md)
- [packages/emulate/src/commands/start.ts](packages/emulate/src/commands/start.ts)
- [packages/emulate/src/registry.ts](packages/emulate/src/registry.ts)
- [packages/emulate/package.json](packages/emulate/package.json)
- [emulate.config.example.yaml](emulate.config.example.yaml)
- [packages/@emulators/core/src/plugin.ts](packages/@emulators/core/src/plugin.ts)
- [packages/@emulators/core/src/store.ts](packages/@emulators/core/src/store.ts)
</details>

# Explain It Simply — What Emulate Does

This page gives you the plain-language mental model you need before diving into the codebase or the detailed API docs. It answers three questions: what problem does emulate exist to solve, what is the one analogy that unlocks how it works, and what are the three ideas you must carry with you before reading anything else.

If you have ever tried to write a test that calls GitHub, hit a rate limit, got the wrong user back, or discovered your CI pipeline has no network access at all — you have already felt the pain emulate was built to remove.

---

## The Problem It Solves

Your app calls real external APIs: GitHub to manage repositories, Google for OAuth and Gmail, Slack to post messages, AWS to read from S3, Stripe to process payments. Testing those flows is painful for three reasons.

1. **Real APIs are slow and flaky.** Network calls add seconds to every test run. Rate limits kick in. The remote service has downtime. Credentials expire.
2. **Real APIs are stateless from your test's point of view.** You cannot control which users exist, which repos are pre-created, or what emails are in the inbox. You have to set up and tear down data on the real service, which is fragile and expensive.
3. **CI environments and preview deployments often have no network access at all.** OAuth redirect URLs change with every Vercel preview deployment. You cannot register a GitHub OAuth app that points at a URL that does not exist yet.

Existing solutions fall into two categories: *mocks* (you write fake response objects by hand) and *record-replay* (you record real HTTP traffic and replay it). Both lie to your test. Mocks are frozen snapshots of what you believed the API returned. Replays become stale the moment the API changes shape.

Emulate takes a third path: **production-fidelity stateful emulation**. It runs a real HTTP server that speaks the same wire protocol as the real service — same endpoints, same request/response shapes, same auth token flow — but the state is entirely under your control.

Sources: [README.md:1-3](), [packages/emulate/package.json:4]()

---

## The One Analogy

Think of emulate as a **flight simulator** for external APIs.

A flight simulator is not a plane. It does not take you anywhere real. But from the perspective of the pilot's instruments — the throttle, the altimeter, the radio — it behaves exactly like a real plane. You can stall it, land it, crash it, and do it again a second later with a fresh runway. None of that is possible with a real aircraft in real airspace.

Emulate does the same thing for your application code. Your app points its HTTP client at `http://localhost:4001` instead of `https://api.github.com`. The emulator responds with GitHub-shaped JSON, issues real OAuth tokens, delivers webhook payloads to your app, and keeps everything in memory. Your app never knows it is not talking to the real GitHub.

The simulator analogy also explains what emulate is *not*: it is not a mock (a static recorded response), and it is not a proxy (a passthrough to the real service). It is an independent implementation of the same protocol.

---

## Three Ideas You Must Not Forget

### 1. Stateful, Not Frozen

Every emulated service maintains a live in-memory `Store` of typed `Collection<T>` instances. When your test creates a GitHub repo via `POST /user/repos`, that repo exists for all subsequent calls in the same session. When you merge a pull request, the branch protection rules are enforced, the PR state changes to `merged`, and linked issues may be closed — exactly as the real GitHub would behave. Nothing is a canned response.

The store can be reset between test runs by calling `emulator.reset()`, which wipes the collections and replays the original seed data. This gives you a clean slate for each test without restarting the process.

Sources: [packages/@emulators/core/src/store.ts:1-37](), [README.md:136-137]()

### 2. Seed-First Design

The starting state of every emulated service is defined by a *seed*: a YAML or JSON file (or inline TypeScript object) that describes the users, repos, OAuth clients, tokens, and other entities that should exist before your first request arrives. Emulate's CLI auto-detects `emulate.config.yaml` in the current directory, or you can pass `--seed config.yaml` explicitly.

```yaml
# emulate.config.example.yaml (lines 9-35)
github:
  users:
    - login: octocat
      name: The Octocat
  repos:
    - owner: octocat
      name: hello-world
      auto_init: true
tokens:
  gho_test_token_admin:
    login: admin
    scopes: [repo, user, admin:org, admin:repo_hook]
```

This design means tests are deterministic. The same seed produces the same starting state every time. You do not have to create test data programmatically in `beforeEach` hooks — declare it once and reset.

Sources: [emulate.config.example.yaml:1-35](), [packages/emulate/src/commands/start.ts:31-72]()

### 3. One Plugin Per Service, One Core

Each external service is its own package (`@emulators/github`, `@emulators/google`, etc.) that exports a `ServicePlugin`. A plugin has exactly one job: register HTTP routes on a shared Hono app and read/write state through the shared `Store`. The `@emulators/core` package owns the HTTP server, auth middleware, store implementation, webhook dispatcher, and persistence layer. Individual service packages contain only the route logic and seed helpers — they know nothing about the network.

```typescript
// packages/@emulators/core/src/plugin.ts
export interface ServicePlugin {
  name: string;
  register(app: Hono<AppEnv>, store: Store, webhooks: WebhookDispatcher, baseUrl: string, tokenMap?: TokenMap): void;
  seed?(store: Store, baseUrl: string): void;
}
```

This architecture means you can embed a single emulator inside a Next.js route handler, run several in parallel from the CLI, or instantiate them programmatically in a Vitest setup file. The same plugin code runs in all three contexts.

Sources: [packages/@emulators/core/src/plugin.ts:1-18](), [packages/emulate/src/registry.ts:3-7](), [README.md:929-946]()

---

## How the Parts Fit Together

```text
┌──────────────────────────────────────────────────────────────────┐
│  Your app / tests                                                │
│  (points HTTP client at http://localhost:4001, 4002, etc.)       │
└────────────┬─────────────────────────────────────────────────────┘
             │  HTTP  (same wire protocol as real service)
             ▼
┌──────────────────────────────────────────────────────────────────┐
│  emulate CLI  /  createEmulator()  /  Next.js adapter           │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  @emulators/core                                          │   │
│  │  ┌──────────┐  ┌───────────────┐  ┌───────────────────┐ │   │
│  │  │  Store   │  │  Auth middle  │  │  Webhook dispatch │ │   │
│  │  │  (typed  │  │  (tokens +    │  │  (HTTP POST to    │ │   │
│  │  │  CRUD)   │  │   OAuth)      │  │   your server)    │ │   │
│  │  └──────────┘  └───────────────┘  └───────────────────┘ │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                  │
│  Service plugins (each is an independent package):              │
│  @emulators/github  @emulators/google  @emulators/slack         │
│  @emulators/vercel  @emulators/aws     @emulators/stripe  ...   │
└──────────────────────────────────────────────────────────────────┘
             │  optional
             ▼
┌────────────────────────────┐
│  portless (HTTPS proxy)    │
│  github.emulate.localhost  │
│  google.emulate.localhost  │
└────────────────────────────┘
```

Sources: [README.md:929-946](), [packages/emulate/src/commands/start.ts:162-192]()

---

## What Services Are Supported

The full list of services registered in the CLI is defined in `packages/emulate/src/registry.ts`:

| Service | Coverage |
|---|---|
| `github` | Users, repos, issues, PRs, reviews, branches, git data, orgs, webhooks, actions, checks, search |
| `google` | OAuth 2.0 / OIDC, Gmail, Calendar, Drive |
| `slack` | Web API, OAuth v2, channels, messages, files, pins, bookmarks, views, incoming webhooks |
| `vercel` | Projects, deployments, domains, env vars, teams, integrations |
| `apple` | Sign In with Apple (OIDC, RS256 ID tokens) |
| `microsoft` | Entra ID / Azure AD (OIDC, PKCE, Graph `/me`) |
| `okta` | OIDC / OAuth 2.0, management API, users, groups, authorization servers |
| `aws` | S3, SQS, IAM, STS |
| `stripe` | Customers, payment intents, checkout sessions, products, prices, webhooks |
| `resend` | Emails, domains, contacts, API keys |
| `mongoatlas` | Atlas Admin API, Atlas Data API |
| `clerk` | OIDC, users, organizations, sessions, invitations |

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

---

## How to Start (30-Second Version)

```bash
# Zero config: starts all services on ports 4000–4011
npx emulate

# Only what you need
npx emulate --service github,google

# With seed data
npx emulate --seed emulate.config.yaml
```

For tests, use the programmatic API so emulators start and stop with your test suite:

```typescript
import { createEmulator } from 'emulate'

const github = await createEmulator({ service: 'github', port: 4001 })
process.env.GITHUB_API_URL = github.url   // point your app here

// between tests:
github.reset()   // wipe state, replay seed

// when done:
await github.close()
```

Sources: [README.md:99-138](), [packages/emulate/src/commands/start.ts:79-99]()

---

## Closing Summary

Emulate is a local, fully stateful, in-process replacement for the external APIs your application depends on. Its plugin architecture separates the generic concerns (HTTP server, authentication, store, webhooks) in `@emulators/core` from the service-specific route logic in the individual `@emulators/*` packages. The seed-first design gives tests deterministic starting state, and `reset()` gives them a clean slate between runs — no mocks, no recorded traffic, no network required.

Sources: [packages/@emulators/core/src/plugin.ts:14-18](), [README.md:1-3]()

---

## 02. Telling Emulate What World to Create — Seed Config

> How the YAML/JSON seed file pre-populates users, repos, OAuth clients, messages, and tokens before any test runs, and why this makes tests deterministic without touching the real internet.

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/02-telling-emulate-what-world-to-create-seed-config.md
- Generated: 2026-05-24T18:13:52.814Z

### Source Files

- `emulate.config.example.yaml`
- `packages/emulate/src/commands/start.ts`
- `packages/emulate/src/commands/init.ts`
- `packages/emulate/src/registry.ts`

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

- [emulate.config.example.yaml](emulate.config.example.yaml)
- [packages/emulate/src/commands/start.ts](packages/emulate/src/commands/start.ts)
- [packages/emulate/src/commands/init.ts](packages/emulate/src/commands/init.ts)
- [packages/emulate/src/registry.ts](packages/emulate/src/registry.ts)
- [packages/@emulators/github/src/index.ts](packages/@emulators/github/src/index.ts)
- [packages/@emulators/google/src/index.ts](packages/@emulators/google/src/index.ts)
</details>

# Telling Emulate What World to Create — Seed Config

When you run `emulate start`, the emulator boots up knowing nothing: no users, no repos, no tokens, no messages. The **seed config file** is how you hand it a ready-made world before any test code runs. Think of it as the stage crew that sets the props before the actors arrive — once the server is up, every API call your app makes will find exactly the data you described, every time, without touching the real internet.

This page explains the shape of that file, how `emulate` finds and loads it, how each service's data is written into the in-memory store, and what defaults the emulator falls back to when no config is present.

---

## Where the Config File Lives

When `emulate start` is called without an explicit `--seed` flag, it walks through a fixed lookup list in priority order:

```
emulate.config.yaml
emulate.config.yml
emulate.config.json
service-emulator.config.yaml
service-emulator.config.yml
service-emulator.config.json
```

The first matching file wins. If you pass `--seed path/to/my-file.yaml` (or `.json`), that path is resolved and loaded directly instead.

The file format is detected from the extension: `.json` uses `JSON.parse`; everything else is parsed as YAML.

Sources: [packages/emulate/src/commands/start.ts:31-71]()

---

## Generating a Starter Config

The `emulate init` command writes a `emulate.config.yaml` in your current directory by reading each service's `initConfig` out of the registry and serialising it back to YAML:

```bash
npx emulate init              # generates config for all services
npx emulate init --service github
```

Each service in the registry carries an `initConfig` property that is a representative example for that service. `init` merges `DEFAULT_TOKENS` (the top-level `tokens` block) with whichever service configs you requested and writes the result.

Sources: [packages/emulate/src/commands/init.ts:10-38](), [packages/emulate/src/registry.ts:507-518]()

---

## Top-Level Structure

A seed config is a plain YAML (or JSON) document with two kinds of top-level keys:

| Key | Purpose |
|-----|---------|
| `tokens` | Static bearer tokens pre-mapped to user logins and OAuth scopes |
| `github`, `vercel`, `google`, `slack`, … | Per-service fixture data |

```yaml
# emulate.config.example.yaml (top)
tokens:
  gho_test_token_admin:
    login: admin
    scopes: [repo, user, admin:org, admin:repo_hook]
  gho_test_token_octocat:
    login: octocat
    scopes: [repo, user, read:user, "user:email", workflow]

github:
  users: [...]
  repos: [...]
  ...
```

Sources: [emulate.config.example.yaml:1-8]()

The emulator also uses which top-level service keys exist to **auto-select which services to start**. If your file has only a `github:` key, only the GitHub emulator boots; if it has `github:` and `google:`, both boot. You can override this with `--service github,google` on the command line.

Sources: [packages/emulate/src/commands/start.ts:74-98]()

---

## How Tokens Work

The `tokens` block is a map from token string → `{ login, scopes[] }`. At startup, `start.ts` reads this block and assigns each entry an incrementing numeric ID (starting at 100). Any HTTP call that carries one of these strings as a `Bearer` or `token` header is immediately authenticated as the mapped user.

```typescript
// start.ts:107-115
const tokens: Record<string, { login: string; id: number; scopes?: string[] }> = {};
if (seedConfig?.tokens) {
  let tokenId = 100;
  for (const [token, user] of Object.entries(seedConfig.tokens)) {
    tokens[token] = { login: user.login, id: tokenId++, scopes: user.scopes };
  }
} else {
  tokens["test_token_admin"] = { login: "admin", id: 2, scopes: ["repo", "user", "admin:org", "admin:repo_hook"] };
}
```

If no `tokens` block is present, the emulator installs a single built-in `test_token_admin` → `admin` entry so you always have at least one working credential.

Sources: [packages/emulate/src/commands/start.ts:107-115]()

The `DEFAULT_TOKENS` exported from the registry provides the canonical example used by `emulate init`:

```typescript
// registry.ts:507-518
export const DEFAULT_TOKENS = {
  tokens: {
    test_token_admin: { login: "admin", scopes: ["repo", "user", "admin:org", "admin:repo_hook"] },
    test_token_user1: { login: "octocat", scopes: ["repo", "user"] },
  },
};
```

Sources: [packages/emulate/src/registry.ts:507-518]()

---

## How Seed Data Reaches the Store

For each service, `start.ts` calls two seeding steps in sequence:

```typescript
// start.ts:184-188
loadedSvc.plugin.seed?.(store, baseUrl);               // 1. plugin built-in defaults

if (svcSeedConfig && loadedSvc.seedFromConfig) {
  loadedSvc.seedFromConfig(store, baseUrl, svcSeedConfig, webhooks); // 2. your config
}
```

Step 1 lets a plugin set up anything it always needs (e.g., system-level Gmail labels). Step 2 is where your YAML fixtures are written into the in-memory store. Each service package exports its own `seedFromConfig` function that knows the exact shape of its sub-section.

Sources: [packages/emulate/src/commands/start.ts:184-188]()

### The `LoadedService` interface

The contract every service must satisfy:

```typescript
// registry.ts:3-7
export interface LoadedService {
  plugin: ServicePlugin;
  seedFromConfig?(store: Store, baseUrl: string, config: unknown, webhooks?: WebhookDispatcher): void;
  createAppKeyResolver?(store: Store): AppKeyResolver;
}
```

`seedFromConfig` is optional — a service that needs no pre-population can omit it.

Sources: [packages/emulate/src/registry.ts:1-7]()

---

## Per-Service Seed Shape

### GitHub

```yaml
github:
  users:
    - login: octocat
      name: The Octocat
      email: octocat@github.com
      bio: I am the Octocat
      company: GitHub
      location: San Francisco
  orgs:
    - login: my-org
      name: My Organization
  repos:
    - owner: octocat
      name: hello-world
      language: JavaScript
      auto_init: true          # seeds an initial commit, tree, branch, and ref
  oauth_apps:
    - client_id: emu_github_client_id
      client_secret: emu_github_client_secret
      name: Code App
      redirect_uris:
        - http://localhost:3000/api/auth/callback/github
  apps:
    - app_id: 12345
      slug: my-github-app
      private_key: |
        -----BEGIN RSA PRIVATE KEY-----
        ...
      installations:
        - installation_id: 100
          account: octocat
          repository_selection: all
```

When `auto_init: true` is set on a repo, `seedFromConfig` also creates an initial commit record, a root tree, a branch, and a ref so the repo looks freshly initialised without requiring any Git operations.

The full `GitHubSeedConfig` interface supports: `users`, `orgs`, `repos`, `oauth_apps`, `apps` (with nested `installations`), and `tokens`.

Sources: [packages/@emulators/github/src/index.ts:30-85](), [emulate.config.example.yaml:9-53]()

### Google (Gmail, Calendar, Drive)

```yaml
google:
  users:
    - email: testuser@gmail.com
      name: Test User
  oauth_clients:
    - client_id: emu_google_client_id
      client_secret: emu_google_client_secret
      redirect_uris:
        - http://localhost:3000/api/auth/callback/google
  labels:
    - id: Label_ops
      user_email: testuser@gmail.com
      name: Ops/Review
      color_background: "#DDEEFF"
  messages:
    - id: msg_welcome
      user_email: testuser@gmail.com
      from: welcome@example.com
      to: testuser@gmail.com
      subject: Welcome to the Gmail emulator
      label_ids: [INBOX, UNREAD, CATEGORY_UPDATES]
      date: 2025-01-04T10:00:00.000Z
  calendars:
    - id: primary
      user_email: testuser@gmail.com
      primary: true
      time_zone: UTC
  calendar_events:
    - id: evt_kickoff
      user_email: testuser@gmail.com
      calendar_id: primary
      summary: Project Kickoff
      start_date_time: 2025-01-10T09:00:00.000Z
      end_date_time: 2025-01-10T09:30:00.000Z
  drive_items:
    - id: drv_docs
      user_email: testuser@gmail.com
      name: Docs
      mime_type: application/vnd.google-apps.folder
      parent_ids: [root]
```

For each user in `users`, `seedFromConfig` also calls `ensureSystemLabels` to insert the standard Gmail system labels (INBOX, SENT, DRAFT, SPAM, TRASH, etc.) automatically. You do not need to declare those yourself.

Sources: [packages/@emulators/google/src/index.ts:294-355](), [emulate.config.example.yaml:80-130]()

### Other Services at a Glance

| Service key | Key fixture sections |
|-------------|----------------------|
| `vercel` | `users`, `teams`, `projects`, `integrations` |
| `slack` | `team`, `users`, `channels`, `bots`, `oauth_apps` |
| `apple` | `users`, `oauth_clients` |
| `microsoft` | `users`, `oauth_clients` |
| `okta` | `users`, `groups`, `authorization_servers`, `oauth_clients` |
| `aws` | `region`, `s3.buckets`, `sqs.queues`, `iam.users`, `iam.roles` |
| `resend` | `domains`, `contacts` |
| `stripe` | `customers`, `products`, `prices` |
| `mongoatlas` | `projects`, `clusters`, `database_users`, `databases` |
| `clerk` | `users`, `organizations`, `oauth_applications` |

All of these follow the same pattern: declare the data in YAML, and `seedFromConfig` upserts it into the in-memory store before any HTTP traffic arrives.

Sources: [packages/emulate/src/registry.ts:34-504]()

---

## Deduplication and Upsert Semantics

`seedFromConfig` implementations use a consistent upsert pattern: check whether the record already exists by a natural key, then insert or update:

```typescript
// github/src/index.ts (representative excerpt)
const existing = gh.users.findOneBy("login", user.login);
if (existing) {
  gh.users.update(existing.id, { ...fields });
} else {
  gh.users.insert({ ...fields });
}
```

This means re-running the emulator with the same config is safe and idempotent — no duplicate records accumulate.

Sources: [packages/@emulators/github/src/index.ts:135-373]()

---

## Fallback User (Unauthenticated Requests)

Each service entry in the registry defines a `defaultFallback` function. This determines which user identity is assumed when an API request arrives with no valid token. The fallback reads from the `svcSeedConfig` if available, otherwise uses a hard-coded default:

```typescript
// registry.ts — GitHub entry
defaultFallback(cfg) {
  const firstLogin = (cfg?.users as Array<{ login?: string }> | undefined)?.[0]?.login ?? "admin";
  return { login: firstLogin, id: 1, scopes: ["repo", "user", "admin:org", "admin:repo_hook"] };
},
```

For most services, the fallback is the first user declared in your seed config. This keeps unauthenticated test calls (e.g., `fetch('/api/user')` without a token) pointing at a real seeded user rather than an anonymous phantom.

Sources: [packages/emulate/src/registry.ts:86-89](), [packages/emulate/src/commands/start.ts:172-179]()

---

## Startup Sequence: How It All Fits Together

```text
emulate start
│
├─ loadSeedConfig()          reads YAML/JSON from --seed or auto-detected file
│
├─ inferServicesFromConfig()  decides which services to boot from top-level keys
│
├─ tokens block              maps token strings → { login, id, scopes }
│
├─ for each service:
│   ├─ entry.load()           dynamic-imports the service package
│   ├─ createServer(plugin, { tokens, fallbackUser, ... })
│   ├─ plugin.seed(store)     built-in defaults (system Gmail labels, etc.)
│   └─ seedFromConfig(store, baseUrl, svcSeedConfig)
│       └─ upserts users, repos, messages, tokens, OAuth clients, …
│
└─ serve(fetch, port)         HTTP server is live with pre-populated data
```

Sources: [packages/emulate/src/commands/start.ts:79-210]()

---

## Why This Matters for Test Determinism

Because all data is written into an in-memory store at startup — before any test request fires — every test run starts from the exact same baseline. There is no shared state leaking between sessions, no race conditions with external APIs, and no network calls to GitHub, Google, or any real provider. Your fixtures are the ground truth, and the emulator enforces it.

When a test run finishes, calling `store.reset()` (which `shutdown` does automatically on SIGINT/SIGTERM) wipes everything, ensuring the next run starts clean. The seed config is replayed from scratch on the next `emulate start`.

Sources: [packages/emulate/src/commands/start.ts:196-210]()

---

## 03. The Engine Under the Hood — @emulators/core

> What the shared core package actually provides: the in-memory Store (collections of entities), the Hono HTTP server, auth middleware, OAuth helpers, webhook dispatcher, and pagination — the pieces every service plugin relies on.

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/03-the-engine-under-the-hood-emulators-core.md
- Generated: 2026-05-24T18:13:09.146Z

### Source Files

- `packages/@emulators/core/src/index.ts`
- `packages/@emulators/core/src/server.ts`
- `packages/@emulators/core/src/plugin.ts`
- `packages/@emulators/core/src/middleware/auth.ts`
- `packages/@emulators/core/src/persistence.ts`

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

- [packages/@emulators/core/src/index.ts](packages/@emulators/core/src/index.ts)
- [packages/@emulators/core/src/server.ts](packages/@emulators/core/src/server.ts)
- [packages/@emulators/core/src/plugin.ts](packages/@emulators/core/src/plugin.ts)
- [packages/@emulators/core/src/store.ts](packages/@emulators/core/src/store.ts)
- [packages/@emulators/core/src/middleware/auth.ts](packages/@emulators/core/src/middleware/auth.ts)
- [packages/@emulators/core/src/middleware/pagination.ts](packages/@emulators/core/src/middleware/pagination.ts)
- [packages/@emulators/core/src/webhooks.ts](packages/@emulators/core/src/webhooks.ts)
- [packages/@emulators/core/src/oauth-helpers.ts](packages/@emulators/core/src/oauth-helpers.ts)
- [packages/@emulators/core/src/persistence.ts](packages/@emulators/core/src/persistence.ts)
</details>

# The Engine Under the Hood — @emulators/core

`@emulators/core` is the shared foundation that every service emulator in this monorepo builds on. It provides a single `createServer` function that wires together an HTTP server, an in-memory data store, authentication middleware, a webhook dispatcher, and pagination helpers — so individual service plugins (GitHub, Stripe, etc.) only need to define their own routes and seed data, not rebuild all the plumbing.

Think of it as a mini-framework: you plug in a `ServicePlugin` and get back a fully configured local API server that speaks the same token-auth, rate-limiting, and pagination conventions as the real services it emulates.

---

## Architecture Overview

```text
┌─────────────────────────────────────────────────────────────┐
│                      createServer(plugin)                    │
│                                                             │
│  ┌──────────┐   ┌───────────────────────────────────────┐  │
│  │  Store   │   │         Hono HTTP App (AppEnv)        │  │
│  │          │   │                                       │  │
│  │ collection│  │  registerFontRoutes(app)              │  │
│  │ getData  │   │  app.onError(createApiErrorHandler)   │  │
│  │ setData  │   │  app.use("*", cors())                 │  │
│  │ snapshot │   │  app.use("*", createErrorHandler)     │  │
│  │ restore  │   │  app.use("*", authMiddleware)         │  │
│  └──────────┘   │  app.use("*", rateLimitMiddleware)    │  │
│                  │  plugin.register(app, store, ...)    │  │
│  ┌───────────┐  │  app.notFound(...)                   │  │
│  │ Webhook   │  └───────────────────────────────────────┘  │
│  │Dispatcher │                                             │
│  │           │   Returns: { app, store, webhooks,         │
│  │ register  │             port, baseUrl, tokenMap }       │
│  │ dispatch  │                                             │
│  └───────────┘                                             │
└─────────────────────────────────────────────────────────────┘
```

Sources: [packages/@emulators/core/src/server.ts:24-105]()

---

## The Store: In-Memory Data Layer

The `Store` is the central data container — a named registry of `Collection<T>` objects, each holding a typed set of entities. It requires no database; all data lives in process memory, making emulators fast and hermetic.

### Entity Shape

Every entity stored in a `Collection` must conform to `Entity`:

```ts
// packages/@emulators/core/src/store.ts
export interface Entity {
  id: number;
  created_at: string;
  updated_at: string;
}
```

When you call `collection.insert(data)`, the store auto-assigns an integer `id` (auto-incrementing unless you supply one explicitly) and stamps `created_at` / `updated_at` with the current ISO timestamp.

Sources: [packages/@emulators/core/src/store.ts:1-7, 99-115]()

### Collection CRUD and Indexing

`Collection<T>` is the typed workhorse:

| Method | What it does |
|---|---|
| `insert(data)` | Adds a new entity, returns it with `id` and timestamps |
| `get(id)` | Retrieves by numeric ID |
| `findBy(field, value)` | Filters by field value, uses index when available |
| `findOneBy(field, value)` | First match for a field value |
| `update(id, data)` | Merges partial data, updates `updated_at`, refreshes indexes |
| `delete(id)` | Removes entity and cleans index entries |
| `all()` | All items as an array |
| `query(options)` | Filter + sort + paginate, returns `PaginatedResult<T>` |
| `count(filter?)` | Efficient count, optionally filtered |
| `clear()` | Empties the collection and resets auto-ID |

When you open a collection with `indexFields`, the `Collection` maintains per-field hash maps (`Map<string, Set<number>>`) for O(1) lookups on those fields. This is why `findBy` on an indexed field is faster than a full scan.

```ts
// A service plugin obtains a collection like this:
const repos = store.collection<Repo>("repos", ["owner_id", "name"]);
```

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

### The Store Container

`Store` wraps multiple collections and also provides a freeform key-value bag (`getData` / `setData`) for any data that doesn't fit the entity model (counters, config state, etc.).

```ts
export class Store {
  collection<T extends Entity>(name: string, indexFields?: (keyof T)[]): Collection<T>
  getData<V>(key: string): V | undefined
  setData<V>(key: string, value: V): void
  reset(): void
  snapshot(): StoreSnapshot
  restore(snap: StoreSnapshot): void
}
```

`snapshot()` serializes all collections (including any `Map` or `Set` values via `serializeValue`) into a plain `StoreSnapshot` object. `restore(snap)` reconstructs them. This pair is the persistence hook.

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

---

## The HTTP Server: Hono + Middleware Stack

`createServer` builds a [Hono](https://hono.dev/) application and stacks middleware in a fixed order before handing control to the plugin.

```ts
export function createServer(plugin: ServicePlugin, options: ServerOptions = {})
```

### ServerOptions

| Option | Default | Purpose |
|---|---|---|
| `port` | `4000` | TCP port to listen on |
| `baseUrl` | `http://localhost:{port}` | Base URL used in Link headers and docs links |
| `docsUrl` | `https://emulate.dev/{plugin.name}` | URL embedded in error responses |
| `tokens` | `{}` | Pre-seeded token → user mapping |
| `appKeyResolver` | — | Resolves GitHub App RSA keys for JWT auth |
| `fallbackUser` | — | User identity for unknown tokens (dev convenience) |

Sources: [packages/@emulators/core/src/server.ts:15-22]()

### Middleware Layer Order

The layers applied to every request, in order:

```text
Request
  │
  ▼
[Font routes]          — static font assets
  │
  ▼
[CORS]                 — permissive cors() for all origins
  │
  ▼
[Error handler]        — wraps downstream errors into JSON
  │
  ▼
[Auth middleware]      — resolves token/JWT → authUser / authApp on context
  │
  ▼
[Rate limit]           — 5 000 req/hour per token, sliding window with pruning
  │
  ▼
[plugin.register]      — service-specific routes (GitHub, Stripe, …)
  │
  ▼
[notFound]             — 404 JSON fallback
```

Sources: [packages/@emulators/core/src/server.ts:45-103]()

### Rate Limiting

The server tracks per-token request counts in a `Map<string, { remaining, resetAt }>`. The window is 1 hour; the limit is 5 000 requests. Responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers to match the real GitHub API convention. When the counter hits zero, the server returns HTTP 403. Stale entries are pruned once per hour.

Sources: [packages/@emulators/core/src/server.ts:52-90]()

---

## Auth Middleware

`authMiddleware` runs on every request and resolves who is calling.

### Two Auth Paths

**1. Bearer token (static):** If the `Authorization` header contains a plain token, it is looked up in the `TokenMap` (`Map<string, AuthUser>`). On a hit, the Hono context variables `authUser`, `authToken`, and `authScopes` are set.

**2. JWT (GitHub App):** If the token starts with `eyJ` (a Base64-encoded JWT header), the middleware extracts the `iss` claim as the app ID, resolves the RSA private key via `appKeyResolver`, and verifies the JWT with `jose`. On success, `authApp` is set instead.

```ts
// packages/@emulators/core/src/middleware/auth.ts
export function authMiddleware(tokens: TokenMap, appKeyResolver?: AppKeyResolver, fallbackUser?: AuthFallback)
```

A `fallbackUser` option lets any unknown token resolve to a default identity — useful in dev environments where you don't care about specific users.

Sources: [packages/@emulators/core/src/middleware/auth.ts:70-112]()

### Guard Helpers

Two route-level guards are exported for use inside service plugins:

| Helper | Blocks when | Response |
|---|---|---|
| `requireAuth()` | `authUser` is absent | HTTP 401 "Requires authentication" |
| `requireAppAuth()` | `authApp` is absent | HTTP 401 "A JSON web token could not be decoded" |

Sources: [packages/@emulators/core/src/middleware/auth.ts:114-144]()

### Token Serialization

The `TokenMap` (a `Map<string, AuthUser>`) can be exported to a plain array with `serializeTokenMap` and restored with `restoreTokenMap`. This supports snapshot/restore workflows where the token state must survive a process restart.

Sources: [packages/@emulators/core/src/middleware/auth.ts:34-48]()

---

## WebhookDispatcher

The `WebhookDispatcher` manages subscriptions and fires real HTTP `POST` requests to registered URLs whenever a service plugin triggers an event. It mirrors the GitHub webhooks model.

### Lifecycle

```text
plugin calls webhooks.register(sub)
    ↓
event happens in service plugin
    ↓
plugin calls webhooks.dispatch(event, action, payload, owner, repo?)
    ↓
dispatcher filters active subscriptions matching owner/repo/event
    ↓
for each match:
  - serialize payload to JSON
  - compute HMAC-SHA256 signature if secret present → X-Hub-Signature-256
  - POST to sub.url with X-GitHub-Event, X-GitHub-Delivery headers
  - record WebhookDelivery (status, duration, success)
  - cap delivery log at 1000 entries
```

Sources: [packages/@emulators/core/src/webhooks.ts:73-138]()

### Data Shapes

```ts
interface WebhookSubscription {
  id: number;
  url: string;
  events: string[];   // ["push", "pull_request"] or ["*"]
  active: boolean;
  secret?: string;
  owner: string;
  repo?: string;      // undefined = org-level hook
}

interface WebhookDelivery {
  id: number;
  hook_id: number;
  event: string;
  action?: string;
  payload: unknown;
  status_code: number | null;
  delivered_at: string;
  duration: number | null;
  success: boolean;
}
```

A 10-second `AbortSignal.timeout` guards each delivery so a slow or unresponsive receiver doesn't stall the emulator.

Sources: [packages/@emulators/core/src/webhooks.ts:1-23, 112-124]()

---

## Pagination

`parsePagination` and `setLinkHeader` are the two helpers that implement RFC 5988-style cursor-free pagination.

```ts
// packages/@emulators/core/src/middleware/pagination.ts
export function parsePagination(c: Context): PaginationParams
export function setLinkHeader(c: Context, totalCount: number, page: number, perPage: number): void
```

`parsePagination` reads `?page=` and `?per_page=` query parameters, defaulting to page 1 and 30 items per page, clamping `per_page` to a maximum of 100.

`setLinkHeader` computes `next`, `last`, `first`, and `prev` URLs and writes them as a single `Link:` header value, matching the GitHub API's pagination contract.

The `Collection.query()` method respects the same `page` / `per_page` options and returns a `PaginatedResult<T>` with `has_next` and `has_prev` flags alongside the sliced items.

Sources: [packages/@emulators/core/src/middleware/pagination.ts:1-37](), [packages/@emulators/core/src/store.ts:162-188]()

---

## OAuth Helpers

`oauth-helpers.ts` provides small, security-minded utilities used by service plugins that implement OAuth flows:

| Function | Purpose |
|---|---|
| `normalizeUri(uri)` | Strips trailing slashes and query strings for redirect URI comparison |
| `matchesRedirectUri(incoming, registered[])` | Checks normalized incoming URI against an allowlist |
| `constantTimeSecretEqual(a, b)` | Timing-safe string comparison using `crypto.timingSafeEqual` to prevent timing attacks |
| `bodyStr(v)` | Safely coerces a form body value to string |
| `parseCookies(header)` | Splits a `Cookie` header into a key→value map |

`constantTimeSecretEqual` is explicitly designed to prevent secret-comparison timing leaks — the buffer lengths must match before calling `timingSafeEqual`.

Sources: [packages/@emulators/core/src/oauth-helpers.ts:1-37]()

---

## Persistence Adapter

The `PersistenceAdapter` interface is a two-method contract that decouples snapshot storage from the rest of the core:

```ts
export interface PersistenceAdapter {
  load(): Promise<string | null>;
  save(data: string): Promise<void>;
}
```

`filePersistence(path)` is the provided implementation. It writes JSON to a file (creating parent directories with `mkdir -p` semantics) and reads it back, returning `null` if the file doesn't exist. Service plugins call `store.snapshot()` to get a serializable object, stringify it, and hand the string to `adapter.save()`.

Sources: [packages/@emulators/core/src/persistence.ts:1-23]()

---

## ServicePlugin Contract

A service plugin is a plain object with three members:

```ts
export interface ServicePlugin {
  name: string;
  register(app: Hono<AppEnv>, store: Store, webhooks: WebhookDispatcher, baseUrl: string, tokenMap?: TokenMap): void;
  seed?(store: Store, baseUrl: string): void;
}
```

- `name` — used by `createServer` to build the default `docsUrl`.
- `register` — called once during server setup; the plugin mounts all its routes here.
- `seed` (optional) — called separately to populate the store with default test data.

This is the only interface a new emulator package must implement. Everything else — the HTTP server, auth, rate limiting, webhooks, pagination, persistence — comes from `@emulators/core` without configuration.

Sources: [packages/@emulators/core/src/plugin.ts:6-18]()

---

## What `createServer` Returns

```ts
return { app, store, webhooks, port, baseUrl, tokenMap };
```

The caller receives all five live objects. This lets a service's entry point start the HTTP listener, let tests reach into `store` to assert state, or let a CLI seed the store before starting the server — without needing any internal knowledge of how the core package was assembled.

Sources: [packages/@emulators/core/src/server.ts:104]()

---

## Summary

`@emulators/core` is a lean, self-contained runtime kit: it gives every service emulator an in-memory store with indexing and pagination, a Hono HTTP server pre-wired with CORS, error handling, JWT/token auth, and per-token rate limiting, a webhook dispatcher that signs and delivers real HTTP POST calls, OAuth comparison utilities that are safe against timing attacks, and a two-method persistence adapter for snapshotting state to disk. A service plugin only needs to implement the three-member `ServicePlugin` interface — `name`, `register`, and optionally `seed` — and the core handles the rest.

Sources: [packages/@emulators/core/src/index.ts:1-79]()

---

## 04. The Registry — How Each Service Gets Wired In

> How registry.ts acts as the master switchboard: each ServiceEntry knows how to lazy-load its plugin package, what endpoints it covers, what default auth fallback to use, and what starter config to generate with `emulate init`.

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/04-the-registry-how-each-service-gets-wired-in.md
- Generated: 2026-05-24T18:13:12.807Z

### Source Files

- `packages/emulate/src/registry.ts`
- `packages/emulate/src/commands/list.ts`
- `packages/emulate/src/base-url.ts`
- `packages/emulate/src/portless.ts`

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

- [packages/emulate/src/registry.ts](packages/emulate/src/registry.ts)
- [packages/emulate/src/commands/start.ts](packages/emulate/src/commands/start.ts)
- [packages/emulate/src/commands/init.ts](packages/emulate/src/commands/init.ts)
- [packages/emulate/src/commands/list.ts](packages/emulate/src/commands/list.ts)
- [packages/emulate/src/base-url.ts](packages/emulate/src/base-url.ts)
- [packages/emulate/src/portless.ts](packages/emulate/src/portless.ts)
- [packages/emulate/src/index.ts](packages/emulate/src/index.ts)
</details>

# The Registry — How Each Service Gets Wired In

`registry.ts` is the single file that tells the `emulate` CLI what services exist and everything it needs to know about each one. It is the master switchboard: before a single HTTP server starts, before a config file is written, and before the `list` command prints its table, the CLI looks here first.

Every service — Vercel, GitHub, Google, Slack, Apple, Microsoft, Okta, AWS, Resend, Stripe, MongoDB Atlas, and Clerk — has one entry in `SERVICE_REGISTRY`. That entry answers four questions at once: what is this service called, which API endpoints does it cover, how should it load its plugin code, what identity should an unauthenticated request fall back to, and what does a good starter config look like? Keeping those four concerns together in one record is what lets the rest of the CLI stay small and data-driven.

## The `ServiceEntry` Shape

Each entry in `SERVICE_REGISTRY` conforms to the `ServiceEntry` interface:

```typescript
// packages/emulate/src/registry.ts:9-15
export interface ServiceEntry {
  label: string;
  endpoints: string;
  load(): Promise<LoadedService>;
  defaultFallback(svcSeedConfig?: Record<string, unknown>): AuthFallback;
  initConfig: Record<string, unknown>;
}
```

| Field | Type | Purpose |
|---|---|---|
| `label` | `string` | Human-readable one-liner shown by `emulate list` |
| `endpoints` | `string` | Comma-separated summary of supported API areas |
| `load()` | `() => Promise<LoadedService>` | Lazy-loads the plugin package on demand |
| `defaultFallback()` | `(cfg?) => AuthFallback` | Provides a fallback identity when no token matches |
| `initConfig` | `Record<string, unknown>` | Template written to `emulate.config.yaml` by `emulate init` |

`LoadedService`, returned by `load()`, carries up to three things: the `plugin` object the HTTP server uses, an optional `seedFromConfig` function that pre-populates the in-memory store from the YAML config, and an optional `createAppKeyResolver` for services that issue signed tokens (currently only GitHub Apps).

```typescript
// packages/emulate/src/registry.ts:3-7
export interface LoadedService {
  plugin: ServicePlugin;
  seedFromConfig?(store: Store, baseUrl: string, config: unknown, webhooks?: WebhookDispatcher): void;
  createAppKeyResolver?(store: Store): AppKeyResolver;
}
```

## How Lazy Loading Works

The `load()` function for each entry does a dynamic `import()` of its scoped package. No service plugin is loaded at process start — it is loaded when the `startCommand` actually needs it.

```typescript
// packages/emulate/src/registry.ts:38-41 (Vercel entry)
async load() {
  const mod = await import("@emulators/vercel");
  return { plugin: mod.vercelPlugin, seedFromConfig: mod.seedFromConfig };
},
```

The GitHub entry additionally wires up an `AppKeyResolver` inline so the core server can verify GitHub App JWT tokens against data stored in the emulated GitHub store:

```typescript
// packages/emulate/src/registry.ts:67-84
async load() {
  const mod = await import("@emulators/github");
  return {
    plugin: mod.githubPlugin,
    seedFromConfig: mod.seedFromConfig,
    createAppKeyResolver(store: Store): AppKeyResolver {
      return (appId: number) => {
        try {
          const gh = mod.getGitHubStore(store);
          const ghApp = gh.apps.all().find((a) => a.app_id === appId);
          if (!ghApp) return null;
          return { privateKey: ghApp.private_key, slug: ghApp.slug, name: ghApp.name };
        } catch { return null; }
      };
    },
  };
},
```

This is the only service that needs a key resolver because GitHub App requests carry JWTs signed with a private key, not plain bearer tokens.

## The `defaultFallback` Field

When an incoming HTTP request carries no token, or carries a token that is not in the seed config, the `emulate` server needs to act as *someone*. The `defaultFallback` function provides that identity.

Most services derive the fallback from the first user listed in the seed config, and fall back to a hard-coded safe default if no config is present. The pattern varies slightly per service identity model:

| Service | Identity field | Hard-coded default |
|---|---|---|
| `vercel` | `cfg.users[0].username` | `"admin"` |
| `github` | `cfg.users[0].login` | `"admin"` with full OAuth scopes |
| `google`, `microsoft`, `apple`, `clerk` | `cfg.users[0].email` | Service-specific email |
| `okta` | `cfg.users[0].login` or `cfg.users[0].email` | `"testuser@okta.local"` |
| `slack` | _(always)_ | Slack user ID `"U000000001"` |
| `aws`, `resend`, `stripe`, `mongoatlas` | _(always)_ | Service-specific admin identity |

```typescript
// packages/emulate/src/registry.ts:86-89 (GitHub)
defaultFallback(cfg) {
  const firstLogin = (cfg?.users as Array<{ login?: string }> | undefined)?.[0]?.login ?? "admin";
  return { login: firstLogin, id: 1, scopes: ["repo", "user", "admin:org", "admin:repo_hook"] };
},
```

The fallback is passed as `fallbackUser` to `createServer` in `start.ts`. Sources: [packages/emulate/src/commands/start.ts:172-179]().

## The `initConfig` Field and `emulate init`

`initConfig` is the template that `emulate init` serializes to `emulate.config.yaml`. It contains realistic-but-obviously-fake sample data: placeholder client IDs, redirect URIs pointing at `localhost:3000`, and seed users that match what a Next.js or Node app would need during development.

`initCommand` simply reads this field and calls `yamlStringify`:

```typescript
// packages/emulate/src/commands/init.ts:26-31
const entry = SERVICE_REGISTRY[options.service as ServiceName];
// ...
config = { ...DEFAULT_TOKENS, ...entry.initConfig };
const content = yamlStringify(config);
writeFileSync(fullPath, content, "utf-8");
```

When `--service all` is passed, every `initConfig` in the registry is merged together into one YAML file:

```typescript
// packages/emulate/src/commands/init.ts:21-24
config = { ...DEFAULT_TOKENS };
for (const name of SERVICE_NAMES) {
  Object.assign(config, SERVICE_REGISTRY[name].initConfig);
}
```

Sources: [packages/emulate/src/commands/init.ts:10-39]().

## Service Name Enumeration

The registry also exports two name-related values consumed throughout the CLI:

```typescript
// packages/emulate/src/registry.ts:17-32
const SERVICE_NAME_LIST = [
  "vercel", "github", "google", "slack", "apple",
  "microsoft", "okta", "aws", "resend", "stripe",
  "mongoatlas", "clerk",
] as const;
export type ServiceName = (typeof SERVICE_NAME_LIST)[number];
export const SERVICE_NAMES: readonly ServiceName[] = SERVICE_NAME_LIST;
```

`ServiceName` is a TypeScript union literal type. `SERVICE_NAMES` is the runtime array that `inferServicesFromConfig` uses in `start.ts` to detect which services are mentioned in a seed file, and that `listCommand` iterates when printing the service table.

## How `startCommand` Drives the Registry

The startup sequence in `startCommand` follows a strict order that touches every part of each `ServiceEntry`:

```text
┌─────────────────────────────────────────────────────────┐
│  startCommand (start.ts)                                │
│                                                         │
│  1. Load seed config (YAML / JSON)                      │
│  2. Infer service list from config keys OR use all      │
│  3. For each ServiceName:                               │
│     a. entry.load()        → LoadedService              │
│     b. entry.defaultFallback(svcSeedConfig) → identity  │
│     c. resolveBaseUrl()    → URL string                 │
│     d. createServer(plugin, {tokens, fallbackUser})     │
│     e. loadedSvc.seedFromConfig(store, baseUrl, cfg)    │
│     f. serve({ fetch: app.fetch, port })                │
└─────────────────────────────────────────────────────────┘
```

Step (b) feeds the `ServiceEntry.defaultFallback` result directly into `createServer`. Step (e) only runs if both `svcSeedConfig` and `loadedSvc.seedFromConfig` are present — that is, the YAML has a section matching the service name, and the plugin package exported a seeding function.

Sources: [packages/emulate/src/commands/start.ts:133-192]().

## Base URL Resolution

Once a service is loaded, its publicly advertised URL is resolved through a five-step fallback chain defined in `base-url.ts`:

1. Per-service `baseUrl` key inside the seed config section
2. `--base-url` CLI flag (or programmatic option)
3. `EMULATE_BASE_URL` environment variable (supports `{service}` interpolation)
4. `PORTLESS_URL` environment variable (supports `{service}` interpolation)
5. `http://localhost:<port>` (default)

```typescript
// packages/emulate/src/base-url.ts:16-32
export function resolveBaseUrl(opts: ResolveBaseUrlOptions): string {
  if (opts.seedBaseUrl)  return opts.seedBaseUrl.replace(/\{service\}/g, opts.service);
  if (opts.baseUrl)      return opts.baseUrl.replace(/\{service\}/g, opts.service);
  const envBaseUrl = process.env.EMULATE_BASE_URL;
  if (envBaseUrl)        return envBaseUrl.replace(/\{service\}/g, opts.service);
  const portlessUrl = process.env.PORTLESS_URL;
  if (portlessUrl)       return portlessUrl.replace(/\{service\}/g, opts.service);
  return `http://localhost:${opts.port}`;
}
```

When `--portless` is used, `portlessBaseUrl(serviceName)` in `portless.ts` generates `https://<service>.emulate.localhost`, and `registerAliases` maps each `<service>.emulate` name to its localhost port via the `portless` CLI. Sources: [packages/emulate/src/portless.ts:91-93]() and [packages/emulate/src/portless.ts:66-80]().

## The `emulate list` Command

`listCommand` is the simplest consumer of the registry — it iterates `SERVICE_REGISTRY` and prints `entry.label` and `entry.endpoints` for each service:

```typescript
// packages/emulate/src/commands/list.ts:3-10
export function listCommand(): void {
  console.log("\nAvailable services:\n");
  for (const [name, entry] of Object.entries(SERVICE_REGISTRY)) {
    console.log(`  ${name.padEnd(10)}${entry.label}`);
    console.log(`            Endpoints: ${entry.endpoints}`);
  }
}
```

This means the output of `emulate list` is purely data-driven from the registry: adding a new service to `SERVICE_REGISTRY` automatically makes it appear in the CLI help output.

## `DEFAULT_TOKENS`

The registry also exports `DEFAULT_TOKENS`, a static object prepended by `initCommand` to every generated config file. It provides two ready-to-use bearer tokens — `test_token_admin` (mapped to `admin`) and `test_token_user1` (mapped to `octocat`) — so a project can make authenticated API calls immediately after `emulate init` without defining any tokens:

```typescript
// packages/emulate/src/registry.ts:507-518
export const DEFAULT_TOKENS = {
  tokens: {
    test_token_admin: {
      login: "admin",
      scopes: ["repo", "user", "admin:org", "admin:repo_hook"],
    },
    test_token_user1: {
      login: "octocat",
      scopes: ["repo", "user"],
    },
  },
};
```

## Summary

`registry.ts` acts as the single source of truth for every service emulate supports. Its `SERVICE_REGISTRY` record makes three commands — `start`, `init`, and `list` — purely data-driven: `start` calls `load()` and `defaultFallback()` at runtime, `init` serializes `initConfig` to YAML, and `list` reads `label` and `endpoints` for display. Adding a new service requires only a new `ServiceEntry` in this one file; no other command code needs to change. Sources: [packages/emulate/src/registry.ts:34-505]().

---

## 05. 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.

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/05-using-emulate-in-code-the-programmatic-api.md
- Generated: 2026-05-24T18:14:41.965Z

### 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)

---

## 06. OAuth & Identity Services — Google, Apple, Microsoft, Okta, Clerk

> The five emulators that pretend to be identity providers: what OAuth/OIDC flows each one covers, how the auto-login fallback bypasses the browser consent screen in CI, and what each package lives in.

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/06-oauth-identity-services-google-apple-microsoft-okta-clerk.md
- Generated: 2026-05-24T18:22:48.066Z

### Source Files

- `packages/@emulators/google/src`
- `packages/@emulators/apple/src`
- `packages/@emulators/microsoft/src`
- `packages/@emulators/okta/src`
- `packages/@emulators/clerk/src`
- `packages/emulate/src/registry.ts`
- `skills/google/SKILL.md`
- `skills/apple/SKILL.md`

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

- [packages/emulate/src/registry.ts](packages/emulate/src/registry.ts)
- [packages/@emulators/core/src/middleware/auth.ts](packages/@emulators/core/src/middleware/auth.ts)
- [packages/@emulators/google/src/routes/oauth.ts](packages/@emulators/google/src/routes/oauth.ts)
- [packages/@emulators/google/src/index.ts](packages/@emulators/google/src/index.ts)
- [packages/@emulators/google/src/entities.ts](packages/@emulators/google/src/entities.ts)
- [packages/@emulators/apple/src/routes/oauth.ts](packages/@emulators/apple/src/routes/oauth.ts)
- [packages/@emulators/apple/src/entities.ts](packages/@emulators/apple/src/entities.ts)
- [packages/@emulators/microsoft/src/routes/oauth.ts](packages/@emulators/microsoft/src/routes/oauth.ts)
- [packages/@emulators/microsoft/src/entities.ts](packages/@emulators/microsoft/src/entities.ts)
- [packages/@emulators/okta/src/routes/oauth.ts](packages/@emulators/okta/src/routes/oauth.ts)
- [packages/@emulators/okta/src/routes/users.ts](packages/@emulators/okta/src/routes/users.ts)
- [packages/@emulators/okta/src/entities.ts](packages/@emulators/okta/src/entities.ts)
- [packages/@emulators/clerk/src/routes/oauth.ts](packages/@emulators/clerk/src/routes/oauth.ts)
- [packages/@emulators/clerk/src/routes/sessions.ts](packages/@emulators/clerk/src/routes/sessions.ts)
- [packages/@emulators/clerk/src/entities.ts](packages/@emulators/clerk/src/entities.ts)
- [skills/google/SKILL.md](skills/google/SKILL.md)
- [skills/apple/SKILL.md](skills/apple/SKILL.md)
</details>

# OAuth & Identity Services — Google, Apple, Microsoft, Okta, Clerk

This page covers the five identity-provider emulators in the `vercel-labs/emulate` repository: Google, Apple, Microsoft, Okta, and Clerk. Each one impersonates a real SSO or OIDC provider so your app can complete login flows, issue tokens, and exercise user-management APIs — all without touching the real internet.

Every emulator shares a common pattern: it exposes the same HTTP paths as the real provider, issues real-looking JWTs (signed by an in-process key), and renders a minimal browser consent page with clickable user buttons. In CI or headless environments, a *fallback mechanism* eliminates even that click — any bearer token that arrives at the emulator is automatically accepted and mapped to the first seeded user. Understanding this two-mode design (interactive browser flow vs. headless token bypass) is the key to using these emulators effectively.

---

## How the Auto-Login Fallback Works

In a real browser flow the user visits the `/auth/authorize` page, clicks a user button, and gets redirected back with a code. In CI there is no browser, so the emulators support a second path: the `defaultFallback` mechanism.

Every service entry in the registry defines a `defaultFallback()` function that returns a hardcoded `AuthFallback` object:

```ts
// packages/emulate/src/registry.ts:140-143
defaultFallback(cfg) {
  const firstEmail = (cfg?.users as Array<...> | undefined)?.[0]?.email ?? "testuser@gmail.com";
  return { login: firstEmail, id: 1, scopes: ["openid", "email", "profile"] };
},
```

This fallback is passed to the core `authMiddleware`. When the middleware receives any `Authorization: Bearer <token>` header, it first looks the token up in the known token map. If it is **not found but is non-empty**, and a fallback user is configured, the middleware silently resolves the request as that fallback user instead of returning 401:

```ts
// packages/@emulators/core/src/middleware/auth.ts:98-108
let user = tokens.get(token);
if (!user && fallbackUser && token.length > 0) {
  debug("auth", "fallback user for unknown token", { login: fallbackUser.login, id: fallbackUser.id });
  user = { login: fallbackUser.login, id: fallbackUser.id, scopes: fallbackUser.scopes };
}
if (user) {
  c.set("authUser", user);
  c.set("authToken", token);
  c.set("authScopes", user.scopes);
}
```

**Practical consequence:** in your test suite, set any non-empty bearer token (e.g. `Bearer test_token`) and configure the emulator with a seed user. Every API call will succeed as that user without ever opening a browser.

```text
Browser flow (interactive):
  App → GET /oauth/authorize → consent UI → user click → code → POST /oauth/token → JWT

CI fallback (headless):
  App → any API call with Bearer <anything> → authMiddleware resolves fallback user → request succeeds
```

Sources: [packages/@emulators/core/src/middleware/auth.ts:64-112](), [packages/emulate/src/registry.ts:140-143]()

---

## Google — `@emulators/google`

**Package:** `@emulators/google` v0.6.0  
**Default port:** 4002  
**Registry label:** "Google OAuth 2.0 / OpenID Connect + Gmail, Calendar, and Drive emulator"

Google is the most feature-rich of the five. It wraps three real Google product APIs in addition to OAuth itself.

### OAuth/OIDC Flow

| Endpoint | Real Google URL | Emulator path |
|---|---|---|
| OIDC Discovery | `accounts.google.com/.well-known/openid-configuration` | `/.well-known/openid-configuration` |
| JWKS | `oauth2.googleapis.com/oauth2/v3/certs` | `/oauth2/v3/certs` |
| Authorization | `accounts.google.com/o/oauth2/v2/auth` | `/o/oauth2/v2/auth` |
| Token exchange | `oauth2.googleapis.com/oauth2/token` | `/oauth2/token` |
| Userinfo | `oauth2.googleapis.com/oauth2/v2/userinfo` | `/oauth2/v2/userinfo` |
| Revocation | `oauth2.googleapis.com/oauth2/revoke` | `/oauth2/revoke` |

The emulator issues **HS256** id_tokens (signed with a static secret `"emulate-google-jwt-secret"`) rather than RS256, which simplifies key management locally. PKCE (both `plain` and `S256`) and `client_secret_basic` / `client_secret_post` authentication are both supported for token exchange. The `hd` (hosted domain) claim is automatically derived from the user's email domain; consumer domains (`gmail.com`, `googlemail.com`) have it omitted.

Sources: [packages/@emulators/google/src/routes/oauth.ts:64-122]()

### Gmail, Calendar, and Drive

Google goes far beyond just login. Three additional route groups are mounted:

| API surface | Key endpoints |
|---|---|
| **Gmail messages** | List, get, insert, import, send, modify, batchModify, batchDelete, trash, untrash, delete, attachments |
| **Gmail drafts** | List, get, create, update (PUT), send, delete |
| **Gmail threads** | List, get, modify, trash, untrash, delete |
| **Gmail labels** | List, get, create, update (PUT/PATCH), delete |
| **Gmail history** | List history, watch, stop |
| **Gmail settings** | Filters, forwarding addresses, send-as |
| **Calendar** | calendarList, events (list/create/delete with timeMin/timeMax), freeBusy |
| **Drive** | files list (query syntax), create (JSON + multipart), get (`alt=media` download), update with addParents/removeParents |

The seed config accepts `messages`, `calendars`, `calendar_events`, and `drive_items` keys so tests start with realistic data already in place.

Sources: [packages/@emulators/google/src/index.ts](), [skills/google/SKILL.md]()

---

## Apple — `@emulators/apple`

**Package:** `@emulators/apple` v0.6.0  
**Default port:** 4000 (standalone) / configurable  
**Registry label:** "Apple Sign In / OAuth emulator"

Apple's "Sign in with Apple" is OIDC-based but has several quirks that the emulator faithfully replicates.

### OAuth/OIDC Flow

| Endpoint | Real Apple URL | Emulator path |
|---|---|---|
| OIDC Discovery | `appleid.apple.com/.well-known/openid-configuration` | `/.well-known/openid-configuration` |
| JWKS | `appleid.apple.com/auth/keys` | `/auth/keys` |
| Authorization | `appleid.apple.com/auth/authorize` | `/auth/authorize` |
| Token exchange | `appleid.apple.com/auth/token` | `/auth/token` |
| Revocation | `appleid.apple.com/auth/revoke` | `/auth/revoke` |

Apple uses **RS256** — the emulator generates a fresh RSA key pair at module load time (`kid: "emulate-apple-1"`) and exposes it via JWKS. The discovery document reports `subject_types_supported: ["pairwise"]`, exactly matching the real provider.

### Apple-Specific Behaviors Emulated

**`response_mode` support:** Apple supports `query`, `fragment`, and `form_post` redirect modes. The emulator handles all three, rendering a full HTML form for `form_post` responses — important because most server-side apps use form_post.

**First-authorization `user` JSON blob:** Real Apple only sends the user's name/email in the authorization response on the *first* login ever. The emulator tracks this with an in-memory `firstAuthTracker` Set (keyed `email:clientId`) and sends the JSON blob only on the first authorization, then never again.

**Private Relay Email:** Users seeded with `is_private_email: true` get a `@privaterelay.appleid.com` address in their id_token, mirroring Apple's email privacy feature.

**Refresh tokens:** Token refresh issues a new `access_token` and `id_token` but no new `refresh_token` — matching Apple's real behavior.

```ts
// packages/@emulators/apple/src/entities.ts
interface AppleUser {
  is_private_email: boolean;
  private_relay_email: string; // @privaterelay.appleid.com address
  real_user_status: number;    // 0/1/2
  // ...
}
```

Sources: [packages/@emulators/apple/src/routes/oauth.ts:19-68](), [packages/@emulators/apple/src/entities.ts](), [skills/apple/SKILL.md]()

---

## Microsoft — `@emulators/microsoft`

**Package:** `@emulators/microsoft` v0.6.0  
**Registry label:** "Microsoft Entra ID OAuth 2.0 / OpenID Connect emulator"

The Microsoft emulator covers Entra ID (formerly Azure AD) including both the v2.0 OIDC endpoints and the legacy v1.0 endpoint.

### OAuth/OIDC Flow

| Endpoint | Emulator path | Notes |
|---|---|---|
| Global discovery | `/.well-known/openid-configuration` | |
| Tenant-scoped discovery | `/:tenant/v2.0/.well-known/openid-configuration` | Supports `common`, `organizations`, or actual tenant ID |
| JWKS | `/discovery/v2.0/keys` | RS256 |
| Authorization | `/oauth2/v2.0/authorize` | |
| Token exchange (v2) | `/oauth2/v2.0/token` | auth_code, refresh_token, client_credentials |
| Token exchange (v1) | `/:tenant/oauth2/token` | translates `resource` → `scope` |
| Userinfo | `/oidc/userinfo` | |
| Microsoft Graph `/me` | `/v1.0/me` | Returns OIDC claims in Graph format |
| User by ID | `/v1.0/users/:id` | |
| Logout | `/oauth2/v2.0/logout` | |

The emulator handles **`client_secret_basic`** (credentials in the `Authorization` header) in addition to `client_secret_post`, and the v1 endpoint translates the `resource` parameter into scopes so apps targeting older ADAL-style flows work without changes.

Users carry a `tenant_id` field (defaulting to `"9188040d-6c67-4c5b-b112-36a304b66dad"`, the well-known consumer tenant) and an `oid` (object ID) generated as a UUID, matching the real Entra ID claim shape.

Sources: [packages/@emulators/microsoft/src/routes/oauth.ts](), [packages/@emulators/microsoft/src/helpers.ts]()

---

## Okta — `@emulators/okta`

**Package:** `@emulators/okta` v0.6.0  
**Registry label:** "Okta OAuth 2.0 / OpenID Connect + management API emulator"

Okta is the most structurally complex emulator because Okta itself is a full identity platform with multiple authorization servers, group management, and an extensive management API.

### Multi-Server Architecture

Okta supports multiple *authorization servers*, each with its own issuer URL and JWKS. The emulator replicates this:

- **Org auth server** (`org`): `<base>/oauth2/org/…` — used for Okta admin API tokens
- **Default auth server** (`default`): `<base>/oauth2/default/…` — the typical app-facing server with audience `api://default`
- **Custom servers**: any `auth_server_id` seeded in config gets its own discovery document and JWKS

Each server exposes: `/.well-known/openid-configuration`, JWKS, `/authorize`, `/token` (auth_code + refresh_token + **client_credentials**), `/userinfo`, `/revoke`, `/introspect`, `/logout`.

### Token Introspection

The `/introspect` endpoint returns an RFC 7662-compliant response:

```json
{ "active": true, "sub": "testuser@okta.local", "scope": "openid profile email", ... }
```

Tokens not found in the store return `{ "active": false }`.

### Groups Claim

When the `groups` scope is requested, the id_token includes a `groups` array claim populated from the user's actual group memberships in the store — important for testing role-based access controls.

### Management API

Beyond OAuth, Okta exposes a full management REST API:

| Resource | Endpoints |
|---|---|
| Users | CRUD + lifecycle (activate/deactivate/suspend/unsuspend/reactivate) |
| Groups | CRUD + member management |
| Apps | CRUD + lifecycle + user assignments |
| Authorization Servers | CRUD + lifecycle; deletes cascade to oauth_clients |

Two-step user deletion matches real Okta: the first `DELETE /api/v1/users/:id` call deactivates the user; the second call removes them.

The default fallback uses `login` or `email` from the first seeded user, with scopes `["openid", "profile", "email", "groups"]`.

Sources: [packages/@emulators/okta/src/routes/oauth.ts](), [packages/@emulators/okta/src/routes/users.ts](), [packages/@emulators/okta/src/helpers.ts](), [packages/emulate/src/registry.ts:359-365]()

---

## Clerk — `@emulators/clerk`

**Package:** `@emulators/clerk` v0.6.0  
**Registry label:** "Clerk authentication and user management emulator"

Clerk differs from the other four: it is not a raw OIDC provider but an authentication-as-a-service platform. Its emulator therefore covers both the OIDC/OAuth surface *and* Clerk's Backend API.

### OAuth/OIDC Flow

| Endpoint | Emulator path | Notes |
|---|---|---|
| OIDC Discovery | `/.well-known/openid-configuration` | |
| JWKS | `/v1/jwks` | RS256 |
| Authorization | `/oauth/authorize` | Supports PKCE S256/plain |
| Token exchange | `/oauth/token` | auth_code only; creates a session record |
| Userinfo | `/oauth/userinfo` | Returns `sid`, `email`, `email_verified`, `name` |

Unlike the others, Clerk's token endpoint creates a **session record** in the store each time a code is exchanged. Session tokens issued via `/v1/sessions/:id/tokens` include organization claims (`org_id`, `org_role`, `org_slug`, `org_permissions`) from the user's first active membership.

### Backend API (Secret Key)

All Backend API endpoints require a `sk_test_` or `sk_live_` secret key in the `Authorization: Bearer` header. This matches Clerk's production authentication model for server-side code.

| Resource | Endpoints |
|---|---|
| Users | CRUD, ban/unban, lock/unlock, metadata PATCH, verify_password |
| Email addresses | CRUD; promoting a primary demotes all others |
| Organizations | CRUD, metadata PATCH; creator auto-gets admin membership |
| Memberships | CRUD, metadata PATCH; `org:admin` role gets 6 permissions by default |
| Invitations | List, get, create, bulk create, revoke; tracks `pending_invitations_count` |
| Sessions | List, get, revoke, issue tokens (with org claims), template tokens |

The Clerk `defaultFallback` picks the first email address from the first seeded user:

```ts
// packages/emulate/src/registry.ts:471-475
defaultFallback(cfg) {
  const firstEmail =
    (cfg?.users as Array<{ email_addresses?: string[] }> | undefined)?.[0]?.email_addresses?.[0]
    ?? "test@example.com";
  return { login: firstEmail, id: 1, scopes: [] };
},
```

Sources: [packages/@emulators/clerk/src/routes/oauth.ts](), [packages/@emulators/clerk/src/routes/sessions.ts](), [packages/@emulators/clerk/src/route-helpers.ts](), [packages/emulate/src/registry.ts:463-504]()

---

## Side-by-Side Comparison

| | Google | Apple | Microsoft | Okta | Clerk |
|---|---|---|---|---|---|
| **npm package** | `@emulators/google` | `@emulators/apple` | `@emulators/microsoft` | `@emulators/okta` | `@emulators/clerk` |
| **Default port** | 4002 | 4000 | — | — | — |
| **JWT algorithm** | HS256 | RS256 | RS256 | RS256 | RS256 |
| **PKCE support** | S256, plain | — | — | — | S256, plain |
| **client_credentials grant** | No | No | Yes | Yes | No |
| **Token introspection** | No | No | No | Yes | No |
| **User management API** | No | No | Graph `/me` only | Full (users/groups/apps) | Full (Backend API) |
| **Extra APIs** | Gmail, Calendar, Drive | — | — | Multi auth-server | Organizations, Sessions |
| **Special quirks** | `hd` claim, workspace domains | first-auth blob, private relay, form_post | v1 `resource` → `scope` | multi-server, groups claim, 2-step delete | session records, org claims |

Sources: [packages/emulate/src/registry.ts:132-504]()

---

## Seeding and Configuration

All five emulators accept a YAML/JSON seed config that maps directly onto their store. Each one exposes a `seedFromConfig(store, baseUrl, config)` function called at startup. The minimum viable config for each:

```yaml
# Google
google:
  users:
    - email: "dev@example.com"
      name: "Dev User"
      email_verified: true
  oauth_clients:
    - client_id: "my-client.apps.googleusercontent.com"
      client_secret: "GOCSPX-secret"
      redirect_uris: ["http://localhost:3000/api/auth/callback/google"]

# Apple
apple:
  users:
    - email: "dev@icloud.com"
      name: "Dev User"
  oauth_clients:
    - client_id: "com.example.app"
      team_id: "TEAM001"
      redirect_uris: ["http://localhost:3000/api/auth/callback/apple"]

# Microsoft
microsoft:
  users:
    - email: "dev@contoso.com"
      name: "Dev User"
  oauth_clients:
    - client_id: "my-app-client-id"
      client_secret: "my-secret"
      redirect_uris: ["http://localhost:3000/api/auth/callback/microsoft-entra-id"]

# Okta
okta:
  users:
    - login: "dev@okta.local"
      email: "dev@okta.local"
  authorization_servers:
    - id: "default"
      name: "default"
      audiences: ["api://default"]
  oauth_clients:
    - client_id: "my-okta-client"
      client_secret: "my-secret"
      auth_server_id: "default"
      redirect_uris: ["http://localhost:3000/callback"]

# Clerk
clerk:
  users:
    - first_name: "Dev"
      last_name: "User"
      email_addresses: ["dev@example.com"]
  oauth_applications:
    - client_id: "clerk_client"
      client_secret: "clerk_secret"
      redirect_uris: ["http://localhost:3000/api/auth/callback/clerk"]
```

Sources: [packages/emulate/src/registry.ts:144-504]()

---

## Summary

The five identity emulators cover the most common SSO providers used in web applications. Google adds an unusually broad API surface (Gmail, Calendar, Drive) on top of standard OIDC. Apple accurately replicates the quirks of "Sign in with Apple" — form_post mode, first-login user blobs, and Private Relay emails. Microsoft handles both the modern v2.0 and legacy v1.0 Entra ID endpoints including `client_credentials`. Okta stands out with multi-authorization-server support, token introspection, and a full management REST API. Clerk treats authentication as a full service layer, combining OIDC with organizations, sessions, and a secret-key-authenticated Backend API. In every case, the `defaultFallback` in `authMiddleware` lets CI pipelines skip the browser entirely by treating any non-empty bearer token as the first seeded user. Sources: [packages/@emulators/core/src/middleware/auth.ts:70-112]()

---

## 07. Platform & Infrastructure Services — GitHub, Vercel, Slack, AWS, Stripe, Resend, MongoDB Atlas

> The seven emulators covering developer platforms and cloud infrastructure: what REST surface area each one reproduces, how webhooks are dispatched in-process, and what the examples directory shows about wiring them into a Next.js app.

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/07-platform-infrastructure-services-github-vercel-slack-aws-stripe-resend-mongodb-atlas.md
- Generated: 2026-05-24T18:22:50.804Z

### Source Files

- `packages/@emulators/github/src`
- `packages/@emulators/vercel/src`
- `packages/@emulators/slack/src`
- `packages/@emulators/aws/src`
- `packages/@emulators/stripe/src`
- `packages/@emulators/resend/src`
- `packages/@emulators/mongoatlas/src`
- `examples/stripe-checkout/README.md`
- `examples/oauth/README.md`

> ⚠️ The agent returned an invalid wiki page. This page needs recovery.
>
> First failure: the page included too few precise line citations; include at least 3 citations like Sources: [path/to/file.ts:12-40]() outside the opening source list
> Retry failure: the page did not include the required "# Platform & Infrastructure Services — GitHub, Vercel, Slack, AWS, Stripe, Resend, MongoDB Atlas" heading near the top

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

- [packages/@emulators/github/src/index.ts](packages/@emulators/github/src/index.ts)
- [packages/@emulators/github/src/routes/issues.ts](packages/@emulators/github/src/routes/issues.ts)
- [packages/@emulators/github/src/routes/webhooks.ts](packages/@emulators/github/src/routes/webhooks.ts)
- [packages/@emulators/github/src/routes/actions.ts](packages/@emulators/github/src/routes/actions.ts)
- [packages/@emulators/vercel/src/index.ts](packages/@emulators/vercel/src/index.ts)
- [packages/@emulators/vercel/src/routes/deployments.ts](packages/@emulators/vercel/src/routes/deployments.ts)
- [packages/@emulators/slack/src/index.ts](packages/@emulators/slack/src/index.ts)
- [packages/@emulators/slack/src/routes/chat.ts](packages/@emulators/slack/src/routes/chat.ts)
- [packages/@emulators/slack/src/routes/webhooks.ts](packages/@emulators/slack/src/routes/webhooks.ts)
- [packages/@emulators/aws/src/index.ts](packages/@emulators/aws/src/index.ts)
- [packages/@emulators/aws/src/routes/s3.ts](packages/@emulators/aws/src/routes/s3.ts)
- [packages/@emulators/aws/src/routes/sqs.ts](packages/@emulators/aws/src/routes/sqs.ts)
- [packages/@emulators/stripe/src/index.ts](packages/@emulators/stripe/src/index.ts)
- [packages/@emulators/stripe/src/routes/checkout-sessions.ts](packages/@emulators/stripe/src/routes/checkout-sessions.ts)
- [packages/@emulators/stripe/src/routes/payment-intents.ts](packages/@emulators/stripe/src/routes/payment-intents.ts)
- [packages/@emulators/resend/src/index.ts](packages/@emulators/resend/src/index.ts)
- [packages/@emulators/resend/src/routes/emails.ts](packages/@emulators/resend/src/routes/emails.ts)
- [packages/@emulators/resend/src/routes/inbox.ts](packages/@emulators/resend/src/routes/inbox.ts)
- [packages/@emulators/mongoatlas/src/index.ts](packages/@emulators/mongoatlas/src/index.ts)
- [packages/@emulators/mongoatlas/src/routes/admin.ts](packages/@emulators/mongoatlas/src/routes/admin.ts)
- [packages/@emulators/mongoatlas/src/routes/data-api.ts](packages/@emulators/mongoatlas/src/routes/data-api.ts)
- [packages/@emulators/core/src/webhooks.ts](packages/@emulators/core/src/webhooks.ts)
- [examples/stripe-checkout/README.md](examples/stripe-checkout/README.md)
- [examples/oauth/README.md](examples/oauth/README.md)
- [examples/oauth/src/lib/providers.ts](examples/oauth/src/lib/providers.ts)
- [examples/nextjs-embedded/README.md](examples/nextjs-embedded/README.md)
</details>

# Platform & Infrastructure Services — GitHub, Vercel, Slack, AWS, Stripe, Resend, MongoDB Atlas

This page covers the seven platform and infrastructure emulators that ship with `emulate`: **GitHub**, **Vercel**, **Slack**, **AWS** (S3, SQS, IAM), **Stripe**, **Resend**, and **MongoDB Atlas**. Each one is a self-contained Hono plugin that registers REST routes, seeds a typed in-memory store, and participates in the shared webhook dispatch system — so your test suite (or a Next.js preview deployment) can talk to realistic local counterparts of these services with no real accounts, network calls, or money involved.

Understanding how these emulators fit together matters because they all share the same `WebhookDispatcher`, `Store`, and `RouteContext` primitives from `@emulators/core`. That means a single action — creating a Stripe PaymentIntent, pushing a commit event through GitHub, or sending an email through Resend — can fire an in-process webhook delivery to your app's handler in the same test process, without any external tunnel or queue.

---

## Architecture overview

Every emulator is a `ServicePlugin`:

```ts
// packages/@emulators/core/src/plugin.ts (interface shape)
export interface ServicePlugin {
  name: string;
  register(app, store, webhooks, baseUrl, tokenMap?): void;
  seed(store, baseUrl): void;
}
```

`register` mounts all the HTTP routes; `seed` populates the store with sensible defaults so the emulator is usable immediately. The `webhooks` argument is the shared `WebhookDispatcher` from `@emulators/core`.

```text
┌─────────────────────────────────────────────────────────┐
│                    Hono HTTP server                      │
│  ┌────────────┐  ┌─────────┐  ┌──────┐  ┌───────────┐  │
│  │  github/*  │  │vercel/* │  │slack/│  │  stripe/* │  │
│  └────────────┘  └─────────┘  └──────┘  └───────────┘  │
│  ┌─────────┐  ┌──────────┐  ┌────────────────────────┐  │
│  │  aws/*  │  │ resend/* │  │   mongoatlas/*         │  │
│  └─────────┘  └──────────┘  └────────────────────────┘  │
│                    ↓ shared                              │
│          Store   WebhookDispatcher                       │
└─────────────────────────────────────────────────────────┘
```

---

## GitHub emulator

### REST surface area

The GitHub emulator is the most feature-rich of the seven. It registers route modules for:

| Module | Key endpoints |
|--------|--------------|
| `usersRoutes` | `GET /user`, `GET /users/:login` |
| `reposRoutes` | CRUD for repos, forks, collaborators, git objects |
| `issuesRoutes` | CRUD for issues, state transitions, assignees |
| `pullsRoutes` | PR creation, listing, merging |
| `commentsRoutes` | Issue and PR comments |
| `reviewsRoutes` | PR reviews and review comments |
| `labelsAndMilestonesRoutes` | Labels and milestones |
| `branchesAndGitRoutes` | Branches, refs, commits, trees, blobs |
| `orgsAndTeamsRoutes` | Orgs, teams, memberships |
| `releasesRoutes` | Releases and release assets |
| `searchRoutes` | Code, issues, PR search |
| `actionsRoutes` | Workflow runs, jobs, secrets, artifacts |
| `checksRoutes` | Check runs and check suites |
| `rateLimitRoutes` | `GET /rate_limit` |
| `metaRoutes` | `GET /meta`, `GET /octocat` |
| `oauthRoutes` | OAuth device flow, web flow |
| `appsRoutes` | GitHub Apps, installation tokens |
| `webhooksRoutes` | Repo and org webhook CRUD |

Sources: [packages/@emulators/github/src/index.ts:8-26](packages/@emulators/github/src/index.ts)

### GitHub Apps and webhook enrichment

The GitHub emulator goes further than a plain REST stub: it integrates GitHub App installation semantics into the webhook pipeline. When `githubPlugin.register` runs, it **wraps** the shared `WebhookDispatcher.dispatch` method. Before forwarding an event to registered subscribers, it:

1. Finds all active app installations that match the event's owner/repo scope and subscribe to that event type.
2. Enriches the payload with an `installation` object (`{ id, node_id }`).
3. Calls the original dispatcher (in-process subscribers).
4. Separately POSTs to any `webhook_url` configured on the GitHub App, signing the body with HMAC-SHA256 if a `webhook_secret` is set.

```ts
// packages/@emulators/github/src/index.ts:460-475
webhooks.dispatch = async (event, action, payload, owner, repo) => {
  const installations = findInstallationsForRepo(gh, owner, repo, event);
  const enrichedPayload =
    installations.length > 0 ? enrichPayloadWithInstallation(payload, installations[0]) : payload;

  await originalDispatch(event, action, enrichedPayload, owner, repo);
  await deliverToAppWebhookUrls(gh, event, action, payload, owner, repo);
};
```

Sources: [packages/@emulators/github/src/index.ts:460-475](packages/@emulators/github/src/index.ts), [packages/@emulators/github/src/index.ts:415-453](packages/@emulators/github/src/index.ts)

### Seed config

`GitHubSeedConfig` supports seeding users, orgs, repos (with auto-init commits and branches), OAuth apps, and full GitHub App definitions including multiple installations with per-installation repository selection and event subscriptions.

Sources: [packages/@emulators/github/src/index.ts:30-85](packages/@emulators/github/src/index.ts)

---

## Vercel emulator

### REST surface area

The Vercel emulator covers the core Vercel REST API used by integrations and CI tooling:

| Module | Key endpoints |
|--------|--------------|
| `oauthRoutes` | OAuth authorization, token exchange, userinfo |
| `userRoutes` | `GET /v2/user`, update user |
| `projectsRoutes` | Project CRUD, list by team/user |
| `deploymentsRoutes` | Create, list, get, cancel, delete deployments; deployment events; aliases |
| `domainsRoutes` | Domain registration and lookup |
| `envRoutes` | Environment variable CRUD per project |
| `apiKeysRoutes` | API key creation and revocation |

Sources: [packages/@emulators/vercel/src/index.ts:6-12](packages/@emulators/vercel/src/index.ts)

The deployment module implements cursor-based pagination (Vercel's `until`/`since` cursor style) and synthesizes deployment hostnames from the project name and a UID slice:

```ts
// packages/@emulators/vercel/src/routes/deployments.ts:57-59
function deploymentHostname(name, uid, baseUrl) {
  const slug = `${name}-${uid.slice(4, 12)}`;
  return `${slug}.${primaryHostFromBaseUrl(baseUrl)}`;
}
```

Sources: [packages/@emulators/vercel/src/routes/deployments.ts:57-64](packages/@emulators/vercel/src/routes/deployments.ts)

### Seed config

`VercelSeedConfig` covers users, teams (with automatic team membership for all seeded users), projects (with framework settings, build commands, and env vars), and OAuth app registrations (Vercel integrations).

Sources: [packages/@emulators/vercel/src/index.ts:17-50](packages/@emulators/vercel/src/index.ts)

---

## Slack emulator

### REST surface area

The Slack emulator covers the Web API endpoints that real Slack bots and integrations rely on:

| Module | Key endpoints |
|--------|--------------|
| `authRoutes` | `auth.test`, `auth.revoke` |
| `chatRoutes` | `chat.postMessage`, `chat.update`, `chat.delete`, `chat.scheduleMessage`, `chat.getPermalink` |
| `conversationsRoutes` | `conversations.list`, `.info`, `.create`, `.join`, `.invite`, `.archive`, `.members`, `.history`, `.replies` |
| `usersRoutes` | `users.list`, `.info`, `.profile.get`, `.profile.set`, `.getPresence` |
| `reactionsRoutes` | `reactions.add`, `.remove`, `.list`, `.get` |
| `teamRoutes` | `team.info` |
| `oauthRoutes` | `oauth.v2.access` |
| `webhookRoutes` | Incoming webhooks via `POST /services/:teamId/:botId/:token` |
| `filesRoutes` | File listing (stub) |
| `pinsRoutes` | `pins.add`, `.remove`, `.list` |
| `bookmarksRoutes` | Bookmark CRUD |
| `viewsRoutes` | Modal view push/update/open |
| `inspectorRoutes` | Dev UI for inspecting emulator state |

Sources: [packages/@emulators/slack/src/index.ts:6-18](packages/@emulators/slack/src/index.ts)

### Incoming webhooks

The Slack emulator supports Slack's Incoming Webhook pattern. `POST /services/:teamId/:botId/:token` accepts both `application/json` and `application/x-www-form-urlencoded` (with a `payload` field), resolves the target channel, and stores the message in the in-memory channel history — the same store that `chat.postMessage` writes to. The webhook token is matched against the `incomingWebhooks` table seeded at startup.

Sources: [packages/@emulators/slack/src/routes/webhooks.ts:14-60](packages/@emulators/slack/src/routes/webhooks.ts)

### Token authentication

Slack's token format differs from the Bearer-token convention used by most other emulators. The Slack plugin installs a middleware that reads `Authorization: Bearer xoxb-...` (or `Authorization: token xoxb-...`) before every request, looks up the token in the store, and injects `authToken`, `authScopes`, and `authUser` into the Hono context:

```ts
// packages/@emulators/slack/src/index.ts:402-416
function applySlackTokenAuth(c, store) {
  const token = slackRequestToken(c);
  if (!token) return;
  const record = getSlackStore(store).tokens.findOneBy("token", token);
  if (!record) return;
  c.set("authToken", record.token);
  c.set("authScopes", record.scopes);
  c.set("authUser", { login: record.user_id, ... });
}
```

The `strict_scopes` seed option controls whether missing scopes return a `missing_scope` error or are silently allowed — useful for loosening validation during initial development.

Sources: [packages/@emulators/slack/src/index.ts:361-390](packages/@emulators/slack/src/index.ts), [packages/@emulators/slack/src/index.ts:402-423](packages/@emulators/slack/src/index.ts)

---

## AWS emulator

### REST surface area

The AWS emulator covers three services: **S3**, **SQS**, and **IAM**. All three use the same in-process store.

#### S3

Routes follow the AWS S3 REST API shape (path-style). The module handles:

- `GET /` — list all buckets
- `PUT /:bucket` — create bucket
- `DELETE /:bucket` — delete bucket
- `GET /:bucket` — list objects in bucket
- `PUT /:bucket/:key` — upload object
- `GET /:bucket/:key` — download object
- `DELETE /:bucket/:key` — delete object
- `HEAD /:bucket/:key` — object metadata

Responses are XML, matching AWS's actual wire format. Object content is stored in-memory as `Buffer`.

Sources: [packages/@emulators/aws/src/routes/s3.ts:13-61](packages/@emulators/aws/src/routes/s3.ts)

#### SQS

SQS uses the AWS query-protocol format: `POST /sqs/` with an `Action` parameter. Supported actions:

| Action | Description |
|--------|-------------|
| `CreateQueue` | Create a new queue (FIFO-aware) |
| `DeleteQueue` | Delete queue and its messages |
| `ListQueues` | List all queues, optional prefix filter |
| `GetQueueUrl` | Resolve queue name to URL |
| `GetQueueAttributes` | Return queue metadata |
| `SendMessage` | Enqueue a message, returns MD5 and MessageId |
| `ReceiveMessage` | Dequeue up to N messages with visibility timeout |
| `DeleteMessage` | Acknowledge and remove by receipt handle |
| `PurgeQueue` | Delete all messages |

Sources: [packages/@emulators/aws/src/routes/sqs.ts:21-47](packages/@emulators/aws/src/routes/sqs.ts)

#### IAM

The IAM module emulates user and role management for tests that need to create access keys or assume roles without hitting real AWS:

- User CRUD with embedded access key list
- Role CRUD with `AssumeRolePolicyDocument`

Sources: [packages/@emulators/aws/src/index.ts:12-13](packages/@emulators/aws/src/index.ts)

### Seed config

`AwsSeedConfig` covers region, account ID override, S3 bucket list, SQS queue list (with FIFO support), IAM users (optional `create_access_key`), and IAM roles. The seed defaults insert one bucket (`emulate-default`), one queue (`emulate-default-queue`), and one IAM user (`admin`) with well-known example credentials.

Sources: [packages/@emulators/aws/src/index.ts:13-88](packages/@emulators/aws/src/index.ts)

---

## Stripe emulator

### REST surface area

The Stripe emulator speaks the Stripe API's `application/x-www-form-urlencoded` body format (`parseStripeBody`) and returns JSON shaped exactly like the Stripe SDK expects.

| Module | Key endpoints |
|--------|--------------|
| `customerRoutes` | `POST/GET/POST/DELETE /v1/customers`, `GET /v1/customers/:id` |
| `paymentMethodRoutes` | Create, attach, detach, list payment methods |
| `paymentIntentRoutes` | Create, confirm, capture, cancel payment intents |
| `chargeRoutes` | List and retrieve charges |
| `productRoutes` | Product CRUD |
| `priceRoutes` | Price CRUD (recurring and one-time) |
| `checkoutSessionRoutes` | Create sessions, host checkout page, expire |
| `customerSessionRoutes` | Customer portal sessions |

Sources: [packages/@emulators/stripe/src/index.ts:5-13](packages/@emulators/stripe/src/index.ts)

### Webhooks

The Stripe emulator fires events through `WebhookDispatcher` on mutations. For example, creating a PaymentIntent immediately dispatches a `payment_intent.created` event:

```ts
// packages/@emulators/stripe/src/routes/payment-intents.ts:54-59
await webhooks.dispatch(
  "payment_intent.created",
  undefined,
  { type: "payment_intent.created", data: { object: formatPaymentIntent(pi) } },
  "stripe",
);
```

Similarly, when the emulator's hosted checkout page is submitted, it dispatches `checkout.session.completed` with the full session object.

Webhook endpoints can be registered in `StripeSeedConfig.webhooks` — each entry specifies a `url`, `events` array, and optional `secret`. Registration calls `webhooks.register(...)` on the shared dispatcher.

Sources: [packages/@emulators/stripe/src/routes/payment-intents.ts:54-60](packages/@emulators/stripe/src/routes/payment-intents.ts), [packages/@emulators/stripe/src/index.ts:104-114](packages/@emulators/stripe/src/index.ts)

### Checkout page

`checkoutSessionRoutes` also serves an HTML checkout page at `GET /checkout/:sessionId`. This is a rendered HTML form (using `renderCheckoutPage` from `@emulators/core`) that shows line items and a "Pay and Complete" button — enough to drive end-to-end browser tests without a real Stripe.js integration.

Sources: [packages/@emulators/stripe/src/routes/checkout-sessions.ts:1-25](packages/@emulators/stripe/src/routes/checkout-sessions.ts)

---

## Resend emulator

### REST surface area

The Resend emulator covers the email sending, domain management, audience, and contact surfaces:

| Module | Key endpoints |
|--------|--------------|
| `emailRoutes` | `POST /emails` (single), `POST /emails/batch` (up to 100), `GET /emails/:id`, `PATCH /emails/:id` (reschedule), `POST /emails/:id/cancel` |
| `domainRoutes` | Domain CRUD with pre-populated DNS record stubs |
| `apiKeyRoutes` | API key CRUD |
| `contactRoutes` | Contact CRUD per audience |
| `inboxRoutes` | `GET /inbox`, `GET /inbox/:id` — dev UI to view sent emails |

Sources: [packages/@emulators/resend/src/index.ts:5-9](packages/@emulators/resend/src/index.ts)

### Webhook dispatch

Sending an email fires two events through the shared `WebhookDispatcher`: `email.sent` and `email.delivered`. Scheduled emails (`scheduled_at` parameter) skip these events until they are explicitly triggered. Batch sends validate all emails before inserting any, so a validation error in item 3 of a batch leaves the store untouched.

```ts
// packages/@emulators/resend/src/routes/emails.ts:62-75
if (!scheduledAt) {
  await webhooks.dispatch("email.sent", undefined,
    { type: "email.sent", data: { email_id: uuid, to: toArray, from, subject } }, "resend");
  await webhooks.dispatch("email.delivered", undefined,
    { type: "email.delivered", data: { email_id: uuid, ... } }, "resend");
}
```

Sources: [packages/@emulators/resend/src/routes/emails.ts:62-75](packages/@emulators/resend/src/routes/emails.ts)

### Inbox UI

`GET /inbox` returns an HTML page listing all sent emails with their status (`delivered`, `bounced`, `scheduled`), sender, and recipients. `GET /inbox/:id` shows the full email including HTML body — a quick way to verify that your transactional email template rendered correctly without a real mail server.

Sources: [packages/@emulators/resend/src/routes/inbox.ts:11-47](packages/@emulators/resend/src/routes/inbox.ts)

---

## MongoDB Atlas emulator

### REST surface area

The Atlas emulator splits into two distinct API surfaces:

**Admin API** (`adminRoutes`) — mirrors the Atlas Administration API v2 at `/api/atlas/v2/...`:

| Resource | Operations |
|----------|------------|
| Projects (`/groups`) | CRUD, cascade-deletes clusters on project delete |
| Clusters | CRUD with provider settings, connection string generation |
| Database users | Create, list, delete per project |

**Data API** (`dataApiRoutes`) — mirrors the Atlas Data API v1 at `/app/data-api/v1/action/...`:

| Action | Description |
|--------|-------------|
| `findOne` | Find first document matching a filter |
| `find` | Find documents with filter, sort, skip, limit |
| `insertOne` | Insert a document with auto-generated `_id` |
| `insertMany` | Batch insert |
| `updateOne` / `updateMany` | MongoDB `$set`-style update |
| `deleteOne` / `deleteMany` | Delete by filter |
| `aggregate` | Basic aggregation pipeline (subset of operators) |

Sources: [packages/@emulators/mongoatlas/src/index.ts:1-7](packages/@emulators/mongoatlas/src/index.ts), [packages/@emulators/mongoatlas/src/routes/admin.ts:1-79](packages/@emulators/mongoatlas/src/routes/admin.ts), [packages/@emulators/mongoatlas/src/routes/data-api.ts:10-44](packages/@emulators/mongoatlas/src/routes/data-api.ts)

Documents are stored in a flat in-memory table keyed by `(cluster_id, database, collection)`. The Data API routes parse a `filter` field using an in-process `matchFilter` helper that supports basic equality and `$in`/`$gt`/`$lt` operators.

---

## Shared webhook dispatch

All seven emulators share one `WebhookDispatcher` instance from `@emulators/core`. The dispatcher maintains an in-memory subscription list and a delivery log (capped at 1000 entries):

```ts
// packages/@emulators/core/src/webhooks.ts:27-30
export class WebhookDispatcher {
  private subscriptions: WebhookSubscription[] = [];
  private deliveries: WebhookDelivery[] = [];
  ...
}
```

When an emulator calls `webhooks.dispatch(event, action, payload, owner, repo?)`, the dispatcher:

1. Filters subscriptions by `owner`, `repo`, and `events` list.
2. Signs the JSON body with HMAC-SHA256 if the subscription has a `secret`, adding `X-Hub-Signature-256`.
3. POSTs to the subscription URL using `fetch` with a 10-second `AbortSignal` timeout.
4. Records a `WebhookDelivery` entry (status code, duration, success flag) regardless of outcome — failures are logged, not re-tried.

Sources: [packages/@emulators/core/src/webhooks.ts:73-138](packages/@emulators/core/src/webhooks.ts)

```mermaid
sequenceDiagram
  participant App as Your app code
  participant Emulator as Emulator route
  participant Dispatcher as WebhookDispatcher
  participant Handler as Your webhook handler

  App->>Emulator: POST /v1/payment_intents
  Emulator->>Dispatcher: dispatch("payment_intent.created", payload)
  Dispatcher->>Handler: POST /api/webhooks/stripe (signed body)
  Handler-->>Dispatcher: 200 OK
  Dispatcher-->>Emulator: delivery recorded
  Emulator-->>App: { id: "pi_...", status: "requires_payment_method" }
```

---

## Examples

### stripe-checkout

The `examples/stripe-checkout` Next.js app demonstrates the full Stripe Checkout flow end-to-end:

1. A product catalog page fetches products and prices via the official `stripe` Node SDK, pointed at `localhost` with `protocol: http`.
2. A server action calls `POST /v1/checkout/sessions` to create a session.
3. The browser redirects to the emulator's hosted checkout page (`GET /checkout/:id`).
4. The user clicks "Pay and Complete"; the emulator marks the session as paid and fires `checkout.session.completed` to `POST /api/webhooks/stripe` in the same Next.js app.
5. The webhook handler records the order; the user is redirected to a success page.

A thin proxy route at `/v1/[...path]` rewrites Stripe SDK calls from the default Stripe API domain to the embedded emulator. The emulator is mounted at `/emulate/stripe/` via `@emulators/adapter-next`.

Sources: [examples/stripe-checkout/README.md:1-73](examples/stripe-checkout/README.md)

### oauth example

`examples/oauth` shows multi-provider OAuth sign-in (GitHub, Google, Vercel) against separate emulator processes:

```ts
// examples/oauth/src/lib/providers.ts:19-21
const GITHUB_URL = process.env.GITHUB_EMULATOR_URL ?? "http://localhost:4001";
const VERCEL_URL = process.env.VERCEL_EMULATOR_URL ?? "http://localhost:4000";
const GOOGLE_URL = process.env.GOOGLE_EMULATOR_URL ?? "http://localhost:4002";
```

The app redirects to the emulator's authorize endpoint, which shows a user-picker UI. After selection, the emulator issues a code, and the callback route exchanges it for a token via the emulator's token endpoint — identical to real OAuth from the application's perspective.

Sources: [examples/oauth/src/lib/providers.ts:19-51](examples/oauth/src/lib/providers.ts), [examples/oauth/README.md:26-34](examples/oauth/README.md)

### nextjs-embedded

`examples/nextjs-embedded` solves a specific Vercel preview deployment problem: OAuth callback URLs change with every deployment, so a fixed external emulator URL won't work. The solution embeds the emulators inside the Next.js app at `/emulate/github/**` and `/emulate/google/**`. Because the emulators run on the same origin as the app, callback URLs are always valid regardless of which preview URL Vercel assigned. The trade-off: client credentials can be set to `"any"` since validation is bypassed in embedded mode.

Sources: [examples/nextjs-embedded/README.md:1-58](examples/nextjs-embedded/README.md)

---

## Summary

The seven platform emulators in `emulate` each implement a meaningful slice of a real service's REST API — from GitHub's full issues/PRs/Actions surface to Stripe's checkout flow to MongoDB Atlas's Data API. They all share one `WebhookDispatcher`, so mutations trigger in-process webhook deliveries signed with HMAC-SHA256, matching what a production deployment would receive. The `examples/` directory shows three concrete wiring patterns: a standalone emulator process (oauth), a Next.js embedded adapter (nextjs-embedded), and a full checkout flow with webhook roundtrip (stripe-checkout). Each emulator exposes a typed `SeedConfig` interface, which is the primary entry point for declaring what state the emulator starts with before tests run.

Sources: [packages/@emulators/core/src/webhooks.ts:27-153](packages/@emulators/core/src/webhooks.ts)

---

## 08. The One Map to Keep — Core Ideas Recapped

> A single-page recap: the core idea in one sentence, the three moving parts every developer must know (Store, ServicePlugin, registry entry), the analogy that holds, important caveats about what emulate is NOT, and where to go next.

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-emulate-ddc6091d171d/pages/08-the-one-map-to-keep-core-ideas-recapped.md
- Generated: 2026-05-24T18:15:38.494Z

### Source Files

- `README.md`
- `packages/emulate/src/api.ts`
- `packages/@emulators/core/src/plugin.ts`
- `packages/emulate/src/registry.ts`

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

- [README.md](README.md)
- [packages/emulate/src/api.ts](packages/emulate/src/api.ts)
- [packages/emulate/src/registry.ts](packages/emulate/src/registry.ts)
- [packages/@emulators/core/src/plugin.ts](packages/@emulators/core/src/plugin.ts)
- [packages/@emulators/core/src/store.ts](packages/@emulators/core/src/store.ts)
- [packages/@emulators/core/src/server.ts](packages/@emulators/core/src/server.ts)
</details>

# The One Map to Keep — Core Ideas Recapped

This page is the cheat sheet you keep open when everything else is closed. It answers four questions at once: what emulate *actually does* in one sentence, which three moving parts every developer must understand to contribute or consume it, what analogy holds up under scrutiny, and what emulate explicitly is *not*. Once you hold this map, the rest of the codebase slots into place.

Read this page first. Read the other wiki pages when you need depth on a specific service or integration pattern.

---

## The Core Idea in One Sentence

**emulate runs fully-stateful, production-fidelity HTTP servers on your laptop that impersonate real third-party APIs — so your tests, CI jobs, and preview deployments never touch the real internet.**

That is the whole idea. Every architectural choice in the repository exists in service of that one sentence.

Sources: [README.md:1-3]()

---

## The Three Moving Parts

Everything in the codebase is an expression of three abstractions. Learn these three; derive everything else.

### 1. `Store` — the shared in-memory database

`Store` is a simple, typed key-value database that lives entirely in process memory. It has no external dependency.

Each service plugin creates typed sub-stores by calling `store.collection<T>(name, indexFields)`. A collection is an in-memory table: it auto-assigns IDs, maintains field-level indexes for fast lookup, and supports CRUD plus paginated queries.

```typescript
// packages/@emulators/core/src/store.ts:221-241
export class Store {
  private collections = new Map<string, Collection<any>>();
  private _data = new Map<string, unknown>();

  collection<T extends Entity>(name: string, indexFields: (keyof T)[] = []): Collection<T> {
    // returns existing collection or creates new one
  }
}
```

The `Store` also exposes `snapshot()` and `restore()` for the optional persistence layer, and a `reset()` that wipes all collections so test suites can start fresh between runs.

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

---

### 2. `ServicePlugin` — the contract every service implements

A `ServicePlugin` is a plain TypeScript interface with three fields:

```typescript
// packages/@emulators/core/src/plugin.ts:14-18
export interface ServicePlugin {
  name: string;
  register(app: Hono<AppEnv>, store: Store, webhooks: WebhookDispatcher, baseUrl: string, tokenMap?: TokenMap): void;
  seed?(store: Store, baseUrl: string): void;
}
```

- `name` — identifies the service (used in error messages and docs URLs).
- `register` — the method where the service mounts all its HTTP routes onto the shared Hono app. This is the only place routes exist; there is no route auto-discovery or convention magic.
- `seed` (optional) — populates the store with default data before any request arrives (for example, a default branch or a built-in OAuth scope list).

Every concrete emulator — `githubPlugin`, `vercelPlugin`, `slackPlugin`, etc. — satisfies exactly this interface. The core knows nothing about GitHub or Slack; it only knows `ServicePlugin`.

Sources: [packages/@emulators/core/src/plugin.ts:14-18]()

---

### 3. `ServiceEntry` (registry entry) — the lazy loader and metadata record

The registry maps service names to `ServiceEntry` objects. An entry is not the plugin itself; it is a descriptor that knows *how to load* the plugin package on demand, plus the metadata needed before loading happens.

```typescript
// packages/emulate/src/registry.ts:9-15
export interface ServiceEntry {
  label: string;
  endpoints: string;
  load(): Promise<LoadedService>;
  defaultFallback(svcSeedConfig?: Record<string, unknown>): AuthFallback;
  initConfig: Record<string, unknown>;
}
```

The `load()` method is a dynamic `import()`. The GitHub entry, for example, does:

```typescript
// packages/emulate/src/registry.ts:69-78
async load() {
  const mod = await import("@emulators/github");
  return {
    plugin: mod.githubPlugin,
    seedFromConfig: mod.seedFromConfig,
    createAppKeyResolver(store: Store): AppKeyResolver { ... },
  };
},
```

This means service packages (`@emulators/github`, `@emulators/slack`, …) are tree-shaken and only loaded if the user requests them. If you run `npx emulate --service github`, the AWS package is never loaded.

Sources: [packages/emulate/src/registry.ts:9-15](), [packages/emulate/src/registry.ts:63-85]()

---

## How the Three Parts Fit Together

When `createEmulator({ service: 'github', port: 4001 })` is called:

```
┌─────────────────────────────────────────────────────────────────┐
│  createEmulator (api.ts)                                        │
│                                                                 │
│  1. Look up SERVICE_REGISTRY['github']  →  ServiceEntry        │
│  2. entry.load()                        →  ServicePlugin + seed │
│  3. createServer(plugin, options)       →  Hono app + Store     │
│  4. plugin.register(app, store, …)      →  HTTP routes mounted  │
│  5. plugin.seed(store, baseUrl)         →  default data seeded  │
│  6. seedFromConfig(store, …, config)    →  user seed data added │
│  7. serve({ fetch: app.fetch, port })   →  HTTP server starts   │
│                                                                 │
│  Returns: { url, reset(), close() }                             │
└─────────────────────────────────────────────────────────────────┘
```

`createServer` builds the shared Hono application, attaches auth middleware, rate-limit middleware, and CORS, then hands the `store` and `webhooks` dispatcher to `plugin.register`. The plugin owns what routes exist; the core owns how requests are authenticated and errors are formatted.

Sources: [packages/emulate/src/api.ts:25-86](), [packages/@emulators/core/src/server.ts:24-104]()

---

## The Analogy That Holds

Think of emulate as a **flight simulator for HTTP integrations**.

| Flight simulator | emulate |
|-----------------|---------|
| Simulates cockpit physics locally | Simulates API wire protocol locally |
| Doesn't connect to real aircraft systems | Doesn't connect to GitHub / Stripe / etc. |
| State is in memory; reset between sessions | `store.reset()` wipes state between tests |
| Instruments respond faithfully to inputs | Full CRUD: creates/updates persist and affect related entities |
| Can be seeded with a scenario (weather, flight plan) | Seed config pre-populates users, repos, tokens |
| You control time and conditions | You control auth, scopes, pagination, webhook delivery |

The analogy holds because both systems reproduce the *behavior* of the real thing without touching it. Seed data is the scenario; the `Store` is the simulated world state; the plugin routes are the instruments.

---

## What emulate Is NOT

These are explicit non-goals, grounded in what the codebase does and does not contain.

**Not a mock library.** There is no `jest.mock()` or stubbing layer. emulate is a real HTTP server. Your code talks to `http://localhost:4001` exactly as it would talk to `https://api.github.com`. No monkey-patching, no import interception.

**Not a record-and-replay proxy.** emulate does not record real API traffic and replay it. All responses are synthesized from the in-memory `Store`. This means you get consistent, deterministic behavior but also means edge cases specific to a real API's current data are not captured.

**Not a contract test tool.** emulate does not verify that its responses match the real API's current schema. It is maintained separately. If GitHub deprecates a field, emulate will not automatically detect the drift.

**Not persistent by default.** The `Store` is in-memory. Every restart (or `reset()` call) wipes it. Persistence across restarts requires an explicit adapter passed at construction time.

**Not a complete API mirror.** The README documents specific gaps — for example, Slack Connect, Enterprise Grid admin APIs, and Socket Mode are not implemented. Assume "production-fidelity" means the common request paths, not every edge case.

Sources: [README.md:1-3](), [README.md:752-754](), [packages/@emulators/core/src/store.ts:251-256]()

---

## A Quick Class Model

```text
@emulators/core
┌─────────────────────────────────────────────────────────┐
│ ServicePlugin (interface)                               │
│   name: string                                          │
│   register(app, store, webhooks, baseUrl, tokenMap)     │
│   seed?(store, baseUrl)                                 │
└────────────────────┬────────────────────────────────────┘
                     │ implements
       ┌─────────────┼───────────────┐
  githubPlugin  vercelPlugin  slackPlugin  …

@emulators/core
┌─────────────────────────────────────────────────────────┐
│ Store                                                   │
│   collection<T>(name, indexFields) → Collection<T>      │
│   reset()                                               │
│   snapshot() / restore()                               │
│                                                         │
│   Collection<T>                                         │
│     insert / get / findBy / update / delete / query     │
└─────────────────────────────────────────────────────────┘

emulate (CLI package)
┌─────────────────────────────────────────────────────────┐
│ ServiceEntry (registry entry)                           │
│   label, endpoints                                      │
│   load() → Promise<LoadedService>    (dynamic import)   │
│   defaultFallback(cfg) → AuthFallback                   │
│   initConfig                                            │
└─────────────────────────────────────────────────────────┘
```

Sources: [packages/@emulators/core/src/plugin.ts:14-18](), [packages/@emulators/core/src/store.ts:63-219](), [packages/emulate/src/registry.ts:9-15]()

---

## Where to Go Next

| You want to… | Go to… |
|---|---|
| Add a new service | Implement `ServicePlugin`, add a `ServiceEntry` to the registry |
| Understand a specific service's routes | Read `packages/@emulators/<service>/src/` |
| Understand state persistence | Read `packages/@emulators/core/src/persistence.ts` |
| Embed in a Next.js app | Read `packages/@emulators/adapter-next/` and the README Next.js section |
| Add seed data | Pass a `seed` object to `createEmulator` or use the YAML config format |
| Reset between tests | Call `emulator.reset()` in `afterEach` |

---

The whole system is smaller than it looks: one `Store`, one `ServicePlugin` interface, and one registry entry per service. Everything else — OAuth flows, webhook delivery, rate limiting, pagination — is built on top of those three primitives. Hold that map and the rest is detail.

Sources: [packages/@emulators/core/src/server.ts:92](), [packages/emulate/src/api.ts:58]()

---
