@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/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/package.json
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@databehave/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A JSON file config-driven mock server",
|
|
5
|
-
"
|
|
6
|
-
"mock",
|
|
7
|
-
"mock-server",
|
|
8
|
-
"openapi",
|
|
9
|
-
"databehave",
|
|
10
|
-
"testing",
|
|
11
|
-
"http",
|
|
12
|
-
"json",
|
|
13
|
-
"jsonc"
|
|
14
|
-
],
|
|
15
|
-
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
16
6
|
"author": "ochairo",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/ochairo/databehave/tree/main/packages/server#readme",
|
|
9
|
+
"readme": "README.md",
|
|
17
10
|
"repository": {
|
|
18
11
|
"type": "git",
|
|
19
12
|
"url": "git+https://github.com/ochairo/databehave.git",
|
|
@@ -22,8 +15,18 @@
|
|
|
22
15
|
"bugs": {
|
|
23
16
|
"url": "https://github.com/ochairo/databehave/issues"
|
|
24
17
|
},
|
|
25
|
-
"
|
|
26
|
-
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mock-server",
|
|
20
|
+
"api-mock",
|
|
21
|
+
"openapi-mock",
|
|
22
|
+
"http-mock",
|
|
23
|
+
"schema-driven",
|
|
24
|
+
"test-server",
|
|
25
|
+
"json",
|
|
26
|
+
"jsonc",
|
|
27
|
+
"typescript",
|
|
28
|
+
"databehave"
|
|
29
|
+
],
|
|
27
30
|
"main": "./dist/index.js",
|
|
28
31
|
"types": "./dist/index.d.ts",
|
|
29
32
|
"exports": {
|
|
@@ -37,10 +40,9 @@
|
|
|
37
40
|
},
|
|
38
41
|
"files": [
|
|
39
42
|
"dist",
|
|
40
|
-
"docs",
|
|
41
43
|
"README.md",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"CHANGELOG.md"
|
|
44
46
|
],
|
|
45
47
|
"engines": {
|
|
46
48
|
"node": ">=18"
|
|
@@ -60,7 +62,7 @@
|
|
|
60
62
|
"typescript": "^6.0.3",
|
|
61
63
|
"vite": "^6.4.3",
|
|
62
64
|
"vitest": "^3.2.6",
|
|
63
|
-
"@databehave/schema": "0.1.
|
|
65
|
+
"@databehave/schema": "0.1.2"
|
|
64
66
|
},
|
|
65
67
|
"publishConfig": {
|
|
66
68
|
"access": "public",
|
|
@@ -69,12 +71,12 @@
|
|
|
69
71
|
"scripts": {
|
|
70
72
|
"build:admin": "vite build --config vite.admin.config.ts && mv dist/admin/ui-shell.html dist/admin/ui.html",
|
|
71
73
|
"build": "pnpm run build:admin && tsc -p tsconfig.build.json && chmod +x dist/bin.js",
|
|
72
|
-
"clean": "rm -rf dist coverage dist-test",
|
|
73
|
-
"bench": "vitest bench --run",
|
|
74
|
-
"pretypecheck": "pnpm --filter @databehave/schema build",
|
|
75
74
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.build.json --noEmit && tsc -p tsconfig.ui.json --noEmit",
|
|
76
75
|
"test": "vitest run --coverage",
|
|
77
76
|
"test:watch": "vitest",
|
|
78
|
-
"test:no-coverage": "vitest run"
|
|
77
|
+
"test:no-coverage": "vitest run",
|
|
78
|
+
"bench": "vitest bench --run",
|
|
79
|
+
"clean": "rm -rf dist coverage dist-test",
|
|
80
|
+
"pretypecheck": "pnpm --filter @databehave/schema build"
|
|
79
81
|
}
|
|
80
82
|
}
|
package/docs/admin/error-mode.md
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# `ErrorMode` reference
|
|
2
|
-
|
|
3
|
-
| Kind | Minimal body | Description |
|
|
4
|
-
| - | - | - |
|
|
5
|
-
| `http-status` | `{"kind":"http-status","status":500}` | Force the response status. Body from OAS schema if any. |
|
|
6
|
-
| `business-failure` | `{"kind":"business-failure","message":"…","extra":{ }}` | 2xx envelope `{success:false, message, …extra}`. |
|
|
7
|
-
| `custom-body` | `{"kind":"custom-body","body":{…},"status":200,"contentType":"application/json"}` | Arbitrary body / status / content-type.` |
|
|
8
|
-
| `empty-body` | `{"kind":"empty-body","status":204}` | Empty response body. |
|
|
9
|
-
| `malformed-json` | `{"kind":"malformed-json","status":200}` | Body is literally `{` — clients see `JSON.parse` failure. |
|
|
10
|
-
| `delay` | `{"kind":"delay","ms":2000,"then":{"kind":"http-status","status":500}}` | Sleep `ms`, then run `then` (or passthrough if omitted). |
|
|
11
|
-
| `hang` | `{"kind":"hang"}` | Never resolve — exercises client timeout / hang detection. |
|
|
12
|
-
| `destroy` | `{"kind":"destroy"}` | Drop the socket. Gated by `allowDestroy: true`. |
|
|
13
|
-
|
|
14
|
-
`delay.then` cannot itself be `delay`, `hang`, or `destroy`.
|
package/docs/admin/headers.md
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# `x-mock-*` header reference
|
|
2
|
-
|
|
3
|
-
Per-request overrides. A header beats every sticky override and is
|
|
4
|
-
discarded after the request. Conflicting terminal headers, an invalid
|
|
5
|
-
base64 payload, or invalid JSON return **HTTP 400** with
|
|
6
|
-
`{error: "invalid …: <reason>"}` so the typo is loud.
|
|
7
|
-
|
|
8
|
-
| Header | ErrorMode kind | Example value |
|
|
9
|
-
| ------------------------------------------------ | ---------------------- | -------------------------------------- |
|
|
10
|
-
| `x-mock-status` | `http-status` | `500` |
|
|
11
|
-
| `x-mock-business-failure` | `business-failure` | `update conflict` |
|
|
12
|
-
| `x-mock-business-failure-b64` | `business-failure` | `5pON5L2c44GM…` (base64 of UTF-8) |
|
|
13
|
-
| `x-mock-business-failure-extra` | merged into `extra` | base64-of-JSON object |
|
|
14
|
-
| `x-mock-body` (+ `-status`, `-content-type`) | `custom-body` | base64 JSON |
|
|
15
|
-
| `x-mock-empty` (+ `-status`) | `empty-body` | `1` |
|
|
16
|
-
| `x-mock-malformed` (+ `-status`) | `malformed-json` | `1` |
|
|
17
|
-
| `x-mock-delay` | `delay` (wraps others) | `2000` |
|
|
18
|
-
| `x-mock-hang` | `hang` | `1` |
|
|
19
|
-
| `x-mock-destroy` | `destroy` | `1` (rejected with 503 if `allowDestroy:false`) |
|
|
20
|
-
|
|
21
|
-
Base64 is standard (not URL-safe). Non-ASCII messages must use the
|
|
22
|
-
`-b64` variant — HTTP headers reject byte values > 255.
|
|
23
|
-
|
|
24
|
-
```sh
|
|
25
|
-
# unicode business-failure
|
|
26
|
-
MSG_B64=$(printf 'cannot copy: record was modified during operation — café' | base64)
|
|
27
|
-
curl -sS -X POST http://127.0.0.1:8000/api/v1/widgets/copy \
|
|
28
|
-
-H "x-mock-business-failure-b64: $MSG_B64" \
|
|
29
|
-
-H 'content-type: application/json' -d '{}'
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## `x-mock-injected` response header
|
|
33
|
-
|
|
34
|
-
Whenever the admin inject hook overrides a response — whether from
|
|
35
|
-
an `x-mock-*` request header or a sticky override — the response is
|
|
36
|
-
tagged with `x-mock-injected: <kind>:<source>`:
|
|
37
|
-
|
|
38
|
-
| `kind` | Where it came from |
|
|
39
|
-
| ------ | --- |
|
|
40
|
-
| `http-status`, `business-failure`, `custom-body`, `empty-body`, `malformed-json`, `delay`, `hang`, `destroy` | The ErrorMode that fired. |
|
|
41
|
-
| `header-error` | Header parsing failed; the request was rejected with 400. |
|
|
42
|
-
|
|
43
|
-
| `source` | Meaning |
|
|
44
|
-
| -------- | --- |
|
|
45
|
-
| `header` | The override came from an `x-mock-*` request header. |
|
|
46
|
-
| `sticky` | The override came from a `POST /overrides` sticky entry. |
|
|
47
|
-
|
|
48
|
-
Examples:
|
|
49
|
-
|
|
50
|
-
```text
|
|
51
|
-
x-mock-injected: http-status:header
|
|
52
|
-
x-mock-injected: business-failure:sticky
|
|
53
|
-
x-mock-injected: header-error
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
This is the single observability signal that proves an override
|
|
57
|
-
fired — without it, a 500 from the mock server is indistinguishable
|
|
58
|
-
from a 500 the real handler would have produced. Tests assert on
|
|
59
|
-
this header to verify a mock pathway, and dashboards filter by it
|
|
60
|
-
to separate injected failures from organic ones.
|
|
61
|
-
|
|
62
|
-
`header-error` carries no `:source` suffix because the rejection
|
|
63
|
-
happens before the override taxonomy applies.
|
package/docs/admin/index.md
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# Admin mode
|
|
2
|
-
|
|
3
|
-
Opt-in error-injection panel + REST API + per-request `x-mock-*` headers,
|
|
4
|
-
mounted under a configurable base path (default `/databehave`). All routes,
|
|
5
|
-
headers, and behaviours below are served **only** when
|
|
6
|
-
`admin.enabled: true`. With the flag off, the kit behaves exactly as
|
|
7
|
-
the pre-`admin` versions did — no admin routes, no header parsing, no
|
|
8
|
-
overrides store.
|
|
9
|
-
|
|
10
|
-
For the `admin` config schema (`enabled`, `path`, `bind`, `cors`,
|
|
11
|
-
`allowDestroy`, `scenariosDir`) see
|
|
12
|
-
[../config/admin.md](../config/admin.md).
|
|
13
|
-
|
|
14
|
-
## Sub-pages
|
|
15
|
-
|
|
16
|
-
- [rest-api.md](./rest-api.md) — every `__databehave/*` endpoint.
|
|
17
|
-
- [headers.md](./headers.md) — every `x-mock-*` request header.
|
|
18
|
-
- [overrides.md](./overrides.md) — sticky overrides, matchers, resolution priority.
|
|
19
|
-
- [scenarios.md](./scenarios.md) — scenario file format and lifecycle.
|
|
20
|
-
- [error-mode.md](./error-mode.md) — `ErrorMode` reference.
|
|
21
|
-
|
|
22
|
-
## Quick start
|
|
23
|
-
|
|
24
|
-
```jsonc
|
|
25
|
-
// databehave.jsonc
|
|
26
|
-
{
|
|
27
|
-
"openapi": "./openapi.json",
|
|
28
|
-
"basePath": "/api/v1",
|
|
29
|
-
"endpoints": { /* ... */ },
|
|
30
|
-
"admin": { "enabled": true }
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
```sh
|
|
35
|
-
npx @databehave/server databehave.jsonc
|
|
36
|
-
# stdout: [@databehave/server] admin panel ready at http://127.0.0.1:8000/databehave (dev mock — disable in production)
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Point a browser at `http://127.0.0.1:8000/databehave`.
|
|
40
|
-
|
|
41
|
-
Programmatic equivalent:
|
|
42
|
-
|
|
43
|
-
```ts
|
|
44
|
-
import { createServer } from '@databehave/server'
|
|
45
|
-
|
|
46
|
-
const server = createServer({
|
|
47
|
-
routes: { 'GET /api/v1/ping': () => ({ json: { ok: true } }) },
|
|
48
|
-
admin: { enabled: true },
|
|
49
|
-
})
|
|
50
|
-
await server.listen({ port: 8000, host: '127.0.0.1' })
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## UI overview
|
|
54
|
-
|
|
55
|
-
The UI is shipped as `dist/admin/{ui.html, ui.js, ui.css}` and rendered
|
|
56
|
-
with native Web Components — zero runtime framework, no CDN dependency.
|
|
57
|
-
English-only.
|
|
58
|
-
|
|
59
|
-
- **Endpoint list** — populated from `GET {path}/openapi-routes`, grouped by
|
|
60
|
-
OpenAPI tag, filterable. Each row opens an inject panel that pre-fills
|
|
61
|
-
from any existing sticky override targeting the same route.
|
|
62
|
-
- **Global override modal** — top-bar button. Owns `kind: 'global'`
|
|
63
|
-
overrides (the per-endpoint inject panel only offers `exact` and `path`
|
|
64
|
-
scopes).
|
|
65
|
-
- **Active overrides side panel** — right side. Global rows are tagged
|
|
66
|
-
`GLOBAL` and float above `path` / `exact` rows. A floating bottom-right
|
|
67
|
-
pill shows the active count.
|
|
68
|
-
- **Try-it-out** — runs the live request through the dispatcher so the
|
|
69
|
-
injected override actually applies. The response viewer renders the
|
|
70
|
-
body as a collapsible **schema tree** for JSON responses (each
|
|
71
|
-
property is a fold-able row) and falls back to a raw text panel for
|
|
72
|
-
non-JSON bodies.
|
|
73
|
-
- **Scenarios menu** — list, save (snapshot of the current overrides),
|
|
74
|
-
load, and delete named scenarios from disk.
|
|
75
|
-
- **Toasts** — successful saves, deletes, and override-create actions
|
|
76
|
-
fire a top-right toast (`success` / `error` / `info` variants);
|
|
77
|
-
failures show the REST [error envelope](./rest-api.md#error-envelope)
|
|
78
|
-
detail verbatim.
|
|
79
|
-
- **Keyboard** — `/` and `Cmd/Ctrl+K` focus the route filter, `Esc`
|
|
80
|
-
closes modals, `?` shows the help overlay.
|
|
81
|
-
|
|
82
|
-
### Non-default `admin.path`
|
|
83
|
-
|
|
84
|
-
The UI is path-agnostic. When `admin.path` is set to anything
|
|
85
|
-
other than the default `/databehave`, the boot-time HTML injects a
|
|
86
|
-
`window.__DATABEHAVE_BASE__ = '<path>'` shim before `ui.js` runs.
|
|
87
|
-
The UI reads that constant for every REST call so the panel works
|
|
88
|
-
unchanged behind a reverse proxy or a custom mount point.
|
|
89
|
-
|
|
90
|
-
If you load the panel through a build of your own (rare), set
|
|
91
|
-
`window.__DATABEHAVE_BASE__` yourself before importing
|
|
92
|
-
`ui.js` — the script falls back to `/databehave` only when the
|
|
93
|
-
constant is absent.
|
|
94
|
-
|
|
95
|
-
## Security notes
|
|
96
|
-
|
|
97
|
-
`admin` is a developer tool — not a hardened endpoint. Do not expose
|
|
98
|
-
the admin panel to untrusted networks.
|
|
99
|
-
|
|
100
|
-
- **Startup notice.** Every enabled boot writes
|
|
101
|
-
`admin panel ready at http://…/databehave (dev mock — disable in production)` to stdout. Misconfiguration is visible.
|
|
102
|
-
- **`bind: 'loopback-only'` (default).** The CLI exits 1 (and
|
|
103
|
-
`server.listen()` rejects) when the configured host is not a loopback
|
|
104
|
-
address. Set `bind: 'any'` to opt out — only in trusted environments.
|
|
105
|
-
- **`cors: 'auto'` (default).** Returns `Access-Control-Allow-Origin: *`
|
|
106
|
-
on loopback (so a localhost bookmarklet works) and switches to
|
|
107
|
-
`same-origin` (no CORS headers added) when `bind: 'any'`. Override with
|
|
108
|
-
`'any'`, `'same-origin'`, or `{ origin }`.
|
|
109
|
-
- **`allowDestroy: true` (default).** Honours `x-mock-destroy` and the
|
|
110
|
-
matching `destroy` ErrorMode. Set `false` to reject those requests with
|
|
111
|
-
HTTP 503 instead.
|
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.
|