File size: 2,966 Bytes
6111b2b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**

 * Request ID — Correlation ID Middleware (T-23)

 *

 * Generates and propagates `x-request-id` headers for

 * request tracing across the proxy pipeline.

 *

 * Uses AsyncLocalStorage to make request ID available

 * anywhere in the call stack without explicit passing.

 *

 * @module shared/utils/requestId

 */

import { AsyncLocalStorage } from "node:async_hooks";
import { randomUUID } from "crypto";

type HeaderReader = {
  get?: (name: string) => string | null | undefined;
};

type RequestLike = {
  headers?: HeaderReader | null;
} | null;

const requestIdStore = new AsyncLocalStorage<string>();

function getHeaderValue(request: RequestLike, name: string): string | null {
  const value = request?.headers?.get?.(name);
  return typeof value === "string" && value.length > 0 ? value : null;
}

/**

 * Get the current request ID from the async context.

 * Returns null if not inside a request context.

 *

 * @returns {string | null}

 */
export function getRequestId() {
  return requestIdStore.getStore() || null;
}

/**

 * Run a handler with a request ID in the async context.

 * If the incoming request has an `x-request-id` header, it is reused;

 * otherwise a new UUID v4 is generated.

 *

 * @template T

 * @param {Request} request - Incoming request

 * @param {() => T | Promise<T>} handler - Handler to execute

 * @returns {Promise<T>}

 */
export async function withRequestId<T>(
  request: RequestLike,
  handler: () => T | Promise<T>
): Promise<T> {
  const existingId = getHeaderValue(request, "x-request-id");
  const requestId = existingId || randomUUID();
  return requestIdStore.run(requestId, handler);
}

/**

 * Create headers object with the current request ID.

 * Useful for outgoing provider requests.

 *

 * @param {Record<string, string>} [headers={}] - Existing headers

 * @returns {Record<string, string>} Headers with x-request-id added

 */
export function addRequestIdHeader(headers: Record<string, string> = {}): Record<string, string> {
  const requestId = getRequestId();
  if (requestId) {
    return { ...headers, "x-request-id": requestId };
  }
  return headers;
}

/**

 * Next.js middleware-compatible wrapper.

 * Attaches `x-request-id` to response headers.

 *

 * @param {Request} request

 * @param {Response} response

 * @returns {Response} Response with request ID header

 */
export function attachRequestIdToResponse(request: RequestLike, response: Response): Response {
  const requestId = getRequestId() || getHeaderValue(request, "x-request-id") || randomUUID();

  const headers = new Headers(response.headers);
  headers.set("x-request-id", requestId);

  return new Response(response.body, {
    status: response.status,
    statusText: response.statusText,
    headers,
  });
}

/**

 * Generate a new request ID (UUID v4).

 * @returns {string}

 */
export function generateRequestId() {
  return randomUUID();
}