diff --git a/README.md b/README.md index 516f2a0..ca7423b 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,37 @@ stop() // stop 2 => 100+ms stop('foo') // => 100+ms ``` +### Merging with Existing Server-Timing Headers + +The library now supports merging `header-timers` values with existing Server-Timing headers without overwriting. This can be particularly useful when managing multiple instances of timers or incorporating user-provided headers. + +#### Using `mergeWithExisting` Method + +The `mergeWithExisting` method allows for the combination of `header-timers` values with pre-existing Server-Timing headers. It can take either a string representing the existing Server-Timing header or an object containing all headers, including the Server-Timing entry. + +##### Example with String Input + +```js +const existingHeaders = 'cache;desc="Cache Read";dur=23.2'; +const mergedHeaders = timers.mergeWithExisting(existingHeaders); +console.log(mergedHeaders); +// Outputs: "Server-Timing: cache;desc="Cache Read";dur=23.2, open;dur=0.014ms, database;desc="collecting data";dur=603.512ms, analytics;desc="log request data";dur=101.475709ms" +``` + +##### Example with Object Input + +```js +const existingHeaders = { + 'Content-Type': 'application/json', + 'Server-Timing': 'cache;desc="Cache Read";dur=23.2' +}; +const mergedHeaders = timers.mergeWithExisting(existingHeaders); +console.log(mergedHeaders); +// Outputs: "Server-Timing: cache;desc="Cache Read";dur=23.2, open;dur=0.014ms, database;desc="collecting data";dur=603.512ms, analytics;desc="log request data";dur=101.475709ms" +``` + +This feature ensures that the `Server-Timing` values generated by `header-timers` can be seamlessly integrated with existing headers, providing a comprehensive view of server-side timing information without data loss. + ## Goals Be helpful and accurate. Stay small and fast. Then be intuitive. This means: diff --git a/index-test.js b/index-test.js index e19a24e..0c881a9 100644 --- a/index-test.js +++ b/index-test.js @@ -3,7 +3,7 @@ import test from 'node:test' import HeaderTimers from './index.js' test('header-timers', async (t) => { - const { key, start, stop, reset, timers, count, values, value, toObject, toString } = + const { key, start, stop, reset, timers, count, values, value, toObject, toString, mergeWithExisting } = HeaderTimers() await t.test('baseline without timers', () => { @@ -73,6 +73,33 @@ test('header-timers', async (t) => { const vals = values() assert(vals[0].startsWith('n1;dur=')) }) + + await t.test('merging with existing Server-Timing headers as string', () => { + reset() + + start('db', 'Database query') + stop('db') + + const existingHeaders = 'cache;desc="Cache Read";dur=23.2'; + const mergedHeaders = mergeWithExisting(existingHeaders); + + assert.equal(mergedHeaders, `${key}: cache;desc="Cache Read";dur=23.2, db;desc="Database query";dur=${timers()[0].ms}`); + }) + + await t.test('merging with existing Server-Timing headers as object', () => { + reset() + + start('api', 'API call') + stop('api') + + const existingHeaders = { + 'Content-Type': 'application/json', + 'Server-Timing': 'auth;desc="Authentication";dur=12.3' + }; + const mergedHeaders = mergeWithExisting(existingHeaders); + + assert.equal(mergedHeaders, `${key}: auth;desc="Authentication";dur=12.3, api;desc="API call";dur=${timers()[0].ms}`); + }) }) test('header-timers with config', async (t) => { diff --git a/index.js b/index.js index e312934..b62b178 100644 --- a/index.js +++ b/index.js @@ -15,6 +15,7 @@ const KEY = 'Server-Timing' * @property {() => Record} toObject * @property {() => string} toString * @property {() => void} reset + * @property {(existingHeaders: string | Record) => string} mergeWithExisting */ /** @@ -34,6 +35,7 @@ export default function ({ enabled = true, precision = 3, prefix = 'n', key = KE toObject: () => ({}), toString: () => '', reset: () => null, + mergeWithExisting: () => '', } } @@ -108,9 +110,28 @@ export default function ({ enabled = true, precision = 3, prefix = 'n', key = KE /** @returns {string} */ const value = () => values().join(',') /** @returns {Record} */ - const toObject = () => ({ [key]: value() }) + const toObject = (existingHeaders = {}) => { + const serverTimingHeader = value(); + if (typeof existingHeaders === 'string') { + return { [key]: existingHeaders ? `${existingHeaders}, ${serverTimingHeader}` : serverTimingHeader }; + } else if (typeof existingHeaders === 'object' && existingHeaders[key]) { + return { ...existingHeaders, [key]: `${existingHeaders[key]}, ${serverTimingHeader}` }; + } + return { ...existingHeaders, [key]: serverTimingHeader }; + } /** @returns {string} */ const toString = () => `${key}: ${value()}` + + /** + * Merges existing Server-Timing headers with header-timers values without overwriting. + * @param {string | Record} existingHeaders - The existing headers as a string or object. + * @returns {string} - The merged Server-Timing header string. + */ + function mergeWithExisting(existingHeaders) { + const mergedHeaders = toObject(existingHeaders); + return `${key}: ${mergedHeaders[key]}`; + } + /** @returns {{ name: string, description?: string, start: bigint, end?: bigint, ms?: number }[]} */ const timers = () => Array.from(_timers.values()) /** @returns {number} */ @@ -127,5 +148,6 @@ export default function ({ enabled = true, precision = 3, prefix = 'n', key = KE toObject, toString, reset, + mergeWithExisting, } }