From eefa6afd187aceb2da86a8bf7ebd3ed87fea0b65 Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Fri, 7 Feb 2025 16:15:15 -0800 Subject: [PATCH 1/7] feat: Open Utility for Merging Headers --- src/gaxios.ts | 43 +++++++++++++++++++++++-------------------- test/test.getch.ts | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/gaxios.ts b/src/gaxios.ts index 51ef2f1..513b276 100644 --- a/src/gaxios.ts +++ b/src/gaxios.ts @@ -115,10 +115,10 @@ export class Gaxios implements FetchCompliance { // prepare headers if (input && typeof input === 'object' && 'headers' in input) { - this.#mergeHeaders(headers, input.headers); + Gaxios.mergeHeaders(headers, input.headers); } if (init) { - this.#mergeHeaders(headers, new Headers(init.headers)); + Gaxios.mergeHeaders(headers, new Headers(init.headers)); } // prepare request @@ -362,23 +362,6 @@ export class Gaxios implements FetchCompliance { return promiseChain; } - /** - * Merges headers. - * - * @param base headers to append/overwrite to - * @param append headers to append/overwrite with - * @returns the base headers instance with merged `Headers` - */ - #mergeHeaders(base: Headers, append?: Headers) { - append?.forEach((value, key) => { - // set-cookie is the only header that would repeat. - // A bit of background: https://developer.mozilla.org/en-US/docs/Web/API/Headers/getSetCookie - key === 'set-cookie' ? base.append(key, value) : base.set(key, value); - }); - - return base; - } - /** * Validates the options, merges them with defaults, and prepare request. * @@ -390,7 +373,7 @@ export class Gaxios implements FetchCompliance { ): Promise { // Prepare Headers - copy in order to not mutate the original objects const preparedHeaders = new Headers(this.defaults.headers); - this.#mergeHeaders(preparedHeaders, options.headers); + Gaxios.mergeHeaders(preparedHeaders, options.headers); // Merge options const opts = extend(true, {}, this.defaults, options); @@ -664,4 +647,24 @@ export class Gaxios implements FetchCompliance { return this.#fetch; } + + /** + * Merges headers. + * + * @param base headers to append/overwrite to + * @param append headers to append/overwrite with + * @returns the base headers instance with merged `Headers` + */ + static mergeHeaders(base: HeadersInit, append?: HeadersInit): Headers { + base = base instanceof Headers ? base : new Headers(base); + append = append instanceof Headers ? append : new Headers(append); + + append.forEach((value, key) => { + // set-cookie is the only header that would repeat. + // A bit of background: https://developer.mozilla.org/en-US/docs/Web/API/Headers/getSetCookie + key === 'set-cookie' ? base.append(key, value) : base.set(key, value); + }); + + return base; + } } diff --git a/test/test.getch.ts b/test/test.getch.ts index debef58..f27473b 100644 --- a/test/test.getch.ts +++ b/test/test.getch.ts @@ -1511,3 +1511,41 @@ describe('fetch-compatible API', () => { assert.deepStrictEqual(res.data, {}); }); }); + +describe('merge headers', () => { + it('should merge headers', () => { + const base = {a: 'a'}; + const append = {b: 'b'}; + const expected = new Headers({...base, ...append}); + + const matrixBase: HeadersInit[] = [ + {...base}, + Object.entries(base), + new Headers(base), + ]; + + const matrixAppend: HeadersInit[] = [ + {...append}, + Object.entries(append), + new Headers(append), + ]; + + for (const base of matrixBase) { + for (const append of matrixAppend) { + const headers = Gaxios.mergeHeaders(base, append); + + assert.deepStrictEqual(headers, expected); + } + } + }); + + it('should merge set-cookie headers', () => { + const base = {'set-cookie': 'a=a'}; + const append = {'set-cookie': 'b=b'}; + const expected = new Headers({'set-cookie': 'a=a, b=b'}); + + const headers = Gaxios.mergeHeaders(base, append); + + assert.deepStrictEqual(headers, expected); + }); +}); From 515aa0892b8fcf2f06060e09d531529055800fee Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Fri, 7 Feb 2025 16:27:07 -0800 Subject: [PATCH 2/7] fix: standard call --- test/test.getch.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/test.getch.ts b/test/test.getch.ts index f27473b..40ce2ba 100644 --- a/test/test.getch.ts +++ b/test/test.getch.ts @@ -1539,13 +1539,16 @@ describe('merge headers', () => { } }); - it('should merge set-cookie headers', () => { + it.only('should merge set-cookie headers', () => { const base = {'set-cookie': 'a=a'}; const append = {'set-cookie': 'b=b'}; - const expected = new Headers({'set-cookie': 'a=a, b=b'}); + const expected = new Headers([ + ['set-cookie', 'a=a'], + ['set-cookie', 'b=b'], + ]); const headers = Gaxios.mergeHeaders(base, append); - assert.deepStrictEqual(headers, expected); + assert.deepStrictEqual(headers.getSetCookie(), expected.getSetCookie()); }); }); From a806b9a984a388deb1398d3a2ed11f83d867c14a Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Fri, 7 Feb 2025 16:30:51 -0800 Subject: [PATCH 3/7] fix: only --- test/test.getch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.getch.ts b/test/test.getch.ts index 40ce2ba..8c3d74b 100644 --- a/test/test.getch.ts +++ b/test/test.getch.ts @@ -1539,7 +1539,7 @@ describe('merge headers', () => { } }); - it.only('should merge set-cookie headers', () => { + it('should merge set-cookie headers', () => { const base = {'set-cookie': 'a=a'}; const append = {'set-cookie': 'b=b'}; const expected = new Headers([ From dbd95702b3863a960de45c28cc59617add913a78 Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Fri, 7 Feb 2025 17:06:55 -0800 Subject: [PATCH 4/7] chore: HeadersInit global does not exist in TS by default --- src/gaxios.ts | 2 ++ test/test.getch.ts | 9 ++------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/gaxios.ts b/src/gaxios.ts index 513b276..639ab3f 100644 --- a/src/gaxios.ts +++ b/src/gaxios.ts @@ -668,3 +668,5 @@ export class Gaxios implements FetchCompliance { return base; } } + +type HeadersInit = ConstructorParameters[0]; diff --git a/test/test.getch.ts b/test/test.getch.ts index 8c3d74b..28eecfb 100644 --- a/test/test.getch.ts +++ b/test/test.getch.ts @@ -1518,13 +1518,8 @@ describe('merge headers', () => { const append = {b: 'b'}; const expected = new Headers({...base, ...append}); - const matrixBase: HeadersInit[] = [ - {...base}, - Object.entries(base), - new Headers(base), - ]; - - const matrixAppend: HeadersInit[] = [ + const matrixBase = [{...base}, Object.entries(base), new Headers(base)]; + const matrixAppend = [ {...append}, Object.entries(append), new Headers(append), From 613cff41a9b741209662a5020cbab53de8e92281 Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Fri, 7 Feb 2025 23:23:38 -0800 Subject: [PATCH 5/7] feat: Support merging multiple headers in one call --- src/gaxios.ts | 17 ++++++++++------- test/test.getch.ts | 15 +++++++++++++-- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/gaxios.ts b/src/gaxios.ts index 639ab3f..86e4b54 100644 --- a/src/gaxios.ts +++ b/src/gaxios.ts @@ -655,15 +655,18 @@ export class Gaxios implements FetchCompliance { * @param append headers to append/overwrite with * @returns the base headers instance with merged `Headers` */ - static mergeHeaders(base: HeadersInit, append?: HeadersInit): Headers { + static mergeHeaders(base: HeadersInit, ...append: HeadersInit[]): Headers { base = base instanceof Headers ? base : new Headers(base); - append = append instanceof Headers ? append : new Headers(append); - append.forEach((value, key) => { - // set-cookie is the only header that would repeat. - // A bit of background: https://developer.mozilla.org/en-US/docs/Web/API/Headers/getSetCookie - key === 'set-cookie' ? base.append(key, value) : base.set(key, value); - }); + for (const headers of append) { + const add = headers instanceof Headers ? headers : new Headers(headers); + + add.forEach((value, key) => { + // set-cookie is the only header that would repeat. + // A bit of background: https://developer.mozilla.org/en-US/docs/Web/API/Headers/getSetCookie + key === 'set-cookie' ? base.append(key, value) : base.set(key, value); + }); + } return base; } diff --git a/test/test.getch.ts b/test/test.getch.ts index 28eecfb..d02102b 100644 --- a/test/test.getch.ts +++ b/test/test.getch.ts @@ -1513,7 +1513,7 @@ describe('fetch-compatible API', () => { }); describe('merge headers', () => { - it('should merge headers', () => { + it('should merge Headers', () => { const base = {a: 'a'}; const append = {b: 'b'}; const expected = new Headers({...base, ...append}); @@ -1534,7 +1534,18 @@ describe('merge headers', () => { } }); - it('should merge set-cookie headers', () => { + it('should merge multiple Headers', () => { + const base = {a: 'a'}; + const append = {b: 'b'}; + const appendMore = {c: 'c'}; + const expected = new Headers({...base, ...append, ...appendMore}); + + const headers = Gaxios.mergeHeaders(base, append, appendMore); + + assert.deepStrictEqual(headers, expected); + }); + + it('should merge Set-Cookie Headers', () => { const base = {'set-cookie': 'a=a'}; const append = {'set-cookie': 'b=b'}; const expected = new Headers([ From fa3061084ed446161af7c5a68bd8cd80851985cf Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Sat, 8 Feb 2025 01:24:13 -0800 Subject: [PATCH 6/7] docs: additional docs --- src/gaxios.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/gaxios.ts b/src/gaxios.ts index 86e4b54..8b41892 100644 --- a/src/gaxios.ts +++ b/src/gaxios.ts @@ -650,12 +650,20 @@ export class Gaxios implements FetchCompliance { /** * Merges headers. + * If the base headers do not exist a new `Headers` object will be returned. + * + * @remarks + * + * Using this utility can be helpful when the headers are not known to exist: + * - if they exist as `Headers`, that instance will be used + * - if they exist in another form, they will be used as a new Headers object + * - if they do not exist * * @param base headers to append/overwrite to * @param append headers to append/overwrite with * @returns the base headers instance with merged `Headers` */ - static mergeHeaders(base: HeadersInit, ...append: HeadersInit[]): Headers { + static mergeHeaders(base?: HeadersInit, ...append: HeadersInit[]): Headers { base = base instanceof Headers ? base : new Headers(base); for (const headers of append) { From 6be7a1c994ef3c5894a1ee6b8fc5f55df2752dfc Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Sat, 8 Feb 2025 12:23:14 -0800 Subject: [PATCH 7/7] docs: complete --- src/gaxios.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gaxios.ts b/src/gaxios.ts index 8b41892..b60e8c2 100644 --- a/src/gaxios.ts +++ b/src/gaxios.ts @@ -656,8 +656,9 @@ export class Gaxios implements FetchCompliance { * * Using this utility can be helpful when the headers are not known to exist: * - if they exist as `Headers`, that instance will be used - * - if they exist in another form, they will be used as a new Headers object - * - if they do not exist + * - it improves performance and allows users to use their existing references to their `Headers` + * - if they exist in another form (`HeadersInit`), they will be used to create a new `Headers` object + * - if the base headers do not exist a new `Headers` object will be created * * @param base headers to append/overwrite to * @param append headers to append/overwrite with