@databehave/server 0.1.4 → 0.3.0
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/CHANGELOG.md +30 -0
- package/README.md +85 -83
- package/dist/cors.d.ts +1 -1
- package/dist/cors.js +1 -1
- package/dist/cors.js.map +1 -1
- package/dist/http.d.ts +94 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +23 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +10 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -18
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts +9 -49
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +16 -92
- package/dist/log.js.map +1 -1
- package/dist/node-authority.d.ts +12 -0
- package/dist/node-authority.d.ts.map +1 -0
- package/dist/node-authority.js +72 -0
- package/dist/node-authority.js.map +1 -0
- package/dist/node-body.d.ts +11 -0
- package/dist/node-body.d.ts.map +1 -0
- package/dist/node-body.js +61 -0
- package/dist/node-body.js.map +1 -0
- package/dist/node-http.d.ts +19 -0
- package/dist/node-http.d.ts.map +1 -0
- package/dist/node-http.js +210 -0
- package/dist/node-http.js.map +1 -0
- package/dist/plugins/access-log.d.ts +11 -0
- package/dist/plugins/access-log.d.ts.map +1 -0
- package/dist/plugins/access-log.js +27 -0
- package/dist/plugins/access-log.js.map +1 -0
- package/dist/plugins/cors.d.ts +5 -0
- package/dist/plugins/cors.d.ts.map +1 -0
- package/dist/plugins/cors.js +35 -0
- package/dist/plugins/cors.js.map +1 -0
- package/dist/plugins/routes.d.ts +5 -0
- package/dist/plugins/routes.d.ts.map +1 -0
- package/dist/plugins/routes.js +10 -0
- package/dist/plugins/routes.js.map +1 -0
- package/dist/request.d.ts +2 -2
- package/dist/request.d.ts.map +1 -1
- package/dist/request.js +6 -4
- package/dist/request.js.map +1 -1
- package/dist/route-key.d.ts +2 -0
- package/dist/route-key.d.ts.map +1 -1
- package/dist/route-key.js +5 -1
- package/dist/route-key.js.map +1 -1
- package/dist/server.d.ts +7 -59
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +289 -466
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +10 -156
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/package.json +11 -24
- package/dist/admin/admin-routes.d.ts +0 -42
- package/dist/admin/admin-routes.d.ts.map +0 -1
- package/dist/admin/admin-routes.js +0 -472
- package/dist/admin/admin-routes.js.map +0 -1
- package/dist/admin/admin-types.d.ts +0 -170
- package/dist/admin/admin-types.d.ts.map +0 -1
- package/dist/admin/admin-types.js +0 -30
- package/dist/admin/admin-types.js.map +0 -1
- package/dist/admin/banner.d.ts +0 -11
- package/dist/admin/banner.d.ts.map +0 -1
- package/dist/admin/banner.js +0 -5
- package/dist/admin/banner.js.map +0 -1
- package/dist/admin/header-parser.d.ts +0 -14
- package/dist/admin/header-parser.d.ts.map +0 -1
- package/dist/admin/header-parser.js +0 -171
- package/dist/admin/header-parser.js.map +0 -1
- package/dist/admin/inject.d.ts +0 -48
- package/dist/admin/inject.d.ts.map +0 -1
- package/dist/admin/inject.js +0 -167
- package/dist/admin/inject.js.map +0 -1
- package/dist/admin/overrides-store.d.ts +0 -18
- package/dist/admin/overrides-store.d.ts.map +0 -1
- package/dist/admin/overrides-store.js +0 -100
- package/dist/admin/overrides-store.js.map +0 -1
- package/dist/admin/resolve-cors.d.ts +0 -22
- package/dist/admin/resolve-cors.d.ts.map +0 -1
- package/dist/admin/resolve-cors.js +0 -28
- package/dist/admin/resolve-cors.js.map +0 -1
- package/dist/admin/scenarios-store.d.ts +0 -15
- package/dist/admin/scenarios-store.d.ts.map +0 -1
- package/dist/admin/scenarios-store.js +0 -109
- package/dist/admin/scenarios-store.js.map +0 -1
- package/dist/admin/ui.css +0 -1
- package/dist/admin/ui.html +0 -13
- package/dist/admin/ui.js +0 -240
- package/dist/bin-helpers.d.ts +0 -46
- package/dist/bin-helpers.d.ts.map +0 -1
- package/dist/bin-helpers.js +0 -61
- package/dist/bin-helpers.js.map +0 -1
- package/dist/bin.d.ts +0 -3
- package/dist/bin.d.ts.map +0 -1
- package/dist/bin.js +0 -86
- package/dist/bin.js.map +0 -1
- package/dist/define.d.ts +0 -26
- package/dist/define.d.ts.map +0 -1
- package/dist/define.js +0 -55
- package/dist/define.js.map +0 -1
- package/dist/json-config.d.ts +0 -170
- package/dist/json-config.d.ts.map +0 -1
- package/dist/json-config.js +0 -684
- package/dist/json-config.js.map +0 -1
- package/dist/jsonc.d.ts +0 -24
- package/dist/jsonc.d.ts.map +0 -1
- package/dist/jsonc.js +0 -125
- package/dist/jsonc.js.map +0 -1
- package/dist/middleware/request-validation.d.ts +0 -58
- package/dist/middleware/request-validation.d.ts.map +0 -1
- package/dist/middleware/request-validation.js +0 -328
- package/dist/middleware/request-validation.js.map +0 -1
- package/dist/mock-mode.d.ts +0 -95
- package/dist/mock-mode.d.ts.map +0 -1
- package/dist/mock-mode.js +0 -95
- package/dist/mock-mode.js.map +0 -1
- package/dist/openapi/auto-schema.d.ts +0 -83
- package/dist/openapi/auto-schema.d.ts.map +0 -1
- package/dist/openapi/auto-schema.js +0 -232
- package/dist/openapi/auto-schema.js.map +0 -1
- package/dist/openapi/generate.d.ts +0 -20
- package/dist/openapi/generate.d.ts.map +0 -1
- package/dist/openapi/generate.js +0 -121
- package/dist/openapi/generate.js.map +0 -1
- package/dist/openapi/loader.d.ts +0 -18
- package/dist/openapi/loader.d.ts.map +0 -1
- package/dist/openapi/loader.js +0 -25
- package/dist/openapi/loader.js.map +0 -1
- package/dist/openapi/register.d.ts +0 -58
- package/dist/openapi/register.d.ts.map +0 -1
- package/dist/openapi/register.js +0 -166
- package/dist/openapi/register.js.map +0 -1
- package/dist/openapi/seed.d.ts +0 -16
- package/dist/openapi/seed.d.ts.map +0 -1
- package/dist/openapi/seed.js +0 -58
- package/dist/openapi/seed.js.map +0 -1
- package/dist/openapi/translate.d.ts +0 -55
- package/dist/openapi/translate.d.ts.map +0 -1
- package/dist/openapi/translate.js +0 -319
- package/dist/openapi/translate.js.map +0 -1
- package/dist/openapi/walker.d.ts +0 -44
- package/dist/openapi/walker.d.ts.map +0 -1
- package/dist/openapi/walker.js +0 -15
- package/dist/openapi/walker.js.map +0 -1
- package/dist/run.d.ts +0 -27
- package/dist/run.d.ts.map +0 -1
- package/dist/run.js +0 -87
- package/dist/run.js.map +0 -1
- package/dist/validation/validate.d.ts +0 -38
- package/dist/validation/validate.d.ts.map +0 -1
- package/dist/validation/validate.js +0 -372
- package/dist/validation/validate.js.map +0 -1
package/dist/log.js
CHANGED
|
@@ -1,103 +1,27 @@
|
|
|
1
1
|
const RESET = '\x1b[0m';
|
|
2
|
-
// Method palette mirrors the admin-UI badge colours.
|
|
3
2
|
const METHOD_COLOR = {
|
|
4
|
-
GET: '\x1b[32m',
|
|
5
|
-
POST: '\x1b[34m', // blue
|
|
6
|
-
PUT: '\x1b[33m', // orange-ish (yellow ANSI)
|
|
7
|
-
DELETE: '\x1b[31m', // red
|
|
8
|
-
PATCH: '\x1b[36m', // teal (cyan ANSI)
|
|
3
|
+
GET: '\x1b[32m', POST: '\x1b[34m', PUT: '\x1b[33m', DELETE: '\x1b[31m', PATCH: '\x1b[36m',
|
|
9
4
|
};
|
|
10
|
-
const statusColor = (
|
|
11
|
-
if (
|
|
5
|
+
const statusColor = (status) => {
|
|
6
|
+
if (status === 'terminated' || status >= 500)
|
|
12
7
|
return '\x1b[31m';
|
|
13
|
-
if (
|
|
8
|
+
if (status >= 400)
|
|
14
9
|
return '\x1b[33m';
|
|
15
|
-
if (
|
|
10
|
+
if (status >= 300)
|
|
16
11
|
return '\x1b[36m';
|
|
17
|
-
if (
|
|
12
|
+
if (status >= 200)
|
|
18
13
|
return '\x1b[32m';
|
|
19
14
|
return '\x1b[90m';
|
|
20
15
|
};
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
24
|
-
return
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
* Resolve a user-supplied `log` config into a normalised shape, or
|
|
32
|
-
* `null` when logging is disabled. Pure — safe to call at construction.
|
|
33
|
-
*/
|
|
34
|
-
export const resolveLogConfig = (cfg) => {
|
|
35
|
-
if (cfg === undefined || cfg === false)
|
|
36
|
-
return null;
|
|
37
|
-
if (cfg === true) {
|
|
38
|
-
return {
|
|
39
|
-
access: true,
|
|
40
|
-
includeAdmin: false,
|
|
41
|
-
colors: resolveColors('auto'),
|
|
42
|
-
format: 'pretty',
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
const access = cfg.access !== false;
|
|
46
|
-
if (!access)
|
|
47
|
-
return null;
|
|
48
|
-
return {
|
|
49
|
-
access,
|
|
50
|
-
includeAdmin: cfg.includeAdmin === true,
|
|
51
|
-
colors: resolveColors(cfg.colors ?? 'auto'),
|
|
52
|
-
format: cfg.format ?? 'pretty',
|
|
53
|
-
};
|
|
54
|
-
};
|
|
55
|
-
/**
|
|
56
|
-
* Build an access logger bound to the resolved config + admin path.
|
|
57
|
-
* `adminPath` is the resolved `admin.path` (e.g. `/mock`), or
|
|
58
|
-
* `undefined` when admin mode is off. Returns `null` when logging is
|
|
59
|
-
* disabled — callers should short-circuit on `null` to avoid any
|
|
60
|
-
* per-request allocation.
|
|
61
|
-
*/
|
|
62
|
-
export const createAccessLogger = (cfg, adminPath) => {
|
|
63
|
-
const resolved = resolveLogConfig(cfg);
|
|
64
|
-
if (!resolved)
|
|
65
|
-
return null;
|
|
66
|
-
const adminPrefix = adminPath;
|
|
67
|
-
const isAdminPath = (path) => {
|
|
68
|
-
if (adminPrefix === undefined)
|
|
69
|
-
return false;
|
|
70
|
-
return path === adminPrefix || path.startsWith(`${adminPrefix}/`);
|
|
71
|
-
};
|
|
72
|
-
const write = (line) => {
|
|
73
|
-
process.stdout.write(line);
|
|
74
|
-
};
|
|
75
|
-
const emit = (ev) => {
|
|
76
|
-
if (!resolved.includeAdmin && isAdminPath(ev.path))
|
|
77
|
-
return;
|
|
78
|
-
if (resolved.format === 'json') {
|
|
79
|
-
const obj = {
|
|
80
|
-
t: new Date().toISOString(),
|
|
81
|
-
method: ev.method,
|
|
82
|
-
path: ev.path,
|
|
83
|
-
status: ev.status,
|
|
84
|
-
ms: ev.ms,
|
|
85
|
-
bytes: ev.bytes,
|
|
86
|
-
};
|
|
87
|
-
if (ev.override !== undefined)
|
|
88
|
-
obj.override = ev.override;
|
|
89
|
-
if (ev.error !== undefined)
|
|
90
|
-
obj.error = ev.error;
|
|
91
|
-
write(`${JSON.stringify(obj)}\n`);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const c = resolved.colors;
|
|
95
|
-
const methodPainted = paint(c, METHOD_COLOR[ev.method] ?? '\x1b[90m', ev.method);
|
|
96
|
-
const statusPainted = paint(c, statusColor(ev.status), String(ev.status));
|
|
97
|
-
const overrideSuffix = ev.override !== undefined ? ` [override:${ev.override}]` : '';
|
|
98
|
-
const errorSuffix = ev.error !== undefined ? ` error=${ev.error}` : '';
|
|
99
|
-
write(`${methodPainted} ${ev.path} → ${statusPainted} ${ev.ms} ms · ${ev.bytes}${overrideSuffix}${errorSuffix}\n`);
|
|
100
|
-
};
|
|
101
|
-
return { emit, isAdminPath };
|
|
16
|
+
/** Format one newline-terminated event without reading or consuming a response body. */
|
|
17
|
+
export const formatAccessLog = (event, options) => {
|
|
18
|
+
if (options.format === 'json')
|
|
19
|
+
return `${JSON.stringify({ t: new Date().toISOString(), ...event })}\n`;
|
|
20
|
+
const colors = options.colors === 'always' || (options.colors !== 'never' && Boolean(process.stdout.isTTY));
|
|
21
|
+
const paint = (color, value) => colors ? `${color}${value}${RESET}` : value;
|
|
22
|
+
const method = paint(METHOD_COLOR[event.method] ?? '\x1b[90m', event.method);
|
|
23
|
+
const status = paint(statusColor(event.status), String(event.status));
|
|
24
|
+
const error = event.error === undefined ? '' : ` error=${event.error.replace(/[\r\n]/g, ' ')}`;
|
|
25
|
+
return `${method} ${event.path} → ${status} ${event.ms} ms · ${event.bytes}${error}\n`;
|
|
102
26
|
};
|
|
103
27
|
//# sourceMappingURL=log.js.map
|
package/dist/log.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAgBA,MAAM,KAAK,GAAG,SAAS,CAAA;AACvB,MAAM,YAAY,GAAqC;IACrD,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU;CAC1F,CAAA;AACD,MAAM,WAAW,GAAG,CAAC,MAA0B,EAAU,EAAE;IACzD,IAAI,MAAM,KAAK,YAAY,IAAI,MAAM,IAAI,GAAG;QAAE,OAAO,UAAU,CAAA;IAC/D,IAAI,MAAM,IAAI,GAAG;QAAE,OAAO,UAAU,CAAA;IACpC,IAAI,MAAM,IAAI,GAAG;QAAE,OAAO,UAAU,CAAA;IACpC,IAAI,MAAM,IAAI,GAAG;QAAE,OAAO,UAAU,CAAA;IACpC,OAAO,UAAU,CAAA;AACnB,CAAC,CAAA;AAED,wFAAwF;AACxF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAe,EAAE,OAAyB,EAAU,EAAE;IACpF,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC,IAAI,CAAA;IACtG,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;IAC3G,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,KAAa,EAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;IACnG,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IAC5E,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAA;IACrE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,CAAA;IAC9F,OAAO,GAAG,MAAM,IAAI,KAAK,CAAC,IAAI,MAAM,MAAM,IAAI,KAAK,CAAC,EAAE,SAAS,KAAK,CAAC,KAAK,GAAG,KAAK,IAAI,CAAA;AACxF,CAAC,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
import type { HttpBind } from './http.js';
|
|
3
|
+
/** A transport validation failure which must not reach application dispatch. */
|
|
4
|
+
export declare class InvalidHttpRequest extends Error {
|
|
5
|
+
readonly status: 400 | 421;
|
|
6
|
+
constructor(status: 400 | 421, message: string);
|
|
7
|
+
}
|
|
8
|
+
/** Normalize an explicit DNS name or IP literal, never a URL, port or wildcard. */
|
|
9
|
+
export declare const normalizeHostname: (value: string) => string;
|
|
10
|
+
/** Validate the raw HTTP authority and target before constructing a Fetch URL. */
|
|
11
|
+
export declare const requestUrl: (incoming: IncomingMessage, bind: HttpBind, allowedHosts: readonly string[]) => URL;
|
|
12
|
+
//# sourceMappingURL=node-authority.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-authority.d.ts","sourceRoot":"","sources":["../src/node-authority.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAEhD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAEzC,gFAAgF;AAChF,qBAAa,kBAAmB,SAAQ,KAAK;IAC/B,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG;gBAAjB,MAAM,EAAE,GAAG,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM;CACxD;AAED,mFAAmF;AACnF,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,KAAG,MASjD,CAAA;AAKD,kFAAkF;AAClF,eAAO,MAAM,UAAU,GAAI,UAAU,eAAe,EAAE,MAAM,QAAQ,EAAE,cAAc,SAAS,MAAM,EAAE,KAAG,GA4BvG,CAAA"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { isIP } from 'node:net';
|
|
2
|
+
/** A transport validation failure which must not reach application dispatch. */
|
|
3
|
+
export class InvalidHttpRequest extends Error {
|
|
4
|
+
status;
|
|
5
|
+
constructor(status, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/** Normalize an explicit DNS name or IP literal, never a URL, port or wildcard. */
|
|
11
|
+
export const normalizeHostname = (value) => {
|
|
12
|
+
const hostname = value.toLowerCase();
|
|
13
|
+
if (hostname.startsWith('[') && hostname.endsWith(']') && isIP(hostname.slice(1, -1)) === 6)
|
|
14
|
+
return new URL(`http://${hostname}`).hostname;
|
|
15
|
+
if (isIP(hostname) === 6)
|
|
16
|
+
return new URL(`http://[${hostname}]`).hostname;
|
|
17
|
+
if (!/^(?=.{1,253}$)[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?\.?$/.test(hostname)
|
|
18
|
+
|| hostname.replace(/\.$/, '').split('.').some((label) => !label || label.length > 63 || label.startsWith('-') || label.endsWith('-'))) {
|
|
19
|
+
throw new Error(`Invalid allowed hostname: ${value}`);
|
|
20
|
+
}
|
|
21
|
+
return hostname.replace(/\.$/, '');
|
|
22
|
+
};
|
|
23
|
+
const loopback = (hostname) => hostname === 'localhost' || hostname === '[::1]'
|
|
24
|
+
|| /^\[::ffff:7f[0-9a-f]{2}:[0-9a-f]{1,4}\]$/.test(hostname) || hostname.startsWith('127.') && isIP(hostname) === 4;
|
|
25
|
+
/** Validate the raw HTTP authority and target before constructing a Fetch URL. */
|
|
26
|
+
export const requestUrl = (incoming, bind, allowedHosts) => {
|
|
27
|
+
const target = incoming.url ?? '/';
|
|
28
|
+
if (!target.startsWith('/') || target.startsWith('//') || /[\\\x00-\x20\x7f#]/.test(target)) {
|
|
29
|
+
throw new InvalidHttpRequest(400, 'Invalid request target');
|
|
30
|
+
}
|
|
31
|
+
const hosts = [];
|
|
32
|
+
for (let i = 0; i < incoming.rawHeaders.length; i += 2) {
|
|
33
|
+
if (incoming.rawHeaders[i]?.toLowerCase() === 'host')
|
|
34
|
+
hosts.push(incoming.rawHeaders[i + 1] ?? '');
|
|
35
|
+
}
|
|
36
|
+
if (hosts.length !== 1)
|
|
37
|
+
throw new InvalidHttpRequest(400, 'Exactly one Host header is required');
|
|
38
|
+
const authority = hosts[0] ?? '';
|
|
39
|
+
const match = /^(\[[0-9a-fA-F:.]+\]|[a-zA-Z0-9.-]+)(?::([0-9]+))?$/.exec(authority);
|
|
40
|
+
if (!match?.[1])
|
|
41
|
+
throw new InvalidHttpRequest(400, 'Invalid Host header');
|
|
42
|
+
let hostname;
|
|
43
|
+
try {
|
|
44
|
+
hostname = normalizeHostname(match[1]);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new InvalidHttpRequest(400, 'Invalid Host header');
|
|
48
|
+
}
|
|
49
|
+
if (match[1].startsWith('[') && isIP(match[1].slice(1, -1)) !== 6)
|
|
50
|
+
throw new InvalidHttpRequest(400, 'Invalid IPv6 Host header');
|
|
51
|
+
const port = match[2] === undefined ? 80 : Number(match[2]);
|
|
52
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
53
|
+
throw new InvalidHttpRequest(400, 'Invalid Host port');
|
|
54
|
+
const local = normalizeHostname(incoming.socket.localAddress ?? bind.host);
|
|
55
|
+
const configured = normalizeHostname(bind.host);
|
|
56
|
+
const approved = hostname === local || hostname === configured || allowedHosts.includes(hostname)
|
|
57
|
+
|| loopback(local) && loopback(hostname);
|
|
58
|
+
if (port !== incoming.socket.localPort || !approved)
|
|
59
|
+
throw new InvalidHttpRequest(421, 'Unapproved Host authority');
|
|
60
|
+
let url;
|
|
61
|
+
try {
|
|
62
|
+
url = new URL(target, `http://${authority}`);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
throw new InvalidHttpRequest(400, 'Invalid request URL');
|
|
66
|
+
}
|
|
67
|
+
// WHATWG normalization must not reinterpret the validated authority (for example legacy IPv4 syntax).
|
|
68
|
+
if (normalizeHostname(url.hostname) !== hostname || Number(url.port || 80) !== port)
|
|
69
|
+
throw new InvalidHttpRequest(400, 'Invalid request authority');
|
|
70
|
+
return url;
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=node-authority.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-authority.js","sourceRoot":"","sources":["../src/node-authority.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
/** Transport-owned body state: wire completion does not mean the Web body was consumed. */
|
|
3
|
+
export interface IncomingBody {
|
|
4
|
+
readonly stream: ReadableStream<Uint8Array>;
|
|
5
|
+
readonly completed: boolean;
|
|
6
|
+
readonly cancellation: AbortSignal;
|
|
7
|
+
stop(reason?: unknown): void;
|
|
8
|
+
}
|
|
9
|
+
/** Pull bounded chunks without letting Web cancellation destroy the response socket. */
|
|
10
|
+
export declare const incomingBody: (incoming: IncomingMessage) => IncomingBody;
|
|
11
|
+
//# sourceMappingURL=node-body.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-body.d.ts","sourceRoot":"","sources":["../src/node-body.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAEhD,2FAA2F;AAC3F,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,CAAA;IAC3C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAA;IAClC,IAAI,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CAC7B;AAED,wFAAwF;AACxF,eAAO,MAAM,YAAY,GAAI,UAAU,eAAe,KAAG,YAkDxD,CAAA"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Pull bounded chunks without letting Web cancellation destroy the response socket. */
|
|
2
|
+
export const incomingBody = (incoming) => {
|
|
3
|
+
let completed = false;
|
|
4
|
+
let stopped = false;
|
|
5
|
+
const cancellation = new AbortController();
|
|
6
|
+
let controller;
|
|
7
|
+
let pending;
|
|
8
|
+
const settle = () => { pending?.(); pending = undefined; };
|
|
9
|
+
const detach = () => {
|
|
10
|
+
incoming.off('readable', deliver);
|
|
11
|
+
incoming.off('end', end);
|
|
12
|
+
incoming.off('aborted', aborted);
|
|
13
|
+
incoming.pause();
|
|
14
|
+
settle();
|
|
15
|
+
};
|
|
16
|
+
const end = () => {
|
|
17
|
+
if (stopped)
|
|
18
|
+
return;
|
|
19
|
+
stopped = true;
|
|
20
|
+
completed = true;
|
|
21
|
+
controller.close();
|
|
22
|
+
detach();
|
|
23
|
+
};
|
|
24
|
+
const stop = (reason) => {
|
|
25
|
+
if (stopped)
|
|
26
|
+
return;
|
|
27
|
+
stopped = true;
|
|
28
|
+
controller.error(reason ?? new Error('Request body intake stopped'));
|
|
29
|
+
detach();
|
|
30
|
+
cancellation.abort(reason);
|
|
31
|
+
};
|
|
32
|
+
const aborted = () => stop(new Error('Request body aborted'));
|
|
33
|
+
const deliver = () => {
|
|
34
|
+
if (stopped || !pending)
|
|
35
|
+
return;
|
|
36
|
+
const chunk = incoming.read(Math.min(incoming.readableLength, 16 * 1024));
|
|
37
|
+
if (chunk instanceof Uint8Array) {
|
|
38
|
+
controller.enqueue(chunk);
|
|
39
|
+
settle();
|
|
40
|
+
}
|
|
41
|
+
else if (incoming.readableEnded)
|
|
42
|
+
end();
|
|
43
|
+
};
|
|
44
|
+
// Keep an error observer after cancellation: aborted uploads can error after body listeners detach.
|
|
45
|
+
const error = (cause) => stop(cause);
|
|
46
|
+
incoming.on('error', error);
|
|
47
|
+
incoming.once('close', () => { incoming.off('error', error); if (!incoming.complete)
|
|
48
|
+
aborted(); });
|
|
49
|
+
const stream = new ReadableStream({
|
|
50
|
+
start(output) {
|
|
51
|
+
controller = output;
|
|
52
|
+
incoming.on('readable', deliver);
|
|
53
|
+
incoming.on('end', end);
|
|
54
|
+
incoming.on('aborted', aborted);
|
|
55
|
+
},
|
|
56
|
+
pull() { return new Promise((resolve) => { pending = resolve; deliver(); }); },
|
|
57
|
+
cancel(reason) { stopped = true; detach(); cancellation.abort(reason); },
|
|
58
|
+
}, { highWaterMark: 0 });
|
|
59
|
+
return { stream, get completed() { return completed; }, cancellation: cancellation.signal, stop };
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=node-body.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-body.js","sourceRoot":"","sources":["../src/node-body.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Plugin } from '@databehave/core';
|
|
2
|
+
import { type HttpBind } from './http.js';
|
|
3
|
+
/** Node listener configuration. Additional DNS authorities must be explicitly allowed. */
|
|
4
|
+
export interface NodeHttpOptions {
|
|
5
|
+
readonly host?: string;
|
|
6
|
+
readonly port?: number;
|
|
7
|
+
readonly allowedHosts?: readonly string[];
|
|
8
|
+
readonly onServerError?: (error: Error) => void;
|
|
9
|
+
}
|
|
10
|
+
/** Listener address is unavailable until the open lifecycle phase has completed. */
|
|
11
|
+
export interface HttpListener {
|
|
12
|
+
readonly address: HttpBind | undefined;
|
|
13
|
+
}
|
|
14
|
+
export declare const listenerCapability: import("@databehave/core").Capability<HttpListener>;
|
|
15
|
+
/** Report listener failures without allowing a failing reporter to suppress the original error. */
|
|
16
|
+
export declare const handleRuntimeServerError: (error: Error, report: ((error: Error) => void) | undefined) => void;
|
|
17
|
+
/** Open Node ingress only after all activation and bind policy checks have succeeded. */
|
|
18
|
+
export declare const nodeHttpPlugin: (options?: NodeHttpOptions) => Plugin;
|
|
19
|
+
//# sourceMappingURL=node-http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-http.d.ts","sourceRoot":"","sources":["../src/node-http.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,EAAkB,KAAK,QAAQ,EAAoB,MAAM,WAAW,CAAA;AAI3E,0FAA0F;AAC1F,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CAChD;AAED,oFAAoF;AACpF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,SAAS,CAAA;CACvC;AAED,eAAO,MAAM,kBAAkB,qDAA+D,CAAA;AAE9F,mGAAmG;AACnG,eAAO,MAAM,wBAAwB,GAAI,OAAO,KAAK,EAAE,QAAQ,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,SAAS,KAAG,IASrG,CAAA;AAiGD,yFAAyF;AACzF,eAAO,MAAM,cAAc,GAAI,UAAS,eAAoB,KAAG,MAqD9D,CAAA"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { capability } from '@databehave/core';
|
|
3
|
+
import { httpCapability } from './http.js';
|
|
4
|
+
import { InvalidHttpRequest, normalizeHostname, requestUrl } from './node-authority.js';
|
|
5
|
+
import { incomingBody } from './node-body.js';
|
|
6
|
+
export const listenerCapability = capability('databehave.http.node-listener', 1);
|
|
7
|
+
/** Report listener failures without allowing a failing reporter to suppress the original error. */
|
|
8
|
+
export const handleRuntimeServerError = (error, report) => {
|
|
9
|
+
if (report) {
|
|
10
|
+
try {
|
|
11
|
+
report(error);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
catch (reportError) {
|
|
15
|
+
// eslint-disable-next-line no-console
|
|
16
|
+
console.error('HTTP error reporter failed:', reportError);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// eslint-disable-next-line no-console
|
|
20
|
+
console.error('HTTP server error:', error);
|
|
21
|
+
};
|
|
22
|
+
const handleRequest = async (http, incoming, outgoing, signal, bind, allowedHosts) => {
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
let body;
|
|
25
|
+
const abort = () => { controller.abort(); body?.stop(controller.signal.reason); };
|
|
26
|
+
let closing = false;
|
|
27
|
+
let closeConnection = false;
|
|
28
|
+
const closeAfterResponse = () => {
|
|
29
|
+
if (closing)
|
|
30
|
+
return;
|
|
31
|
+
closing = true;
|
|
32
|
+
closeConnection = true;
|
|
33
|
+
if (!outgoing.headersSent)
|
|
34
|
+
outgoing.setHeader('connection', 'close');
|
|
35
|
+
const socket = incoming.socket;
|
|
36
|
+
const timer = setTimeout(() => socket.destroy(), Math.max(0, (http.drainDeadline ?? performance.now() + http.drainTimeoutMs) - performance.now()));
|
|
37
|
+
timer.unref();
|
|
38
|
+
socket.once('close', () => clearTimeout(timer));
|
|
39
|
+
outgoing.once('finish', () => socket.end());
|
|
40
|
+
};
|
|
41
|
+
const finish = () => new Promise((resolve) => {
|
|
42
|
+
if (closeConnection) {
|
|
43
|
+
body?.stop();
|
|
44
|
+
closeAfterResponse();
|
|
45
|
+
}
|
|
46
|
+
if (outgoing.destroyed || outgoing.writableFinished) {
|
|
47
|
+
resolve();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const done = () => { outgoing.off('finish', done); outgoing.off('close', done); resolve(); };
|
|
51
|
+
outgoing.once('finish', done);
|
|
52
|
+
outgoing.once('close', done);
|
|
53
|
+
outgoing.end();
|
|
54
|
+
});
|
|
55
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
56
|
+
incoming.once('aborted', abort);
|
|
57
|
+
outgoing.once('close', abort);
|
|
58
|
+
if (signal.aborted)
|
|
59
|
+
abort();
|
|
60
|
+
try {
|
|
61
|
+
const url = requestUrl(incoming, bind, allowedHosts);
|
|
62
|
+
const headers = new Headers();
|
|
63
|
+
for (let i = 0; i < incoming.rawHeaders.length; i += 2) {
|
|
64
|
+
const name = incoming.rawHeaders[i];
|
|
65
|
+
const value = incoming.rawHeaders[i + 1];
|
|
66
|
+
if (name !== undefined && value !== undefined)
|
|
67
|
+
headers.append(name, value);
|
|
68
|
+
}
|
|
69
|
+
const method = incoming.method ?? 'GET';
|
|
70
|
+
const hasBody = method !== 'GET' && method !== 'HEAD';
|
|
71
|
+
const init = { method, headers, signal: controller.signal };
|
|
72
|
+
if (hasBody) {
|
|
73
|
+
body = incomingBody(incoming);
|
|
74
|
+
init.body = body.stream;
|
|
75
|
+
init.duplex = 'half';
|
|
76
|
+
if (controller.signal.aborted)
|
|
77
|
+
body.stop(controller.signal.reason);
|
|
78
|
+
}
|
|
79
|
+
const request = new Request(url, init);
|
|
80
|
+
const outcome = await http.dispatch(request);
|
|
81
|
+
if (outcome.kind === 'terminate') {
|
|
82
|
+
incoming.socket.destroy();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (outgoing.destroyed) {
|
|
86
|
+
await outcome.response.body?.cancel();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
outgoing.statusCode = outcome.response.status;
|
|
90
|
+
outcome.response.headers.forEach((value, name) => outgoing.setHeader(name, value));
|
|
91
|
+
// Response pulls may still consume input after the handler returns.
|
|
92
|
+
closeConnection = body ? !body.completed : !incoming.complete;
|
|
93
|
+
if (closeConnection)
|
|
94
|
+
outgoing.setHeader('connection', 'close');
|
|
95
|
+
body?.cancellation.addEventListener('abort', closeAfterResponse, { once: true });
|
|
96
|
+
if (body?.cancellation.aborted)
|
|
97
|
+
closeAfterResponse();
|
|
98
|
+
if (!outcome.response.body) {
|
|
99
|
+
await finish();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const reader = outcome.response.body.getReader();
|
|
103
|
+
const cancelBody = () => { void reader.cancel().catch(() => undefined); };
|
|
104
|
+
controller.signal.addEventListener('abort', cancelBody, { once: true });
|
|
105
|
+
if (controller.signal.aborted)
|
|
106
|
+
cancelBody();
|
|
107
|
+
try {
|
|
108
|
+
while (!controller.signal.aborted) {
|
|
109
|
+
const chunk = await reader.read();
|
|
110
|
+
if (chunk.done)
|
|
111
|
+
break;
|
|
112
|
+
if (!outgoing.write(chunk.value)) {
|
|
113
|
+
await new Promise((resolve) => {
|
|
114
|
+
const done = () => { outgoing.off('drain', done); outgoing.off('close', done); resolve(); };
|
|
115
|
+
outgoing.once('drain', done);
|
|
116
|
+
outgoing.once('close', done);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
controller.signal.removeEventListener('abort', cancelBody);
|
|
123
|
+
await reader.cancel().catch(() => undefined);
|
|
124
|
+
}
|
|
125
|
+
await finish();
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
if (!outgoing.destroyed) {
|
|
129
|
+
if (outgoing.headersSent)
|
|
130
|
+
outgoing.destroy(error instanceof Error ? error : undefined);
|
|
131
|
+
else {
|
|
132
|
+
outgoing.statusCode = error instanceof InvalidHttpRequest ? error.status : 500;
|
|
133
|
+
closeAfterResponse();
|
|
134
|
+
outgoing.write(error instanceof InvalidHttpRequest ? error.message : 'Internal Server Error');
|
|
135
|
+
await finish();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
body?.cancellation.removeEventListener('abort', closeAfterResponse);
|
|
141
|
+
body?.stop();
|
|
142
|
+
signal.removeEventListener('abort', abort);
|
|
143
|
+
incoming.off('aborted', abort);
|
|
144
|
+
outgoing.off('close', abort);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
/** Open Node ingress only after all activation and bind policy checks have succeeded. */
|
|
148
|
+
export const nodeHttpPlugin = (options = {}) => {
|
|
149
|
+
const bind = Object.freeze({ host: options.host ?? '127.0.0.1', port: options.port ?? 0 });
|
|
150
|
+
const allowedHosts = Object.freeze((options.allowedHosts ?? []).map(normalizeHostname));
|
|
151
|
+
let address;
|
|
152
|
+
return {
|
|
153
|
+
id: 'databehave.http.node-listener', apiVersion: 1,
|
|
154
|
+
requires: [httpCapability], provides: [listenerCapability],
|
|
155
|
+
setup(ctx) { ctx.provide(listenerCapability, { get address() { return address; } }); },
|
|
156
|
+
async activate(ctx) { await ctx.use(httpCapability).checkBind(bind); },
|
|
157
|
+
async open(ctx) {
|
|
158
|
+
const http = ctx.use(httpCapability);
|
|
159
|
+
const sockets = new Set();
|
|
160
|
+
const server = createServer((incoming, outgoing) => { void handleRequest(http, incoming, outgoing, ctx.signal, bind, allowedHosts); });
|
|
161
|
+
server.on('connection', (socket) => { sockets.add(socket); socket.once('close', () => sockets.delete(socket)); });
|
|
162
|
+
const listening = new AbortController();
|
|
163
|
+
let stopped = false;
|
|
164
|
+
let resolveClosed;
|
|
165
|
+
const closed = new Promise((resolve) => { resolveClosed = resolve; });
|
|
166
|
+
const stopAccepting = () => {
|
|
167
|
+
if (stopped)
|
|
168
|
+
return;
|
|
169
|
+
stopped = true;
|
|
170
|
+
server.close(() => { address = undefined; resolveClosed?.(); });
|
|
171
|
+
server.closeIdleConnections();
|
|
172
|
+
listening.abort();
|
|
173
|
+
};
|
|
174
|
+
ctx.ownIngress({
|
|
175
|
+
stopAccepting,
|
|
176
|
+
async drainAndClose() {
|
|
177
|
+
stopAccepting();
|
|
178
|
+
const timer = setTimeout(() => { for (const socket of sockets)
|
|
179
|
+
socket.destroy(); }, Math.max(0, (http.drainDeadline ?? performance.now() + http.drainTimeoutMs) - performance.now()));
|
|
180
|
+
try {
|
|
181
|
+
await closed;
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
clearTimeout(timer);
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
await new Promise((resolve, reject) => {
|
|
189
|
+
const removeStartupListeners = () => {
|
|
190
|
+
server.off('error', onError);
|
|
191
|
+
server.off('listening', onListening);
|
|
192
|
+
listening.signal.removeEventListener('abort', onAbort);
|
|
193
|
+
};
|
|
194
|
+
const onError = (error) => { removeStartupListeners(); reject(error); };
|
|
195
|
+
const onListening = () => { removeStartupListeners(); resolve(); };
|
|
196
|
+
const onAbort = () => { removeStartupListeners(); reject(new Error('HTTP listener closed during startup')); };
|
|
197
|
+
server.once('error', onError);
|
|
198
|
+
server.once('listening', onListening);
|
|
199
|
+
listening.signal.addEventListener('abort', onAbort, { once: true });
|
|
200
|
+
server.listen({ ...bind, signal: listening.signal });
|
|
201
|
+
});
|
|
202
|
+
const bound = server.address();
|
|
203
|
+
if (!bound || typeof bound === 'string')
|
|
204
|
+
throw new Error('Node HTTP listener did not return a TCP address');
|
|
205
|
+
address = Object.freeze({ host: bound.address, port: bound.port });
|
|
206
|
+
server.on('error', (error) => handleRuntimeServerError(error, options.onServerError));
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
};
|
|
210
|
+
//# sourceMappingURL=node-http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-http.js","sourceRoot":"","sources":["../src/node-http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Plugin } from '@databehave/core';
|
|
2
|
+
import { type HttpObservation } from '../http.js';
|
|
3
|
+
import { type LogFormatOptions } from '../log.js';
|
|
4
|
+
/** Access logging is opt-in; filtering and output belong to the caller. */
|
|
5
|
+
export interface AccessLogOptions extends LogFormatOptions {
|
|
6
|
+
readonly filter?: (observation: HttpObservation) => boolean;
|
|
7
|
+
readonly write?: (line: string) => void;
|
|
8
|
+
}
|
|
9
|
+
/** Observe each final request outcome once, including HEAD and transport termination. */
|
|
10
|
+
export declare const accessLogPlugin: (options?: AccessLogOptions) => Plugin;
|
|
11
|
+
//# sourceMappingURL=access-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-log.d.ts","sourceRoot":"","sources":["../../src/plugins/access-log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AACjE,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAElE,2EAA2E;AAC3E,MAAM,WAAW,gBAAiB,SAAQ,gBAAgB;IACxD,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,eAAe,KAAK,OAAO,CAAA;IAC3D,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC;AAED,yFAAyF;AACzF,eAAO,MAAM,eAAe,GAAI,UAAS,gBAAqB,KAAG,MAmB/D,CAAA"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { httpCapability } from '../http.js';
|
|
2
|
+
import { formatAccessLog } from '../log.js';
|
|
3
|
+
/** Observe each final request outcome once, including HEAD and transport termination. */
|
|
4
|
+
export const accessLogPlugin = (options = {}) => ({
|
|
5
|
+
id: 'databehave.http.access-log', apiVersion: 1, requires: [httpCapability],
|
|
6
|
+
setup(ctx) {
|
|
7
|
+
ctx.use(httpCapability).registerMiddleware({
|
|
8
|
+
owner: 'databehave.http.access-log', order: ctx.order, stage: 'observation',
|
|
9
|
+
handle(observation) {
|
|
10
|
+
if (options.filter && !options.filter(observation))
|
|
11
|
+
return;
|
|
12
|
+
const { request, outcome, durationMs, error } = observation;
|
|
13
|
+
const status = outcome.kind === 'response' ? outcome.response.status : 'terminated';
|
|
14
|
+
const line = formatAccessLog({
|
|
15
|
+
method: request.method.toUpperCase(), path: request.path, status, ms: Math.round(durationMs),
|
|
16
|
+
bytes: outcome.kind === 'response' ? outcome.response.headers.get('content-length') ?? '-' : '-',
|
|
17
|
+
...(error === undefined ? {} : { error: error instanceof Error ? error.message : String(error) }),
|
|
18
|
+
}, options);
|
|
19
|
+
if (options.write)
|
|
20
|
+
options.write(line);
|
|
21
|
+
else
|
|
22
|
+
process.stdout.write(line);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=access-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-log.js","sourceRoot":"","sources":["../../src/plugins/access-log.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAwB,MAAM,YAAY,CAAA;AACjE,OAAO,EAAE,eAAe,EAAyB,MAAM,WAAW,CAAA;AAQlE,yFAAyF;AACzF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,UAA4B,EAAE,EAAU,EAAE,CAAC,CAAC;IAC1E,EAAE,EAAE,4BAA4B,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,cAAc,CAAC;IAC3E,KAAK,CAAC,GAAG;QACP,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,kBAAkB,CAAC;YACzC,KAAK,EAAE,4BAA4B,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,aAAa;YAC3E,MAAM,CAAC,WAAW;gBAChB,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC;oBAAE,OAAM;gBAC1D,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,WAAW,CAAA;gBAC3D,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAA;gBACnF,MAAM,IAAI,GAAG,eAAe,CAAC;oBAC3B,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;oBAC5F,KAAK,EAAE,OAAO,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG;oBAChG,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;iBAClG,EAAE,OAAO,CAAC,CAAA;gBACX,IAAI,OAAO,CAAC,KAAK;oBAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;;oBACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YACjC,CAAC;SACF,CAAC,CAAA;IACJ,CAAC;CACF,CAAC,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Plugin } from '@databehave/core';
|
|
2
|
+
import type { CorsConfig } from '../types.js';
|
|
3
|
+
/** Apply application CORS only to resolved routes, honoring each route owner's policy. */
|
|
4
|
+
export declare const corsPlugin: (options?: CorsConfig) => Plugin;
|
|
5
|
+
//# sourceMappingURL=cors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../../src/plugins/cors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAG9C,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,aAAa,CAAA;AAE1D,0FAA0F;AAC1F,eAAO,MAAM,UAAU,GAAI,UAAS,UAAe,KAAG,MA0BpD,CAAA"}
|