@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/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
|
-
```
|
package/docs/config/mock-mode.md
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# `mockMode`
|
|
2
|
-
|
|
3
|
-
```jsonc
|
|
4
|
-
"mockMode": {
|
|
5
|
-
"enabled": true,
|
|
6
|
-
"header": "x-mock-status",
|
|
7
|
-
"healthPaths": ["/health", "/healthz", "/api/v1/health"],
|
|
8
|
-
"defaultStatus": 200,
|
|
9
|
-
"allowHeaderOverride": false,
|
|
10
|
-
"pathOverrides": {
|
|
11
|
-
"GET /api/v1/products/list": 500,
|
|
12
|
-
"/api/v1/admin/users-delete": 401
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
| Field | Default | Meaning |
|
|
18
|
-
| --- | --- | --- |
|
|
19
|
-
| `enabled` | `false` | Master switch. `false` / absent = feature off. |
|
|
20
|
-
| `header` | `'x-mock-status'` | Response header tagged with the resolved status. Also the request header consulted when `allowHeaderOverride: true`. |
|
|
21
|
-
| `healthPaths` | `[]` | Paths exempt from injection (typically health probes). |
|
|
22
|
-
| `defaultStatus` | undefined | Status applied when no override matches. Omit for passthrough. |
|
|
23
|
-
| `allowHeaderOverride` | `false` | When `true`, an incoming request may flip the mocked status by sending the same `header` (default `x-mock-status: 500`). Disabled by default so production-style mock servers don't expose status injection to arbitrary clients. |
|
|
24
|
-
| `pathOverrides` | `{}` | Per-route status overrides. Keys are `"METHOD /path"` or `"/path"` (path must start with `/`). Values are HTTP status codes. |
|
|
25
|
-
| `logger` | `console` | Programmatic-only sink for non-fatal mock-mode diagnostics (e.g. out-of-range header overrides). Pass the host application's logger to keep mock-server output in one place. Not settable from JSONC. |
|
|
26
|
-
|
|
27
|
-
## Resolution priority (canonical)
|
|
28
|
-
|
|
29
|
-
For each request, mock-mode picks a status in this order — first hit
|
|
30
|
-
wins. When `admin.enabled` is also set, the admin inject hook
|
|
31
|
-
runs **before** mock-mode and can short-circuit any of the steps
|
|
32
|
-
below; see [../admin/overrides.md#sticky-override-resolution](../admin/overrides.md#sticky-override-resolution)
|
|
33
|
-
for the unified pipeline.
|
|
34
|
-
|
|
35
|
-
1. Request `header` value (only when `allowHeaderOverride: true`).
|
|
36
|
-
2. `pathOverrides["<METHOD> <path>"]` (method-scoped exact match).
|
|
37
|
-
3. `pathOverrides["<path>"]` (any-method exact match).
|
|
38
|
-
4. `defaultStatus`.
|
|
39
|
-
5. Passthrough — the real handler runs.
|
|
40
|
-
|
|
41
|
-
`healthPaths` short-circuits the entire chain: matching paths are
|
|
42
|
-
served by their handler regardless of any override.
|
|
43
|
-
|
|
44
|
-
## `pathOverrides` key validation
|
|
45
|
-
|
|
46
|
-
Keys are validated at config load. Malformed keys raise at boot
|
|
47
|
-
(via `AggregateError` so every gap surfaces at once) — there is no
|
|
48
|
-
silent "this key never matches" failure mode.
|
|
49
|
-
|
|
50
|
-
- Keys must match `(?:METHOD\s+)?/path`. The optional method prefix
|
|
51
|
-
is one of `GET POST PUT DELETE PATCH HEAD OPTIONS`. The path must
|
|
52
|
-
start with `/` and contain no whitespace.
|
|
53
|
-
- Method names are normalised to upper-case: `"get /api/x"`,
|
|
54
|
-
`"GET /api/x"`, and `"Get /api/x"` all canonicalise to
|
|
55
|
-
`"GET /api/x"` so a lower-case key still matches.
|
|
56
|
-
- Values must be finite numbers (HTTP status codes).
|
|
57
|
-
|
|
58
|
-
## Body resolution
|
|
59
|
-
|
|
60
|
-
When a non-passthrough status `N` fires:
|
|
61
|
-
|
|
62
|
-
1. `status === 204` → empty body.
|
|
63
|
-
2. The body resolver returns the OAS `responses[N]` schema mock when
|
|
64
|
-
the route is OAS-walker-backed and a response schema exists for
|
|
65
|
-
that status (see [../openapi/fallback.md#per-status-body-generators](../openapi/fallback.md#per-status-body-generators)).
|
|
66
|
-
3. Fallback envelope `{ error: true, status: N }`.
|
|
67
|
-
|
|
68
|
-
The configured `header` (default `x-mock-status`) is always tagged on
|
|
69
|
-
the response with the resolved status code so a client can confirm
|
|
70
|
-
which override fired.
|
|
71
|
-
|
|
72
|
-
## `allowHeaderOverride` validation
|
|
73
|
-
|
|
74
|
-
When the request header is set but the value is not a finite integer
|
|
75
|
-
in `[200, 600)`, the value is **ignored** and a single `warn` line
|
|
76
|
-
is emitted via `logger` (default `console`) so the typo is visible
|
|
77
|
-
without breaking legitimate clients that ship the header for other
|
|
78
|
-
reasons:
|
|
79
|
-
|
|
80
|
-
```text
|
|
81
|
-
@databehave/server/mock-mode: ignored x-mock-status="abc" (must be an integer in [200, 600))
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
A per-endpoint shortcut also exists — see [endpoints.md](./endpoints.md).
|
package/docs/config/openapi.md
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# `openapi`
|
|
2
|
-
|
|
3
|
-
Path (relative to the config file) to an OpenAPI 3.x document.
|
|
4
|
-
**JSON only** — convert YAML first via `yq -o=json openapi.yaml > openapi.json`.
|
|
5
|
-
When set, the walker registers fallback handlers for every
|
|
6
|
-
`paths.*.<method>` not declared in `endpoints`.
|
|
7
|
-
|
|
8
|
-
See [../openapi/fallback.md](../openapi/fallback.md) for walker behaviour.
|
package/docs/config/schema.md
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# `schema`
|
|
2
|
-
|
|
3
|
-
Top-level field that opts the server into **auto-schema mode** — every
|
|
4
|
-
endpoint absent from `endpoints` is served from data generated by
|
|
5
|
-
`@databehave/schema`'s `mock()` engine, derived directly from your
|
|
6
|
-
OpenAPI document.
|
|
7
|
-
|
|
8
|
-
**Minimal:**
|
|
9
|
-
|
|
10
|
-
```jsonc
|
|
11
|
-
{
|
|
12
|
-
"openapi": "./openapi.json",
|
|
13
|
-
"schema": { "enabled": true }, // "enabled" gates auto-mode; defaults equivalent to { "seed": "stable" }
|
|
14
|
-
"endpoints": {}
|
|
15
|
-
}
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
**With knobs:**
|
|
19
|
-
|
|
20
|
-
```jsonc
|
|
21
|
-
{
|
|
22
|
-
"openapi": "./openapi.json",
|
|
23
|
-
"schema": {
|
|
24
|
-
"enabled": true,
|
|
25
|
-
"seed": 42, // number = fixed; "stable" = derive from endpoint|query|params; "random" = fresh per request; default "stable"
|
|
26
|
-
"locale": "ja",
|
|
27
|
-
"arrayCount": 20 // default array length when the OAS lacks minItems
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
For the architecture, install path, missing-install error, precedence,
|
|
33
|
-
and default-behaviour rules, see
|
|
34
|
-
[../openapi/auto-schema.md](../openapi/auto-schema.md).
|
package/docs/config/server.md
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# `server` and `basePath`
|
|
2
|
-
|
|
3
|
-
## `server`
|
|
4
|
-
|
|
5
|
-
```jsonc
|
|
6
|
-
"server": {
|
|
7
|
-
"host": "${MOCK_SERVER_HOST:127.0.0.1}",
|
|
8
|
-
"port": "${MOCK_SERVER_PORT:8000}"
|
|
9
|
-
}
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
`host` defaults to `127.0.0.1`, `port` to `3000`. The CLI reads this
|
|
13
|
-
section; the programmatic API ignores it (you pass `listen({ host,
|
|
14
|
-
port })` directly).
|
|
15
|
-
|
|
16
|
-
## `basePath`
|
|
17
|
-
|
|
18
|
-
```jsonc
|
|
19
|
-
"basePath": "/api/v1"
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Automatically prepended to every `endpoints` key that does NOT start
|
|
23
|
-
with `/` after the method. Must start with `/` and must not end with
|
|
24
|
-
`/`. OpenAPI paths are left untouched — they already carry their full
|
|
25
|
-
path in the OAS document.
|
|
26
|
-
|
|
27
|
-
```jsonc
|
|
28
|
-
"endpoints": {
|
|
29
|
-
"GET products/list": "./src/list.js", // → /api/v1/products/list
|
|
30
|
-
"GET /health": "./src/health.js" // → /health (absolute)
|
|
31
|
-
}
|
|
32
|
-
```
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
# `validation`
|
|
2
|
-
|
|
3
|
-
Opt-in inbound request validation against the OpenAPI document.
|
|
4
|
-
Disabled by default — when `validation` is omitted or
|
|
5
|
-
`validation.request !== true`, `server.fetch(Request)` is
|
|
6
|
-
byte-identical to the no-validation behaviour.
|
|
7
|
-
|
|
8
|
-
```jsonc
|
|
9
|
-
"validation": {
|
|
10
|
-
"request": true, // master switch; default false
|
|
11
|
-
"maxBodyBytes": 102400 // default 100 KB (matches express body-parser)
|
|
12
|
-
}
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Source: `src/types.ts:Config.validation`,
|
|
16
|
-
`src/middleware/request-validation.ts`,
|
|
17
|
-
`src/validation/validate.ts`.
|
|
18
|
-
|
|
19
|
-
## Field reference
|
|
20
|
-
|
|
21
|
-
### `validation.request` (boolean)
|
|
22
|
-
|
|
23
|
-
Master switch. Default `false`. When `true`, every inbound request
|
|
24
|
-
is matched against the OAS document and runs through the
|
|
25
|
-
build-time-compiled validators below before reaching the
|
|
26
|
-
dispatcher. Routes outside the OAS (hand-written `endpoints` keys
|
|
27
|
-
that have no matching OAS path) are passed through unchanged — the
|
|
28
|
-
validator has no contract to enforce.
|
|
29
|
-
|
|
30
|
-
### `validation.maxBodyBytes` (number)
|
|
31
|
-
|
|
32
|
-
Hard cap on the JSON request body size, measured in **UTF-8 bytes**
|
|
33
|
-
(not JS string length). Default `102400` (100 KB) — matches
|
|
34
|
-
express's body-parser default. Bodies above the cap are rejected
|
|
35
|
-
with `413 Payload Too Large` before `JSON.parse` runs. Raise only
|
|
36
|
-
if the OpenAPI document genuinely declares large request bodies.
|
|
37
|
-
|
|
38
|
-
Source: `src/middleware/request-validation.ts:42-46`,
|
|
39
|
-
`:300-380`.
|
|
40
|
-
|
|
41
|
-
## Validation pipeline
|
|
42
|
-
|
|
43
|
-
For every matching route (built once at server-creation,
|
|
44
|
-
re-used per request) the validator runs four steps in order:
|
|
45
|
-
|
|
46
|
-
1. **Security gate** — if the OAS declares a `security` block on
|
|
47
|
-
the operation (or globally), at least one requirement must be
|
|
48
|
-
satisfied. Failure → `401` with a `WWW-Authenticate` challenge.
|
|
49
|
-
Schemes recognised: `http` (presence-only `Authorization: <scheme> …`
|
|
50
|
-
match), `apiKey` (`in: header | query`; cookie schemes are
|
|
51
|
-
ignored), `oauth2`, `openIdConnect` (presence-only
|
|
52
|
-
`Authorization` check; full token introspection is out of scope
|
|
53
|
-
for a zero-dep mock — layer it via `hooks.onRequest` if needed).
|
|
54
|
-
2. **Content-Type guard** — for routes whose `requestBody.content`
|
|
55
|
-
declares only `application/json`, requests with a non-JSON
|
|
56
|
-
`Content-Type` are rejected with `415`.
|
|
57
|
-
3. **Body parse + validate** — the body is read once, capped at
|
|
58
|
-
`maxBodyBytes`, then `JSON.parse`-d. Parse failure → `400`. Any
|
|
59
|
-
schema violations are collected as `Violation[]` and emitted in
|
|
60
|
-
the `422` response. **Body validation is strict** — no type
|
|
61
|
-
coercion. A JSON `"42"` will not satisfy a schema with
|
|
62
|
-
`type: integer`.
|
|
63
|
-
4. **Param validation (with coercion)** — query, path, and header
|
|
64
|
-
parameters arrive as strings on the wire and are coerced
|
|
65
|
-
(`integer`, `number`, `boolean`) before validation. Required
|
|
66
|
-
params missing from the request emit a `required`-keyword
|
|
67
|
-
violation. Cookie parameters are ignored.
|
|
68
|
-
|
|
69
|
-
Source: `src/middleware/request-validation.ts:300-460`.
|
|
70
|
-
|
|
71
|
-
## RFC 7807 problem-detail response
|
|
72
|
-
|
|
73
|
-
All rejections use `Content-Type: application/problem+json` and the
|
|
74
|
-
shape:
|
|
75
|
-
|
|
76
|
-
```json
|
|
77
|
-
{
|
|
78
|
-
"type": "https://github.com/ochairo/databehave/blob/main/docs/errors/<slug>.md",
|
|
79
|
-
"title": "Request validation failed",
|
|
80
|
-
"status": 422,
|
|
81
|
-
"detail": "request validation failed (3 violations)",
|
|
82
|
-
"violations": [
|
|
83
|
-
{ "path": "/body/items/0/sku", "keyword": "minLength", "message": "..." },
|
|
84
|
-
{ "path": "/query/page", "keyword": "minimum", "message": "..." },
|
|
85
|
-
{ "path": "/header/x-id", "keyword": "required", "message": "..." }
|
|
86
|
-
]
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
Source: `src/middleware/request-validation.ts:24-50` (slug map +
|
|
91
|
-
title map), `:240-280` (envelope builder).
|
|
92
|
-
|
|
93
|
-
| Status | `title` | `type` slug |
|
|
94
|
-
| --- | --- | --- |
|
|
95
|
-
| `400` | `Malformed request` | `malformed-body` |
|
|
96
|
-
| `401` | `Unauthorized` | `unauthorized` |
|
|
97
|
-
| `413` | `Payload too large` | `payload-too-large` |
|
|
98
|
-
| `415` | `Unsupported media type` | `unsupported-media-type` |
|
|
99
|
-
| `422` | `Request validation failed` | `request-validation` |
|
|
100
|
-
|
|
101
|
-
`violations` is omitted when empty (e.g. on `400` / `415` / `413`).
|
|
102
|
-
The `Violation` shape (`{ path, keyword, message }`) is documented
|
|
103
|
-
in `src/validation/validate.ts:Violation`. Each `path` is a
|
|
104
|
-
JSON-Pointer rooted at one of `/body`, `/query/<name>`, `/path/<name>`,
|
|
105
|
-
or `/header/<name>`.
|
|
106
|
-
|
|
107
|
-
## Validator scope
|
|
108
|
-
|
|
109
|
-
The hand-rolled validator is intentionally a JSON-Schema **subset**
|
|
110
|
-
(audit-it-yourself: ~600 LoC, zero dependencies). Supported
|
|
111
|
-
keywords: `type`, `enum`, `required`, `properties`,
|
|
112
|
-
`additionalProperties`, `items`, `pattern`, `minLength`,
|
|
113
|
-
`maxLength`, `minimum`, `maximum`, `exclusiveMinimum`,
|
|
114
|
-
`exclusiveMaximum`, `minItems`, `maxItems`, `$ref`
|
|
115
|
-
(intra-document only — `#/components/schemas/...`), `oneOf`,
|
|
116
|
-
`anyOf`, `allOf`, `discriminator`, `format` (`date`, `date-time`,
|
|
117
|
-
`email`, `uuid`, `uri`), and `nullable` (OAS 3.0) /
|
|
118
|
-
`["X","null"]` (OAS 3.1).
|
|
119
|
-
|
|
120
|
-
Unsupported keywords are **rejected at server-start, not runtime**:
|
|
121
|
-
`if`, `then`, `else`, `dependentSchemas`, `dependentRequired`,
|
|
122
|
-
`unevaluatedProperties`, `unevaluatedItems`, `contentEncoding`,
|
|
123
|
-
`contentMediaType`, `propertyNames`, `patternProperties`, and
|
|
124
|
-
remote (`http://…` / `https://…`) `$ref`. The same set is rejected
|
|
125
|
-
by the auto-schema translator (single source of truth on JSON
|
|
126
|
-
Schema parity).
|
|
127
|
-
|
|
128
|
-
Source: `src/validation/validate.ts:51-58` (unsupported set),
|
|
129
|
-
`:21-44` (supported set; mirrored in
|
|
130
|
-
`src/openapi/translate.ts`).
|
|
131
|
-
|
|
132
|
-
## Security guarantees
|
|
133
|
-
|
|
134
|
-
The validator is built to be hostile-input safe:
|
|
135
|
-
|
|
136
|
-
- **No `eval`, no `new Function`, no dynamic `require`** — pure
|
|
137
|
-
recursion (`src/validation/validate.ts:18-23`).
|
|
138
|
-
- **Prototype-pollution guard** — input objects whose own keys
|
|
139
|
-
include `__proto__` / `constructor` / `prototype` are rejected as
|
|
140
|
-
per-property violations (`:50-56`).
|
|
141
|
-
- **ReDoS mitigation** — `pattern` length capped at 1024 chars;
|
|
142
|
-
invalid regexes rejected at build time, never request time
|
|
143
|
-
(`:42`).
|
|
144
|
-
- **Recursion depth cap 64** — both schema build (`$ref` cycles)
|
|
145
|
-
and `deepEqual` (used to compare against `enum` choices)
|
|
146
|
-
(`:38-41`).
|
|
147
|
-
|
|
148
|
-
## Cross-references
|
|
149
|
-
|
|
150
|
-
- Symptoms and diagnosis: [../troubleshooting.md](../troubleshooting.md).
|
|
151
|
-
- Verbatim error catalogue (status mapping, `WWW-Authenticate`,
|
|
152
|
-
parser errors): [../errors.md](../errors.md).
|
|
153
|
-
- Which OAS keywords each pipeline accepts:
|
|
154
|
-
[../openapi/keywords.md](../openapi/keywords.md).
|
|
155
|
-
- Trust model for OAS-author input (the validator's caps are part
|
|
156
|
-
of it): [../openapi/fallback.md#trust-model](../openapi/fallback.md#trust-model).
|
|
157
|
-
- Bypass via custom handlers: a hand-written `endpoints` route
|
|
158
|
-
receives the raw `Request` (post-security, post-content-type,
|
|
159
|
-
post-body checks unless you opt out at the route level — but the
|
|
160
|
-
route is still part of the validation universe). To skip
|
|
161
|
-
validation entirely for a route, omit it from the OAS document.
|