@databehave/server 0.1.0 → 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/README.md +1 -1
- package/dist/admin/ui.css +1 -2
- package/dist/admin/ui.js +102 -102
- package/dist/bin.js +33 -4
- package/dist/bin.js.map +1 -1
- package/dist/json-config.js +1 -1
- package/dist/json-config.js.map +1 -1
- package/package.json +29 -27
- package/docs/admin/error-mode.md +0 -14
- package/docs/admin/headers.md +0 -63
- package/docs/admin/index.md +0 -111
- package/docs/admin/overrides.md +0 -23
- package/docs/admin/rest-api.md +0 -90
- package/docs/admin/scenarios.md +0 -28
- package/docs/cli.md +0 -76
- package/docs/config/admin.md +0 -62
- package/docs/config/cors.md +0 -72
- package/docs/config/endpoints.md +0 -162
- package/docs/config/index.md +0 -107
- package/docs/config/log.md +0 -43
- package/docs/config/mock-mode.md +0 -84
- package/docs/config/openapi.md +0 -8
- package/docs/config/schema.md +0 -34
- package/docs/config/server.md +0 -32
- package/docs/config/validation.md +0 -161
- package/docs/design.md +0 -517
- package/docs/errors.md +0 -192
- package/docs/getting-started.md +0 -146
- package/docs/index.md +0 -23
- package/docs/openapi/auto-schema.md +0 -146
- package/docs/openapi/fallback.md +0 -242
- package/docs/openapi/keywords.md +0 -78
- package/docs/recipes.md +0 -258
- package/docs/stability.md +0 -118
- package/docs/troubleshooting.md +0 -191
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'`.
|