diff --git a/.changeset/wise-adults-doubt.md b/.changeset/wise-adults-doubt.md new file mode 100644 index 00000000..ba10a4cd --- /dev/null +++ b/.changeset/wise-adults-doubt.md @@ -0,0 +1,7 @@ +--- +'@farfetched/core': minor +--- + +Added `request.fetch` feature to createJson\* methods, which allows to apply any valid `RequestInit` setting to underlying `fetch` call + +The top-level `request.credentials` is deprecated in favor of `request.fetch.credentials` diff --git a/apps/website/docs/api/factories/create_json_mutation.md b/apps/website/docs/api/factories/create_json_mutation.md index d20b1e4b..9c95c795 100644 --- a/apps/website/docs/api/factories/create_json_mutation.md +++ b/apps/website/docs/api/factories/create_json_mutation.md @@ -16,10 +16,11 @@ Config fields: - `body`: _[Sourced](/api/primitives/sourced) Json_, any value which can be serialized to JSON and parsed back without loses by JavaScript native module JSON. For example, `{ a: 1, b: 2 }`. Note that body cannot be used in `GET` and `HEAD` requests. - `query?`: _[Sourced](/api/primitives/sourced) object_, keys of the object must be `String` and values must be `String` or `Array` or (since v0.8) _[Sourced](/api/primitives/sourced) String_ containing ready-to-use query string - `headers?`: _[Sourced](/api/primitives/sourced) object_, keys of the object must be `String` and values must be `String` or `Array` - - `credentials?`: _String_, [available values](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials): + - `credentials?`: _String_, [available values](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials). **Deprecated**: use `fetch.credentials` instead. - `omit` — do not include credentials - `same-origin` — include credentials only if the request URL is the same origin - `include` — include credentials on all requests + - `fetch?`: _Object or [Store](https://effector.dev/docs/api/effector/Store) with Object_, additional [RequestInit](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#options) options to pass to the underlying fetch request. This allows configuring options like `mode`, `cache`, `redirect`, `referrerPolicy`, `integrity`, `keepalive`, etc. If `credentials` is specified both at the top level and in `fetch`, the top-level value takes precedence. - `response`: declarative rules to handle response from the API. - `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. diff --git a/apps/website/docs/api/factories/create_json_query.md b/apps/website/docs/api/factories/create_json_query.md index 220522f8..981056e0 100644 --- a/apps/website/docs/api/factories/create_json_query.md +++ b/apps/website/docs/api/factories/create_json_query.md @@ -18,10 +18,11 @@ Config fields: - `body`: _[Sourced](/api/primitives/sourced) Json_, any value which can be serialized to JSON and parsed back without loses by JavaScript native module JSON. For example, `{ a: 1, b: 2 }`. Note that body cannot be used in `GET` and `HEAD` requests. - `query?`: _[Sourced](/api/primitives/sourced) object_, keys of the object must be `String` and values must be `String` or `Array` or (since v0.8) _[Sourced](/api/primitives/sourced) String_ containing ready-to-use query string - `headers?`: _[Sourced](/api/primitives/sourced) object_, keys of the object must be `String` and values must be `String` or `Array` - - `credentials?`: _String_, [available values](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials): + - `credentials?`: _String_, [available values](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials). **Deprecated**: use `fetch.credentials` instead. - `omit` — do not include credentials - `same-origin` — include credentials only if the request URL is the same origin - `include` — include credentials on all requests + - `fetch?`: _Object or [Store](https://effector.dev/docs/api/effector/Store) with Object_, additional [RequestInit](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#options) options to pass to the underlying fetch request. This allows configuring options like `mode`, `cache`, `redirect`, `referrerPolicy`, `integrity`, `keepalive`, etc. If `credentials` is specified both at the top level and in `fetch`, the top-level value takes precedence. - `response`: declarative rules to handle response from the API. - `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. diff --git a/packages/core/src/fetch/__tests__/api.request.fetchOptions.test.ts b/packages/core/src/fetch/__tests__/api.request.fetchOptions.test.ts new file mode 100644 index 00000000..a7204260 --- /dev/null +++ b/packages/core/src/fetch/__tests__/api.request.fetchOptions.test.ts @@ -0,0 +1,199 @@ +import { allSettled, createStore, fork } from 'effector'; +import { describe, test, expect, vi } from 'vitest'; + +import { createApiRequest, type FetchOptions } from '../api'; +import { fetchFx } from '../fetch'; + +describe('fetch/api.request.fetch', () => { + // Does not matter + const mapBody = () => 'any body'; + const url = 'https://api.salo.com'; + const method = 'GET'; + + // Does not matter + const response = { + extract: async (v: T) => v, + }; + + test('pass static fetch on creation to request', async () => { + const callApiFx = createApiRequest({ + request: { + mapBody, + method, + url, + fetch: { + mode: 'cors', + cache: 'no-cache', + referrerPolicy: 'no-referrer', + }, + }, + response, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('test')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(callApiFx, { scope, params: {} }); + + const request = fetchMock.mock.calls[0][0] as Request; + expect(request.mode).toEqual('cors'); + expect(request.cache).toEqual('no-cache'); + expect(request.referrerPolicy).toEqual('no-referrer'); + }); + + test('pass reactive fetch on creation to request', async () => { + const $fetch = createStore({ + mode: 'cors', + cache: 'no-cache', + }); + + const callApiFx = createApiRequest({ + request: { mapBody, method, url, fetch: $fetch }, + response, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('test')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + // with original value + await allSettled(callApiFx, { scope, params: {} }); + let request = fetchMock.mock.calls[0][0] as Request; + expect(request.mode).toEqual('cors'); + expect(request.cache).toEqual('no-cache'); + + // with new value + await allSettled($fetch, { + scope, + params: { mode: 'no-cors', cache: 'force-cache' }, + }); + await allSettled(callApiFx, { scope, params: {} }); + request = fetchMock.mock.calls[1][0] as Request; + expect(request.mode).toEqual('no-cors'); + expect(request.cache).toEqual('force-cache'); + }); + + test('top-level credentials takes precedence over fetch.credentials', async () => { + const callApiFx = createApiRequest({ + request: { + mapBody, + method, + url, + credentials: 'include', + fetch: { + credentials: 'omit', + cache: 'no-cache', + }, + }, + response, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('test')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(callApiFx, { scope, params: {} }); + + const request = fetchMock.mock.calls[0][0] as Request; + // top-level credentials should win + expect(request.credentials).toEqual('include'); + // other fetch should still apply + expect(request.cache).toEqual('no-cache'); + }); + + test('fetch.credentials is used when top-level credentials is not set', async () => { + const callApiFx = createApiRequest({ + request: { + mapBody, + method, + url, + fetch: { + credentials: 'include', + }, + }, + response, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('test')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(callApiFx, { scope, params: {} }); + + const request = fetchMock.mock.calls[0][0] as Request; + expect(request.credentials).toEqual('include'); + }); + + test('pass fetch with keepalive option', async () => { + const callApiFx = createApiRequest({ + request: { + mapBody, + method, + url, + fetch: { + keepalive: true, + }, + }, + response, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('test')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(callApiFx, { scope, params: {} }); + + const request = fetchMock.mock.calls[0][0] as Request; + expect(request.keepalive).toEqual(true); + }); + + test('pass fetch with redirect option', async () => { + const callApiFx = createApiRequest({ + request: { + mapBody, + method, + url, + fetch: { + redirect: 'manual', + }, + }, + response, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('test')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(callApiFx, { scope, params: {} }); + + const request = fetchMock.mock.calls[0][0] as Request; + expect(request.redirect).toEqual('manual'); + }); + + test('pass fetch with integrity option', async () => { + const integrityValue = + 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC'; + + const callApiFx = createApiRequest({ + request: { + mapBody, + method, + url, + fetch: { + integrity: integrityValue, + }, + }, + response, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('test')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(callApiFx, { scope, params: {} }); + + const request = fetchMock.mock.calls[0][0] as Request; + expect(request.integrity).toEqual(integrityValue); + }); +}); diff --git a/packages/core/src/fetch/api.ts b/packages/core/src/fetch/api.ts index ffc833a2..3e4eb1be 100644 --- a/packages/core/src/fetch/api.ts +++ b/packages/core/src/fetch/api.ts @@ -33,6 +33,12 @@ export type HttpMethod = export type RequestBody = Blob | BufferSource | FormData | string; +// Future-proof: automatically includes any new RequestInit fields from the browser +export type FetchOptions = Omit< + RequestInit, + 'method' | 'headers' | 'body' | 'signal' +>; + // These settings can be defined only statically export interface StaticOnlyRequestConfig { method: StaticOrReactive; @@ -43,6 +49,7 @@ export interface StaticOnlyRequestConfig { export interface ExclusiveRequestConfigShared { url: string; credentials?: RequestCredentials; + fetch?: FetchOptions; abortController?: AbortController; } @@ -139,17 +146,23 @@ export function createApiRequest< query, headers, credentials, + fetch, body, abortController, }) => { const mappedBody = body ? config.request.mapBody(body) : null; const request = new Request(formatUrl(url, query), { + ...fetch, method, headers: formatHeaders(headers), - credentials, body: mappedBody, signal: abortController?.signal, + /** + * `credentials` is available both in `fetch` and in the top-level config. + * The top-level config was introduced much earlier, so it takes precedence. + */ + ...(credentials !== undefined ? { credentials } : {}), }); const response = await requestFx(request).catch((cause: RequestError) => { @@ -240,6 +253,7 @@ export function createApiRequest< query: normalizeStaticOrReactive(config.request.query), headers: normalizeStaticOrReactive(config.request.headers), credentials: normalizeStaticOrReactive(config.request.credentials), + fetch: normalizeStaticOrReactive(config.request.fetch), body: normalizeStaticOrReactive(config.request.body), }, mapParams(dynamicConfig: ApiRequestParams, staticConfig) { @@ -250,11 +264,16 @@ export function createApiRequest< // @ts-expect-error TS cannot infer type correctly, but there is always field in staticConfig or dynamicConfig dynamicConfig.url; - const credentials: RequestCredentials = + const credentials: RequestCredentials | undefined = staticConfig.credentials ?? // @ts-expect-error TS cannot infer type correctly, but there is always field in staticConfig or dynamicConfig dynamicConfig.credentials; + const fetch: FetchOptions | undefined = + staticConfig.fetch ?? + // @ts-expect-error TS cannot infer type correctly, but there is always field in staticConfig or dynamicConfig + dynamicConfig.fetch; + const body: B = staticConfig.body ?? // @ts-expect-error TS cannot infer type correctly, but there is always field in staticConfig or dynamicConfig @@ -276,6 +295,7 @@ export function createApiRequest< query, headers, credentials, + fetch, body, abortController, }; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 02186571..64134558 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -68,7 +68,7 @@ export { type ValidationResult, type Validator } from './validation/type'; export { type Json } from 'effector'; export { type JsonObject } from './fetch/json'; export { type FetchApiRecord } from './fetch/lib'; -export { type JsonApiRequestError } from './fetch/api'; +export { type JsonApiRequestError, type FetchOptions } from './fetch/api'; export { fetchFx } from './fetch/fetch'; // Exposed errors diff --git a/packages/core/src/mutation/__tests__/create_json_mutation.request.fetchOptions.test.ts b/packages/core/src/mutation/__tests__/create_json_mutation.request.fetchOptions.test.ts new file mode 100644 index 00000000..640a5d78 --- /dev/null +++ b/packages/core/src/mutation/__tests__/create_json_mutation.request.fetchOptions.test.ts @@ -0,0 +1,165 @@ +import { allSettled, createStore, fork } from 'effector'; +import { describe, test, expect, vi } from 'vitest'; + +import { createJsonMutation } from '../create_json_mutation'; +import { unknownContract } from '../../contract/unknown_contract'; +import { fetchFx } from '../../fetch/fetch'; +import { type FetchOptions } from '../../fetch/api'; + +describe('remote_data/mutation/json.request.fetch', () => { + test('pass static fetch config to request', async () => { + const mutation = createJsonMutation({ + response: { contract: unknownContract }, + request: { + url: 'http://api.salo.com', + method: 'POST' as const, + fetch: { + mode: 'cors', + cache: 'no-cache', + referrerPolicy: 'no-referrer', + }, + }, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('{}')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(mutation.start, { scope }); + + expect(fetchMock).toBeCalledTimes(1); + const request = fetchMock.mock.calls[0][0] as Request; + expect(request.mode).toEqual('cors'); + expect(request.cache).toEqual('no-cache'); + expect(request.referrerPolicy).toEqual('no-referrer'); + }); + + test('pass reactive fetch config to request', async () => { + const $fetch = createStore({ + mode: 'cors', + cache: 'no-cache', + }); + + const mutation = createJsonMutation({ + response: { contract: unknownContract }, + request: { + url: 'http://api.salo.com', + method: 'POST' as const, + fetch: $fetch, + }, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('{}')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + // with original value + await allSettled(mutation.start, { scope }); + let request = fetchMock.mock.calls[0][0] as Request; + expect(request.mode).toEqual('cors'); + expect(request.cache).toEqual('no-cache'); + + // with new value + await allSettled($fetch, { + scope, + params: { mode: 'no-cors', cache: 'force-cache' }, + }); + await allSettled(mutation.start, { scope }); + request = fetchMock.mock.calls[1][0] as Request; + expect(request.mode).toEqual('no-cors'); + expect(request.cache).toEqual('force-cache'); + }); + + test('top-level credentials takes precedence over fetch.credentials', async () => { + const mutation = createJsonMutation({ + response: { contract: unknownContract }, + request: { + url: 'http://api.salo.com', + method: 'POST' as const, + credentials: 'include', + fetch: { + credentials: 'omit', + cache: 'no-cache', + }, + }, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('{}')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(mutation.start, { scope }); + + const request = fetchMock.mock.calls[0][0] as Request; + // top-level credentials should win + expect(request.credentials).toEqual('include'); + // other fetch options should still apply + expect(request.cache).toEqual('no-cache'); + }); + + test('fetch.credentials is used when top-level credentials is not set', async () => { + const mutation = createJsonMutation({ + response: { contract: unknownContract }, + request: { + url: 'http://api.salo.com', + method: 'POST' as const, + fetch: { + credentials: 'include', + }, + }, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('{}')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(mutation.start, { scope }); + + const request = fetchMock.mock.calls[0][0] as Request; + expect(request.credentials).toEqual('include'); + }); + + test('pass fetch config with keepalive option', async () => { + const mutation = createJsonMutation({ + response: { contract: unknownContract }, + request: { + url: 'http://api.salo.com', + method: 'POST' as const, + fetch: { + keepalive: true, + }, + }, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('{}')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(mutation.start, { scope }); + + const request = fetchMock.mock.calls[0][0] as Request; + expect(request.keepalive).toEqual(true); + }); + + test('pass fetch config with redirect option', async () => { + const mutation = createJsonMutation({ + response: { contract: unknownContract }, + request: { + url: 'http://api.salo.com', + method: 'POST' as const, + fetch: { + redirect: 'manual', + }, + }, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('{}')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(mutation.start, { scope }); + + const request = fetchMock.mock.calls[0][0] as Request; + expect(request.redirect).toEqual('manual'); + }); +}); diff --git a/packages/core/src/mutation/create_json_mutation.ts b/packages/core/src/mutation/create_json_mutation.ts index 8ae85ccb..9b809a07 100644 --- a/packages/core/src/mutation/create_json_mutation.ts +++ b/packages/core/src/mutation/create_json_mutation.ts @@ -2,13 +2,18 @@ import { attach, type Json, createEffect } from 'effector'; import { type Contract } from '../contract/type'; import { unknownContract } from '../contract/unknown_contract'; -import { type HttpMethod, type JsonApiRequestError } from '../fetch/api'; +import { + type HttpMethod, + type JsonApiRequestError, + type FetchOptions, +} from '../fetch/api'; import { createJsonApiRequest } from '../fetch/json'; import { type FetchApiRecord } from '../fetch/lib'; import { type ParamsDeclaration } from '../remote_operation/params'; import { type DynamicallySourcedField, type SourcedField, + type StaticOrReactive, normalizeSourced, } from '../libs/patronus'; import { type Validator } from '../validation/type'; @@ -26,6 +31,7 @@ type RequestConfig = { url: SourcedField; credentials?: RequestCredentials; + fetch?: StaticOrReactive; query?: | SourcedField | SourcedField; @@ -222,9 +228,17 @@ export function createJsonMutation< export function createJsonMutation(config: any): Mutation { const credentials: RequestCredentials | undefined = config.request.credentials; + const fetch: StaticOrReactive | undefined = + config.request.fetch; + + if (credentials !== undefined) { + console.warn( + 'Farfetched: `request.credentials` is deprecated, use `request.fetch.credentials` instead' + ); + } const requestFx = createJsonApiRequest({ - request: { method: config.request.method, credentials }, + request: { method: config.request.method, credentials, fetch }, response: { status: config.response.status }, }); diff --git a/packages/core/src/query/__tests__/create_json_query.request.fetchOptions.test.ts b/packages/core/src/query/__tests__/create_json_query.request.fetchOptions.test.ts new file mode 100644 index 00000000..0c7b1830 --- /dev/null +++ b/packages/core/src/query/__tests__/create_json_query.request.fetchOptions.test.ts @@ -0,0 +1,165 @@ +import { allSettled, createStore, fork } from 'effector'; +import { describe, test, expect, vi } from 'vitest'; + +import { createJsonQuery } from '../create_json_query'; +import { unknownContract } from '../../contract/unknown_contract'; +import { fetchFx } from '../../fetch/fetch'; +import { type FetchOptions } from '../../fetch/api'; + +describe('remote_data/query/json.request.fetch', () => { + test('pass static fetch to request', async () => { + const query = createJsonQuery({ + response: { contract: unknownContract }, + request: { + url: 'http://api.salo.com', + method: 'GET' as const, + fetch: { + mode: 'cors', + cache: 'no-cache', + referrerPolicy: 'no-referrer', + }, + }, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('{}')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(query.start, { scope }); + + expect(fetchMock).toBeCalledTimes(1); + const request = fetchMock.mock.calls[0][0] as Request; + expect(request.mode).toEqual('cors'); + expect(request.cache).toEqual('no-cache'); + expect(request.referrerPolicy).toEqual('no-referrer'); + }); + + test('pass reactive fetch to request', async () => { + const $fetch = createStore({ + mode: 'cors', + cache: 'no-cache', + }); + + const query = createJsonQuery({ + response: { contract: unknownContract }, + request: { + url: 'http://api.salo.com', + method: 'GET' as const, + fetch: $fetch, + }, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('{}')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + // with original value + await allSettled(query.start, { scope }); + let request = fetchMock.mock.calls[0][0] as Request; + expect(request.mode).toEqual('cors'); + expect(request.cache).toEqual('no-cache'); + + // with new value + await allSettled($fetch, { + scope, + params: { mode: 'no-cors', cache: 'force-cache' }, + }); + await allSettled(query.start, { scope }); + request = fetchMock.mock.calls[1][0] as Request; + expect(request.mode).toEqual('no-cors'); + expect(request.cache).toEqual('force-cache'); + }); + + test('top-level credentials takes precedence over fetch.credentials', async () => { + const query = createJsonQuery({ + response: { contract: unknownContract }, + request: { + url: 'http://api.salo.com', + method: 'GET' as const, + credentials: 'include', + fetch: { + credentials: 'omit', + cache: 'no-cache', + }, + }, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('{}')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(query.start, { scope }); + + const request = fetchMock.mock.calls[0][0] as Request; + // top-level credentials should win + expect(request.credentials).toEqual('include'); + // other fetch should still apply + expect(request.cache).toEqual('no-cache'); + }); + + test('fetch.credentials is used when top-level credentials is not set', async () => { + const query = createJsonQuery({ + response: { contract: unknownContract }, + request: { + url: 'http://api.salo.com', + method: 'GET' as const, + fetch: { + credentials: 'include', + }, + }, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('{}')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(query.start, { scope }); + + const request = fetchMock.mock.calls[0][0] as Request; + expect(request.credentials).toEqual('include'); + }); + + test('pass fetch with keepalive option', async () => { + const query = createJsonQuery({ + response: { contract: unknownContract }, + request: { + url: 'http://api.salo.com', + method: 'GET' as const, + fetch: { + keepalive: true, + }, + }, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('{}')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(query.start, { scope }); + + const request = fetchMock.mock.calls[0][0] as Request; + expect(request.keepalive).toEqual(true); + }); + + test('pass fetch with redirect option', async () => { + const query = createJsonQuery({ + response: { contract: unknownContract }, + request: { + url: 'http://api.salo.com', + method: 'GET' as const, + fetch: { + redirect: 'manual', + }, + }, + }); + + const fetchMock = vi.fn().mockResolvedValue(new Response('{}')); + + const scope = fork({ handlers: [[fetchFx, fetchMock]] }); + + await allSettled(query.start, { scope }); + + const request = fetchMock.mock.calls[0][0] as Request; + expect(request.redirect).toEqual('manual'); + }); +}); diff --git a/packages/core/src/query/create_json_query.ts b/packages/core/src/query/create_json_query.ts index c17fafb7..9b6bb95f 100644 --- a/packages/core/src/query/create_json_query.ts +++ b/packages/core/src/query/create_json_query.ts @@ -2,7 +2,12 @@ import { attach, createEffect, type Json } from 'effector'; import { type Contract } from '../contract/type'; import { createJsonApiRequest } from '../fetch/json'; -import { type HttpMethod, type JsonApiRequestError } from '../fetch/api'; +import { + type HttpMethod, + type JsonApiRequestError, + type FetchOptions, +} from '../fetch/api'; +import { type StaticOrReactive } from '../libs/patronus'; import { normalizeSourced, type SourcedField, @@ -26,6 +31,7 @@ type RequestConfig = { url: SourcedField; credentials?: RequestCredentials; + fetch?: StaticOrReactive; query?: | SourcedField | SourcedField; @@ -356,12 +362,21 @@ export function createJsonQuery< export function createJsonQuery(config: any) { const credentials: RequestCredentials | undefined = config.request.credentials; + const fetch: StaticOrReactive | undefined = + config.request.fetch; + + if (credentials !== undefined) { + console.warn( + 'Farfetched: `request.credentials` is deprecated, use `request.fetch.credentials` instead' + ); + } // Basement const requestFx = createJsonApiRequest({ request: { method: config.request.method, credentials, + fetch, }, });