UNPKG

@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
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