@cosmicdrift/kumiko-framework 0.285.2 → 0.286.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/package.json +4 -4
- package/src/api/index.ts +4 -1
- package/src/api/request-id-middleware.ts +42 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.286.0",
|
|
4
4
|
"description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -198,8 +198,8 @@
|
|
|
198
198
|
"./package.json": "./package.json"
|
|
199
199
|
},
|
|
200
200
|
"dependencies": {
|
|
201
|
-
"@cosmicdrift/kumiko-http": "0.
|
|
202
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
201
|
+
"@cosmicdrift/kumiko-http": "0.286.0",
|
|
202
|
+
"@cosmicdrift/kumiko-types": "0.286.0",
|
|
203
203
|
"bullmq": "^5.76.7",
|
|
204
204
|
"bun-types": "^1.3.13",
|
|
205
205
|
"hono": "^4.13.1",
|
|
@@ -215,7 +215,7 @@
|
|
|
215
215
|
"zod": "^4.4.3"
|
|
216
216
|
},
|
|
217
217
|
"devDependencies": {
|
|
218
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
218
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.286.0",
|
|
219
219
|
"bun-types": "^1.3.13",
|
|
220
220
|
"pino-pretty": "^13.1.3"
|
|
221
221
|
},
|
package/src/api/index.ts
CHANGED
|
@@ -44,7 +44,10 @@ export { patAllows, qnMatches } from "./pat-scope";
|
|
|
44
44
|
export type { RedisSseBroker, RedisSseBrokerOptions } from "./redis-sse-broker";
|
|
45
45
|
export { createDefaultSseBroker, createRedisSseBroker, isRedisSseBroker } from "./redis-sse-broker";
|
|
46
46
|
export { type RequestContextData, requestContext } from "./request-context";
|
|
47
|
-
export {
|
|
47
|
+
export {
|
|
48
|
+
buildRequestContextDataFromRequest,
|
|
49
|
+
requestIdMiddleware,
|
|
50
|
+
} from "./request-id-middleware";
|
|
48
51
|
export { createApiRoutes } from "./routes";
|
|
49
52
|
export type { KumikoServer, ServerOptions } from "./server";
|
|
50
53
|
export { buildServer } from "./server";
|
|
@@ -16,43 +16,47 @@ function sanitizeClientId(value: string | undefined): string | undefined {
|
|
|
16
16
|
return value !== undefined && SAFE_ID_RE.test(value) ? value : undefined;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// Request.headers.get() returns `string | null` (Fetch API); Hono's
|
|
20
|
+
// c.req.header() normalizes that to `string | undefined`. Match Hono's
|
|
21
|
+
// contract here so both builders return the exact same RequestContextData
|
|
22
|
+
// shape regardless of which one a call-site uses.
|
|
23
|
+
function header(req: Request, name: string): string | undefined {
|
|
24
|
+
return req.headers.get(name) ?? undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
/**
|
|
20
|
-
* Builds the RequestContextData record for a
|
|
28
|
+
* Builds the RequestContextData record for a raw Fetch Request — requestId
|
|
21
29
|
* (client-supplied + sanitized, or generated), correlationId (mirrors
|
|
22
30
|
* requestId unless the client set its own), the underlying abort signal,
|
|
23
|
-
* and the client IP/User-Agent. Extracted out of `
|
|
24
|
-
* call-sites that
|
|
25
|
-
*
|
|
26
|
-
* the same AsyncLocalStorage
|
|
31
|
+
* and the client IP/User-Agent. Extracted out of `buildRequestContextData`
|
|
32
|
+
* so call-sites that only have a `Request` (no Hono `Context`) — e.g.
|
|
33
|
+
* server-runtime's static-fallback page-head resolver, which runs outside
|
|
34
|
+
* Hono's router entirely — can still populate the same AsyncLocalStorage
|
|
35
|
+
* record via `requestContext.run(...)`.
|
|
27
36
|
*/
|
|
28
|
-
export function
|
|
29
|
-
const requestId =
|
|
30
|
-
|
|
31
|
-
const correlationId = sanitizeClientId(c.req.header(CORRELATION_ID_HEADER)) ?? requestId;
|
|
37
|
+
export function buildRequestContextDataFromRequest(req: Request): RequestContextData {
|
|
38
|
+
const requestId = sanitizeClientId(header(req, REQUEST_ID_HEADER)) ?? requestContext.generateId();
|
|
39
|
+
const correlationId = sanitizeClientId(header(req, CORRELATION_ID_HEADER)) ?? requestId;
|
|
32
40
|
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
// adapter combos may not populate `c.req.raw.signal`; conditional
|
|
38
|
-
// spread keeps `signal: undefined` out of the stored record so
|
|
39
|
-
// downstream `signal?` checks behave as if no signal exists.
|
|
40
|
-
const signal = c.req.raw?.signal;
|
|
41
|
+
// The Fetch Request's `signal` aborts when the client disconnects (mobile
|
|
42
|
+
// back-press, tab close). We propagate it through requestContext so
|
|
43
|
+
// framework internals can honour cancellation at long-running checkpoints.
|
|
44
|
+
const signal = req.signal;
|
|
41
45
|
// Client IP for per-IP rate limiting. Trust `x-forwarded-for` when
|
|
42
46
|
// present (proxy/CDN) — first hop is the originating client. Adapter-
|
|
43
47
|
// specific socket-address fallback (bun, node) is not standardized
|
|
44
48
|
// in Hono; deployments behind a proxy should always set xff. Without
|
|
45
49
|
// either we leave `ip` undefined and skip ip-bucketed checks rather
|
|
46
50
|
// than fabricate one.
|
|
47
|
-
const xff =
|
|
51
|
+
const xff = header(req, "x-forwarded-for");
|
|
48
52
|
const ip = xff?.split(",")[0]?.trim();
|
|
49
|
-
const userAgent =
|
|
53
|
+
const userAgent = header(req, "user-agent");
|
|
50
54
|
// Runs before auth-middleware, so this reaches public routes too (e.g.
|
|
51
55
|
// signup-request) — that's the whole point: the active UI locale must
|
|
52
56
|
// survive to anonymous callers, not just authenticated ones.
|
|
53
57
|
const locale = resolveHeaderLocale({
|
|
54
|
-
headerLocale:
|
|
55
|
-
acceptLanguage:
|
|
58
|
+
headerLocale: header(req, LOCALE_HEADER_NAME),
|
|
59
|
+
acceptLanguage: header(req, "accept-language"),
|
|
56
60
|
});
|
|
57
61
|
|
|
58
62
|
return {
|
|
@@ -65,6 +69,23 @@ export function buildRequestContextData(c: Context): RequestContextData {
|
|
|
65
69
|
};
|
|
66
70
|
}
|
|
67
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Builds the RequestContextData record for a Hono request. Thin wrapper
|
|
74
|
+
* around `buildRequestContextDataFromRequest(c.req.raw)` — kept as its own
|
|
75
|
+
* export because most call-sites (server.ts's httpRoute→systemQuery mount,
|
|
76
|
+
* `requestIdMiddleware` below) already hold a Hono `Context`.
|
|
77
|
+
*/
|
|
78
|
+
export function buildRequestContextData(c: Context): RequestContextData {
|
|
79
|
+
// Older Hono / adapter combos may leave c.req.raw unset even though it's
|
|
80
|
+
// typed as Request — degrade to a bare id pair (no signal/ip/ua/locale)
|
|
81
|
+
// instead of letting req.headers.get() throw on every request.
|
|
82
|
+
if (!c.req.raw) {
|
|
83
|
+
const requestId = requestContext.generateId();
|
|
84
|
+
return { requestId, correlationId: requestId };
|
|
85
|
+
}
|
|
86
|
+
return buildRequestContextDataFromRequest(c.req.raw);
|
|
87
|
+
}
|
|
88
|
+
|
|
68
89
|
/**
|
|
69
90
|
* Assigns a requestId + correlationId to every request and wraps execution
|
|
70
91
|
* in AsyncLocalStorage. Runs BEFORE auth — both ids are available even for
|