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 +274 -0
- package/dist/api.d.ts +26 -0
- package/dist/api.js +561 -0
- package/dist/api.js.map +1 -0
- package/dist/fonts/GeistPixel-Square.woff2 +0 -0
- package/dist/fonts/favicon.ico +0 -0
- package/dist/fonts/geist-sans.woff2 +0 -0
- package/dist/index.js +887 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -0
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 };
|