api-tracer-kit 1.0.0 → 1.1.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.
@@ -0,0 +1,428 @@
1
+ # The console
2
+
3
+ A separate process that maps every endpoint in your source, receives your app's
4
+ live traffic, and lets you fire any endpoint and read the response. It needs no
5
+ framework, no build step and — in most projects — no configuration.
6
+
7
+ ```bash
8
+ npx api-tracer start
9
+ ```
10
+
11
+ Reads your source, writes a catalog, and opens at `http://127.0.0.1:4400`.
12
+
13
+ ## Commands
14
+
15
+ | | |
16
+ | --- | --- |
17
+ | `api-tracer scan` | read the source, write the endpoint catalog |
18
+ | `api-tracer serve` | run the console |
19
+ | `api-tracer start` | scan, then serve |
20
+ | `api-tracer report` | build the insight report |
21
+ | `api-tracer init` | write a config file from what the scan guessed |
22
+
23
+ ### Options
24
+
25
+ | | |
26
+ | --- | --- |
27
+ | `--root <dir>` | the project to read (default: cwd) |
28
+ | `--data <dir>` | where captures are kept (default: `<root>/.api-tracer`) |
29
+ | `--port <n>` | default `4400` |
30
+ | `--host <addr>` | default `127.0.0.1` |
31
+ | `--preset <name>` | force a scanner preset |
32
+ | `--format md\|json` | report format (default `md`) |
33
+ | `--out <file>` | write the report to a file (default: stdout) |
34
+ | `--env <name>` | which base URL the report describes |
35
+ | `--force` | let `init` overwrite an existing config |
36
+
37
+ ## The catalog
38
+
39
+ `scan` reads your source into `<data>/endpoints.json`: one entry per endpoint,
40
+ with its module, HTTP verb, path template, the `${...}` holes in it, whether it
41
+ sends query params or a body, its auth quirk, and the file and line it came
42
+ from.
43
+
44
+ It is a plain JSON file, so a deployed console needs no source and no build —
45
+ just the file that shipped with it. Re-running `scan` where there is no source
46
+ keeps the catalog that is already there.
47
+
48
+ The scan also counts how many files outside its own module reference each
49
+ endpoint. Anything at zero is flagged **unused in app** and gets its own filter.
50
+
51
+ > This is a substring count, so `import * as services` or a re-export would hide
52
+ > a real usage. Confirm with a grep before deleting anything.
53
+
54
+ See [Configuration](./configuration.md) for how endpoints are found and what to
55
+ do when the presets do not fit your codebase.
56
+
57
+ ## Live recording
58
+
59
+ The main way to use it. Run your app normally with `reportTo` pointed at the
60
+ console, and every call it makes streams in as you click — no export, no import,
61
+ no typing payloads.
62
+
63
+ Each request lands on its endpoint in the tree with the real query params, the
64
+ real payload and the real path ids already filled in. The response is scored
65
+ too, so an endpoint your app just used successfully goes green without you
66
+ running anything.
67
+
68
+ Click **Live** in the header to pause or resume. The counter shows what has
69
+ arrived.
70
+
71
+ A call that no endpoint in the catalog explains is **not** dropped — it is
72
+ adopted as an endpoint of its own, marked *not found in source*, and behaves
73
+ like the rest. Recording only what could be matched would quietly hide real
74
+ traffic. **Clear uncatalogued** removes them.
75
+
76
+ ## Reading pass and fail
77
+
78
+ Some APIs return HTTP 200 for failures and put the real verdict in the envelope:
79
+
80
+ ```json
81
+ { "status": 801, "success": false, "message": "Authentication token header missing" }
82
+ ```
83
+
84
+ So an endpoint counts as **passing** only when the HTTP status is 2xx **and**
85
+ the body's status code is not at or above the failure threshold **and** the
86
+ success flag is not `false`. The response viewer shows the HTTP code and the
87
+ body code side by side.
88
+
89
+ Configure the convention with `envelope` in the config file, or set it to
90
+ `false` if your API uses the status line honestly.
91
+
92
+ ## Using one endpoint
93
+
94
+ Pick it in the tree. The request pane is tabbed like Postman:
95
+
96
+ - **Params** — query params, as JSON, stringified into one param, or as a raw
97
+ query string
98
+ - **Headers** — extra headers, merged on top of the token and `Content-Type` the
99
+ server adds
100
+ - **Body** — the type selector: `none` / `JSON` / `multipart/form-data` /
101
+ `x-www-form-urlencoded`. JSON gives a validated editor; the form types give
102
+ key/value rows. Switching type keeps your values.
103
+ - **Capture** — rules that store response values as `{{variables}}`
104
+
105
+ The body type is preselected from what your app actually sent, so a captured
106
+ `FormData` call opens on **multipart/form-data** with its fields already in the
107
+ table, and **Send** replays it that way. Replaying a form post as JSON would
108
+ fail for reasons that have nothing to do with the endpoint.
109
+
110
+ Also on the pane: the source location (`src/services/appointments.js:91`), the
111
+ resolved URL updating live as you type, a field per path placeholder, and after
112
+ **Send** (or Cmd/Ctrl+Enter) the status, body code, latency, size, response body
113
+ and headers. **Copy as cURL** leaves the token as a placeholder.
114
+
115
+ JSON editors are syntax highlighted with line numbers and Tab indents rather
116
+ than leaving the field — a coloured layer behind a transparent textarea, so
117
+ there is no editor library and no dependency.
118
+
119
+ ### Query params: three modes
120
+
121
+ Switching carries what you typed across.
122
+
123
+ - **JSON** — `{"page": 1, "ids": [3, 4], "filter": {"status": "new"}}`, serialized
124
+ the way axios does by default: `?page=1&ids[]=3&ids[]=4&filter[status]=new`
125
+ - **stringified** — the whole object in one encoded param,
126
+ `?params=%7B%22page%22%3A1%7D`. The param name is editable.
127
+ - **query string** — pasted straight from DevTools and sent byte-for-byte, so an
128
+ already-encoded `%2B` stays `%2B` instead of becoming `%252B`
129
+
130
+ The URL bar shows the fully resolved URL in every mode, built by the same rules
131
+ the server uses, so the preview never disagrees with what goes out.
132
+
133
+ ## Bulk runs and replay
134
+
135
+ **Run every GET endpoint** and **Run all GETs in this module** check many at
136
+ once, six in parallel.
137
+
138
+ Two deliberate limits on bulk GET runs:
139
+
140
+ - **Only GETs are ever run in bulk.** A bulk run that fired nineteen DELETEs
141
+ would be a very bad afternoon.
142
+ - **Path placeholders are left blank**, so `/appointments/${data.id}.json` is
143
+ called as `/appointments/.json`. That proves the endpoint answers; it does not
144
+ prove it behaves correctly with a real id. Treat bulk results as a smoke test.
145
+
146
+ For each placeholder a run takes the first of: the value captured from real
147
+ traffic, a variable sharing the placeholder's name, then blank. The run reports
148
+ how many endpoints went out blank, so a weak result never looks like a strong
149
+ one, and every saved result records the URL that was actually called.
150
+
151
+ ### Replay every captured call
152
+
153
+ Re-sends every endpoint that has a sample — GET and writes alike — with the
154
+ payload, query params, path ids and body type your app used.
155
+
156
+ It shows exactly what it will do before it does anything: a count per method,
157
+ the environment and base URL, and what it is leaving out. Defaults, all
158
+ changeable in that dialog:
159
+
160
+ - **DELETEs excluded.** Opt in with a checkbox; the endpoints are named.
161
+ - **Logout excluded**, since it can invalidate the token the rest depends on.
162
+ - **Endpoints needing a signed token excluded.**
163
+ - **Payloads containing a stripped secret are skipped**, because sending the
164
+ literal `<redacted>` would fail for a reason unrelated to the endpoint.
165
+ - **Runs one at a time**, in order. Captured writes often depend on each other,
166
+ and a failure order you cannot reproduce is worse than a slow run.
167
+ - **Refused entirely on a `prod` environment.**
168
+
169
+ This is not a dry run. Every POST creates a new record each time.
170
+
171
+ While it runs the dialog becomes a progress view: a pass/fail bar, a running
172
+ count, the call in flight, and the last few results. **Stop** halts after the
173
+ call in progress. Results survive navigating away and reloading, because the run
174
+ is held on the server.
175
+
176
+ A call that fails during a replay keeps its response — open that endpoint and
177
+ the response pane shows what came back, labelled *from the last replay*, until
178
+ you send it again yourself. Those endpoints are marked **failed when replayed**.
179
+
180
+ Results are saved and survive a restart, so you can see what changed since
181
+ yesterday. An endpoint that was passing and now fails is marked **broke since
182
+ last run**. Switching environment clears them, since dev results would be
183
+ misleading on stage.
184
+
185
+ ## Contract drift
186
+
187
+ A status check cannot see the failure that matters most on an API that answers
188
+ `200` for everything: a backend that quietly drops a field, or changes an id
189
+ from a number to a string, still looks green.
190
+
191
+ So every passing response is reduced to its **shape** — field paths and types,
192
+ values ignored — and compared with the endpoint's baseline. The first good
193
+ response becomes the baseline; after that a change is reported:
194
+
195
+ ```
196
+ -data[].pinned, +data[].author, data[].id: number->string
197
+ ```
198
+
199
+ Drifted endpoints get an amber dot in the tree, a panel on the endpoint, and
200
+ their own filter. When the change is intended, **This change is expected, make
201
+ it the baseline** promotes the new shape and clears the flag.
202
+
203
+ Live traffic feeds this for free: your app's own calls set and check the
204
+ baseline as you use it.
205
+
206
+ ## Coverage
207
+
208
+ The home screen shows how much of the API your app has actually exercised — how
209
+ many endpoints have ever been seen, which modules remain untouched, and how many
210
+ have drifted. Module rows show `seen/total`.
211
+
212
+ An endpoint nobody has triggered has not been proven either way, which is
213
+ different from failing. The **never captured** filter is the to-do list.
214
+
215
+ ## Variables and chaining
216
+
217
+ Use `{{name}}` in any path value, query param or payload. Set them by hand in
218
+ **Variables**, or capture them from a response: a rule like
219
+ `patient_id = data.0.id` stores that value after a passing call, for every later
220
+ call. Dotted paths walk arrays. A path that finds nothing reports an error
221
+ rather than silently storing a blank.
222
+
223
+ Variables are not a place for tokens — those stay in the console's own token
224
+ store.
225
+
226
+ ## Import
227
+
228
+ For traffic captured earlier, or from someone else's session.
229
+
230
+ 1. Open your app in Chrome, DevTools → **Network**, tick **Preserve log**.
231
+ 2. Click through the screens you care about.
232
+ 3. Right-click the request list → **Save all as HAR with content**.
233
+ 4. Drop the file anywhere on the console page.
234
+
235
+ Each request is matched back to the endpoint the scanner found, by method and
236
+ path shape, so a real call to `POST /appointments/4821/accept` lands on the
237
+ template `/appointments/${data.id}/accept`. From it the console keeps the path
238
+ id, the query params and the request body, which become the endpoint's starting
239
+ values.
240
+
241
+ For a single request, **Copy as cURL** in DevTools and paste it into the import
242
+ dialog.
243
+
244
+ The import reports what it could not place — usually an endpoint the backend has
245
+ but the frontend does not call yet.
246
+
247
+ ## Export
248
+
249
+ Every module page has **Export HAR** and **Export cURL**; the home screen has
250
+ **Export all captured**.
251
+
252
+ | | |
253
+ | --- | --- |
254
+ | **HAR** | a valid HAR 1.2 file with request, query string, payload and recorded response body. Opens in Chrome DevTools, imports into Postman, and re-imports here. |
255
+ | **cURL** | one runnable command per endpoint, labelled with its source location |
256
+ | **Postman** | a Collection v2.1. Modules become folders; each request carries the real query params and body in the right mode, so a captured form post imports as form-data, not broken JSON. Host and token are left as `{{baseUrl}}` and `{{token}}`. |
257
+
258
+ One entry per endpoint, not per call — the most recent capture wins, which keeps
259
+ an export of a busy session readable.
260
+
261
+ All exports carry `<paste your token>` in place of a real token, so an export is
262
+ safe to attach to a ticket.
263
+
264
+ ## The report
265
+
266
+ **Report** in the header builds an insight report from everything the console
267
+ already holds. No extra capture, no new run.
268
+
269
+ | Section | |
270
+ | --- | --- |
271
+ | **Coverage** | exercised vs total, per module, and which modules nobody has walked |
272
+ | **Failing** | status, body code and the message pulled out of the envelope, with regressions and replay failures marked |
273
+ | **Contract drift** | response shapes that changed, with the field-level diff |
274
+ | **Risks** | writes never exercised, captured DELETEs, 2xx responses carrying an error code, payloads whose secrets were stripped |
275
+ | **Hygiene** | endpoints referenced nowhere, duplicate routes, calls not found in the source |
276
+ | **Auth surface** | which endpoints need which token, and which third-party ones deliberately get none |
277
+ | **Latency** | median, p95 and the slowest endpoints |
278
+ | **Inventory** | request params, body fields and response fields per captured endpoint, from real traffic rather than hand-written docs |
279
+ | **Trend** | pass rate and coverage over time, one line appended per run |
280
+
281
+ **Export Markdown** for a ticket, **Export JSON** for CI. Or from the command
282
+ line:
283
+
284
+ ```bash
285
+ npx api-tracer report --format json --out api-report.json
286
+ ```
287
+
288
+ The latency numbers are one measurement per endpoint, from its most recent call
289
+ — indicative, not a benchmark.
290
+
291
+ ## Signing in
292
+
293
+ Click **Set token** and paste one. The token is held by the server, never sent
294
+ back to the page, and written owner-only (`chmod 600`) to the data directory so
295
+ a restart does not sign you out. **Sign out** deletes it.
296
+
297
+ If your API has a multi-step sign-in worth automating — password, then a
298
+ one-time code, then an organisation — describe it in the config file and the
299
+ console will drive it, so nobody has to paste anything. See
300
+ [sign-in flows](./configuration.md#sign-in-flows).
301
+
302
+ Third-party endpoints (anything flagged absolute) never receive your token.
303
+
304
+ ## Why calls go through the server
305
+
306
+ The page posts the composed request to the console and the server forwards it.
307
+ Calling the API from the page directly would be blocked by CORS, and the token
308
+ would have to sit in client-side JavaScript.
309
+
310
+ ## Getting around
311
+
312
+ - **The logo** goes back to the overview from anywhere
313
+ - **Breadcrumbs** at the top of every page, each part clickable
314
+ - **Escape** backs out one level — endpoint, then module, then overview. Ignored
315
+ while you are typing or a dialog is open.
316
+ - Filters: method, and pass/fail/not-run. Search matches function name, path and
317
+ module.
318
+
319
+ Typed path values and payloads are kept in the browser's local storage, so a
320
+ reload does not lose your work.
321
+
322
+ ## Environment variables
323
+
324
+ | | |
325
+ | --- | --- |
326
+ | `API_TRACER_DATA` | where captures are kept (default `<project>/.api-tracer`) |
327
+ | `PORT` / `HOST` | default `4400` / `127.0.0.1` |
328
+ | `BASE_PATH` | serve under a path, e.g. `/api-console`, behind a proxy |
329
+ | `ALLOWED_ORIGINS` | comma-separated origins allowed to post recordings; localhost always works |
330
+ | `CONSOLE_USER` / `CONSOLE_PASS` | basic auth on everything except `/api/record` |
331
+ | `LOCK_RECORDING=1` | recording cannot be paused from the UI |
332
+ | `LOCK_ENV=<name>` | pins the environment |
333
+
334
+ `HOST` stays on `127.0.0.1` by default, so a laptop instance is not exposed by
335
+ accident.
336
+
337
+ ## Running one for a team
338
+
339
+ Every developer whose app has the tracer posts to the console whether or not
340
+ they ever open it. The captures accumulate: one person exercises appointments,
341
+ another exercises rota, and the console ends up holding both.
342
+
343
+ Two variables stop one person's click changing things for everyone:
344
+
345
+ ```bash
346
+ LOCK_RECORDING=1 LOCK_ENV=dev npx api-tracer serve
347
+ ```
348
+
349
+ - Without `LOCK_RECORDING`, one developer clicking **Live** silently stops
350
+ recording for the whole team.
351
+ - Without `LOCK_ENV`, someone switching to stage redirects everyone's next
352
+ replay and clears the shared results.
353
+
354
+ The console also shows **whose** session the shared token belongs to, since
355
+ everyone replays as that user.
356
+
357
+ `CONSOLE_USER` / `CONSOLE_PASS` turn on basic auth. **`/api/record` stays
358
+ open**, because the app posts recordings from a browser that cannot send those
359
+ credentials. The gate protects reading captures, replaying calls and the stored
360
+ token; it does not stop someone who can reach the host from posting junk
361
+ recordings. If that matters, put the whole thing behind VPN or SSO.
362
+
363
+ It is single-user by design: one token, one environment, one set of results. Two
364
+ people on a shared instance will overwrite each other's environment and results.
365
+
366
+ ### Behind a proxy
367
+
368
+ The mount path is forwarded as-is, so tell the console where it lives:
369
+
370
+ ```bash
371
+ BASE_PATH=/api-console HOST=0.0.0.0 ALLOWED_ORIGINS=https://app.example.com npx api-tracer serve
372
+ ```
373
+
374
+ Then point the tracer at the same path:
375
+
376
+ ```ts
377
+ apiTracer.init({ reportTo: '/api-console' });
378
+ ```
379
+
380
+ Same origin means no CORS and no mixed content.
381
+
382
+ > **Mixed content.** An HTTPS app cannot post to an `http://` console; the
383
+ > browser blocks it silently. Terminate TLS in front of the console, or serve it
384
+ > under a path on the app's own origin.
385
+
386
+ ## HTTP API
387
+
388
+ Everything the UI does is available directly.
389
+
390
+ | Route | |
391
+ | --- | --- |
392
+ | `GET /api/endpoints` | catalog, base URLs, auth info, results, samples, live state |
393
+ | `POST /api/token` | store or clear a token |
394
+ | `GET/POST /api/login` | the configured sign-in flow: the next step, and submitting it |
395
+ | `POST /api/env` | switch environment |
396
+ | `POST /api/call` | send one endpoint |
397
+ | `POST /api/run` | bulk run; GETs only unless explicitly allowed |
398
+ | `POST /api/run/preview` | what a run would do |
399
+ | `GET /api/run/status`, `POST /api/run/cancel` | progress and cancellation |
400
+ | `POST /api/record` | one live call from the running app |
401
+ | `GET/POST/DELETE /api/live` | recorder state: counts, pause, reset |
402
+ | `GET/POST/DELETE /api/contracts` | response-shape baselines; POST accepts a drift |
403
+ | `GET /api/export` | `?module=&format=har\|curl\|postman` |
404
+ | `POST /api/import` | import a HAR or a cURL command |
405
+ | `DELETE /api/samples` | drop all captured samples |
406
+ | `DELETE /api/uncatalogued` | drop adopted endpoints |
407
+ | `GET/POST /api/variables` | read or set `{{variables}}` |
408
+ | `DELETE /api/results` | clear saved results |
409
+ | `GET /api/report`, `GET /api/report/export` | the report model, and `?format=md\|json` |
410
+
411
+ ## What lives in the data directory
412
+
413
+ `<project>/.api-tracer/`, or wherever `--data` points.
414
+
415
+ | | |
416
+ | --- | --- |
417
+ | `endpoints.json` | the catalog, written by `scan` |
418
+ | `samples.json` | requests captured from real traffic |
419
+ | `results.json` | the last result per endpoint |
420
+ | `contracts.json` | response-shape baselines |
421
+ | `uncatalogued.json` | endpoints adopted from traffic |
422
+ | `variables.json` | `{{variables}}` |
423
+ | `lastrun.json`, `runs.jsonl` | the last run, and one line per run for the trend |
424
+ | `tokens.json` | the session token, written `chmod 600` |
425
+
426
+ **Add `.api-tracer/` to your `.gitignore`.** It contains real request and
427
+ response bodies from the environment you are watching, and a real token. See
428
+ [Security](./security.md).
@@ -0,0 +1,192 @@
1
+ # Frameworks
2
+
3
+ The tracer only needs one thing: `init()` called once, at the entry point,
4
+ before anything makes a request. No bundler, Babel, TypeScript or ESLint
5
+ configuration is required anywhere.
6
+
7
+ ## Plain JavaScript
8
+
9
+ ```html
10
+ <script type="module">
11
+ import { apiTracer } from 'https://esm.sh/api-tracer-kit';
12
+ apiTracer.init();
13
+ </script>
14
+ ```
15
+
16
+ or in a bundled project:
17
+
18
+ ```js
19
+ import { apiTracer } from 'api-tracer-kit';
20
+ apiTracer.init();
21
+ ```
22
+
23
+ ## React
24
+
25
+ ```tsx
26
+ // src/main.tsx — before ReactDOM.render / createRoot
27
+ import { apiTracer } from 'api-tracer-kit';
28
+
29
+ apiTracer.init({ reportTo: import.meta.env.DEV });
30
+ ```
31
+
32
+ Or from inside the tree, which is StrictMode-safe:
33
+
34
+ ```tsx
35
+ import { useApiTracer } from 'api-tracer-kit/react';
36
+
37
+ function App() {
38
+ useApiTracer({ reportTo: true });
39
+ return <Routes />;
40
+ }
41
+ ```
42
+
43
+ `useApiTracer` deliberately does **not** destroy on unmount: in StrictMode that
44
+ would tear the interceptors down immediately after installing them.
45
+
46
+ To render the traces:
47
+
48
+ ```tsx
49
+ import { useApiTraces } from 'api-tracer-kit/react';
50
+
51
+ function CallCount() {
52
+ const { traces, clear } = useApiTraces();
53
+ return <button onClick={clear}>{traces.length} calls</button>;
54
+ }
55
+ ```
56
+
57
+ Updates are batched on an animation frame, so a burst of calls on page load
58
+ re-renders once rather than once per request.
59
+
60
+ ## Create React App
61
+
62
+ CRA's `ModuleScopePlugin` allows imports only from `src/` and `node_modules/`.
63
+ Always import by **package name** — never by a relative path to a checkout
64
+ outside the project:
65
+
66
+ ```js
67
+ import { apiTracer } from 'api-tracer-kit'; // ✅
68
+ import { apiTracer } from '../../../packages/api-tracer-kit'; // ❌ rejected
69
+ ```
70
+
71
+ A `yarn add file:` dependency creates a **symlink**, and webpack resolves
72
+ symlinks to their real path — which lands outside the project and is rejected
73
+ the same way. Depend on a published version, or on a packed tarball:
74
+
75
+ ```bash
76
+ npm pack --pack-destination ./vendor # in the package
77
+ ```
78
+
79
+ ```json
80
+ "api-tracer-kit": "file:vendor/api-tracer-kit-1.0.1.tgz"
81
+ ```
82
+
83
+ Gate it on `REACT_APP_ENV`, not `NODE_ENV` — a dev or stage deploy is a
84
+ production build, so `NODE_ENV` would compile the tracer out of exactly the
85
+ environments it is meant for:
86
+
87
+ ```js
88
+ // src/services/apiRecorder.js
89
+ import axios from 'axios';
90
+ import { apiTracer } from 'api-tracer-kit';
91
+
92
+ const isEnabled =
93
+ process.env.REACT_APP_ENV === 'dev' || process.env.REACT_APP_ENV === 'stage';
94
+
95
+ if (isEnabled) {
96
+ apiTracer.init({ reportTo: process.env.REACT_APP_API_CONSOLE_URL || 'http://localhost:4400' });
97
+ apiTracer.useAxios(axios);
98
+ }
99
+ ```
100
+
101
+ ```js
102
+ // src/index.js — one import, at the top
103
+ import './services/apiRecorder';
104
+ ```
105
+
106
+ Compare `process.env.REACT_APP_ENV` **directly**, not through a variable, so
107
+ DefinePlugin substitutes the literal and the whole condition folds away in a
108
+ production build.
109
+
110
+ ## Vite
111
+
112
+ ```ts
113
+ // src/main.ts
114
+ import { apiTracer } from 'api-tracer-kit';
115
+
116
+ if (import.meta.env.DEV) {
117
+ apiTracer.init({ reportTo: true });
118
+ }
119
+ ```
120
+
121
+ `import.meta.env.DEV` is statically replaced, so the branch is removed from a
122
+ production build.
123
+
124
+ ## Next.js
125
+
126
+ Importing the package on the server is safe — it evaluates no browser global —
127
+ but there is no point tracing a render, so guard the call:
128
+
129
+ ```tsx
130
+ // app/providers.tsx
131
+ 'use client';
132
+
133
+ import { useApiTracer } from 'api-tracer-kit/react';
134
+
135
+ export function Providers({ children }) {
136
+ useApiTracer({ reportTo: process.env.NODE_ENV === 'development' });
137
+ return children;
138
+ }
139
+ ```
140
+
141
+ For the Pages Router, `pages/_app.tsx` works the same way.
142
+
143
+ To trace server-side calls too, `init()` in `instrumentation.ts` — Node has
144
+ `fetch`, so it works there as well:
145
+
146
+ ```ts
147
+ export async function register() {
148
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
149
+ const { apiTracer } = await import('api-tracer-kit');
150
+ apiTracer.init({ include: ['/api/'] });
151
+ }
152
+ }
153
+ ```
154
+
155
+ Server traces stay in that process's memory; they do not reach the browser
156
+ panel.
157
+
158
+ ## Node.js
159
+
160
+ ```js
161
+ import { apiTracer } from 'api-tracer-kit';
162
+
163
+ apiTracer.init();
164
+ await fetch('https://api.example.com/users');
165
+ console.log(apiTracer.getTraces());
166
+ ```
167
+
168
+ Node 18+ has global `fetch`, so it is traced. There is no `XMLHttpRequest`, and
169
+ that transport is skipped rather than erroring. For axios on Node's `http`
170
+ adapter, `useAxios()` is required — no global patch can reach it.
171
+
172
+ ## Testing
173
+
174
+ Use an isolated instance rather than the shared one, so cases cannot leak into
175
+ each other:
176
+
177
+ ```ts
178
+ import { ApiTracer } from 'api-tracer-kit';
179
+
180
+ let tracer;
181
+ beforeEach(() => { tracer = new ApiTracer().init({ transports: ['fetch'] }); });
182
+ afterEach(() => { tracer.destroy(); });
183
+ ```
184
+
185
+ `destroy()` restores the original `fetch`, so a suite that traces is
186
+ indistinguishable from one that does not.
187
+
188
+ ## Webpack, Rollup, esbuild, Parcel
189
+
190
+ Nothing to configure. The package ships ESM and CJS with `sideEffects: false`,
191
+ so an unused import is dropped entirely and the four entry points are bundled
192
+ separately — importing the tracer never pulls in React.