diff --git a/.changeset/big-crews-pump.md b/.changeset/big-crews-pump.md new file mode 100644 index 000000000000..e125f7ffde71 --- /dev/null +++ b/.changeset/big-crews-pump.md @@ -0,0 +1,50 @@ +--- +'@data-client/core': minor +'@data-client/react': patch +'@data-client/test': patch +--- + +BREAKING CHANGE: setResponseAction.payload -> setResponseAction.response + +This only affects those writing custom [Managers](https://dataclient.io/docs/concepts/managers) that +inspect `SET_RESPONSE_TYPE` `action.payload`. + +#### Before + +```ts +import { SET_RESPONSE_TYPE, type Manager, type Middleware } from '@data-client/react'; + +export default class MyManager implements Manager { + getMiddleware = (): Middleware => controller => next => async action => { + switch (action.type) { + case SET_RESPONSE_TYPE: + console.log('Resolved with value', action.payload); + return next(action); + default: + return next(action); + } + }; + + cleanup() {} +} +``` + +#### After + +```ts +import { SET_RESPONSE_TYPE, type Manager, type Middleware } from '@data-client/react'; + +export default class MyManager implements Manager { + getMiddleware = (): Middleware => controller => next => async action => { + switch (action.type) { + case SET_RESPONSE_TYPE: + console.log('Resolved with value', action.response); + return next(action); + default: + return next(action); + } + }; + + cleanup() {} +} +``` \ No newline at end of file diff --git a/.changeset/light-tables-guess.md b/.changeset/light-tables-guess.md new file mode 100644 index 000000000000..7600dd0df2bb --- /dev/null +++ b/.changeset/light-tables-guess.md @@ -0,0 +1,53 @@ +--- +'@data-client/core': minor +'@data-client/react': patch +'@data-client/test': patch +--- + +BREAKING CHANGE: remove fetchAction.payload + +This only affects those writing custom [Managers](https://dataclient.io/docs/concepts/managers) that +inspect `FETCH_TYPE` `action.fetch`. + +#### Before + +```ts +import { FETCH_TYPE, type Manager, type Middleware } from '@data-client/react'; + +export default class MyManager implements Manager { + getMiddleware = (): Middleware => controller => next => async action => { + switch (action.type) { + case FETCH_TYPE: + // consume fetch, and print the resolution + action.fetch().then(response => console.log(response)); + default: + return next(action); + } + }; + + cleanup() {} +} +``` + +#### After + +```ts +import { FETCH_TYPE, type Manager, type Middleware } from '@data-client/react'; + +export default class MyManager implements Manager { + getMiddleware = (): Middleware => controller => next => async action => { + switch (action.type) { + case FETCH_TYPE: + // consume fetch, and print the resolution + action + .endpoint(...action.meta.args) + .fetch() + .then(response => console.log(response)); + default: + return next(action); + } + }; + + cleanup() {} +} +``` diff --git a/packages/core/src/actions.ts b/packages/core/src/actions.ts index 6a1047341fe3..ce260e286ee9 100644 --- a/packages/core/src/actions.ts +++ b/packages/core/src/actions.ts @@ -57,7 +57,7 @@ export interface SetResponseActionSuccess< type: typeof SET_RESPONSE_TYPE; endpoint: E; meta: SetResponseMeta; - payload: ResolveType; + response: ResolveType; error?: false; } export interface SetResponseActionError< @@ -66,7 +66,7 @@ export interface SetResponseActionError< type: typeof SET_RESPONSE_TYPE; endpoint: E; meta: SetResponseMeta; - payload: UnknownError; + response: UnknownError; error: true; } export type SetResponseAction< @@ -87,7 +87,6 @@ export interface FetchAction = EndpointDefault> { type: typeof FETCH_TYPE; endpoint: E; meta: FetchMeta]>; - payload: () => ReturnType; } /* OPTIMISTIC */ diff --git a/packages/core/src/controller/createFetch.ts b/packages/core/src/controller/createFetch.ts index 877fb25dcebd..877703f85edd 100644 --- a/packages/core/src/controller/createFetch.ts +++ b/packages/core/src/controller/createFetch.ts @@ -30,7 +30,6 @@ export default function createFetch< return { type: FETCH_TYPE, - payload: () => endpoint(...args) as any, meta, endpoint, }; diff --git a/packages/core/src/controller/createSetResponse.ts b/packages/core/src/controller/createSetResponse.ts index 09c679e9d02f..a258120a22ee 100644 --- a/packages/core/src/controller/createSetResponse.ts +++ b/packages/core/src/controller/createSetResponse.ts @@ -59,19 +59,19 @@ export default function createSetResponse< if (process.env.NODE_ENV === 'development' && expiryLength < 0) { throw new Error('Negative expiry length are not allowed.'); } - const now = Date.now(); + const date = Date.now(); const meta: SetResponseMeta = { args: args.map(ensurePojo), - fetchedAt: fetchedAt ?? now, - date: now, - expiresAt: now + expiryLength, + fetchedAt: fetchedAt ?? date, + date, + expiresAt: date + expiryLength, key: endpoint.key(...args), }; const action: SetResponseAction = { type: SET_RESPONSE_TYPE, - payload: response, - endpoint: endpoint, + response, + endpoint, meta, }; if (error) (action as any).error = true; diff --git a/packages/core/src/manager/LogoutManager.ts b/packages/core/src/manager/LogoutManager.ts index 151dd7ce8e4e..050002e5a015 100644 --- a/packages/core/src/manager/LogoutManager.ts +++ b/packages/core/src/manager/LogoutManager.ts @@ -18,7 +18,7 @@ export default class LogoutManager implements Manager { if ( action.type === SET_RESPONSE_TYPE && action.error && - this.shouldLogout(action.payload) + this.shouldLogout(action.response) ) { this.handleLogout(controller); } diff --git a/packages/core/src/manager/NetworkManager.ts b/packages/core/src/manager/NetworkManager.ts index 505e75b40d70..9d0606c8d4e1 100644 --- a/packages/core/src/manager/NetworkManager.ts +++ b/packages/core/src/manager/NetworkManager.ts @@ -153,12 +153,11 @@ export default class NetworkManager implements Manager { * for ensures mutation requests always go through. */ protected handleFetch(action: FetchAction) { - const fetch = action.payload; const { key, resolve, reject, fetchedAt } = action.meta; const throttle = !action.endpoint.sideEffect; const deferedFetch = () => { - let promise = fetch(); + let promise = action.endpoint(...action.meta.args); const resolvePromise = ( promise: Promise, ) => @@ -237,7 +236,7 @@ export default class NetworkManager implements Manager { } else { promiseHandler = this.resolvers[action.meta.key]; } - promiseHandler(action.payload); + promiseHandler(action.response); // since we're resolved we no longer need to keep track of this promise this.clear(action.meta.key); } diff --git a/packages/core/src/manager/__tests__/__snapshots__/pollingSubscription.ts.snap b/packages/core/src/manager/__tests__/__snapshots__/pollingSubscription.ts.snap index b9ddd773aa88..1cb3e4dee17c 100644 --- a/packages/core/src/manager/__tests__/__snapshots__/pollingSubscription.ts.snap +++ b/packages/core/src/manager/__tests__/__snapshots__/pollingSubscription.ts.snap @@ -17,7 +17,6 @@ exports[`PollingSubscription fresh data should call after period 1`] = ` "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", }, ] @@ -34,7 +33,6 @@ exports[`PollingSubscription fresh data should call after period 2`] = ` "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", }, ] diff --git a/packages/core/src/manager/__tests__/networkManager.ts b/packages/core/src/manager/__tests__/networkManager.ts index 6ab08d16fc9e..73b2a48ee17d 100644 --- a/packages/core/src/manager/__tests__/networkManager.ts +++ b/packages/core/src/manager/__tests__/networkManager.ts @@ -6,7 +6,7 @@ import Controller from '../../controller/Controller'; import createFetch from '../../controller/createFetch'; import NetworkManager from '../../manager/NetworkManager'; import { initialState } from '../../state/reducer/createReducer'; -import { Middleware } from '../../types'; +import { Middleware, SetResponseAction } from '../../types'; describe('NetworkManager', () => { const manager = new NetworkManager(); @@ -154,15 +154,17 @@ describe('NetworkManager', () => { middleware(API)(next)(fetchResolveAction); - const data = await fetchResolveAction.payload(); + const response = await fetchResolveAction.endpoint( + ...fetchResolveAction.meta.args, + ); // mutations resolve before dispatch, so we must wait for next tick to see set await new Promise(resolve => setTimeout(resolve, 0)); - const action = { + const action: SetResponseAction = { type: SET_RESPONSE_TYPE, endpoint: fetchResolveAction.endpoint, - payload: data, + response, meta: { args: fetchResolveAction.meta.args, key: fetchResolveAction.meta.key, @@ -187,15 +189,17 @@ describe('NetworkManager', () => { middleware(API)(next)(fetchSetWithUpdatersAction); - const data = await fetchSetWithUpdatersAction.payload(); + const response = await fetchSetWithUpdatersAction.endpoint( + ...fetchSetWithUpdatersAction.meta.args, + ); // mutations resolve before dispatch, so we must wait for next tick to see set await new Promise(resolve => setTimeout(resolve, 0)); - const action = { + const action: SetResponseAction = { type: SET_RESPONSE_TYPE, endpoint: fetchSetWithUpdatersAction.endpoint, - payload: data, + response, meta: { args: fetchSetWithUpdatersAction.meta.args, key: fetchSetWithUpdatersAction.meta.key, @@ -220,15 +224,17 @@ describe('NetworkManager', () => { middleware(API)(next)(fetchRpcWithUpdatersAction); - const data = await fetchRpcWithUpdatersAction.payload(); + const response = await fetchRpcWithUpdatersAction.endpoint( + ...fetchRpcWithUpdatersAction.meta.args, + ); // mutations resolve before dispatch, so we must wait for next tick to see set await new Promise(resolve => setTimeout(resolve, 0)); - const action = { + const action: SetResponseAction = { type: SET_RESPONSE_TYPE, endpoint: fetchRpcWithUpdatersAction.endpoint, - payload: data, + response, meta: { args: fetchRpcWithUpdatersAction.meta.args, key: fetchRpcWithUpdatersAction.meta.key, @@ -253,7 +259,9 @@ describe('NetworkManager', () => { middleware(API)(next)(fetchRpcWithUpdatersAndOptimisticAction); - const data = await fetchRpcWithUpdatersAndOptimisticAction.payload(); + const response = await fetchRpcWithUpdatersAndOptimisticAction.endpoint( + ...fetchRpcWithUpdatersAndOptimisticAction.meta.args, + ); expect(next).toHaveBeenCalled(); // mutations resolve before dispatch, so we must wait for next tick to see set @@ -261,7 +269,7 @@ describe('NetworkManager', () => { expect(dispatch).toHaveBeenCalledWith({ type: SET_RESPONSE_TYPE, endpoint: fetchRpcWithUpdatersAndOptimisticAction.endpoint, - payload: data, + response, meta: { args: fetchRpcWithUpdatersAndOptimisticAction.meta.args, key: fetchRpcWithUpdatersAndOptimisticAction.meta.key, @@ -286,7 +294,7 @@ describe('NetworkManager', () => { endpoint: detailEndpoint.extend({ dataExpiryLength: 314 }), }); - await fetchResolveAction.payload(); + await fetchResolveAction.endpoint(...fetchResolveAction.meta.args); expect(dispatch).toHaveBeenCalled(); const { meta } = dispatch.mock.calls[0][0]; @@ -307,7 +315,7 @@ describe('NetworkManager', () => { endpoint: detailEndpoint.extend({ dataExpiryLength: undefined }), }); - await fetchResolveAction.payload(); + await fetchResolveAction.endpoint(...fetchResolveAction.meta.args); expect(dispatch).toHaveBeenCalled(); const { meta } = dispatch.mock.calls[0][0]; @@ -330,7 +338,7 @@ describe('NetworkManager', () => { expect(next).not.toHaveBeenCalled(); expect(dispatch).toHaveBeenCalledWith({ type: SET_RESPONSE_TYPE, - payload: error, + response: error, meta: { key: fetchRejectAction.meta.key, date: expect.any(Number), diff --git a/packages/core/src/manager/__tests__/subscriptionManager.ts b/packages/core/src/manager/__tests__/subscriptionManager.ts index 29eed7920039..b808d363bc47 100644 --- a/packages/core/src/manager/__tests__/subscriptionManager.ts +++ b/packages/core/src/manager/__tests__/subscriptionManager.ts @@ -48,31 +48,31 @@ describe('SubscriptionManager', () => { describe('middleware', () => { function createSubscribeAction( - payload: Record, + response: Record, reject = false, ): SubscribeAction { const fetch = reject ? () => Promise.reject(new Error('Failed')) - : () => Promise.resolve(payload); + : () => Promise.resolve(response); return { type: SUBSCRIBE_TYPE, endpoint: PollingArticleResource.get, meta: { - key: PollingArticleResource.get.key({ id: payload.id }), - args: [{ id: payload.id }], + key: PollingArticleResource.get.key({ id: response.id }), + args: [{ id: response.id }], }, }; } function createUnsubscribeAction( - payload: Record, + response: Record, ): UnsubscribeAction { return { type: UNSUBSCRIBE_TYPE, endpoint: PollingArticleResource.get, meta: { - key: PollingArticleResource.get.key({ id: payload.id }), - args: [{ id: payload.id }], + key: PollingArticleResource.get.key({ id: response.id }), + args: [{ id: response.id }], }, }; } diff --git a/packages/core/src/state/__tests__/reducer.ts b/packages/core/src/state/__tests__/reducer.ts index 52ac40d25b84..3211b72f0eb7 100644 --- a/packages/core/src/state/__tests__/reducer.ts +++ b/packages/core/src/state/__tests__/reducer.ts @@ -34,10 +34,10 @@ describe('reducer', () => { describe('singles', () => { const id = 20; - const payload = { id, title: 'hi', content: 'this is the content' }; + const response = { id, title: 'hi', content: 'this is the content' }; const action: SetResponseAction = { type: SET_RESPONSE_TYPE, - payload, + response, endpoint: ArticleResource.get, meta: { args: [{ id }], @@ -47,9 +47,9 @@ describe('reducer', () => { fetchedAt: 5000000000, }, }; - const partialResultAction = { + const partialResultAction: SetResponseAction = { ...action, - payload: { id, title: 'hello' }, + response: { id, title: 'hello' }, }; const iniState = initialState; let newState = initialState; @@ -59,7 +59,7 @@ describe('reducer', () => { }); it('should overwrite existing entity', () => { const getEntity = (state: any): Article => - state.entities[Article.key][`${Article.pk(action.payload)}`]; + state.entities[Article.key][`${Article.pk(action.response)}`]; const prevEntity = getEntity(newState); expect(prevEntity).toBeDefined(); const nextState = reducer(newState, action); @@ -69,7 +69,7 @@ describe('reducer', () => { }); it('should merge partial entity with existing entity', () => { const getEntity = (state: any): Article => - state.entities[Article.key][`${Article.pk(action.payload)}`]; + state.entities[Article.key][`${Article.pk(action.response)}`]; const prevEntity = getEntity(newState); expect(prevEntity).toBeDefined(); const nextState = reducer(newState, partialResultAction); @@ -78,16 +78,17 @@ describe('reducer', () => { expect(nextEntity).toBeDefined(); expect(nextEntity.title).not.toBe(prevEntity.title); - expect(nextEntity.title).toBe(partialResultAction.payload.title); + expect(nextEntity.title).toBe(partialResultAction.response.title); expect(nextEntity.content).toBe(prevEntity.content); expect(nextEntity.content).not.toBe(undefined); expect( - nextState.entityMeta[Article.key][`${Article.pk(action.payload)}`], + nextState.entityMeta[Article.key][`${Article.pk(action.response)}`], ).toBeDefined(); expect( - nextState.entityMeta[Article.key][`${Article.pk(action.payload)}`].date, + nextState.entityMeta[Article.key][`${Article.pk(action.response)}`] + .date, ).toBe(action.meta.date); }); @@ -101,7 +102,7 @@ describe('reducer', () => { }, }; const getMeta = (state: any): { expiresAt: number } => - state.entityMeta[Article.key][`${Article.pk(action.payload)}`]; + state.entityMeta[Article.key][`${Article.pk(action.response)}`]; const prevMeta = getMeta(newState); expect(prevMeta).toBeDefined(); const nextState = reducer(newState, localAction); @@ -122,9 +123,9 @@ describe('reducer', () => { }, }; const getMeta = (state: any): { date: number } => - state.entityMeta[Article.key][`${Article.pk(action.payload)}`]; + state.entityMeta[Article.key][`${Article.pk(action.response)}`]; const getEntity = (state: any): Article => - state.entities[Article.key][`${Article.pk(action.payload)}`]; + state.entities[Article.key][`${Article.pk(action.response)}`]; const prevEntity = getEntity(newState); const prevMeta = getMeta(newState); expect(prevMeta).toBeDefined(); @@ -181,9 +182,9 @@ describe('reducer', () => { }, }; const getMeta = (state: any): { date: number; expiresAt: number } => - state.entityMeta[ExpiresSoon.key][`${ExpiresSoon.pk(action.payload)}`]; + state.entityMeta[ExpiresSoon.key][`${ExpiresSoon.pk(action.response)}`]; const getEntity = (state: any): ExpiresSoon => - state.entities[ExpiresSoon.key][`${ExpiresSoon.pk(action.payload)}`]; + state.entities[ExpiresSoon.key][`${ExpiresSoon.pk(action.response)}`]; const prevEntity = getEntity(newState); const prevMeta = getMeta(newState); @@ -317,14 +318,14 @@ describe('reducer', () => { it('mutate should never change endpoints', () => { const id = 20; - const payload = { id, title: 'hi', content: 'this is the content' }; + const response = { id, title: 'hi', content: 'this is the content' }; const action: SetResponseAction = { type: SET_RESPONSE_TYPE, - payload, + response, endpoint: ArticleResource.get, meta: { args: [{ id }], - key: ArticleResource.get.key(payload), + key: ArticleResource.get.key(response), date: 0, fetchedAt: 0, expiresAt: 1000000000000, @@ -332,7 +333,7 @@ describe('reducer', () => { }; const iniState = { ...initialState, - endpoints: { abc: '5', [ArticleResource.get.key(payload)]: `${id}` }, + endpoints: { abc: '5', [ArticleResource.get.key(response)]: `${id}` }, }; const newState = reducer(iniState, action); expect(newState.endpoints).toStrictEqual(iniState.endpoints); @@ -341,7 +342,7 @@ describe('reducer', () => { const id = 20; const action: SetResponseAction = { type: SET_RESPONSE_TYPE, - payload: { id }, + response: { id }, endpoint: ArticleResource.delete, meta: { args: [{ id }], @@ -530,7 +531,7 @@ describe('reducer', () => { const error = new Error('hi'); const action: SetResponseAction = { type: SET_RESPONSE_TYPE, - payload: error, + response: error, endpoint: ArticleResource.get, meta: { args: [{ id }], @@ -550,7 +551,7 @@ describe('reducer', () => { const error = new Error('hi'); const action: SetResponseAction = { type: SET_RESPONSE_TYPE, - payload: error, + response: error, endpoint: ArticleResource.get, meta: { args: [{ id }], @@ -571,7 +572,7 @@ describe('reducer', () => { const error = new Error('hi'); const action: SetResponseAction = { type: SET_RESPONSE_TYPE, - payload: error, + response: error, endpoint: ArticleResource.delete, meta: { args: [{ id }], @@ -603,7 +604,6 @@ describe('reducer', () => { try { const action: FetchAction = { type: FETCH_TYPE, - payload: () => new Promise(() => null), endpoint: ArticleResource.get, meta: { args: [{ id: 5 }], diff --git a/packages/core/src/state/reducer/setResponseReducer.ts b/packages/core/src/state/reducer/setResponseReducer.ts index 9f1650acb1f5..016b5a238c2c 100644 --- a/packages/core/src/state/reducer/setResponseReducer.ts +++ b/packages/core/src/state/reducer/setResponseReducer.ts @@ -15,18 +15,18 @@ export function setResponseReducer( controller: Controller, ) { if (action.error) { - return reduceError(state, action, action.payload); + return reduceError(state, action, action.response); } try { - let payload: any; - // for true set's payload is contained in action + let response: any; + // for true set's response is contained in action if (action.type === OPTIMISTIC_TYPE) { // this should never happen /* istanbul ignore if */ if (!action.endpoint.getOptimisticResponse) return state; try { // compute optimistic response based on current state - payload = action.endpoint.getOptimisticResponse.call( + response = action.endpoint.getOptimisticResponse.call( action.endpoint, controller.snapshot(state, action.meta.fetchedAt), ...action.meta.args, @@ -39,11 +39,11 @@ export function setResponseReducer( throw e; } } else { - payload = action.payload; + response = action.response; } const { result, entities, indexes, entityMeta } = normalize( action.endpoint.schema, - payload, + response, action.meta, state, ); @@ -92,7 +92,7 @@ export function setResponseReducer( undefined, 2, )}\n\nError:\n${error.message}`; - if ('payload' in action) error.payload = action.payload; + if ('response' in action) error.response = action.response; error.status = 400; } diff --git a/packages/react/src/__tests__/__snapshots__/hooks-endpoint.web.tsx.snap b/packages/react/src/__tests__/__snapshots__/hooks-endpoint.web.tsx.snap index 37d976516063..bba6bab93fad 100644 --- a/packages/react/src/__tests__/__snapshots__/hooks-endpoint.web.tsx.snap +++ b/packages/react/src/__tests__/__snapshots__/hooks-endpoint.web.tsx.snap @@ -45,7 +45,6 @@ exports[`useController.fetch should dispatch an action that fetches a create 1`] "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; @@ -66,7 +65,6 @@ exports[`useController.fetch should dispatch an action that fetches a full updat "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; @@ -87,7 +85,6 @@ exports[`useController.fetch should dispatch an action that fetches a partial up "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; @@ -105,7 +102,6 @@ exports[`useController.fetch should dispatch an action with updater in the meta "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; @@ -123,7 +119,6 @@ exports[`useController.fetch should refresh get details 1`] = ` "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; diff --git a/packages/react/src/__tests__/hooks-endpoint.web.tsx b/packages/react/src/__tests__/hooks-endpoint.web.tsx index 912bdede71ad..7f2dce0f28d3 100644 --- a/packages/react/src/__tests__/hooks-endpoint.web.tsx +++ b/packages/react/src/__tests__/hooks-endpoint.web.tsx @@ -40,7 +40,7 @@ async function testDispatchFetch( delete call[0]?.meta?.promise; expect(call[0]).toMatchSnapshot(); const action = call[0]; - const res = await action.payload(); + const res = await action.endpoint(...action.meta.args); expect(res).toEqual(payloads[i]); i++; } diff --git a/packages/react/src/components/__tests__/AsyncBoundary.web.tsx b/packages/react/src/components/__tests__/AsyncBoundary.web.tsx index 3b938c81c21d..ee81f1c8c900 100644 --- a/packages/react/src/components/__tests__/AsyncBoundary.web.tsx +++ b/packages/react/src/components/__tests__/AsyncBoundary.web.tsx @@ -5,7 +5,7 @@ import { ReactElement, StrictMode } from 'react'; import { useSuspense } from '../../hooks'; import AsyncBoundary from '../AsyncBoundary'; -import CacheProvider from '../DataProvider'; +import DataProvider from '../DataProvider'; describe('', () => { function onError(e: any) { @@ -35,11 +35,11 @@ describe('', () => { } const tree = ( - + - + ); const { getByText } = render(tree); diff --git a/packages/react/src/components/__tests__/__snapshots__/provider.native.tsx.snap b/packages/react/src/components/__tests__/__snapshots__/provider.native.tsx.snap index 03460da05b7f..f9dffeca4af4 100644 --- a/packages/react/src/components/__tests__/__snapshots__/provider.native.tsx.snap +++ b/packages/react/src/components/__tests__/__snapshots__/provider.native.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` should change state 1`] = ` +exports[` should change state 1`] = ` { "endpoints": { "GET http://test.com/article-cooler/5": "5", @@ -36,7 +36,7 @@ exports[` should change state 1`] = ` } `; -exports[` should warn users about missing Suspense 1`] = ` +exports[` should warn users about missing Suspense 1`] = ` [ [ "Uncaught suspense. diff --git a/packages/react/src/components/__tests__/__snapshots__/provider.tsx.snap b/packages/react/src/components/__tests__/__snapshots__/provider.tsx.snap index 03460da05b7f..f9dffeca4af4 100644 --- a/packages/react/src/components/__tests__/__snapshots__/provider.tsx.snap +++ b/packages/react/src/components/__tests__/__snapshots__/provider.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` should change state 1`] = ` +exports[` should change state 1`] = ` { "endpoints": { "GET http://test.com/article-cooler/5": "5", @@ -36,7 +36,7 @@ exports[` should change state 1`] = ` } `; -exports[` should warn users about missing Suspense 1`] = ` +exports[` should warn users about missing Suspense 1`] = ` [ [ "Uncaught suspense. diff --git a/packages/react/src/components/__tests__/provider.native.tsx b/packages/react/src/components/__tests__/provider.native.tsx index aa2e2feb2b5e..4b132e9a110d 100644 --- a/packages/react/src/components/__tests__/provider.native.tsx +++ b/packages/react/src/components/__tests__/provider.native.tsx @@ -15,11 +15,11 @@ import { Text } from 'react-native'; import { ControllerContext, StateContext } from '../../context'; import { useController, useSuspense } from '../../hooks'; import { payload } from '../../test-fixtures'; -import CacheProvider, { getDefaultManagers } from '../DataProvider'; +import DataProvider, { getDefaultManagers } from '../DataProvider'; const { SET_RESPONSE_TYPE } = actionTypes; -describe('', () => { +describe('', () => { let warnspy: jest.SpyInstance; beforeEach(() => { warnspy = jest.spyOn(global.console, 'warn').mockImplementation(() => {}); @@ -56,9 +56,9 @@ describe('', () => { return {article.title}; }; const tree = ( - + - + ); const { getByText } = render(tree); const msg = getByText('Uncaught Suspense.'); @@ -73,11 +73,11 @@ describe('', () => { return {article.title}; }; const tree = ( - + loading}> - + ); const { getByText, unmount } = render(tree); const msg = getByText('loading'); @@ -97,21 +97,21 @@ describe('', () => { return null; } const chil = ; - const tree = {chil}; + const tree = {chil}; const { rerender } = render(tree); expect(dispatch).toBeDefined(); let curDisp = dispatch; rerender(tree); expect(curDisp).toBe(dispatch); expect(count).toBe(1); - rerender({chil}); + rerender({chil}); expect(curDisp).toBe(dispatch); expect(count).toBe(1); const managers: any[] = [new NetworkManager()]; - rerender({chil}); + rerender({chil}); expect(count).toBe(1); curDisp = dispatch; - rerender({chil}); + rerender({chil}); expect(curDisp).toBe(dispatch); expect(count).toBe(1); rerender( @@ -134,13 +134,13 @@ describe('', () => { return null; } const chil = ; - const tree = {chil}; + const tree = {chil}; render(tree); expect(dispatch).toBeDefined(); expect(state).toBeDefined(); const action: SetResponseAction = { type: SET_RESPONSE_TYPE, - payload: { id: 5, title: 'hi', content: 'more things here' }, + response: { id: 5, title: 'hi', content: 'more things here' }, endpoint: CoolerArticleResource.get, meta: { args: [{ id: 5 }], diff --git a/packages/react/src/components/__tests__/provider.tsx b/packages/react/src/components/__tests__/provider.tsx index a49a890a08c9..4df7934ef5ca 100644 --- a/packages/react/src/components/__tests__/provider.tsx +++ b/packages/react/src/components/__tests__/provider.tsx @@ -16,11 +16,11 @@ import React, { useContext, Suspense, StrictMode } from 'react'; import { ControllerContext, StateContext } from '../../context'; import { useController, useSuspense } from '../../hooks'; import { payload } from '../../test-fixtures'; -import CacheProvider, { getDefaultManagers } from '../DataProvider'; +import DataProvider, { getDefaultManagers } from '../DataProvider'; const { SET_RESPONSE_TYPE } = actionTypes; -describe('', () => { +describe('', () => { let warnspy: jest.SpyInstance; let debugspy: jest.SpyInstance; beforeEach(() => { @@ -56,9 +56,9 @@ describe('', () => { return
{article.title}
; }; const tree = ( - + - +
); const { getByText } = render(tree); const msg = getByText('Uncaught Suspense.'); @@ -74,11 +74,11 @@ describe('', () => { }; const tree = ( - + - +
); const { getByText } = render(tree); @@ -96,21 +96,21 @@ describe('', () => { return null; } const chil = ; - const tree = {chil}; + const tree = {chil}; const { rerender } = render(tree); expect(dispatch).toBeDefined(); let curDisp = dispatch; rerender(tree); expect(curDisp).toBe(dispatch); expect(count).toBe(1); - rerender({chil}); + rerender({chil}); expect(curDisp).toBe(dispatch); expect(count).toBe(1); const managers: any[] = [new NetworkManager()]; - rerender({chil}); + rerender({chil}); expect(count).toBe(1); curDisp = dispatch; - rerender({chil}); + rerender({chil}); expect(curDisp).toBe(dispatch); expect(count).toBe(1); rerender( @@ -133,13 +133,13 @@ describe('', () => { return null; } const chil = ; - const tree = {chil}; + const tree = {chil}; render(tree); expect(dispatch).toBeDefined(); expect(state).toBeDefined(); const action: SetResponseAction = { type: SET_RESPONSE_TYPE, - payload: { id: 5, title: 'hi', content: 'more things here' }, + response: { id: 5, title: 'hi', content: 'more things here' }, endpoint: CoolerArticleResource.get, meta: { args: [{ id: 5 }], @@ -198,11 +198,11 @@ describe('', () => { return
{article.title}
; }; const tree = ( - + - +
); const { getByText, unmount } = render(tree); const msg = getByText('loading'); diff --git a/packages/react/src/hooks/__tests__/__snapshots__/useDLE.native.tsx.snap b/packages/react/src/hooks/__tests__/__snapshots__/useDLE.native.tsx.snap index f5c6fed22f12..8b257bfa8e92 100644 --- a/packages/react/src/hooks/__tests__/__snapshots__/useDLE.native.tsx.snap +++ b/packages/react/src/hooks/__tests__/__snapshots__/useDLE.native.tsx.snap @@ -13,7 +13,6 @@ exports[`useDLE should dispatch singles 1`] = ` "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; @@ -31,7 +30,6 @@ exports[`useDLE should dispatch with fetch shape defined dataExpiryLength 1`] = "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; @@ -49,7 +47,6 @@ exports[`useDLE should dispatch with fetch shape defined errorExpiryLength 1`] = "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; @@ -67,7 +64,6 @@ exports[`useDLE should dispatch with resource defined dataExpiryLength 1`] = ` "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; diff --git a/packages/react/src/hooks/__tests__/__snapshots__/useFetch.native.tsx.snap b/packages/react/src/hooks/__tests__/__snapshots__/useFetch.native.tsx.snap index 5c0d263a2966..e33eb7d8dd45 100644 --- a/packages/react/src/hooks/__tests__/__snapshots__/useFetch.native.tsx.snap +++ b/packages/react/src/hooks/__tests__/__snapshots__/useFetch.native.tsx.snap @@ -13,7 +13,6 @@ exports[`useFetch should dispatch singles 1`] = ` "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; @@ -31,7 +30,6 @@ exports[`useFetch should dispatch with fetch shape defined dataExpiryLength 1`] "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; @@ -49,7 +47,6 @@ exports[`useFetch should dispatch with fetch shape defined errorExpiryLength 1`] "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; @@ -67,7 +64,6 @@ exports[`useFetch should dispatch with resource defined dataExpiryLength 1`] = ` "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; diff --git a/packages/react/src/hooks/__tests__/__snapshots__/useFetch.web.tsx.snap b/packages/react/src/hooks/__tests__/__snapshots__/useFetch.web.tsx.snap index 5c0d263a2966..e33eb7d8dd45 100644 --- a/packages/react/src/hooks/__tests__/__snapshots__/useFetch.web.tsx.snap +++ b/packages/react/src/hooks/__tests__/__snapshots__/useFetch.web.tsx.snap @@ -13,7 +13,6 @@ exports[`useFetch should dispatch singles 1`] = ` "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; @@ -31,7 +30,6 @@ exports[`useFetch should dispatch with fetch shape defined dataExpiryLength 1`] "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; @@ -49,7 +47,6 @@ exports[`useFetch should dispatch with fetch shape defined errorExpiryLength 1`] "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; @@ -67,7 +64,6 @@ exports[`useFetch should dispatch with resource defined dataExpiryLength 1`] = ` "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; diff --git a/packages/react/src/hooks/__tests__/__snapshots__/useSuspense.native.tsx.snap b/packages/react/src/hooks/__tests__/__snapshots__/useSuspense.native.tsx.snap index 57967aa44cf2..f37650bc7ea0 100644 --- a/packages/react/src/hooks/__tests__/__snapshots__/useSuspense.native.tsx.snap +++ b/packages/react/src/hooks/__tests__/__snapshots__/useSuspense.native.tsx.snap @@ -121,7 +121,6 @@ exports[`useSuspense() should dispatch an action that fetches 1`] = ` "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; diff --git a/packages/react/src/hooks/__tests__/__snapshots__/useSuspense.web.tsx.snap b/packages/react/src/hooks/__tests__/__snapshots__/useSuspense.web.tsx.snap index 57967aa44cf2..f37650bc7ea0 100644 --- a/packages/react/src/hooks/__tests__/__snapshots__/useSuspense.web.tsx.snap +++ b/packages/react/src/hooks/__tests__/__snapshots__/useSuspense.web.tsx.snap @@ -121,7 +121,6 @@ exports[`useSuspense() should dispatch an action that fetches 1`] = ` "reject": [Function], "resolve": [Function], }, - "payload": [Function], "type": "rdc/fetch", } `; diff --git a/packages/react/src/hooks/__tests__/useFetch.web.tsx b/packages/react/src/hooks/__tests__/useFetch.web.tsx index d9c7449857b1..b12c40de12ee 100644 --- a/packages/react/src/hooks/__tests__/useFetch.web.tsx +++ b/packages/react/src/hooks/__tests__/useFetch.web.tsx @@ -39,7 +39,7 @@ async function testDispatchFetch( delete call[0]?.meta?.promise; expect(call[0]).toMatchSnapshot(); const action = call[0]; - const res = await action.payload(); + const res = await action.endpoint(...action.meta.args); expect(res).toEqual(payloads[i]); i++; } diff --git a/packages/react/src/hooks/__tests__/useSuspense.web.tsx b/packages/react/src/hooks/__tests__/useSuspense.web.tsx index 90445de2414a..b139bec35e27 100644 --- a/packages/react/src/hooks/__tests__/useSuspense.web.tsx +++ b/packages/react/src/hooks/__tests__/useSuspense.web.tsx @@ -72,7 +72,7 @@ async function testDispatchFetch( delete call[0]?.meta?.promise; expect(call[0]).toMatchSnapshot(); const action: FetchAction = call[0] as any; - const res = await action.payload(); + const res = await action.endpoint(...action.meta.args); expect(res).toEqual(payloads[i]); i++; } diff --git a/packages/test/src/createControllerInterceptor.tsx b/packages/test/src/createControllerInterceptor.tsx index 9e3cdd7c2cf9..82c290f073ff 100644 --- a/packages/test/src/createControllerInterceptor.tsx +++ b/packages/test/src/createControllerInterceptor.tsx @@ -60,7 +60,7 @@ export function createControllerInterceptor( }, }; } - replacedAction.payload = () => + const fetch = () => new Promise((resolve, reject) => { if (fixture !== undefined) { // delayCollapse determines when the fixture function is 'collapsed' (aka 'run') @@ -90,6 +90,15 @@ export function createControllerInterceptor( } } }); + if (typeof (replacedAction.endpoint as any).extend === 'function') { + replacedAction.endpoint = (replacedAction.endpoint as any).extend({ + fetch, + }); + } else { + // TODO: full testing of this + replacedAction.endpoint = fetch as any; + (replacedAction.endpoint as any).__proto__ = action.endpoint; + } return controller.dispatch(replacedAction); } diff --git a/website/src/components/Playground/editor-types/@data-client/core.d.ts b/website/src/components/Playground/editor-types/@data-client/core.d.ts index a61b8af7513f..ddb10d09636e 100644 --- a/website/src/components/Playground/editor-types/@data-client/core.d.ts +++ b/website/src/components/Playground/editor-types/@data-client/core.d.ts @@ -306,14 +306,14 @@ interface SetResponseActionSuccess = EndpointDefa type: typeof SET_RESPONSE_TYPE; endpoint: E; meta: SetResponseMeta; - payload: ResolveType; + response: ResolveType; error?: false; } interface SetResponseActionError = EndpointDefault> { type: typeof SET_RESPONSE_TYPE; endpoint: E; meta: SetResponseMeta; - payload: UnknownError; + response: UnknownError; error: true; } type SetResponseAction = EndpointDefault> = SetResponseActionSuccess | SetResponseActionError; @@ -323,14 +323,12 @@ interface FetchMeta { resolve: (value?: any | PromiseLike) => void; reject: (reason?: any) => void; promise: PromiseLike; - createdAt: number; - nm?: boolean; + fetchedAt: number; } interface FetchAction = EndpointDefault> { type: typeof FETCH_TYPE; endpoint: E; meta: FetchMeta]>; - payload: () => ReturnType; } interface OptimisticAction = EndpointDefault> { type: typeof OPTIMISTIC_TYPE; @@ -524,7 +522,7 @@ declare class Controller { update?: EndpointUpdateFunction; }>(endpoint: E, ...rest: readonly [...Parameters, Error]) => Promise; /** - * Resolves an inflight fetch. `fetchedAt` should `fetch`'s `createdAt` + * Resolves an inflight fetch. * @see https://dataclient.io/docs/api/Controller#resolve */ resolve: Promise, createdAt: number): Promise; + protected throttle(key: string, fetch: () => Promise, fetchedAt: number): Promise; /** Calls the callback when client is not 'busy' with high priority interaction tasks * * Override for platform-specific implementations