@databehave/server 0.1.1 → 0.1.3
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/dist/admin/ui.js +12 -12
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +5 -3
- package/dist/server.js.map +1 -1
- package/package.json +24 -22
- 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/admin/overrides.md
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# Overrides
|
|
2
|
-
|
|
3
|
-
## Matchers
|
|
4
|
-
|
|
5
|
-
```jsonc
|
|
6
|
-
{ "kind": "exact", "method": "POST", "path": "/api/v1/example" } // METHOD + path
|
|
7
|
-
{ "kind": "path", "path": "/api/v1/example" } // any method
|
|
8
|
-
{ "kind": "global" } // every route
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Resolution priority (first match wins): `exact` → `path` → `global`.
|
|
12
|
-
|
|
13
|
-
## Sticky override resolution
|
|
14
|
-
|
|
15
|
-
For each request, the dispatcher consults sources in this order — first
|
|
16
|
-
hit wins:
|
|
17
|
-
|
|
18
|
-
1. `x-mock-*` request headers (per-request, never stored).
|
|
19
|
-
2. Sticky `exact` override (METHOD + path).
|
|
20
|
-
3. Sticky `path` override (any method).
|
|
21
|
-
4. Sticky `global` override.
|
|
22
|
-
5. Existing `mockMode` resolution (see [../config/mock-mode.md](../config/mock-mode.md)).
|
|
23
|
-
6. Passthrough — the real handler runs.
|
package/docs/admin/rest-api.md
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# REST endpoints
|
|
2
|
-
|
|
3
|
-
All paths are relative to `${admin.path}` (default `/databehave`).
|
|
4
|
-
Successful POSTs return `201`; missing scenarios return `404`.
|
|
5
|
-
|
|
6
|
-
| Method | Path | Body | Response |
|
|
7
|
-
| -------- | --------------------------------- | --------------------------------- | --------------------------------------- |
|
|
8
|
-
| `GET` | `{path}` | — | UI HTML (also `{path}/`) |
|
|
9
|
-
| `GET` | `{path}/ui.js` | — | UI script bundle |
|
|
10
|
-
| `GET` | `{path}/ui.css` | — | UI stylesheet |
|
|
11
|
-
| `GET` | `{path}/openapi-routes` | — | `{ routes: {method,path,summary?,source}[], discoveredAt }` (merged: OAS + hand-written handlers; OAS wins on duplicates; `source` is `'openapi'` or `'handler'`) |
|
|
12
|
-
| `GET` | `{path}/openapi.json` | — | Raw OpenAPI JSON document (only when configured; `404` otherwise) |
|
|
13
|
-
| `GET` | `{path}/overrides` | — | `{ overrides: StickyOverride[] }` |
|
|
14
|
-
| `POST` | `{path}/overrides` | `{ matcher, mode, description? }` | `201 { id, override }` |
|
|
15
|
-
| `DELETE` | `{path}/overrides` | — | `{ cleared: N }` |
|
|
16
|
-
| `DELETE` | `{path}/overrides/:id` | — | `200` or `404` |
|
|
17
|
-
| `GET` | `{path}/scenarios` | — | `{ scenarios: [{ name, count, created }] }` |
|
|
18
|
-
| `POST` | `{path}/scenarios` | `{ name, overrides? }` | `201 { scenario }` (snapshots current if `overrides` omitted) |
|
|
19
|
-
| `GET` | `{path}/scenarios/:name` | — | `{ scenario: { name, overrides[] } }` or `404` |
|
|
20
|
-
| `DELETE` | `{path}/scenarios/:name` | — | `200` or `404` |
|
|
21
|
-
| `POST` | `{path}/scenarios/:name/load` | — | `{ loaded: N }` or `404` |
|
|
22
|
-
|
|
23
|
-
Curl examples:
|
|
24
|
-
|
|
25
|
-
```sh
|
|
26
|
-
# add a sticky 500 to one route
|
|
27
|
-
curl -sS -X POST http://127.0.0.1:8000/databehave/overrides \
|
|
28
|
-
-H 'content-type: application/json' \
|
|
29
|
-
-d '{"matcher":{"kind":"exact","method":"GET","path":"/api/v1/health"},"mode":{"kind":"http-status","status":500}}'
|
|
30
|
-
|
|
31
|
-
# list active overrides
|
|
32
|
-
curl -sS http://127.0.0.1:8000/databehave/overrides
|
|
33
|
-
|
|
34
|
-
# clear everything
|
|
35
|
-
curl -sS -X DELETE http://127.0.0.1:8000/databehave/overrides
|
|
36
|
-
|
|
37
|
-
# save the current overrides as a scenario, then load it
|
|
38
|
-
curl -sS -X POST http://127.0.0.1:8000/databehave/scenarios \
|
|
39
|
-
-H 'content-type: application/json' -d '{"name":"all-500"}'
|
|
40
|
-
curl -sS -X POST http://127.0.0.1:8000/databehave/scenarios/all-500/load
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Caching
|
|
44
|
-
|
|
45
|
-
`GET {path}/openapi.json` is served with `Cache-Control: no-store`.
|
|
46
|
-
The OpenAPI document is the contract the admin UI's route picker
|
|
47
|
-
displays — caching is incorrect when the JSONC config is reloaded
|
|
48
|
-
between requests in dev.
|
|
49
|
-
|
|
50
|
-
`GET {path}/openapi-routes` (the merged OAS + hand-written list) is
|
|
51
|
-
freshly assembled per request and unconditionally non-cacheable;
|
|
52
|
-
no caching headers are emitted.
|
|
53
|
-
|
|
54
|
-
## Error envelope
|
|
55
|
-
|
|
56
|
-
Admin REST responses use a single error shape:
|
|
57
|
-
|
|
58
|
-
```jsonc
|
|
59
|
-
{ "error": "<machine code>", "detail": "<human-readable, optional>" }
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
| Status | Example body | When |
|
|
63
|
-
| ------ | ------------ | --- |
|
|
64
|
-
| `400` | `{"error":"invalid_json"}` | POST body could not be parsed as JSON. |
|
|
65
|
-
| `400` | `{"error":"validation_failed","detail":"matcher.kind must be 'exact'"}` | Body parsed but failed schema validation. |
|
|
66
|
-
| `404` | `{"error":"not_found"}` | Scenario name / override id missing. |
|
|
67
|
-
| `405` | `{"error":"method_not_allowed"}` | Path matched but method is not registered. |
|
|
68
|
-
| `500` | `{"error":"internal_error","detail":"<message>"}` | Unhandled exception in admin code (the message is the underlying `Error.message`). |
|
|
69
|
-
|
|
70
|
-
The envelope is identical to the [non-admin error envelope](../errors.md)
|
|
71
|
-
so a single client-side handler can decode both kinds of responses.
|
|
72
|
-
|
|
73
|
-
## `400 invalid x-mock-* header`
|
|
74
|
-
|
|
75
|
-
When admin-mode is enabled with `headers: true` (the default) and a
|
|
76
|
-
request carries an `x-mock-*` header that fails parsing, the inject
|
|
77
|
-
hook returns `400` **before** the underlying route runs:
|
|
78
|
-
|
|
79
|
-
```text
|
|
80
|
-
HTTP/1.1 400 Bad Request
|
|
81
|
-
content-type: application/json
|
|
82
|
-
x-mock-injected: header-error
|
|
83
|
-
|
|
84
|
-
{"error":"invalid x-mock-* header","detail":"<parse error>"}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
The `x-mock-injected: header-error` response header is the
|
|
88
|
-
observability signal that the rejection came from header parsing
|
|
89
|
-
rather than the user's handler returning a 400 of its own — see
|
|
90
|
-
[headers.md#x-mock-injected-response-header](./headers.md#x-mock-injected-response-header).
|
package/docs/admin/scenarios.md
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# Scenarios
|
|
2
|
-
|
|
3
|
-
A scenario is a named snapshot of the current sticky overrides set, stored
|
|
4
|
-
as JSON under `admin.scenariosDir` (default `${cwd}/mock-scenarios`).
|
|
5
|
-
|
|
6
|
-
- Names are restricted to `[A-Za-z0-9_-]{1,64}`.
|
|
7
|
-
- Writes are atomic (`tmp` + `rename`).
|
|
8
|
-
- A missing directory is treated as "no scenarios", not an error; the
|
|
9
|
-
directory is created on first write.
|
|
10
|
-
- `POST {path}/scenarios/:name/load` clears the active overrides and
|
|
11
|
-
re-adds the snapshot in one step.
|
|
12
|
-
|
|
13
|
-
File format:
|
|
14
|
-
|
|
15
|
-
```json
|
|
16
|
-
{
|
|
17
|
-
"name": "all-500",
|
|
18
|
-
"created": "2026-05-28T10:00:00.000Z",
|
|
19
|
-
"overrides": [
|
|
20
|
-
{
|
|
21
|
-
"id": "ovr_…",
|
|
22
|
-
"matcher": { "kind": "global" },
|
|
23
|
-
"mode": { "kind": "http-status", "status": 500 },
|
|
24
|
-
"createdAt": "2026-05-28T10:00:00.000Z"
|
|
25
|
-
}
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
```
|
package/docs/cli.md
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# CLI
|
|
2
|
-
|
|
3
|
-
```sh
|
|
4
|
-
databehave-server databehave.jsonc # explicit path (required)
|
|
5
|
-
databehave-server --open databehave.jsonc
|
|
6
|
-
databehave-server --help
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
The positional `<config>` argument is **required**. The CLI does not
|
|
10
|
-
fall back to a default file — invoking `databehave-server` with no
|
|
11
|
-
argument prints the usage text to `stderr` and exits with code **2**
|
|
12
|
-
(bad usage). The same exit code is used for unknown flags, which
|
|
13
|
-
additionally print:
|
|
14
|
-
|
|
15
|
-
```txt
|
|
16
|
-
[@databehave/server] unknown option(s): <flag>
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Flags:
|
|
20
|
-
|
|
21
|
-
| Flag | Effect |
|
|
22
|
-
| --- | --- |
|
|
23
|
-
| `--open` | After the server is listening, open the admin UI in the default browser. No-op (with a log line) when `admin` is absent or `admin.enabled !== true`. |
|
|
24
|
-
| `-h`, `--help` | Print usage and exit `0`. |
|
|
25
|
-
|
|
26
|
-
Exit codes:
|
|
27
|
-
|
|
28
|
-
| Code | Meaning |
|
|
29
|
-
| --- | --- |
|
|
30
|
-
| `0` | Clean shutdown (SIGINT / SIGTERM). |
|
|
31
|
-
| `1` | Bootstrap failure (config load, listen, etc.). |
|
|
32
|
-
| `2` | Bad usage (missing `<config>`, unknown flag). |
|
|
33
|
-
|
|
34
|
-
On successful boot the CLI loads the file, builds the config, calls
|
|
35
|
-
`listen()` using the `server` section, and prints exactly:
|
|
36
|
-
|
|
37
|
-
```text
|
|
38
|
-
[@databehave/server] listening on http://${host}:${port}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
via `console.info` (so it lands on stdout, not stderr). When
|
|
42
|
-
`admin.enabled` is also true, a second line follows on the same
|
|
43
|
-
stream:
|
|
44
|
-
|
|
45
|
-
```text
|
|
46
|
-
[@databehave/server] admin panel ready at http://${host}:${port}${admin.path} (dev mock — disable in production)
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Walk errors and empty-schema reports print to `stderr` via
|
|
50
|
-
`console.warn`. `SIGINT` and `SIGTERM` trigger a graceful shutdown
|
|
51
|
-
via `RunHandle.close()`.
|
|
52
|
-
|
|
53
|
-
Set `MOCK_SERVER_HOST` / `MOCK_SERVER_PORT` to override `server.host`
|
|
54
|
-
/ `server.port` when the config uses `${VAR}` interpolation.
|
|
55
|
-
|
|
56
|
-
## `--open` browser launcher
|
|
57
|
-
|
|
58
|
-
`--open` and `admin.openBrowserOnStart: true` (in the JSONC
|
|
59
|
-
config) both trigger the same launcher — they are ORed. With
|
|
60
|
-
`admin.enabled: false`, the flag prints a log line and is a
|
|
61
|
-
no-op:
|
|
62
|
-
|
|
63
|
-
```text
|
|
64
|
-
[@databehave/server] --open requested but admin.enabled is false; skipping
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
The launcher spawns a platform command, never a network request:
|
|
68
|
-
|
|
69
|
-
| Platform | Command |
|
|
70
|
-
| --------------- | --- |
|
|
71
|
-
| `darwin` (macOS) | `open <url>` |
|
|
72
|
-
| `win32` | `cmd /c start "" <url>` |
|
|
73
|
-
| `linux` / other | `xdg-open <url>` |
|
|
74
|
-
|
|
75
|
-
Spawn failures (missing `xdg-open`, headless CI) are caught and
|
|
76
|
-
printed as a warn line; the server keeps running.
|
package/docs/config/admin.md
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# `admin`
|
|
2
|
-
|
|
3
|
-
Opt-in admin panel + REST API for sticky error overrides and per-request
|
|
4
|
-
`x-mock-*` header injection. Default **off** — when absent or
|
|
5
|
-
`enabled: false` the kit serves nothing under the mount path and ignores
|
|
6
|
-
`x-mock-*` headers (zero behaviour change for existing consumers). When
|
|
7
|
-
enabled the kit writes a one-line stdout banner on boot (see
|
|
8
|
-
[admin/index.md#security-notes](../admin/index.md#security-notes) for the
|
|
9
|
-
canonical banner format) and mounts the UI + REST routes under `path`.
|
|
10
|
-
|
|
11
|
-
```jsonc
|
|
12
|
-
"admin": {
|
|
13
|
-
"enabled": true,
|
|
14
|
-
"path": "/databehave",
|
|
15
|
-
"bind": "loopback-only",
|
|
16
|
-
"allowDestroy": true,
|
|
17
|
-
"cors": "auto",
|
|
18
|
-
"scenariosDir": "./mock-scenarios"
|
|
19
|
-
}
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
| Field | Default | Meaning |
|
|
23
|
-
| -------------- | -------------------------------- | --- |
|
|
24
|
-
| `enabled` | `false` | Master switch. Absent or `false` = feature off. |
|
|
25
|
-
| `path` | `'/databehave'` | Mount point for UI + REST. Must start with `/`. |
|
|
26
|
-
| `ui` | `true` | Serve the admin HTML / `ui.js` / `ui.css` at `${path}`. Set `false` for a REST-only deployment (no static assets). |
|
|
27
|
-
| `headers` | `true` | Honour `x-mock-*` request headers. Set `false` to keep the sticky-override REST API enabled while ignoring per-request header overrides. |
|
|
28
|
-
| `bind` | `'loopback-only'` | Refuse to enable when the server host is not loopback. Set `'any'` to opt out (CLI exits 1 if the policy is violated). |
|
|
29
|
-
| `allowDestroy` | `true` | Honour `x-mock-destroy` / `destroy` ErrorMode. `false` → reject with HTTP 503. |
|
|
30
|
-
| `cors` | `'auto'` | `'auto'` \| `'any'` \| `'same-origin'` \| `{ origin }`. `'auto'` resolves to `*` on loopback, `'same-origin'` when `bind: 'any'`. |
|
|
31
|
-
| `scenariosDir` | `${cwd}/mock-scenarios` | Directory used by the file-backed scenarios endpoints. |
|
|
32
|
-
| `openBrowserOnStart` | `false` | When `true`, spawn the platform browser opener on the admin URL after `listening`. Default off — the URL is logged but no browser is launched. ORed with the CLI's `--open` flag. |
|
|
33
|
-
| `openapiBody` | undefined | **Programmatic only.** Override the OpenAPI document the admin route picker advertises at `GET ${path}/openapi-routes` and `GET ${path}/openapi.json`. Accepts the raw JSON text. When omitted, the kit reuses the same OAS bytes the dispatcher loaded from `openapi:`. Useful when the admin UI should expose a different surface than the dispatcher (e.g. an internal-only spec). |
|
|
34
|
-
|
|
35
|
-
> **Security.** `bind: 'loopback-only'` is the default. Setting
|
|
36
|
-
> `bind: 'any'` exposes the overrides REST API and the per-request
|
|
37
|
-
> `x-mock-*` headers to the network — only do this in trusted
|
|
38
|
-
> environments. See [../admin/index.md#security-notes](../admin/index.md#security-notes).
|
|
39
|
-
|
|
40
|
-
> **Route list.** The admin UI lists OAS routes and hand-written
|
|
41
|
-
> `endpoints` together. Routes registered only via programmatic
|
|
42
|
-
> `routes` are also included; OAS wins on duplicates.
|
|
43
|
-
|
|
44
|
-
Full REST surface, matcher / ErrorMode reference, `x-mock-*` header
|
|
45
|
-
table, sticky-override resolution priority, scenarios file format — see
|
|
46
|
-
[../admin/](../admin/index.md).
|
|
47
|
-
|
|
48
|
-
## Route collision detection
|
|
49
|
-
|
|
50
|
-
Admin routes are mounted under `${path}` (`/openapi-routes`,
|
|
51
|
-
`/overrides`, `/scenarios`, …). When a hand-written `endpoints` key
|
|
52
|
-
collides with one of those admin routes, the kit refuses to boot
|
|
53
|
-
rather than silently shadowing one with the other:
|
|
54
|
-
|
|
55
|
-
```text
|
|
56
|
-
@databehave/server: admin route GET /databehave/overrides collides with a user-declared route. Move admin.path to a free prefix or rename the user route.
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Fix: change `admin.path` to a free prefix (`/_admin`, `/__mock`,
|
|
60
|
-
…) or rename the user route. The collision check fires on every
|
|
61
|
-
admin sub-route, including the static UI assets at `${path}/ui.js`
|
|
62
|
-
and `${path}/ui.css`.
|
package/docs/config/cors.md
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# `cors`
|
|
2
|
-
|
|
3
|
-
```jsonc
|
|
4
|
-
"cors": {
|
|
5
|
-
"origin": ["https://app.example.com", "https://admin.example.com"],
|
|
6
|
-
"credentials": true,
|
|
7
|
-
"exposeHeaders": ["x-mock-status"],
|
|
8
|
-
"allowMethods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
9
|
-
"allowHeaders": ["content-type", "authorization"],
|
|
10
|
-
"maxAge": 86400
|
|
11
|
-
}
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
All fields optional. When the `cors` section is present, @databehave/server
|
|
15
|
-
handles `OPTIONS` preflight for every declared route and attaches
|
|
16
|
-
CORS headers on every response.
|
|
17
|
-
|
|
18
|
-
## `cors.origin`
|
|
19
|
-
|
|
20
|
-
`origin` accepts:
|
|
21
|
-
|
|
22
|
-
- **omitted** — the loader installs a reflector that echoes the
|
|
23
|
-
request `Origin` back (or `*` when the request has none).
|
|
24
|
-
- **`string`** — single allowed origin. Treated as a one-entry
|
|
25
|
-
allowlist.
|
|
26
|
-
- **`string[]`** — explicit allowlist. The request `Origin` is matched
|
|
27
|
-
case-insensitively against the list; only an exact match is echoed
|
|
28
|
-
back. Off-allowlist requests get an empty `Access-Control-Allow-Origin`
|
|
29
|
-
value, which the response builder omits entirely (the de-facto
|
|
30
|
-
reject signal — no header is sent).
|
|
31
|
-
|
|
32
|
-
```jsonc
|
|
33
|
-
"cors": {
|
|
34
|
-
"origin": "https://app.example.com", // single origin
|
|
35
|
-
"credentials": true
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
```jsonc
|
|
40
|
-
"cors": {
|
|
41
|
-
"origin": ["https://app.example.com", "https://staging.example.com"],
|
|
42
|
-
"credentials": true
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
For a fully custom reflector (e.g. regex matching), use the
|
|
47
|
-
programmatic API — `cors.origin` there accepts a `(o: string) => string`
|
|
48
|
-
function.
|
|
49
|
-
|
|
50
|
-
## `Vary: Origin`
|
|
51
|
-
|
|
52
|
-
Whenever the `cors` section is present, the response decorator adds
|
|
53
|
-
`Vary: Origin` to every response (preflight included). This prevents
|
|
54
|
-
reverse proxies and CDNs from caching one origin's response and serving
|
|
55
|
-
it to another after the body diverges on the request `Origin`.
|
|
56
|
-
|
|
57
|
-
When a handler also sets `Vary` (e.g. `Vary: Accept-Encoding`), the two
|
|
58
|
-
values are **merged additively**, not replaced. Tokens are deduplicated
|
|
59
|
-
case-insensitively, the first occurrence's casing is preserved, and a
|
|
60
|
-
literal `*` short-circuits to just `*` (HTTP semantic: "varies on every
|
|
61
|
-
possible header").
|
|
62
|
-
|
|
63
|
-
```ts
|
|
64
|
-
// Handler returns Vary: Accept-Encoding
|
|
65
|
-
// CORS adds Vary: Origin
|
|
66
|
-
// On the wire Vary: Accept-Encoding, Origin
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Other CORS response headers (`Access-Control-Allow-Origin`,
|
|
70
|
-
`Access-Control-Allow-Credentials`, `Access-Control-Expose-Headers`)
|
|
71
|
-
follow the standard "handler wins" merge rule — only `Vary` is
|
|
72
|
-
additive.
|
package/docs/config/endpoints.md
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
# `endpoints`
|
|
2
|
-
|
|
3
|
-
The route table. Keys are `"METHOD <path>"`. Values pick one of three
|
|
4
|
-
forms:
|
|
5
|
-
|
|
6
|
-
## 1. String (handler module path)
|
|
7
|
-
|
|
8
|
-
```jsonc
|
|
9
|
-
"GET products/list": "./src/routes/products/get/list.js"
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
Shorthand for `{ "handler": "..." }`. The module must:
|
|
13
|
-
|
|
14
|
-
- be loadable via the runtime's native ESM `import()`,
|
|
15
|
-
- `export default` a `Handler` (a function returning a
|
|
16
|
-
`MockResponse` POJO, sync or async).
|
|
17
|
-
|
|
18
|
-
CommonJS modules are not supported by the JSONC loader's resolver —
|
|
19
|
-
its dynamic-import path runs through `pathToFileURL()` and assumes
|
|
20
|
-
ESM. If the module's default export is missing or is not a function,
|
|
21
|
-
boot fails with:
|
|
22
|
-
|
|
23
|
-
```text
|
|
24
|
-
@databehave/server/config: ./src/routes/products/get/list.js (mapped to GET /api/v1/products/list) has no default-exported handler function
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### Handler resolution
|
|
28
|
-
|
|
29
|
-
Config files reference handlers by their compiled `.js` path so they
|
|
30
|
-
stay valid for production `tsc`-built deployments. In dev / test
|
|
31
|
-
scenarios where only the TypeScript source exists on disk, the
|
|
32
|
-
resolver falls back to a sibling extension automatically — `.js`
|
|
33
|
-
becomes `.ts`, `.tsx`, `.mts`, or `.cts` (in that order) — so test
|
|
34
|
-
runners that register a TS loader (`vitest`, `tsx`, `ts-node`) pick
|
|
35
|
-
the right file without any config change.
|
|
36
|
-
|
|
37
|
-
Set `DATABEHAVE_PREFER_TS=1` to **prefer** `.ts` siblings over the
|
|
38
|
-
declared `.js` even when both exist on disk. Useful in dev when an
|
|
39
|
-
outdated `dist/` is still around and would otherwise win. The
|
|
40
|
-
variable accepts only the literal string `"1"`; any other value
|
|
41
|
-
(including `true`) is treated as off.
|
|
42
|
-
|
|
43
|
-
## 2. Handler object
|
|
44
|
-
|
|
45
|
-
```jsonc
|
|
46
|
-
"POST products/bulk-update": {
|
|
47
|
-
"handler": "./src/routes/products/update/bulk-update.js",
|
|
48
|
-
"status": 500
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
The optional `status` field is a shortcut that registers an entry in
|
|
53
|
-
`mockMode.pathOverrides` for this route. Use it to force an error
|
|
54
|
-
status without editing the handler.
|
|
55
|
-
|
|
56
|
-
## 3. Static response
|
|
57
|
-
|
|
58
|
-
```jsonc
|
|
59
|
-
"GET /health": {
|
|
60
|
-
"response": {
|
|
61
|
-
"status": 200,
|
|
62
|
-
"json": { "ok": true },
|
|
63
|
-
"headers": { "cache-control": "no-store" }
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
No handler module is loaded; the response is returned verbatim. Use
|
|
69
|
-
`"empty": true` instead of `"json"` for an empty body.
|
|
70
|
-
|
|
71
|
-
### `EndpointResponse` fields
|
|
72
|
-
|
|
73
|
-
| Field | Required | Meaning |
|
|
74
|
-
| --------- | -------- | --- |
|
|
75
|
-
| `status` | yes | HTTP status integer. Boot fails with `endpoints["KEY"].response.status must be a number` if missing or non-numeric — there is no implicit `200`. |
|
|
76
|
-
| `json` | no | JSON body. Mutually exclusive with `empty`. May be any JSON value (`null`, `[]`, `{}` are all valid). When omitted on a non-`empty` response the body is `null`. |
|
|
77
|
-
| `empty` | no | When `true`, the response carries no body. Boot fails with `endpoints["KEY"].response.empty must be a boolean` if the value is non-boolean. |
|
|
78
|
-
| `headers` | no | Plain `Record<string, string>` merged into the response. Non-string values raise `endpoints["KEY"].response.headers["H"] must be a string` at boot. Header keys are case-preserved as written. |
|
|
79
|
-
|
|
80
|
-
`handler` and `response` are mutually exclusive. Setting both raises:
|
|
81
|
-
|
|
82
|
-
```text
|
|
83
|
-
@databehave/server/config: endpoints["KEY"] cannot have both "handler" and "response"
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
An empty `{}` value is also rejected:
|
|
87
|
-
|
|
88
|
-
```text
|
|
89
|
-
@databehave/server/config: endpoints["KEY"] must have either "handler" or "response"
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
A `status` field next to `response` (instead of inside it) is rejected
|
|
93
|
-
with a hint pointing at the correct location:
|
|
94
|
-
|
|
95
|
-
```text
|
|
96
|
-
@databehave/server/config: endpoints["KEY"].status cannot be combined with "response" (put status inside response)
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Programmatic `handlers` override
|
|
100
|
-
|
|
101
|
-
`loadConfig(path, { handlers })` accepts an explicit
|
|
102
|
-
module-path → function map that short-circuits the dynamic `import()`
|
|
103
|
-
for matching keys. Use case: test environments (vitest under plain
|
|
104
|
-
Node 18) that cannot load `.ts` source files through native
|
|
105
|
-
`import()`. Construct the map via static imports or
|
|
106
|
-
`import.meta.glob('eager')`, then pass it in.
|
|
107
|
-
|
|
108
|
-
```ts
|
|
109
|
-
import { loadConfig } from '@databehave/server'
|
|
110
|
-
import healthHandler from './src/routes/health.ts'
|
|
111
|
-
|
|
112
|
-
await loadConfig('./databehave.jsonc', {
|
|
113
|
-
handlers: {
|
|
114
|
-
'./src/routes/health.js': healthHandler,
|
|
115
|
-
},
|
|
116
|
-
})
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Keys are canonicalised before lookup: a leading `./` is stripped,
|
|
120
|
-
repeated `/` are collapsed, and the trailing
|
|
121
|
-
`.js` / `.mjs` / `.cjs` / `.ts` / `.tsx` / `.mts` / `.cts` is dropped
|
|
122
|
-
— so `./src/routes/health.js`, `src/routes/health`, and
|
|
123
|
-
`./src/routes/health.ts` all collapse to the same lookup key.
|
|
124
|
-
Handlers not present in the map fall back to dynamic import. Values
|
|
125
|
-
that are not functions raise at boot:
|
|
126
|
-
|
|
127
|
-
```text
|
|
128
|
-
@databehave/server/config: handlers override for "./src/routes/health.js" (mapped to GET /health) is not a function (got TYPE)
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
## Programmatic `logger` injection
|
|
132
|
-
|
|
133
|
-
`loadConfig(path, { logger })` redirects non-fatal
|
|
134
|
-
warnings (OAS walker errors, empty schemas, mock-mode header
|
|
135
|
-
diagnostics) to a custom sink. Defaults to `console.warn`. Inject a
|
|
136
|
-
no-op logger from tests to silence the output, or a structured logger
|
|
137
|
-
in production:
|
|
138
|
-
|
|
139
|
-
```ts
|
|
140
|
-
await loadConfig('./databehave.jsonc', {
|
|
141
|
-
logger: { warn: (m) => myLogger.warn({ src: 'databehave', m }) },
|
|
142
|
-
})
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
The injected logger only catches warnings emitted while loading the
|
|
146
|
-
config and wiring the OpenAPI / mock-mode adapters — it does not
|
|
147
|
-
intercept access logs (those go through the [`log`](./log.md)
|
|
148
|
-
config) or per-request `console.warn` calls from user handlers.
|
|
149
|
-
|
|
150
|
-
## `basePath` collisions
|
|
151
|
-
|
|
152
|
-
When the JSONC config sets `basePath`, every relative endpoint key
|
|
153
|
-
(e.g. `"GET products/list"`) is rewritten to absolute
|
|
154
|
-
(`"GET /api/v1/products/list"`). If two distinct keys collide after
|
|
155
|
-
rewriting — typically because one was already absolute and the other
|
|
156
|
-
relative to the same final path — the loader fails fast at boot:
|
|
157
|
-
|
|
158
|
-
```text
|
|
159
|
-
@databehave/server/config: endpoints keys "GET /api/v1/products/list" and "GET products/list" both resolve to "GET /api/v1/products/list" after applying basePath
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
There is no last-write-wins fallback. Pick one form per route.
|
package/docs/config/index.md
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# Configuration reference
|
|
2
|
-
|
|
3
|
-
`@databehave/server` is configured by one file (`databehave.jsonc`) or one
|
|
4
|
-
`Config` object passed to `createServer`. This page covers
|
|
5
|
-
the file form; the programmatic form mirrors it field-for-field.
|
|
6
|
-
|
|
7
|
-
## File format
|
|
8
|
-
|
|
9
|
-
- Extension can be `.json` or `.jsonc`. Either way, JSONC features are
|
|
10
|
-
accepted:
|
|
11
|
-
- `// line` comments
|
|
12
|
-
- `/* block */` comments
|
|
13
|
-
- trailing commas
|
|
14
|
-
- Every string value supports `${VAR}` and `${VAR:default}` env
|
|
15
|
-
interpolation. Missing env without default → empty string.
|
|
16
|
-
- Variable names accept ASCII letters, digits, and `_`, with the
|
|
17
|
-
leading character restricted to a letter or `_`. Lowercase and
|
|
18
|
-
mixed-case names are allowed (`${path}`, `${myVar}`); the loader
|
|
19
|
-
does not require ALL_CAPS shell convention.
|
|
20
|
-
- Every relative path is resolved against the **directory of the
|
|
21
|
-
config file**.
|
|
22
|
-
|
|
23
|
-
## Environment variables (process-level)
|
|
24
|
-
|
|
25
|
-
Two env vars influence boot behaviour beyond `${VAR}` interpolation:
|
|
26
|
-
|
|
27
|
-
| Variable | Effect |
|
|
28
|
-
| --- | --- |
|
|
29
|
-
| `NODE_ENV` | When **not** equal to `'production'`, both `defineConfig` and the JSONC loader deep-freeze the resolved config so accidental mutation throws in tests. Production skips the freeze so callers may legitimately patch the object after construction. |
|
|
30
|
-
| `DATABEHAVE_PREFER_TS=1` | Prefer a `.ts` / `.tsx` / `.mts` / `.cts` sibling over the declared `.js` for handler resolution, even when the `.js` exists on disk. Useful in dev when an outdated `dist/` is still around. Default off. |
|
|
31
|
-
|
|
32
|
-
## Top-level shape
|
|
33
|
-
|
|
34
|
-
```jsonc
|
|
35
|
-
{
|
|
36
|
-
"openapi": "./openapi.json", // optional, JSON only
|
|
37
|
-
"server": { "host": "...", "port": 8000 },
|
|
38
|
-
"basePath": "/api/v1", // optional
|
|
39
|
-
"cors": { /* ... */ }, // optional
|
|
40
|
-
"mockMode": { /* ... */ }, // optional
|
|
41
|
-
"admin": { /* ... */ }, // optional, default off
|
|
42
|
-
"log": false, // optional, default off
|
|
43
|
-
"endpoints": { /* ... */ } // route → handler / response
|
|
44
|
-
}
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Field reference
|
|
48
|
-
|
|
49
|
-
- [openapi.md](./openapi.md) — `openapi:` (path to the OAS document).
|
|
50
|
-
- [server.md](./server.md) — `server:` host / port and `basePath:`.
|
|
51
|
-
- [endpoints.md](./endpoints.md) — `endpoints:` route table and handler shorthand forms.
|
|
52
|
-
- [cors.md](./cors.md) — `cors:` block.
|
|
53
|
-
- [mock-mode.md](./mock-mode.md) — `mockMode:` block.
|
|
54
|
-
- [admin.md](./admin.md) — `admin:` block (config knobs only — admin behaviour is in [../admin/](../admin/index.md)).
|
|
55
|
-
- [log.md](./log.md) — `log:` block.
|
|
56
|
-
- [schema.md](./schema.md) — `schema:` (auto-schema config knobs — auto-mode behaviour is in [../openapi/auto-schema.md](../openapi/auto-schema.md)).
|
|
57
|
-
- [validation.md](./validation.md) — `validation:` block.
|
|
58
|
-
|
|
59
|
-
For the CLI that consumes the `server` section, see [../cli.md](../cli.md).
|
|
60
|
-
|
|
61
|
-
## Programmatic `Config`
|
|
62
|
-
|
|
63
|
-
```ts
|
|
64
|
-
import { createServer, defineConfig } from '@databehave/server'
|
|
65
|
-
|
|
66
|
-
const config = defineConfig({
|
|
67
|
-
routes: {
|
|
68
|
-
'GET /api/v1/ping': () => ({ json: { ok: true } }),
|
|
69
|
-
},
|
|
70
|
-
hooks: {
|
|
71
|
-
onRequest: (req) => { /* short-circuit by returning a MockResponse */ },
|
|
72
|
-
onResponse: (req, res) => res,
|
|
73
|
-
onError: (req, err) => ({ status: 500, json: { error: true } }),
|
|
74
|
-
},
|
|
75
|
-
cors: { origin: (o) => o || '*', credentials: true },
|
|
76
|
-
openapi: undefined, // pass JSON OAS text here to enable
|
|
77
|
-
onOpenApiWalkError: (m, p, err) => console.warn(`walk failed: ${m} ${p}`, err),
|
|
78
|
-
onOpenApiEmptySchema: (m, p, s) => console.warn(`empty schema: ${m} ${p} ${s}`),
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
const server = createServer(config)
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
`defineConfig` is an identity function — present for IDE support, it
|
|
85
|
-
gives you the strict `Config` type inline. When `NODE_ENV !==
|
|
86
|
-
'production'` it also deep-freezes the returned config so accidental
|
|
87
|
-
mutation (e.g. `config.routes['GET /x'] = …`, pushing into
|
|
88
|
-
`cors.allowMethods`) throws in tests instead of silently corrupting the
|
|
89
|
-
running server. Handler functions are intentionally left mutable — freeze
|
|
90
|
-
stops at function boundaries. Production skips the freeze so callers may
|
|
91
|
-
legitimately patch the object for env-specific wiring.
|
|
92
|
-
|
|
93
|
-
## Programmatic API surface
|
|
94
|
-
|
|
95
|
-
Everything reachable from the package entry point. Every field above
|
|
96
|
-
maps to a typed member of `Config` (see the source
|
|
97
|
-
declaration in `src/types.ts`).
|
|
98
|
-
|
|
99
|
-
| Export | What it is |
|
|
100
|
-
| --- | --- |
|
|
101
|
-
| `createServer(config)` | Build a `Server` from an in-memory config. Returns `{ fetch, listen }`. The HTTP runtime is `node:http` ↔ Web Fetch under the hood. |
|
|
102
|
-
| `defineConfig(config)` | Identity helper for IDE intellisense + dev-mode deep-freeze (see above). |
|
|
103
|
-
| `loadConfig(path, opts?)` | Read a JSONC file from disk and resolve every handler module. `opts.handlers` short-circuits dynamic `import()` (test-friendly bypass — see [endpoints.md](./endpoints.md#programmatic-handlers-override)); `opts.logger` redirects walker / mock-mode warnings to your sink (defaults to `console.warn`). |
|
|
104
|
-
| `run({ config, open? })` / `RunHandle` | One-call boot: load JSONC, build the server, bind the listen port, optionally open the admin URL. `RunHandle.close()` is idempotent. Used by both the CLI and direct library consumers so log lines and error model match exactly. |
|
|
105
|
-
| `seedFor({ endpoint, from?, dayOffset?, extra? })` | Pure helper for building deterministic databehave seed strings (`<endpoint>|<key>=<val>|date=<YYYY-MM-DD>|day=<n>`). Re-exported so consumers can derive their own seeds without reimplementing the format. |
|
|
106
|
-
| `resolveStatus` / `MockModeConfig` / `MockModeLogger` | `resolveStatus(method, path, config)` is exported for unit tests. `MockModeConfig` (and its `logger` sink `MockModeLogger`) is the type of `JsonConfig.mockMode`. The wiring (`buildMockModeHooks`, `BodyResolver`, `MockModeHooks`) is internal — set `mockMode.enabled: true` in JSONC. |
|
|
107
|
-
| `Config` and sibling types (`Method`, `MockRequest`, `MockResponse`, `Cors`, `Handler`, `ListenOptions`, `Server`, `AdminModeConfig`, `ErrorMode`, …) | Strict type declarations exported from `src/types.ts` / `src/admin/admin-types.ts`. |
|
package/docs/config/log.md
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# `log`
|
|
2
|
-
|
|
3
|
-
Opt-in per-request access log written to **stdout**. Off by default —
|
|
4
|
-
no output and no per-request overhead.
|
|
5
|
-
|
|
6
|
-
```jsonc
|
|
7
|
-
"log": true // shorthand: { access: true }
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
Or as an object:
|
|
11
|
-
|
|
12
|
-
```jsonc
|
|
13
|
-
"log": {
|
|
14
|
-
"access": true, // default true when object given
|
|
15
|
-
"includeAdmin": false, // log admin-panel hits too
|
|
16
|
-
"colors": "auto", // 'auto' | 'always' | 'never'
|
|
17
|
-
"format": "pretty" // 'pretty' | 'json'
|
|
18
|
-
}
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
| Field | Default | Meaning |
|
|
22
|
-
| -------------- | ----------- | --- |
|
|
23
|
-
| `access` | `true` | One line per request when `true`. |
|
|
24
|
-
| `includeAdmin` | `false` | When `true`, log hits to `admin.path/*`. |
|
|
25
|
-
| `colors` | `'auto'` | `'auto'` honours `process.stdout.isTTY`. No effect on `json` format. |
|
|
26
|
-
| `format` | `'pretty'` | `'pretty'` (ANSI one-liner) or `'json'` (newline-terminated object). |
|
|
27
|
-
|
|
28
|
-
Sticky / header-driven overrides are flagged with
|
|
29
|
-
`[override:<mode.kind>]` (pretty) or `"override":"<kind>"` (json).
|
|
30
|
-
|
|
31
|
-
Pretty (colours stripped):
|
|
32
|
-
|
|
33
|
-
```txt
|
|
34
|
-
GET /api/v1/health → 200 4 ms · 56
|
|
35
|
-
POST /api/v1/widgets/copy → 500 2 ms · 78 [override:http-status]
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
JSON:
|
|
39
|
-
|
|
40
|
-
```json
|
|
41
|
-
{"t":"2026-05-28T10:00:00.000Z","method":"GET","path":"/api/v1/health","status":200,"ms":4,"bytes":"56"}
|
|
42
|
-
{"t":"2026-05-28T10:00:00.000Z","method":"POST","path":"/api/v1/widgets/copy","status":500,"ms":2,"bytes":"78","override":"http-status"}
|
|
43
|
-
```
|