Skip to content

Commit

Permalink
Pass response original headers to mapData callback in `createJson…
Browse files Browse the repository at this point in the history
…Query`
  • Loading branch information
igorkamyshev committed Nov 28, 2024
1 parent 2ca99e2 commit c019040
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/thin-cougars-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@farfetched/core": minor
---

Pass response original `headers` to `mapData` callback in `createJsonQuery`
12 changes: 10 additions & 2 deletions apps/website/docs/api/factories/create_json_mutation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`: <Badge type="tip" text="since v0.13" /> raw response headers

- `status.expected`: `number` or `Array<number>` 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)
Expand Down
128 changes: 127 additions & 1 deletion packages/core/src/mutation/__tests__/create_json_mutation.test.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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', () => {
Expand Down Expand Up @@ -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<string>(),
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');
});
});
});
4 changes: 2 additions & 2 deletions packages/core/src/mutation/create_json_mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export function createJsonMutation<
response: {
contract: Contract<unknown, Data>;
mapData: DynamicallySourcedField<
{ result: Data; params: Params },
{ result: Data; params: Params; headers?: Headers },
TransformedData,
DataSource
>;
Expand Down Expand Up @@ -171,7 +171,7 @@ export function createJsonMutation<
response: {
contract: Contract<unknown, Data>;
mapData: DynamicallySourcedField<
{ result: Data; params: void },
{ result: Data; params: void; headers?: Headers },
TransformedData,
DataSource
>;
Expand Down

0 comments on commit c019040

Please sign in to comment.