From c019040e8bc793ca3337479a4032906f554a3263 Mon Sep 17 00:00:00 2001 From: Igor Kamyshev Date: Thu, 28 Nov 2024 16:32:26 +0700 Subject: [PATCH] Pass response original `headers` to `mapData` callback in `createJsonQuery` --- .changeset/thin-cougars-press.md | 5 + .../api/factories/create_json_mutation.md | 12 +- .../__tests__/create_json_mutation.test.ts | 128 +++++++++++++++++- .../core/src/mutation/create_json_mutation.ts | 4 +- 4 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 .changeset/thin-cougars-press.md diff --git a/.changeset/thin-cougars-press.md b/.changeset/thin-cougars-press.md new file mode 100644 index 000000000..b4d1c74ec --- /dev/null +++ b/.changeset/thin-cougars-press.md @@ -0,0 +1,5 @@ +--- +"@farfetched/core": minor +--- + +Pass response original `headers` to `mapData` callback in `createJsonQuery` diff --git a/apps/website/docs/api/factories/create_json_mutation.md b/apps/website/docs/api/factories/create_json_mutation.md index 115f406b9..6201d0982 100644 --- a/apps/website/docs/api/factories/create_json_mutation.md +++ b/apps/website/docs/api/factories/create_json_mutation.md @@ -28,8 +28,16 @@ Config fields: - `contract`: [_Contract_](/api/primitives/contract) allows you to validate the response and decide how your application should treat it — as a success response or as a failed one. - `validate?`: [_Validator_](/api/primitives/validator) allows you to dynamically validate received data. - `mapData?`: optional mapper for the response data, available overloads: - - `({ result, params }) => mapped` - - `{ source: Store, fn: ({ result, params }, source) => mapped }` + + - `(res) => mapped` + - `{ source: Store, fn: (data, res) => mapped }` + + `res` object contains: + + - `result`: parsed and validated response data + - `params`: params which were passed to the [_Query_](/api/primitives/query) + - `headers`: raw response headers + - `status.expected`: `number` or `Array` of expected HTTP status codes, if the response status code is not in the list, the mutation will be treated as failed - `concurrency?`: concurrency settings for the [_Query_](/api/primitives/query) diff --git a/packages/core/src/mutation/__tests__/create_json_mutation.test.ts b/packages/core/src/mutation/__tests__/create_json_mutation.test.ts index e5d81cdd0..61c89fc75 100644 --- a/packages/core/src/mutation/__tests__/create_json_mutation.test.ts +++ b/packages/core/src/mutation/__tests__/create_json_mutation.test.ts @@ -1,4 +1,10 @@ -import { allSettled, createEvent, createWatch, fork } from 'effector'; +import { + allSettled, + createEvent, + createStore, + createWatch, + fork, +} from 'effector'; import { setTimeout } from 'timers/promises'; import { describe, test, expect, vi } from 'vitest'; @@ -9,6 +15,7 @@ import { fetchFx } from '../../fetch/fetch'; import { createJsonMutation } from '../create_json_mutation'; import { isMutation } from '../type'; import { concurrency } from '../../concurrency/concurrency'; +import { declareParams } from 'remote_operation/params'; describe('createJsonMutation', () => { test('isMutation', () => { @@ -211,4 +218,123 @@ describe('createJsonMutation', () => { expect.objectContaining({ credentials: 'omit' }) ); }); + + describe('metaResponse', () => { + test('simple callback', async () => { + const mutation = createJsonMutation({ + request: { + method: 'GET', + url: 'https://api.salo.com', + }, + response: { + contract: unknownContract, + mapData: ({ result, headers }) => { + expect(headers?.get('X-Test')).toBe('42'); + + return result; + }, + }, + }); + + // We need to mock it on transport level + // because we can't pass meta to executeFx + const scope = fork({ + handlers: [ + [ + fetchFx, + () => + new Response(JSON.stringify({}), { + headers: { 'X-Test': '42' }, + }), + ], + ], + }); + + await allSettled(mutation.start, { scope }); + + expect(scope.getState(mutation.$status)).toEqual('done'); + }); + + test('sourced callback', async () => { + const mutation = createJsonMutation({ + request: { + method: 'GET', + url: 'https://api.salo.com', + }, + response: { + contract: unknownContract, + mapData: { + source: createStore(''), + fn: ({ result, headers }, s) => { + expect(headers?.get('X-Test')).toBe('42'); + + return result; + }, + }, + }, + }); + + // We need to mock it on transport level + // because we can't pass meta to executeFx + const scope = fork({ + handlers: [ + [ + fetchFx, + () => + new Response(JSON.stringify({}), { + headers: { 'X-Test': '42' }, + }), + ], + ], + }); + + await allSettled(mutation.start, { scope }); + + expect(scope.getState(mutation.$status)).toEqual('done'); + }); + + test('do not mix meta between calls', async () => { + const mutation = createJsonMutation({ + params: declareParams(), + request: { + url: (params) => `http://api.salo.com/${params}`, + method: 'GET' as const, + }, + response: { + contract: unknownContract, + mapData: ({ result, params, headers }) => { + expect(headers?.get('X-Test')).toBe( + `http://api.salo.com/${params}` + ); + + return result; + }, + }, + }); + + // We need to mock it on transport level + // because we can't pass meta to executeFx + const scope = fork({ + handlers: [ + [ + fetchFx, + (req: Request) => + setTimeout(1).then( + () => + new Response(JSON.stringify({}), { + headers: { 'X-Test': req.url }, + }) + ), + ], + ], + }); + + await Promise.all([ + allSettled(mutation.start, { scope, params: '1' }), + allSettled(mutation.start, { scope, params: '2' }), + ]); + + expect(scope.getState(mutation.$status)).toEqual('done'); + }); + }); }); diff --git a/packages/core/src/mutation/create_json_mutation.ts b/packages/core/src/mutation/create_json_mutation.ts index e5befda02..34a57c01b 100644 --- a/packages/core/src/mutation/create_json_mutation.ts +++ b/packages/core/src/mutation/create_json_mutation.ts @@ -114,7 +114,7 @@ export function createJsonMutation< response: { contract: Contract; mapData: DynamicallySourcedField< - { result: Data; params: Params }, + { result: Data; params: Params; headers?: Headers }, TransformedData, DataSource >; @@ -171,7 +171,7 @@ export function createJsonMutation< response: { contract: Contract; mapData: DynamicallySourcedField< - { result: Data; params: void }, + { result: Data; params: void; headers?: Headers }, TransformedData, DataSource >;