diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d90540a0..033bc4ff2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,13 @@ _Note: Gaps between patch versions are faulty, broken or test releases._ ## v?.??.? (????-??-??) +#### :rocket: New Feature + +* Added support for propagating status code and headers from exactly one request of the composition +via `propagateStatusAndHeaders` option `core/request/engines/composition` + +## v?.??.? (????-??-??) + #### :rocket: New Feature * Added support for `redirect` option `core/request/engines/fetch` diff --git a/src/core/request/engines/composition/CHANGELOG.md b/src/core/request/engines/composition/CHANGELOG.md index 472842725..b55256e38 100644 --- a/src/core/request/engines/composition/CHANGELOG.md +++ b/src/core/request/engines/composition/CHANGELOG.md @@ -9,6 +9,13 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v?.??.? (????-??-??) + +#### :rocket: New Feature + +* Added support for propagating status code and headers from exactly one request of the composition +via `propagateStatusAndHeaders` option + ## v4.0.0-alpha.40 (2024-07-02) #### :bug: Bug Fix diff --git a/src/core/request/engines/composition/index.ts b/src/core/request/engines/composition/index.ts index 5d6632c98..188fbbef0 100644 --- a/src/core/request/engines/composition/index.ts +++ b/src/core/request/engines/composition/index.ts @@ -24,7 +24,8 @@ import type { CompositionEngineOpts, CompositionRequestEngine, CompositionRequest, - CompositionRequestOptions + CompositionRequestOptions, + GatheredRequestsData } from 'core/request/engines/composition/interface'; @@ -75,26 +76,40 @@ export function compositionEngine( const promises = compositionRequests.map((r) => SyncPromise.resolve(r.requestFilter?.(options)) .then((filterValue) => { if (filterValue === false) { - return; + return {}; } return r.request(options) .then(boundRequest.bind(null, async)) - .then((request) => isRequestResponseObject(request) ? request.data : request) + .then( + async (request) => isRequestResponseObject(request) ? + ({ + data: await request.data, + headers: request.response.headers, + status: request.response.status + }) : ({ + data: await request, + headers: {}, + status: statusCodes.OK + }) + ) .catch((err) => { if (r.failCompositionOnError) { throw err; } + + return {}; }); })); - gatherDataFromRequests(promises, options).then((data) => { + gatherDataFromRequests(promises, options).then(({data, status, headers}) => { resolve(new Response(data, { parent: requestOptions.parent, important: requestOptions.important, responseType: 'object', okStatuses: requestOptions.okStatuses, - status: statusCodes.OK, + status: status ?? statusCodes.OK, + headers: headers ?? {}, decoder: requestOptions.decoders, noContentStatuses: requestOptions.noContentStatuses })); @@ -153,10 +168,12 @@ function boundRequest( * @param options - Options related to composition requests. */ async function gatherDataFromRequests( - promises: Array>, + promises: Array>, options: CompositionRequestOptions -): Promise { - const accumulator = {}; +): Promise { + const accumulator = { + data: {} + }; if (options.engineOptions?.aggregateErrors) { await Promise.allSettled(promises) @@ -191,25 +208,33 @@ async function gatherDataFromRequests( } /** - * Accumulates data into an accumulator object based on the composition request. + * Accumulates new data into an accumulator object based on the composition request. * * @param accumulator - * @param data + * @param newData * @param compositionRequest */ function accumulateData( - accumulator: Dictionary, - data: unknown, + accumulator: GatheredRequestsData, + newData: GatheredRequestsData, compositionRequest: CompositionRequest -): Dictionary { +): GatheredRequestsData { const - {as} = compositionRequest; + {as} = compositionRequest, + {status, headers, data} = newData; + + accumulator.data ??= {}; if (as === compositionEngineSpreadResult) { - Object.assign(accumulator, data); + Object.assign(accumulator.data, data); } else { - Object.set(accumulator, as, data); + Object.set(accumulator.data, as, data); + } + + if (compositionRequest.propagateStatusAndHeaders === true) { + accumulator.status = status; + accumulator.headers = headers; } return accumulator; diff --git a/src/core/request/engines/composition/interface.ts b/src/core/request/engines/composition/interface.ts index c9d870780..4bf2e9795 100644 --- a/src/core/request/engines/composition/interface.ts +++ b/src/core/request/engines/composition/interface.ts @@ -9,6 +9,8 @@ import type Provider from 'core/data'; import type { ProviderOptions } from 'core/data'; import type { RequestOptions, RequestResponseObject, MiddlewareParams, RequestPromise, RequestEngine } from 'core/request'; +import type { RawHeaders } from 'core/request/headers'; +import type { StatusCodes } from 'core/status-codes'; export interface CompositionEngineOpts { /** @@ -130,6 +132,13 @@ export interface CompositionRequest { * If false / undefined, request errors will be ignored. */ failCompositionOnError?: boolean; + + /** + * If true, status code and reponse headers will be propagated from this request to the whole + * composition. Note that if there are more than one request with this option set to true, + * only last request's data will be propagated. + */ + propagateStatusAndHeaders?: boolean; } export interface CompositionRequestOptions { @@ -166,3 +175,9 @@ export interface CompositionRequestEngine extends RequestEngine { dropCache: NonNullable; destroy: NonNullable; } + +export interface GatheredRequestsData { + data?: Dictionary; + headers?: RawHeaders; + status?: StatusCodes; +}