api-emulator 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,274 @@
1
+ <p align="center">
2
+ <img src="./.README/cover.png" alt="api-emulator" width="1024" />
3
+ </p>
4
+
5
+ <h1 align="center">api-emulator</h1>
6
+
7
+ <p align="center">
8
+ Local API emulators you can run, share, and extend with plugins
9
+ </p>
10
+
11
+ api-emulator runs local, stateful API emulators for development, CI, and no-network sandboxes. The core package is a runtime plus a default plugin catalog; teams can load public, shared, or private provider-shaped plugins without forking the runtime.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install api-emulator
17
+ ```
18
+
19
+ For one-off CLI usage:
20
+
21
+ ```bash
22
+ npx api-emulator
23
+ ```
24
+
25
+ No API keys, Docker daemon, or external sandbox accounts are required for the default plugin catalog.
26
+
27
+ ## CLI
28
+
29
+ ```bash
30
+ # Start the default plugin catalog
31
+ npx api-emulator
32
+
33
+ # Start a subset
34
+ npx api-emulator --service github,stripe,resend
35
+
36
+ # Start on trusted local HTTPS names
37
+ npx api-emulator --portless
38
+
39
+ # Load a seed file
40
+ npx api-emulator --seed api-emulator.config.yaml
41
+
42
+ # Generate a starter config
43
+ npx api-emulator init
44
+
45
+ # List default and external plugins
46
+ npx api-emulator list
47
+ ```
48
+
49
+ With `--portless`, each service gets a named local HTTPS URL:
50
+
51
+ ```text
52
+ GitHub https://github.api-emulator.localhost
53
+ Stripe https://stripe.api-emulator.localhost
54
+ Resend https://resend.api-emulator.localhost
55
+ AWS https://aws.api-emulator.localhost
56
+ ```
57
+
58
+ The CLI reads `API_EMULATOR_PORT`, `API_EMULATOR_BASE_URL`, and `PORTLESS_URL`. It also accepts `EMULATE_PORT`, `EMULATE_BASE_URL`, and `emulate.config.*` names as migration compatibility aliases.
59
+
60
+ ## Canonical API
61
+
62
+ Prefer the programmatic runtime when writing tests:
63
+
64
+ ```ts
65
+ import { createEmulator } from 'api-emulator'
66
+
67
+ const github = await createEmulator({ service: 'github', port: 4001 })
68
+
69
+ process.env.GITHUB_API_BASE = github.url
70
+
71
+ afterEach(() => github.reset())
72
+ afterAll(() => github.close())
73
+ ```
74
+
75
+ Each emulator instance exposes:
76
+
77
+ - `url` for the advertised base URL
78
+ - `reset()` to wipe state and replay seed data
79
+ - `close()` to stop the local server
80
+
81
+ ## Plugins
82
+
83
+ api-emulator is designed around a small runtime spine and provider-shaped plugins. The default providers are loaded from `packages/emulate/src/default-plugin-catalog.ts`; external plugin modules are normalized by `packages/emulate/src/external-plugin-adapter.ts`.
84
+
85
+ Public external plugins live at [`jsj/api-emulator-plugins`](https://github.com/jsj/api-emulator-plugins). Private teams can keep the same shape in internal repos.
86
+
87
+ ```bash
88
+ npx api-emulator --plugin ./api-emulator-plugins/@posthog/api-emulator.mjs --service posthog
89
+ npx api-emulator --plugin ./api-emulator-plugins/@github/api-emulator.mjs,./api-emulator-plugins/@apple/api-emulator.mjs
90
+ ```
91
+
92
+ A plugin exports a `ServicePlugin`:
93
+
94
+ ```ts
95
+ import type { ServicePlugin } from '@emulators/core'
96
+
97
+ export const plugin: ServicePlugin = {
98
+ name: 'internal-billing',
99
+ register(app, store, webhooks, baseUrl) {
100
+ app.get('/v1/customers', (c) => c.json({ data: [] }))
101
+ },
102
+ }
103
+ ```
104
+
105
+ Plugins can also export `label`, `endpoints`, `initConfig`, `seedFromConfig`, and `defaultFallback`. The runtime keeps plugin lifecycle in one service-runtime module, so CLI and programmatic usage share the same seed, reset, token, and close behavior.
106
+
107
+ ## Configuration
108
+
109
+ `api-emulator init` creates `api-emulator.config.yaml`.
110
+
111
+ ```yaml
112
+ tokens:
113
+ test_token_admin:
114
+ login: admin
115
+ scopes: [repo, user]
116
+
117
+ github:
118
+ users:
119
+ - login: octocat
120
+ name: The Octocat
121
+ email: octocat@github.com
122
+ repos:
123
+ - owner: octocat
124
+ name: hello-world
125
+ auto_init: true
126
+
127
+ stripe:
128
+ products:
129
+ - id: prod_tshirt
130
+ name: T-shirt
131
+ prices:
132
+ - id: price_tshirt
133
+ product: prod_tshirt
134
+ unit_amount: 2500
135
+ currency: usd
136
+
137
+ resend:
138
+ apiKeys:
139
+ - re_test_key
140
+ ```
141
+
142
+ The CLI auto-detects:
143
+
144
+ - `api-emulator.config.yaml`
145
+ - `api-emulator.config.yml`
146
+ - `api-emulator.config.json`
147
+ - `emulate.config.yaml`
148
+ - `emulate.config.yml`
149
+ - `emulate.config.json`
150
+ - `service-emulator.config.yaml`
151
+ - `service-emulator.config.yml`
152
+ - `service-emulator.config.json`
153
+
154
+ ## Default plugin catalog
155
+
156
+ api-emulator ships with a default plugin catalog so first run is useful, but these providers are catalog entries rather than hard-coded runtime behavior:
157
+
158
+ - Vercel API
159
+ - GitHub REST, OAuth, Apps, Actions, checks, statuses, and webhooks
160
+ - Google OAuth, OIDC, Gmail, Calendar, and Drive
161
+ - Slack Web API, OAuth, channels, messages, users, and webhooks
162
+ - Apple Sign in with Apple and OIDC
163
+ - Microsoft Entra ID OAuth and OIDC
164
+ - Okta OAuth and OIDC
165
+ - Clerk auth surfaces
166
+ - AWS S3, SQS, IAM, and STS
167
+ - MongoDB Atlas Admin and Data API
168
+ - Resend email API with local inbox
169
+ - Stripe Checkout, customers, products, prices, payment intents, and webhooks
170
+
171
+ ## Next.js embedded mode
172
+
173
+ Use `@emulators/adapter-next` to mount emulators inside a Next.js app on the same origin.
174
+
175
+ ```ts
176
+ // app/emulate/[...path]/route.ts
177
+ import { createEmulateHandler } from '@emulators/adapter-next'
178
+ import { githubPlugin } from '@emulators/github'
179
+ import { stripePlugin } from '@emulators/stripe'
180
+
181
+ export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({
182
+ plugins: [githubPlugin, stripePlugin],
183
+ })
184
+ ```
185
+
186
+ This gives local routes like:
187
+
188
+ ```text
189
+ /emulate/github/login/oauth/authorize
190
+ /emulate/stripe/v1/checkout/sessions
191
+ ```
192
+
193
+ ## Production guidance
194
+
195
+ - Treat emulators as test infrastructure, not production dependencies.
196
+ - Keep real provider credentials out of seed files.
197
+ - Use fake tokens, fake webhook secrets, and local-only API keys.
198
+ - Prefer provider-shaped resource modules over app-specific happy-path stubs.
199
+ - Keep route handlers thin. Put depth in reusable Modules, Interfaces, and adapters.
200
+ - Keep private team plugins outside the public runtime until the interface proves stable.
201
+ - Use inspect and control endpoints for deterministic tests instead of sleeping or polling real systems.
202
+
203
+ ## Development
204
+
205
+ This monorepo uses Bun.
206
+
207
+ ```bash
208
+ bun install
209
+ bun run build
210
+ bun run format:check
211
+ bun run type-check
212
+ bun run lint
213
+ bun run test
214
+ ```
215
+
216
+ Release publishing also uses `bun pm pack` before `npm publish` so workspace dependencies resolve in package tarballs.
217
+
218
+ ## Examples
219
+ These examples use the default plugin catalog. For examples of external provider plugins you can load with `--plugin`, see [jsj/api-emulator-plugins](https://github.com/jsj/api-emulator-plugins).
220
+
221
+ See:
222
+
223
+ - [`examples/oauth`](./examples/oauth)
224
+ - [`examples/nextjs-embedded`](./examples/nextjs-embedded)
225
+ - [`examples/resend-magic-link`](./examples/resend-magic-link)
226
+ - [`examples/stripe-checkout`](./examples/stripe-checkout)
227
+
228
+ ## Features
229
+
230
+ - Stateful local provider APIs for development and CI
231
+ - One CLI that can start many providers at predictable ports
232
+ - Trusted local HTTPS names through portless
233
+ - External plugin loading from files or package names through a dedicated adapter seam
234
+ - YAML and JSON seed data
235
+ - Programmatic test API with reset and close hooks
236
+ - Shared core store, auth, persistence, UI, webhooks, and inspect pages
237
+ - Next.js embedded mode for same-origin OAuth and SDK flows
238
+ - Public, shared, default-catalog, and private plugin workflows
239
+
240
+ ## FAQ
241
+
242
+ <details>
243
+ <summary>Is this a fork of emulate?</summary>
244
+
245
+ Yes. api-emulator is a hard fork with a new identity and a stronger focus on the plugin-spine model: a small runtime plus many provider-shaped plugins.
246
+ </details>
247
+
248
+ <details>
249
+ <summary>Why not just mock fetch calls?</summary>
250
+
251
+ SDKs, OAuth redirects, webhooks, pagination, retries, XML wire formats, and provider state usually live below your app code. api-emulator runs those protocol surfaces locally so tests exercise the integration seam instead of a shallow stub.
252
+ </details>
253
+
254
+ <details>
255
+ <summary>Can teams keep private emulators?</summary>
256
+
257
+ Yes. Private and internal plugins are a first-class workflow. Load them with `--plugin`, keep them in a separate repo, and share only the runtime spine publicly.
258
+ </details>
259
+
260
+ <details>
261
+ <summary>Does api-emulator still support old emulate config names?</summary>
262
+
263
+ Yes. `emulate.config.*`, `EMULATE_PORT`, and `EMULATE_BASE_URL` still work as migration compatibility aliases.
264
+ </details>
265
+
266
+ <details>
267
+ <summary>What should a good plugin emulate?</summary>
268
+
269
+ Provider concepts, not one app's current happy path. Model resources, state transitions, errors, inspect surfaces, and control hooks so multiple products can test against the same plugin.
270
+ </details>
271
+
272
+ ## License
273
+
274
+ MIT
package/dist/api.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ declare const DEFAULT_PLUGIN_NAME_LIST: readonly ["vercel", "github", "google", "slack", "apple", "microsoft", "okta", "aws", "resend", "stripe", "mongoatlas", "clerk"];
2
+ type ServiceName = (typeof DEFAULT_PLUGIN_NAME_LIST)[number];
3
+
4
+ interface SeedConfig {
5
+ tokens?: Record<string, {
6
+ login: string;
7
+ scopes?: string[];
8
+ }>;
9
+ [service: string]: unknown;
10
+ }
11
+
12
+ interface EmulatorOptions {
13
+ service: ServiceName | (string & {});
14
+ port?: number;
15
+ seed?: SeedConfig;
16
+ baseUrl?: string;
17
+ plugins?: string[];
18
+ }
19
+ interface Emulator {
20
+ url: string;
21
+ reset(): void;
22
+ close(): Promise<void>;
23
+ }
24
+ declare function createEmulator(options: EmulatorOptions): Promise<Emulator>;
25
+
26
+ export { type Emulator, type EmulatorOptions, type SeedConfig, type ServiceName, createEmulator };