UNPKG

@psenger/async-context-id

Version:

A lightweight, powerful correlation ID tracking and context store written for Node.js that automatically propagates context through async operations using Node's `async_hooks`. Perfect for distributed tracing, request tracking, and logging correlation in

1 lines 18.6 kB
{"version":3,"file":"index.mjs","sources":["../src/async-context-id.js","../src/lru-map.js","../src/timed-map.js"],"sourcesContent":["import asyncHooks from 'async_hooks'\n\nlet instance = null\n\n/**\n * A singleton class that tracks correlation IDs across asynchronous operations in Node.js.\n * Uses async_hooks to automatically propagate correlation context across async boundaries.\n *\n * @class\n * @description This class provides context propagation across async operations in Node.js\n * applications. It maintains correlation IDs and metadata throughout the async execution chain.\n *\n * @property {Map} contextStore - Storage for async context data\n * @property {AsyncHook} hook - The async_hooks instance for tracking async operations\n * @property {Function} cleanUpFn - Cleanup function registered for process exit\n *\n * @tutorial initializing-async-context-id-in-a-controller\n * @tutorial monkey-patch-logs\n * @tutorial down-stream-http-hand-off\n * @tutorial winston\n */\nclass AsyncContextId {\n contextStore\n correlationIdFn\n hook\n cleanUpFn\n\n /**\n * Creates a new AsyncContextId instance or returns the existing singleton.\n * Initializes async hooks and sets up process cleanup.\n *\n * @param {Object} [options={}] - Configuration options\n * @param {Map} [options.store=new Map()] - Optional Map instance for context storage, this package includes both a LRU ( Least Recently Used ) Map and a Timed Map.\n * @param {fn} [options.correlationIdFn] -Optional function to override default UUID generation. Should return a string\n * @returns {AsyncContextId} The singleton instance\n * @example\n * // Using default Map and UUID generation\n * const tracker = new AsyncContextId();\n *\n * @example\n * // Using custom LRU Map and correlation ID generator\n * const tracker = new AsyncContextId({\n * store: new LruMap(1000),\n * correlationIdFn: () => `custom-${Date.now()}`\n * });\n */\n constructor({store, correlationIdFn} = {\n store: new Map(), correlationIdFn: null\n }) {\n if (instance) {\n return instance\n }\n this.contextStore = store\n this.correlationIdFn = correlationIdFn\n this.hook = asyncHooks.createHook({\n init: (asyncId, type, triggerAsyncId) => {\n // Copy parent context for new async operations to maintain same correlation ID\n if (this.contextStore.has(triggerAsyncId)) {\n const parentContext = this.contextStore.get(triggerAsyncId)\n const newContext = JSON.parse(JSON.stringify(parentContext))\n this.contextStore.set(asyncId, newContext)\n }\n },\n promiseResolve: (asyncId) => {\n const context = this.contextStore.get(asyncId)\n if (context) {\n const currentAsyncId = asyncHooks.executionAsyncId()\n this.contextStore.set(\n currentAsyncId,\n JSON.parse(JSON.stringify(context)),\n )\n }\n },\n destroy: (asyncId) => {\n this.contextStore.delete(asyncId)\n },\n })\n\n /**\n * remove the listener of itself to prevent memory leaks\n */\n this.cleanUpFn = () => {\n process.removeListener('beforeExit', this.cleanUpFn)\n this.hook.disable()\n this.contextStore.clear()\n }\n this.hook.enable()\n process.on('beforeExit', this.cleanUpFn)\n instance = this\n return this\n }\n\n /**\n * Generates a correlation ID for tracking.\n * Uses custom correlation ID generator if provided, otherwise generates UUID v4.\n *\n * @private\n * @returns {string} A correlation ID string\n * @throws {Error} If custom correlationIdFn throws or returns non-string\n *\n * @example\n * // Using default UUID v4 generator\n * const id = this.generateCorrelationId();\n * // Returns: \"123e4567-e89b-12d3-a456-426614174000\"\n *\n * @example\n * // Using custom generator\n * const tracker = new AsyncContextId({\n * correlationIdFn: () => `custom-${Date.now()}`\n * });\n * const id = tracker.generateCorrelationId();\n * // Returns: \"custom-1708704000000\"\n */\n generateCorrelationId() {\n if (this.correlationIdFn) {\n return this.correlationIdFn()\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n }\n\n /**\n * Retrieves the correlation ID for the current async context.\n * Creates a new context with generated ID if none exists.\n *\n * @returns {string} The current correlation ID\n * @throws {Error} If async hooks are not enabled\n *\n * @example\n * const correlationId = tracker.getCorrelationId();\n * res.setHeader('x-correlation-id', correlationId);\n */\n getCorrelationId() {\n const asyncId = asyncHooks.executionAsyncId()\n if (!this.contextStore.has(asyncId)) {\n this.contextStore.set(asyncId, {\n correlationId: this.generateCorrelationId(),\n startTime: Date.now(),\n metadata: {},\n })\n }\n return this.contextStore.get(asyncId).correlationId\n }\n\n /**\n * Sets the correlation ID for the current async context.\n * Creates a new context if none exists.\n *\n * @param {string} correlationId - The correlation ID to set\n * @throws {Error} If the correlationId is not a string\n * @throws {Error} If async hooks are not enabled\n *\n * @example\n * const upstreamId = req.headers['x-correlation-id'];\n * if (upstreamId) {\n * tracker.setCorrelationId(upstreamId);\n * }\n */\n setCorrelationId(correlationId = this.generateCorrelationId()) {\n const asyncId = asyncHooks.executionAsyncId()\n if (!this.contextStore.has(asyncId)) {\n this.contextStore.set(asyncId, {\n correlationId,\n startTime: Date.now(),\n metadata: {},\n })\n } else {\n const existingContext = this.contextStore.get(asyncId)\n const newContext = JSON.parse(JSON.stringify(existingContext))\n newContext.correlationId = correlationId\n this.contextStore.set(asyncId, newContext)\n }\n return correlationId\n }\n\n /**\n * Retrieves the complete context object for the current async operation.\n * Creates a new context if none exists.\n *\n * @returns {Object} The correlation context\n * @returns {string} context.correlationId - The correlation ID\n * @returns {number} context.startTime - Unix timestamp of context creation\n * @returns {Object} context.metadata - Custom metadata object\n * @throws {Error} If async hooks are not enabled\n *\n * @example\n * const context = tracker.getContext();\n * console.log({\n * correlationId: context.correlationId,\n * duration: Date.now() - context.startTime,\n * metadata: context.metadata\n * });\n */\n getContext() {\n const asyncId = asyncHooks.executionAsyncId()\n if (!this.contextStore.has(asyncId)) {\n this.contextStore.set(asyncId, {\n correlationId: this.generateCorrelationId(),\n startTime: Date.now(),\n metadata: {},\n })\n }\n return JSON.parse(JSON.stringify(this.contextStore.get(asyncId)))\n }\n\n /**\n * Updates the context for the current async operation.\n * Creates a new context if none exists. Preserves existing correlationId\n * and startTime unless explicitly overridden.\n *\n * @param {Object} [context={}] - The context object to merge\n * @param {string} [context.correlationId] - Optional correlation ID override\n * @param {Object} [context.metadata] - Optional metadata to merge\n * @throws {Error} If async hooks are not enabled\n * @throws {Error} If context is not an object\n *\n * @example\n * // Add request context\n * tracker.setContext({\n * metadata: {\n * operation: 'processData',\n * requestId: req.id,\n * userId: req.user.id\n * }\n * });\n */\n setContext(context = {}) {\n const asyncId = asyncHooks.executionAsyncId()\n const existingContext = this.contextStore.get(asyncId) || {\n correlationId: this.generateCorrelationId(),\n startTime: Date.now(),\n metadata: {},\n }\n const clonedExisting = JSON.parse(JSON.stringify(existingContext))\n const clonedNew = JSON.parse(JSON.stringify(context))\n Object.assign(clonedExisting, clonedNew)\n clonedExisting.correlationId =\n clonedNew.correlationId || clonedExisting.correlationId\n clonedExisting.startTime = existingContext.startTime\n this.contextStore.set(asyncId, clonedExisting)\n }\n\n /**\n * Removes the correlation context for the current async operation.\n *\n * @throws {Error} If async hooks are not enabled\n *\n * @example\n * try {\n * await processRequest(data);\n * } finally {\n * tracker.clear();\n * }\n */\n clear() {\n const asyncId = asyncHooks.executionAsyncId()\n this.contextStore.delete(asyncId)\n }\n}\n\nexport default AsyncContextId\nexport {instance}\n","/**\n * A Map extension implementing Least Recently Used (LRU) caching strategy.\n * Automatically removes oldest entries when size limit is reached.\n * @extends {Map}\n *\n * @example\n * const cache = new LruMap(3);\n * cache.set('a', 1).set('b', 2).set('c', 3);\n * cache.set('d', 4); // Removes 'a', now contains b,c,d\n */\nclass LruMap extends Map {\n /**\n * Creates an LRU cache with specified maximum size.\n * @param {number} maxSize - Maximum number of entries\n *\n * @example\n * const cache = new LruMap(1000);\n */\n constructor(maxSize) {\n super()\n this.maxSize = maxSize\n }\n\n /**\n * Sets a value, removing oldest entry if size limit reached.\n * @param {*} key - The key to set\n * @param {*} value - The value to store\n * @returns {this} The LruMap instance for chaining\n *\n * @example\n * const cache = new LruMap(2);\n * cache.set('key1', 'value1')\n * .set('key2', 'value2')\n * .set('key3', 'value3'); // Removes key1\n */\n set(key, value) {\n if (!this.has(key) && this.size >= this.maxSize) {\n const firstKey = this.keys().next().value\n this.delete(firstKey)\n }\n return super.set(key, value)\n }\n}\nexport default LruMap\n","/**\n * A Map extension that automatically deletes entries after a specified time-to-live (TTL).\n * @extends {Map}\n *\n * @example\n * const cache = new TimedMap(5000); // 5 second TTL\n * cache.set('key1', 'value1');\n * console.log(cache.get('key1')); // 'value1'\n * // After 5 seconds:\n * console.log(cache.get('key1')); // undefined\n *\n * @example\n * // Resetting TTL on value update\n * const cache = new TimedMap(2000);\n * cache.set('user', { name: 'Alice' });\n * // 1 second later:\n * cache.set('user', { name: 'Alice', age: 30 }); // Resets the 2-second timer\n */\nclass TimedMap extends Map {\n /**\n * Creates a new TimedMap instance.\n * @param {number} ttl - Time-to-live in milliseconds for each key-value pair\n *\n * @example\n * const sessionCache = new TimedMap(1800000); // 30 minute TTL\n */\n constructor(ttl) {\n super()\n this.ttl = ttl\n this.timeouts = new Map()\n }\n\n /**\n * Sets a value in the map with an automatic deletion timer.\n * If the key already exists, its timer is reset.\n * @param {*} key - The key to set\n * @param {*} value - The value to store\n * @returns {this} The TimedMap instance for chaining\n *\n * @example\n * const cache = new TimedMap(10000);\n * cache.set('apiKey', 'xyz123')\n * .set('timestamp', Date.now());\n */\n set(key, value) {\n if (this.timeouts.has(key)) clearTimeout(this.timeouts.get(key))\n this.timeouts.set(\n key,\n setTimeout(() => this.delete(key), this.ttl),\n )\n return super.set(key, value)\n }\n\n /**\n * Deletes a key-value pair and its associated timer.\n * @param {*} key - The key to delete\n * @returns {boolean} True if the element was deleted, false if it didn't exist\n *\n * @example\n * const cache = new TimedMap(5000);\n * cache.set('temp', 'data');\n * cache.delete('temp'); // Manually delete before TTL expires\n */\n delete(key) {\n if (this.timeouts.has(key)) {\n clearTimeout(this.timeouts.get(key))\n this.timeouts.delete(key)\n }\n return super.delete(key)\n }\n}\nexport default TimedMap\n"],"names":[],"mappings":"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"}