@databehave/server 0.1.1 → 0.1.2

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/docs/design.md DELETED
@@ -1,517 +0,0 @@
1
- # @databehave/server — Design Notes
2
-
3
- The smallest possible explanation of *why* @databehave/server is shaped the way it is.
4
-
5
- ## 1. The thesis
6
-
7
- A mock server should be **declarative configuration**, not a small
8
- framework. You declare *which routes exist, what they return, and
9
- when they should fail* — the runtime stays out of the way.
10
-
11
- `@databehave/server` turns one `databehave.jsonc` (or one `Config` object)
12
- into a running HTTP server. There is no app builder, no decorator
13
- soup, no middleware DSL — only:
14
-
15
- - a route table (`endpoints`),
16
- - three optional lifecycle hooks (`onRequest`, `onResponse`,
17
- `onError`),
18
- - declarative `mockMode` for status-injection,
19
- - a CORS section,
20
- - an optional `admin` panel + REST API + `x-mock-*` request
21
- headers for sticky / per-request error injection,
22
- - an optional one-line-per-request access `log`,
23
- - an OpenAPI fallback for gaps.
24
-
25
- The HTTP runtime is a thin `node:http` ↔ Web Fetch adapter living in
26
- `src/server.ts`. It is an implementation detail — handlers never
27
- import it.
28
-
29
- ## 2. Non-goals (and why)
30
-
31
- | Non-goal | Reason |
32
- | --- | --- |
33
- | General-purpose middleware framework | @databehave/server is for mocking. Use Fastify / Hono directly if you need a real app. |
34
- | Persistence layer | Bodies come from handlers or OAS schemas; @databehave/server is stateless across requests. |
35
- | Auth implementation | The `onRequest` hook is enough; baking in JWT/OAuth would couple us to libraries. |
36
- | Bundler | `tsc` to ESM is enough. No esbuild, no rollup. |
37
- | Strict OAS validation | The walker is permissive; gaps are reported via callbacks, not fatal. |
38
- | Public-internet deployment | @databehave/server assumes a trusted network (dev / CI / staging). |
39
- | Public-internet admin panel | `admin` is loopback-only by default and must not be exposed to untrusted networks. See [admin.md → Security notes](./admin.md#security-notes). |
40
-
41
- ## 3. Architecture
42
-
43
- ```bash
44
- ┌───────────────┐ load ┌───────────────┐ build ┌─────────────────┐
45
- │ JSONC config │ ──────▶ │ Config │ ──────▶ │ createServer │
46
- └───────────────┘ └───────────────┘ └─────────────────┘
47
- ▲ │
48
- │ ▼
49
- ┌───────┴───────┐ ┌──────────────┐
50
- │ OAS walker │ │ dispatcher │ ◀──┐
51
- └───────────────┘ └─────┬───────┘ │
52
- │ │
53
- fetch(Request)│listen() │
54
- ▼ │
55
- ┌──────────────────┐ │
56
- │ web Response │ │
57
- └──────────────────┘ │
58
- │
59
- │
60
- admin (opt-in) — UI / REST → overrides-store → inject hook ────┘
61
- ```
62
-
63
- - **JSONC loader** (`src/json-config.ts`) reads the file, strips
64
- comments/trailing commas, interpolates `${VAR}`, dynamic-imports
65
- every `handler`, and produces a fully-assembled `Config`.
66
- - **OAS walker** (`src/openapi/`) consumes the OpenAPI document and
67
- builds handlers for paths absent from `endpoints`. Hand-written
68
- routes always win.
69
- - **Dispatcher** (`src/server.ts`) matches the incoming `Request` to a
70
- static or dynamic route key, runs the lifecycle hooks, calls the
71
- handler, and converts the `MockResponse` POJO into a web
72
- `Response`.
73
-
74
- This separation is what lets the HTTP runtime stay swappable: only
75
- `server.ts` and the small `request.ts` / `response.ts` adapters know
76
- about the underlying `node:http` layer.
77
-
78
- ## 4. Dispatch pipeline
79
-
80
- For every incoming request:
81
-
82
- 0. **Admin route short-circuit** — when `admin.enabled` and the
83
- request path starts with `admin.path`, the request is served
84
- directly by the admin REST / UI handlers. The dispatcher does not
85
- run `mockMode`, sticky overrides, or any real handler for these
86
- paths.
87
- 1. **Hooks.onRequest** — short-circuits if it returns a
88
- `MockResponse` (used by `mockMode` to force a status, by
89
- the admin inject hook to apply a sticky / header override, and by
90
- callers for auth).
91
- 2. **Route match** — exact-path lookup first (static), then a linear
92
- scan of dynamic patterns (`/users/:id`). First match wins.
93
- 3. **Handler** — receives `MockRequest`, returns `MockResponse`.
94
- May be sync or async.
95
- 4. **Hooks.onResponse** — may replace the response (used by `mockMode`
96
- to inject the `x-mock-status` header).
97
- 5. **Hooks.onError** — invoked on any thrown handler error. Returns
98
- the final response (defaulting to a generic 500).
99
- 6. **Response adapter** — turns the POJO into a web `Response`.
100
-
101
- CORS, when configured, runs before step 1 for `OPTIONS` preflight and
102
- attaches headers in step 6 for everything else.
103
-
104
- ### `HEAD` auto-derivation (RFC 7231 §4.3.2)
105
-
106
- A `HEAD /path` request matches the corresponding `GET /path` route
107
- automatically — the dispatcher runs the GET handler, then strips the
108
- body before the response adapter serialises. Routes registered as
109
- `HEAD …` explicitly still win over the auto-derive. There is no
110
- configuration knob; this is the spec-mandated behaviour.
111
-
112
- ### Default `404` and `500` bodies
113
-
114
- When no route matches, the dispatcher returns:
115
-
116
- ```jsonc
117
- HTTP/1.1 404 Not Found
118
- content-type: application/json
119
-
120
- { "error": "not_found", "method": "GET", "path": "/missing" }
121
- ```
122
-
123
- When a handler throws and `hooks.onError` is absent (or itself
124
- throws), the dispatcher falls through to:
125
-
126
- ```jsonc
127
- HTTP/1.1 500 Internal Server Error
128
- content-type: application/json
129
-
130
- { "error": "internal_error", "message": "<Error.message>" }
131
- ```
132
-
133
- The `message` field is taken from the original `Error.message` —
134
- not the stack — so secrets stashed in stack traces don't leak into
135
- the wire response. Handlers that need a different shape should set
136
- `hooks.onError` explicitly.
137
-
138
- ### `hooks.onServerError`
139
-
140
- A separate hook for *infrastructure* errors that bubble out of the
141
- HTTP runtime itself (socket errors, malformed framing, body-parse
142
- failures). Distinct from `hooks.onError`, which only fires for
143
- handler-level throws. `onServerError` receives `(err, req?)`,
144
- returns nothing, and lets the runtime emit its default 500 — it
145
- exists for observability (Sentry, structured logs), not response
146
- shaping.
147
-
148
- ## 5. Route key model
149
-
150
- ```text
151
- 'METHOD /absolute/path'
152
- 'METHOD /users/:id'
153
- ```
154
-
155
- - Method is upper-case (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`).
156
- - Path must start with `/`.
157
- - Segments prefixed with `:` are dynamic parameters captured into
158
- `MockRequest.params` (e.g. route `'GET /users/:id'` matched
159
- against `/users/42` yields `{ id: '42' }`).
160
- - Duplicate route keys raise at boot — including duplicates between
161
- hand-written `endpoints` and the OAS walker (in that case the
162
- walker silently skips and the hand-written route wins).
163
-
164
- In the JSONC config the `basePath` is prepended to any endpoint key
165
- that does not start with `/`. So with `"basePath": "/api/v1"`:
166
-
167
- ```jsonc
168
- "endpoints": {
169
- "GET products/list": "./src/routes/list.js", // /api/v1/products/list
170
- "GET /health": "./src/routes/health.js" // /health (absolute)
171
- }
172
- ```
173
-
174
- ## 6. MockResponse model
175
-
176
- Handlers return a discriminated POJO. Exactly one body variant must
177
- be set:
178
-
179
- ```ts
180
- { json: anyJsonValue } // → application/json
181
- { text: string } // → text/plain
182
- { html: string } // → text/html
183
- { raw: BodyInit } // → caller-controlled
184
- { empty: true } // → 204 / no body
185
- ```
186
-
187
- Plus optional `status` (defaults to 200) and `headers` (merged with
188
- the runtime defaults).
189
-
190
- This is intentional. By forbidding `new Response(...)` in user code we
191
- keep the runtime swappable and make tests trivially comparable.
192
-
193
- `Vary` is the one response header that merges **additively** between a
194
- handler-set value and the runtime-set `Origin` (when CORS is on);
195
- every other header follows handler-wins. See
196
- [config/cors.md#vary-origin](./config/cors.md#vary-origin).
197
-
198
- ## 7. Mock-mode middleware
199
-
200
- The `mockMode` section turns @databehave/server into a programmable
201
- error-injection gateway. Resolution priority, body resolution, and
202
- the `x-mock-status` header tag are documented canonically in
203
- [config/mock-mode.md](./config/mock-mode.md#resolution-priority-canonical).
204
-
205
- The header `x-mock-status` is always tagged on the final response.
206
- Paths in `healthPaths` bypass everything.
207
-
208
- ## 8. OpenAPI walker
209
-
210
- Two concerns, two callbacks:
211
-
212
- - **`onOpenApiWalkError(method, path, err)`** — the walker hit a
213
- genuinely unknown OAS construct. The route falls back to a stub
214
- body (`{}` for GET, `{ success: true, message: null }` for
215
- mutations) and the error is reported so the OAS author can fix it.
216
- - **`onOpenApiEmptySchema(method, path, status)`** — the OAS declares
217
- `schema: {}` (i.e. "any JSON value" per JSON Schema). This is
218
- *valid* but uninformative; @databehave/server serves a stub and surfaces the
219
- gap so the spec gets filled in.
220
-
221
- Body seeds for OAS-served routes are derived from
222
- `endpoint + sorted query string + path params`, so the same URL
223
- always returns the same body. See [openapi.md](openapi.md).
224
-
225
- ## 9. Determinism
226
-
227
- OAS-driven bodies are deterministic by construction: the in-server,
228
- zero-dep generator (`generateFromOasSchema`, `src/openapi/generate.ts`)
229
- walks the OAS node and emits a placeholder JSON value purely from the
230
- schema shape. The seed currently derived from
231
- `endpoint|sorted(query)|params` is reserved for the future seeded-mode
232
- item; the generator never reads the wall clock or `Math.random()`.
233
- Hand-written handlers are free to be non-deterministic, but the
234
- recommended pattern — when the optional `@databehave/schema`
235
- companion is installed for richer fixtures — is to seed it the same
236
- way.
237
-
238
- ## 10. Error model
239
-
240
- - **Boot errors** — config validation failures, duplicate route keys,
241
- unsupported method names. Thrown synchronously from
242
- `loadConfig` / `createServer`.
243
- - **Walker errors** — recoverable; surfaced via
244
- `onOpenApiWalkError` and replaced with a stub.
245
- - **Handler errors** — caught by the dispatcher and routed to
246
- `hooks.onError`. If absent, a generic 500 is returned.
247
-
248
- Nothing else throws across the boundary.
249
-
250
- ## 11. Admin mode
251
-
252
- Opt-in panel + REST API + per-request `x-mock-*` headers. Default off;
253
- flipping `admin.enabled: true` is the only opt-in.
254
-
255
- Design principles:
256
-
257
- - **Default off.** Absent or `enabled: false` keeps the kit byte-for-byte
258
- compatible with pre-`admin` versions — no admin routes, no header
259
- parsing, no overrides store wired in.
260
- - **Loopback-only by default.** `bind: 'loopback-only'` refuses to
261
- enable when the server host is not loopback. Opt out with
262
- `bind: 'any'` (CLI exits 1 if the policy is violated). A one-line
263
- stdout banner on boot (`console.info`, "admin panel ready at …")
264
- surfaces the mount URL so misconfiguration is visible.
265
- - **In-process overrides store.** Sticky overrides live in memory; they
266
- do not survive a restart unless saved as a scenario. The store is the
267
- single source of truth and is consumed by an inject hook installed at
268
- the front of the dispatch pipeline.
269
- - **File-backed scenarios.** Named snapshots persist to
270
- `admin.scenariosDir` (default `${cwd}/mock-scenarios`). Names are
271
- restricted to `[A-Za-z0-9_-]{1,64}`; writes are atomic (tmp + rename).
272
- - **Header > sticky.** `x-mock-*` request headers always beat sticky
273
- overrides and are discarded after the request — useful for one-off
274
- curl checks without polluting the store.
275
- - **Resolution priority.** See [admin/overrides.md#sticky-override-resolution](./admin/overrides.md#sticky-override-resolution) for the canonical priority chain.
276
- - **UI as native Web Components.** Single static bundle in `dist/admin/`
277
- (`ui.html`, `ui.js`, `ui.css`) — zero runtime UI framework, no CDN
278
- dependency. The REST API is the contract; the UI is just one client.
279
-
280
- Full surface: [admin.md](./admin.md).
281
-
282
- ## 12. Access logs
283
-
284
- Opt-in. The top-level `log` config builds a one-line-per-request logger
285
- that writes to **stdout**. Off by default — no logger is constructed
286
- and the request hot path is untouched.
287
-
288
- The admin enable banner also writes to stdout (`console.info`); stderr
289
- carries only runtime warnings and errors.
290
-
291
- - Two formats: `pretty` (ANSI-coloured one-liner) and `json`
292
- (newline-terminated object, suitable for log shippers).
293
- - Admin-panel traffic is suppressed by default. Set `includeAdmin: true`
294
- to log it too — the hot-path check is a single `startsWith` against
295
- the resolved admin base path.
296
- - Sticky / header-driven overrides are surfaced with a trailing
297
- `[override:<kind>]` (pretty) or an `"override":"<kind>"` field (json)
298
- so injected responses are visible at a glance.
299
-
300
- Diagnostics (walker errors, `mockMode` warnings) continue to flow
301
- through the existing logger-injection contract; see
302
- [stability.md](./stability.md#logger-injection).
303
-
304
- ## 13. Programmatic API reference
305
-
306
- This section is the canonical reference for every symbol re-exported
307
- from `src/index.ts`. The set is locked by
308
- `test/public-surface.test.ts`; adding or removing a row here is a
309
- SemVer event (see [stability.md#public-surface](./stability.md#public-surface)).
310
-
311
- ### `run(opts) → Promise<RunHandle>`
312
-
313
- Boot a single server from a JSONC config path. Used by both the
314
- `@databehave/server` binary and direct library callers — same code
315
- path, same log lines, same error model.
316
-
317
- ```ts
318
- import { run, type RunHandle, type RunOptions } from '@databehave/server'
319
-
320
- const handle: RunHandle = await run({
321
- config: './databehave.jsonc', // resolved relative to process.cwd()
322
- open: false, // optional; ORed with admin.openBrowserOnStart
323
- })
324
- console.log(handle.url) // "http://127.0.0.1:8000"
325
- await handle.close() // idempotent — second call is a no-op
326
- ```
327
-
328
- `RunOptions` fields:
329
-
330
- | Field | Type | Default | Notes |
331
- | --- | --- | --- | --- |
332
- | `config` | `string` | required | Resolved relative to `process.cwd()` if not absolute. |
333
- | `open` | `boolean` | `false` | Open admin URL after `listening`. ORed with `admin.openBrowserOnStart`. No-op + log line when admin is absent or `enabled !== true`. |
334
-
335
- `RunHandle` shape:
336
-
337
- | Field | Type | Notes |
338
- | --- | --- | --- |
339
- | `url` | `string` | Bound HTTP URL (e.g. `"http://127.0.0.1:8000"`). |
340
- | `close()` | `() => Promise<void>` | Idempotent. Resolves once the listening socket is freed. |
341
-
342
- The promise resolves *after* the underlying server emits `listening`
343
- and the standard `[@databehave/server] listening on …` line is
344
- written.
345
-
346
- Source: `src/run.ts:1-115`.
347
-
348
- ### `createServer(config) → Server`
349
-
350
- Lower-level entry point. Accepts an already-resolved
351
- `Config` (typically built by `defineConfig` in TS
352
- or returned by `loadConfig`) and returns a server
353
- with both a `fetch(Request) → Promise<Response>` method (in-process,
354
- no socket — used by tests, see
355
- [recipes.md#in-process-testing-with-fetch](./recipes.md#in-process-testing-with-fetch))
356
- and a `listen(opts)` method that binds a real `node:http` server.
357
-
358
- ```ts
359
- import { createServer, defineConfig } from '@databehave/server'
360
-
361
- const config = defineConfig({
362
- endpoints: { 'GET /health': () => ({ json: { ok: true } }) },
363
- })
364
- const server = createServer(config)
365
-
366
- // In-process — no socket bound.
367
- const res = await server.fetch(new Request('http://localhost/health'))
368
-
369
- // Or bind a real server.
370
- const handle = await server.listen({ host: '127.0.0.1', port: 0 })
371
- await handle.close()
372
- ```
373
-
374
- `Server` is the return type — its full shape lives in
375
- `src/types.ts` and is re-exported as a type. See [§4 dispatch
376
- pipeline](#4-dispatch-pipeline) for the request flow inside `fetch`.
377
-
378
- Source: `src/server.ts:253` (`createServer`),
379
- `src/types.ts:Server`.
380
-
381
- ### `seedFor(input) → string`
382
-
383
- Pure helper for building a deterministic seed string. Same input →
384
- same output → companion data-generation engines (e.g.
385
- `@databehave/schema`) yield byte-identical JSON. Re-exported so
386
- consumers don't have to reimplement the format.
387
-
388
- Format: `<endpoint>|<sortedKey>=<val>|...|date=<from>|day=<dayOffset>`.
389
-
390
- ```ts
391
- import { seedFor, type SeedInput } from '@databehave/server'
392
-
393
- const seed = seedFor({
394
- endpoint: '/api/v1/products/list',
395
- extra: { region: 'east', page: 2 }, // keys sorted recursively
396
- from: '2024-01-01', // optional → "date=…"
397
- dayOffset: 0, // optional → "day=…"
398
- })
399
- // → '/api/v1/products/list|page=2|region=east|date=2024-01-01|day=0'
400
- ```
401
-
402
- `SeedInput` rules:
403
-
404
- | Field | Type | Notes |
405
- | --- | --- | --- |
406
- | `endpoint` | `string` | Required. Free-form; the URL path is the convention. |
407
- | `extra` | `Record<string, unknown>` | Optional. Object keys are sorted recursively; arrays preserve order; objects/arrays are JSON-stringified after key-sort; cycles fall back to `String(v)`. |
408
- | `from` | `string` | Optional. Emitted as `date=<from>`. |
409
- | `dayOffset` | `number` | Optional. Emitted as `day=<n>`. |
410
-
411
- Source: `src/openapi/seed.ts:1-65`.
412
-
413
- ### `loadConfig(path, options?) → Promise<LoadedConfig>`
414
-
415
- Read a JSONC config from disk, validate it, dynamic-import every
416
- handler module, and return both a resolved `Config` and
417
- the `listen()` options the CLI uses. Throws synchronously after
418
- parsing on any validation failure — see
419
- [errors.md#1-jsonc-config-errors-srcjson-configts](./errors.md#1-jsonc-config-errors-srcjson-configts).
420
-
421
- ```ts
422
- import {
423
- loadConfig,
424
- type LoadConfigOptions,
425
- type LoadedConfig,
426
- type JsonConfig,
427
- type EndpointSpec,
428
- type EndpointResponse,
429
- } from '@databehave/server'
430
-
431
- const { config, server } = await loadConfig(
432
- './databehave.jsonc',
433
- {
434
- handlers: { // pre-resolved handlers (test bypass)
435
- './src/routes/health.js': () => ({ json: { ok: true } }),
436
- },
437
- logger: { warn: console.warn }, // sink for non-fatal warnings
438
- },
439
- )
440
- ```
441
-
442
- `LoadConfigOptions` fields:
443
-
444
- | Field | Type | Purpose |
445
- | --- | --- | --- |
446
- | `handlers` | `Record<string, Handler>` | Bypass dynamic `import()` for the listed module specifiers. Keys are matched after stripping leading `./` and the extension (`.js` / `.mjs` / `.cjs` / `.ts` / `.tsx` / `.mts` / `.cts`). Routes not in the map fall back to dynamic import. Non-function values throw at boot. Used by vitest under plain Node 18. |
447
- | `logger` | `{ warn(message: string): void }` | Sink for non-fatal walker / empty-schema warnings. Defaults to `console.warn`. |
448
-
449
- `LoadedConfig` shape: `{ config: Config,
450
- server: ListenOptions }`.
451
-
452
- Companion types `EndpointSpec`, `EndpointResponse`, and
453
- `JsonConfig` describe the JSONC shape — see
454
- [config/endpoints.md](./config/endpoints.md) for the full field
455
- matrix.
456
-
457
- Source: `src/json-config.ts:597-906`.
458
-
459
- ### Mock-mode types
460
-
461
- `MockModeConfig` (and its `MockModeLogger` sink) describe the
462
- `mockMode` section of `JsonConfig` consumed by `loadConfig`.
463
- `resolveStatus(method, path, config)` is exported for unit tests.
464
-
465
- The wiring helpers (`buildMockModeHooks`, `BodyResolver`,
466
- `MockModeHooks`) are internal — `@databehave/server` is monolithic
467
- and wires them itself when you set `mockMode.enabled: true` in
468
- JSONC. They are not part of the public surface; do not import them
469
- from `'@databehave/server'`.
470
-
471
- Resolution priority and body resolution are documented canonically in
472
- [config/mock-mode.md#resolution-priority-canonical](./config/mock-mode.md#resolution-priority-canonical).
473
-
474
- Source: `src/mock-mode.ts:84-200`.
475
-
476
- ### `defineConfig(config) → Config`
477
-
478
- Identity helper for TS callers — returns the input untouched at
479
- runtime, but gives editors a clean type-narrowing site. In
480
- non-`production` `NODE_ENV` it also `Object.freeze`s the config
481
- recursively so accidental mutation throws in dev / CI. See
482
- [config/index.md#programmatic-config](./config/index.md#programmatic-config).
483
-
484
- ### Type re-exports — `Config` and friends
485
-
486
- The full kit config surface is exposed as type-only re-exports.
487
- Documented per-field across `config/*.md`; this is the API-surface
488
- roll-up:
489
-
490
- | Type | Purpose | Doc |
491
- | --- | --- | --- |
492
- | `Config` | Top-level config object passed to `createServer`. | [config/index.md#programmatic-config](./config/index.md#programmatic-config) |
493
- | `CorsConfig` | `cors` section. | [config/cors.md](./config/cors.md) |
494
- | `Method` | Union of supported HTTP methods (`'GET' \| 'POST' \| ...`). | [§5 route key model](#5-route-key-model) |
495
- | `ObservedMethod` | `Method \| 'HEAD' \| 'OPTIONS'` — the methods the dispatcher observes (HEAD auto-derives, OPTIONS handles preflight). | [§4 dispatch pipeline](#4-dispatch-pipeline) |
496
- | `RouteKey` | Branded string `'METHOD /path'`. | [§5 route key model](#5-route-key-model) |
497
- | `MockRequest` | Handler input — `{ url, method, path, params, query, headers, body }`. | [§6 response model](#6-response-model) |
498
- | `MockResponse` | Handler output — discriminated POJO. | [§6 response model](#6-response-model) |
499
- | `MockResponseBody` | The body discriminator (one of `json` / `text` / `html` / `raw` / `empty`). | [§6 response model](#6-response-model) |
500
- | `Handler` | `(req: MockRequest) => MockResponse \| Promise<MockResponse>`. | [config/endpoints.md](./config/endpoints.md) |
501
- | `ListenOptions` | Argument to `server.listen()` — `{ host?, port?, signal? }`. | [recipes.md#bind-to-a-random-port-in-tests](./recipes.md#bind-to-a-random-port-in-tests) |
502
- | `ListenHandle` | Return of `server.listen()` — `{ host, port, close() }`. | [recipes.md#bind-to-a-random-port-in-tests](./recipes.md#bind-to-a-random-port-in-tests) |
503
- | `Server` | Return of `createServer` — `{ fetch, listen }`. | This section. |
504
-
505
- Source: `src/types.ts:1-310`.
506
-
507
- ### Admin types (public surface)
508
-
509
- `AdminModeConfig`, `ErrorMode`, `StickyOverride`, `OverrideMatcher`,
510
- `AdminModeCors` are documented in [admin/](./admin/index.md) and
511
- [admin/error-mode.md](./admin/error-mode.md). They appear on
512
- `Config.admin` and on the JSONC `admin` section.
513
-
514
- The wiring helpers (`createOverridesStore`, `createInjectHook`,
515
- `parseMockHeaders`) are internal — `@databehave/server` wires them
516
- itself when `admin.enabled: true`. They are not part of the public
517
- surface; do not import them from `'@databehave/server'`.