@pulzar/core
Version:
Next-generation Node.js framework for ultra-fast web applications with zero-reflection DI, GraphQL, WebSockets, events, and edge runtime support
276 lines • 8.41 kB
JavaScript
import { AsyncLocalStorage } from "async_hooks";
import { randomBytes } from "crypto";
import { logger } from "../utils/logger";
// AsyncLocalStorage instance for request context
const requestContextStorage = new AsyncLocalStorage();
/**
* Get the current request context
*/
export function getRequestContext() {
return requestContextStorage.getStore();
}
/**
* Set request context data
*/
export function setRequestContext(context) {
const current = requestContextStorage.getStore();
if (current) {
// Merge with existing context
Object.assign(current, context);
}
}
/**
* Update specific fields in request context
*/
export function updateRequestContext(updates) {
const current = requestContextStorage.getStore();
if (current) {
Object.assign(current, updates);
}
}
/**
* Run a function within a request context
*/
export function runWithRequestContext(context, fn) {
return requestContextStorage.run(context, fn);
}
/**
* Get a specific value from request context
*/
export function getContextValue(key) {
const context = requestContextStorage.getStore();
return context?.[key];
}
/**
* Set a specific value in request context
*/
export function setContextValue(key, value) {
const context = requestContextStorage.getStore();
if (context) {
context[key] = value;
}
}
/**
* Get metadata from request context
*/
export function getContextMetadata(key) {
const context = requestContextStorage.getStore();
return context?.metadata[key];
}
/**
* Set metadata in request context
*/
export function setContextMetadata(key, value) {
const context = requestContextStorage.getStore();
if (context) {
context.metadata[key] = value;
}
}
/**
* Generate a unique request ID
*/
export function generateRequestId() {
return randomBytes(16).toString("hex");
}
/**
* Generate a unique trace ID
*/
export function generateTraceId() {
return randomBytes(16).toString("hex");
}
/**
* Generate a unique span ID
*/
export function generateSpanId() {
return randomBytes(8).toString("hex");
}
/**
* Extract trace information from headers
*/
export function extractTraceInfo(request) {
const headers = request.headers;
return {
traceId: headers["x-trace-id"] ||
headers["traceparent"]?.split("-")[1] ||
generateTraceId(),
spanId: headers["x-span-id"] ||
headers["traceparent"]?.split("-")[2] ||
generateSpanId(),
correlationId: headers["x-correlation-id"],
};
}
/**
* Create request context from Fastify request
*/
export function createRequestContext(request) {
const requestId = request.headers["x-request-id"] || generateRequestId();
const traceInfo = extractTraceInfo(request);
return {
requestId,
traceId: traceInfo.traceId,
spanId: traceInfo.spanId,
correlationId: traceInfo.correlationId,
startTime: performance.now(),
ip: request.ip,
userAgent: request.headers["user-agent"],
metadata: {},
};
}
/**
* Fastify plugin for request context
*/
export async function requestContextPlugin(fastify) {
// Add request context to all requests
fastify.addHook("onRequest", async (request, reply) => {
const context = createRequestContext(request);
// Store in AsyncLocalStorage
requestContextStorage.enterWith(context);
// Add to request object for easy access
request.context = context;
// Set response headers
reply.header("x-request-id", context.requestId);
if (context.traceId) {
reply.header("x-trace-id", context.traceId);
}
logger.debug("Request context created", {
requestId: context.requestId,
traceId: context.traceId,
method: request.method,
url: request.url,
});
});
// Log request completion
fastify.addHook("onResponse", async (request, reply) => {
const context = getRequestContext();
if (context) {
const duration = performance.now() - context.startTime;
logger.info("Request completed", {
requestId: context.requestId,
traceId: context.traceId,
method: request.method,
url: request.url,
statusCode: reply.statusCode,
duration: `${duration.toFixed(2)}ms`,
userAgent: context.userAgent,
ip: context.ip,
});
}
});
// Log errors with context
fastify.addHook("onError", async (request, reply, error) => {
const context = getRequestContext();
logger.error("Request error", {
requestId: context?.requestId,
traceId: context?.traceId,
method: request.method,
url: request.url,
error: error.message,
stack: error.stack,
});
});
}
/**
* Middleware to extract user information from JWT or session
*/
export function extractUserContext(token, session) {
const updates = {};
// Extract from JWT token
if (token) {
try {
// This is a simplified JWT parsing
// In a real implementation, you'd properly verify and decode the JWT
const tokenParts = token.split(".");
if (tokenParts.length >= 2 && tokenParts[1]) {
const payload = JSON.parse(Buffer.from(tokenParts[1], "base64").toString());
updates.userId = payload.sub || payload.userId;
updates.userRoles = payload.roles || [];
updates.userPermissions = payload.permissions || [];
}
}
catch (error) {
logger.warn("Failed to parse JWT token for user context", { error });
}
}
// Extract from session
if (session?.user) {
updates.userId = session.user.id;
updates.userRoles = session.user.roles || [];
updates.userPermissions = session.user.permissions || [];
}
return updates;
}
/**
* Check if user has required role
*/
export function hasRole(role) {
const context = getRequestContext();
return context?.userRoles?.includes(role) ?? false;
}
/**
* Check if user has required permission
*/
export function hasPermission(permission) {
const context = getRequestContext();
return context?.userPermissions?.includes(permission) ?? false;
}
/**
* Check if user has any of the required roles
*/
export function hasAnyRole(roles) {
const context = getRequestContext();
if (!context?.userRoles)
return false;
return roles.some((role) => context.userRoles.includes(role));
}
/**
* Check if user has any of the required permissions
*/
export function hasAnyPermission(permissions) {
const context = getRequestContext();
if (!context?.userPermissions)
return false;
return permissions.some((permission) => context.userPermissions.includes(permission));
}
/**
* Get user-specific child logger
*/
export function getUserLogger() {
const context = getRequestContext();
return logger.child({
requestId: context?.requestId,
traceId: context?.traceId,
userId: context?.userId,
});
}
/**
* Measure operation performance within request context
*/
export async function measureOperation(operationName, operation) {
const startTime = performance.now();
const context = getRequestContext();
try {
const result = await operation();
const duration = performance.now() - startTime;
logger.debug("Operation completed", {
operation: operationName,
duration: `${duration.toFixed(2)}ms`,
requestId: context?.requestId,
traceId: context?.traceId,
});
return result;
}
catch (error) {
const duration = performance.now() - startTime;
logger.error("Operation failed", {
operation: operationName,
duration: `${duration.toFixed(2)}ms`,
requestId: context?.requestId,
traceId: context?.traceId,
error: error instanceof Error ? error.message : String(error),
});
throw error;
}
}
// Export the storage instance for advanced usage
export { requestContextStorage };
//# sourceMappingURL=request-context.js.map