diff --git a/package.json b/package.json index 823fb70..23aabae 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "husky": "^4.2.5", "jest": "^26.5.2", "lerna": "^3.22.1", - "lockfile-lint": "^4.3.7" + "lockfile-lint": "^4.3.7", + "prettier": "^2.8.8" }, "husky": { "hooks": { diff --git a/packages/fetchye-core/__tests__/__snapshots__/actions.spec.js.snap b/packages/fetchye-core/__tests__/__snapshots__/actions.spec.js.snap index f692237..091786e 100644 --- a/packages/fetchye-core/__tests__/__snapshots__/actions.spec.js.snap +++ b/packages/fetchye-core/__tests__/__snapshots__/actions.spec.js.snap @@ -42,3 +42,25 @@ Object { }, } `; + +exports[`Actions setQuery should have correct shape 1`] = ` +Object { + "hash": "abc1234", + "query": undefined, + "type": "@fetchye/SET_QUERY", +} +`; + +exports[`Actions updateDataAction should have correct shape 1`] = ` +Object { + "hash": "abc1234", + "type": "@fetchye/UPDATE_DATA", + "value": Object { + "body": Object { + "fakeData": true, + }, + "ok": true, + "status": 200, + }, +} +`; diff --git a/packages/fetchye-core/__tests__/defaultFetcher.spec.js b/packages/fetchye-core/__tests__/defaultFetcher.spec.js index f7452ff..54f9642 100644 --- a/packages/fetchye-core/__tests__/defaultFetcher.spec.js +++ b/packages/fetchye-core/__tests__/defaultFetcher.spec.js @@ -14,14 +14,158 @@ * permissions and limitations under the License. */ +import { jsonToGraphQLQuery } from 'json-to-graphql-query'; +import { trimQueryBody } from '../src/trimQueryBody'; import { defaultFetcher } from '../src/defaultFetcher'; +jest.mock('json-to-graphql-query', () => ({ + jsonToGraphQLQuery: jest.fn(), +})); +jest.mock('../src/trimQueryBody', () => ({ + trimQueryBody: jest.fn(), +})); + global.console.error = jest.fn(); describe('defaultFetcher', () => { beforeEach(() => { jest.resetAllMocks(); }); + it('should handle graphql query', async () => { + const fetchClient = jest.fn(async () => ({ + ok: true, + status: 200, + text: async () => JSON.stringify({ jsonBodyMock: 'jsonBodyMock' }), + headers: new global.Headers({ + 'Content-Type': 'application/json', + }), + })); + jsonToGraphQLQuery.mockImplementationOnce(() => 'jsonToGraphQLQueryMock'); + trimQueryBody.mockImplementationOnce(() => 'trimQueryBodyMock'); + const data = await defaultFetcher(fetchClient, 'http://example.com', { + isGraphQL: true, + body: { + query: 'queryMock', + }, + }); + expect(data).toMatchInlineSnapshot(` + Object { + "error": undefined, + "payload": Object { + "body": Object { + "jsonBodyMock": "jsonBodyMock", + }, + "headers": Object { + "content-type": "application/json", + }, + "ok": true, + "status": 200, + }, + } + `); + expect(jsonToGraphQLQuery).toHaveBeenCalledWith('queryMock', { pretty: true }); + }); + it('should handle graphql query with missing body', async () => { + const fetchClient = jest.fn(async () => ({ + ok: true, + status: 200, + text: async () => JSON.stringify({ jsonBodyMock: 'jsonBodyMock' }), + headers: new global.Headers({ + 'Content-Type': 'application/json', + }), + })); + jsonToGraphQLQuery.mockImplementationOnce(() => 'jsonToGraphQLQueryMock'); + trimQueryBody.mockImplementationOnce(() => 'trimQueryBodyMock'); + const data = await defaultFetcher(fetchClient, 'http://example.com', { + isGraphQL: true, + body: null, + }); + expect(data).toMatchInlineSnapshot(` + Object { + "error": undefined, + "payload": Object { + "body": Object { + "jsonBodyMock": "jsonBodyMock", + }, + "headers": Object { + "content-type": "application/json", + }, + "ok": true, + "status": 200, + }, + } + `); + }); + it('should handle graphql query with existing query', async () => { + const fetchClient = jest.fn(async () => ({ + ok: true, + status: 200, + text: async () => JSON.stringify({ jsonBodyMock: 'jsonBodyMock' }), + headers: new global.Headers({ + 'Content-Type': 'application/json', + }), + })); + jsonToGraphQLQuery.mockImplementationOnce(() => 'jsonToGraphQLQueryMock'); + trimQueryBody.mockImplementationOnce(() => 'trimQueryBodyMock'); + const data = await defaultFetcher(fetchClient, 'http://example.com', { + isGraphQL: true, + existingQuery: 'existingQueryMock', + body: { + query: 'queryMock', + }, + }); + expect(data).toMatchInlineSnapshot(` + Object { + "error": undefined, + "payload": Object { + "body": Object { + "jsonBodyMock": "jsonBodyMock", + }, + "headers": Object { + "content-type": "application/json", + }, + "ok": true, + "status": 200, + }, + } + `); + expect(jsonToGraphQLQuery).toHaveBeenCalledWith('trimQueryBodyMock', { pretty: true }); + expect(trimQueryBody).toHaveBeenCalledWith('queryMock', 'existingQueryMock'); + }); + + it('should handle graphql query with existing query and no body', async () => { + const fetchClient = jest.fn(async () => ({ + ok: true, + status: 200, + text: async () => JSON.stringify({ jsonBodyMock: 'jsonBodyMock' }), + headers: new global.Headers({ + 'Content-Type': 'application/json', + }), + })); + jsonToGraphQLQuery.mockImplementationOnce(() => 'jsonToGraphQLQueryMock'); + trimQueryBody.mockImplementationOnce(() => 'trimQueryBodyMock'); + const data = await defaultFetcher(fetchClient, 'http://example.com', { + isGraphQL: true, + existingQuery: 'existingQueryMock', + body: null, + }); + expect(data).toMatchInlineSnapshot(` + Object { + "error": undefined, + "payload": Object { + "body": Object { + "jsonBodyMock": "jsonBodyMock", + }, + "headers": Object { + "content-type": "application/json", + }, + "ok": true, + "status": 200, + }, + } + `); + }); + it('should return payload and undefined error when status 200', async () => { const fetchClient = jest.fn(async () => ({ ok: true, diff --git a/packages/fetchye-core/__tests__/trimOptions.spec.js b/packages/fetchye-core/__tests__/trimOptions.spec.js new file mode 100644 index 0000000..f666e44 --- /dev/null +++ b/packages/fetchye-core/__tests__/trimOptions.spec.js @@ -0,0 +1,38 @@ +import { jsonToGraphQLQuery } from 'json-to-graphql-query'; +import { trimOptions } from '../src/trimOptions'; +import { trimQueryBody } from '../src/trimQueryBody'; + +jest.mock('json-to-graphql-query', () => ({ + jsonToGraphQLQuery: jest.fn(), +})); +jest.mock('../src/trimQueryBody', () => ({ + trimQueryBody: jest.fn(), +})); + +describe('trimOptions', () => { + it('should return options if isGraphQL is false', () => { + const options = { isGraphQL: false }; + expect(trimOptions(options)).toEqual(options); + }); + it('should return options if isGraphQL is true and existingQuery is not provided', () => { + const options = { isGraphQL: true, body: { query: 'query' } }; + jsonToGraphQLQuery.mockImplementation((value) => value); + const expectedBody = JSON.stringify({ + query: 'query', + }); + expect(trimOptions(options)).toEqual({ ...options, body: expectedBody }); + }); + it('should return options with trimmed query if isGraphQL is true and existingQuery is provided', () => { + const options = { isGraphQL: true, existingQuery: 'existingQuery', body: { query: 'query' } }; + const trimmedQuery = 'trimmedQuery'; + trimQueryBody.mockReturnValue(trimmedQuery); + jsonToGraphQLQuery.mockImplementation((value) => value); + const expectedBody = JSON.stringify({ + query: 'trimmedQuery', + }); + expect(trimOptions(options)).toEqual({ ...options, body: expectedBody }); + }); + it('should handle if options is not provided', () => { + expect(trimOptions()).toEqual(undefined); + }); +}); diff --git a/packages/fetchye-core/__tests__/trimQueryBody.spec.js b/packages/fetchye-core/__tests__/trimQueryBody.spec.js new file mode 100644 index 0000000..7503371 --- /dev/null +++ b/packages/fetchye-core/__tests__/trimQueryBody.spec.js @@ -0,0 +1,175 @@ +import deepEqual from 'deep-equal'; +import { trimQueryBody } from '../src/trimQueryBody'; + +jest.mock('deep-equal', () => jest.fn()); + +describe('trimQueryBody', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + it('should return empty object if key is not in existingQuery', () => { + deepEqual.mockImplementationOnce(() => true); + const newQuery = { + a: 1, + }; + const existingQuery = {}; + const result = trimQueryBody(newQuery, existingQuery); + expect(result).toEqual({ a: 1 }); + }); + it('should return query if args are different', () => { + deepEqual.mockImplementationOnce(() => false); + const newQuery = { + a: { + __args: { a: 1 }, + }, + }; + const existingQuery = { + a: { + __args: { a: 2 }, + }, + }; + const result = trimQueryBody(newQuery, existingQuery); + expect(result).toEqual({}); + }); + it('should return query if names are different', () => { + deepEqual.mockImplementationOnce(() => false); + const newQuery = { + a: { + __name: 'a', + }, + }; + const existingQuery = { + a: { + __name: 'b', + }, + }; + const result = trimQueryBody(newQuery, existingQuery); + expect(result).toEqual({}); + }); + it('should retrigger trimQueryBody and find a new field', () => { + deepEqual.mockImplementation(() => true); + const newQuery = { + a: { + b: { + c: 1, + }, + }, + }; + const existingQuery = { + a: { + b: { + }, + }, + }; + const result = trimQueryBody(newQuery, existingQuery); + expect(result).toEqual({ a: { b: { c: 1 } } }); + }); + it('should retrigger trimQueryBody and find a new field with args', () => { + deepEqual.mockImplementation(() => true); + const newQuery = { + a: { + __args: { a: 1 }, + b: { + c: 1, + }, + }, + }; + const existingQuery = { + a: { + __args: { a: 1 }, + b: { + }, + }, + }; + const result = trimQueryBody(newQuery, existingQuery); + expect(result).toEqual({ a: { __args: { a: 1 }, b: { c: 1 } } }); + }); + it('should retrigger trimQueryBody and find a new field with name', () => { + deepEqual.mockImplementation(() => true); + const newQuery = { + a: { + __name: 'a', + b: { + c: 1, + }, + }, + }; + const existingQuery = { + a: { + __name: 'a', + b: { + }, + }, + }; + const result = trimQueryBody(newQuery, existingQuery); + expect(result).toEqual({ a: { __name: 'a', b: { c: 1 } } }); + }); + it('should retrigger trimQueryBody and find a new field with name and args', () => { + deepEqual.mockImplementation(() => true); + const newQuery = { + a: { + __name: 'a', + __args: { a: 1 }, + b: { + c: 1, + }, + }, + }; + const existingQuery = { + a: { + __name: 'a', + __args: { a: 1 }, + b: { + }, + }, + }; + const result = trimQueryBody(newQuery, existingQuery); + expect(result).toEqual({ a: { __name: 'a', __args: { a: 1 }, b: { c: 1 } } }); + }); + it('should retrigger trimQueryBody and find a new field with name and args and other fields', () => { + deepEqual.mockImplementation(() => true); + const newQuery = { + a: { + __name: 'a', + __args: { a: 1 }, + b: { + c: 1, + }, + d: 1, + }, + }; + const existingQuery = { + a: { + __name: 'a', + __args: { a: 1 }, + b: { + }, + }, + }; + const result = trimQueryBody(newQuery, existingQuery); + expect(result).toEqual({ + a: { + __name: 'a', __args: { a: 1 }, b: { c: 1 }, d: 1, + }, + }); + }); + it('should retrigger trimQueryBody and not find a new field', () => { + deepEqual.mockImplementation(() => true); + const newQuery = { + a: { + b: { + c: 1, + }, + }, + }; + const existingQuery = { + a: { + b: { + c: 1, + }, + }, + }; + const result = trimQueryBody(newQuery, existingQuery); + expect(result).toEqual({}); + }); +}); diff --git a/packages/fetchye-core/package.json b/packages/fetchye-core/package.json index 20906e9..df234db 100644 --- a/packages/fetchye-core/package.json +++ b/packages/fetchye-core/package.json @@ -29,7 +29,9 @@ "Ruben Casas (https://github.com/infoxicator)" ], "dependencies": { - "create-shared-react-context": "^1.0.3" + "create-shared-react-context": "^1.0.3", + "deep-equal": "^2.2.3", + "json-to-graphql-query": "^2.2.5" }, "devDependencies": { "@babel/cli": "^7.12.1", diff --git a/packages/fetchye-core/src/actions.js b/packages/fetchye-core/src/actions.js index 1c45fbc..f587644 100644 --- a/packages/fetchye-core/src/actions.js +++ b/packages/fetchye-core/src/actions.js @@ -20,6 +20,8 @@ import { DELETE_DATA, ERROR, CLEAR_ERROR, + UPDATE_DATA, + SET_QUERY, } from './constants'; export function loadingAction({ @@ -41,6 +43,16 @@ export function setAction({ }; } +export function updateDataAction({ + hash, value, +}) { + return { + type: UPDATE_DATA, + hash, + value, + }; +} + export function deleteAction({ hash, }) { @@ -68,3 +80,13 @@ export function clearErrorsAction({ hash, }; } + +export function setQuery({ + hash, query, +}) { + return { + type: SET_QUERY, + hash, + query, + }; +} diff --git a/packages/fetchye-core/src/constants.js b/packages/fetchye-core/src/constants.js index e7bd5e2..1c93cf9 100644 --- a/packages/fetchye-core/src/constants.js +++ b/packages/fetchye-core/src/constants.js @@ -17,6 +17,8 @@ export const ACTION_NAMESPACE = '@fetchye'; export const IS_LOADING = `${ACTION_NAMESPACE}/IS_LOADING`; export const SET_DATA = `${ACTION_NAMESPACE}/SET_DATA`; +export const UPDATE_DATA = `${ACTION_NAMESPACE}/UPDATE_DATA`; +export const SET_QUERY = `${ACTION_NAMESPACE}/SET_QUERY`; export const DELETE_DATA = `${ACTION_NAMESPACE}/DELETE_DATA`; export const ERROR = `${ACTION_NAMESPACE}/ERROR`; export const CLEAR_ERROR = `${ACTION_NAMESPACE}/CLEAR_ERROR`; diff --git a/packages/fetchye-core/src/defaultFetcher.js b/packages/fetchye-core/src/defaultFetcher.js index 9b734b9..27f20f6 100644 --- a/packages/fetchye-core/src/defaultFetcher.js +++ b/packages/fetchye-core/src/defaultFetcher.js @@ -14,6 +14,8 @@ * permissions and limitations under the License. */ +import { trimOptions } from './trimOptions'; + // Object.fromEntries is not compatible with Node v10 // provide our own lightweight solution export const headersToObject = (headers = []) => [...headers] @@ -21,10 +23,11 @@ export const headersToObject = (headers = []) => [...headers] acc[key] = value; return acc; }, {}); -export const defaultFetcher = async (fetchClient, key, options) => { +export const defaultFetcher = async (fetchClient, key, initialOptions) => { let res; let payload; let error; + const options = trimOptions(initialOptions); try { res = await fetchClient(key, options); let body = await res.text(); diff --git a/packages/fetchye-core/src/trimOptions.js b/packages/fetchye-core/src/trimOptions.js new file mode 100644 index 0000000..8d28450 --- /dev/null +++ b/packages/fetchye-core/src/trimOptions.js @@ -0,0 +1,34 @@ +import { jsonToGraphQLQuery } from 'json-to-graphql-query'; +import { trimQueryBody } from './trimQueryBody'; + +export const trimOptions = (options) => { + const { existingQuery, isGraphQL } = options || {}; + const { + body: requestBody, + ...restOfOptions + } = options || {}; + const { + query, + ...restOfBody + } = requestBody || {}; + + if (isGraphQL && query && existingQuery) { + return { + ...restOfOptions, + body: JSON.stringify({ + query: jsonToGraphQLQuery(trimQueryBody(query, existingQuery), { pretty: true }), + ...restOfBody, + }), + }; + } + if (isGraphQL && query) { + return { + ...restOfOptions, + body: JSON.stringify({ + query: jsonToGraphQLQuery(query, { pretty: true }), + ...restOfBody, + }), + }; + } + return options; +}; diff --git a/packages/fetchye-core/src/trimQueryBody.js b/packages/fetchye-core/src/trimQueryBody.js new file mode 100644 index 0000000..38fb68c --- /dev/null +++ b/packages/fetchye-core/src/trimQueryBody.js @@ -0,0 +1,21 @@ +const graphQLTerms = ['__args', '__name', '__aliasFor', '__variables', '__directives', '__all_on', '__on', '__typeName', 'mutation']; + +export const trimQueryBody = (newQuery, existingQuery) => { + const trimmedQuery = {}; + Object.keys(newQuery).forEach((key) => { + if (newQuery[key] && !existingQuery[key]) { + trimmedQuery[key] = newQuery[key]; + } else if (graphQLTerms.indexOf(key) > -1) { + trimmedQuery[key] = newQuery[key]; + } else if (typeof newQuery[key] === 'object' && !Array.isArray(newQuery[key]) && newQuery[key] !== null) { + const cutQueryObject = trimQueryBody(newQuery[key], existingQuery[key]); + const { + __args, __name, ...rest + } = cutQueryObject; + if (Object.keys(rest).length > 0) { + trimmedQuery[key] = cutQueryObject; + } + } + }); + return trimmedQuery; +}; diff --git a/packages/fetchye-immutable-cache/__tests__/__snapshots__/ImmutableCache.spec.js.snap b/packages/fetchye-immutable-cache/__tests__/__snapshots__/ImmutableCache.spec.js.snap index 04f521b..610b988 100644 --- a/packages/fetchye-immutable-cache/__tests__/__snapshots__/ImmutableCache.spec.js.snap +++ b/packages/fetchye-immutable-cache/__tests__/__snapshots__/ImmutableCache.spec.js.snap @@ -7,6 +7,7 @@ Immutable.Map { }, "loading": Immutable.Set [], "data": Immutable.Map {}, + "query": Immutable.Map {}, } `; @@ -25,6 +26,7 @@ Immutable.Map { "abc1234", ], "data": Immutable.Map {}, + "query": Immutable.Map {}, } `; @@ -35,6 +37,7 @@ Immutable.Map { }, "loading": Immutable.Set [], "data": Immutable.Map {}, + "query": Immutable.Map {}, } `; @@ -43,6 +46,7 @@ Immutable.Map { "errors": Immutable.Map {}, "loading": Immutable.Set [], "data": Immutable.Map {}, + "query": Immutable.Map {}, } `; @@ -59,6 +63,7 @@ Immutable.Map { "status": 200, }, }, + "query": Immutable.Map {}, } `; @@ -67,6 +72,7 @@ Immutable.Map { "errors": Immutable.Map {}, "loading": Immutable.Set [], "data": Immutable.Map {}, + "query": Immutable.Map {}, } `; @@ -85,6 +91,7 @@ Immutable.Map { "status": 200, }, }, + "query": Immutable.Map {}, } `; @@ -95,6 +102,7 @@ Immutable.Map { }, "loading": Immutable.Set [], "data": Immutable.Map {}, + "query": Immutable.Map {}, } `; @@ -103,6 +111,7 @@ Immutable.Map { "errors": Immutable.Map {}, "loading": Immutable.Set [], "data": Immutable.Map {}, + "query": Immutable.Map {}, } `; @@ -119,6 +128,7 @@ Immutable.Map { "status": 200, }, }, + "query": Immutable.Map {}, } `; @@ -129,5 +139,6 @@ Immutable.Map { }, "loading": Immutable.Set [], "data": Immutable.Map {}, + "query": Immutable.Map {}, } `; diff --git a/packages/fetchye-immutable-cache/__tests__/mergeGraphQLResponse.spec.js b/packages/fetchye-immutable-cache/__tests__/mergeGraphQLResponse.spec.js new file mode 100644 index 0000000..ae14a1c --- /dev/null +++ b/packages/fetchye-immutable-cache/__tests__/mergeGraphQLResponse.spec.js @@ -0,0 +1,32 @@ +import { mergeGraphQLResponses } from '../src/mergeGraphQLResponse'; + +describe('mergeGraphQLResponses', () => { + it('should merge two responses', () => { + const existingResponse = { + body: { + a: 1, + }, + }; + const newResponse = { + body: { + b: 2, + }, + }; + const result = mergeGraphQLResponses(existingResponse, newResponse); + expect(result).toEqual({ + body: { + a: 1, + b: 2, + }, + }); + }); + it('should return newResponse if existingResponse is null', () => { + const newResponse = { + body: { + b: 2, + }, + }; + const result = mergeGraphQLResponses(null, newResponse); + expect(result).toEqual(newResponse); + }); +}); diff --git a/packages/fetchye-immutable-cache/__tests__/reducer.spec.js b/packages/fetchye-immutable-cache/__tests__/reducer.spec.js index 003b11c..2259afc 100644 --- a/packages/fetchye-immutable-cache/__tests__/reducer.spec.js +++ b/packages/fetchye-immutable-cache/__tests__/reducer.spec.js @@ -21,8 +21,15 @@ import { deleteAction, errorAction, clearErrorsAction, + setQuery, + updateDataAction, } from 'fetchye-core'; import reducer from '../src/reducer'; +import { mergeGraphQLResponses } from '../src/mergeGraphQLResponse'; + +jest.mock('../src/mergeGraphQLResponse', () => ({ + mergeGraphQLResponses: jest.fn(), +})); const fakeError = new Error('Fake Error'); const fakeData = { @@ -32,12 +39,15 @@ const fakeData = { fakeData: true, }, }; +const fakeQuery = 'query { test }'; const actions = { loadingAction, setAction, deleteAction, errorAction, + setQuery, + updateDataAction, clearErrorsAction, }; @@ -49,6 +59,7 @@ const createScenario = (dispatch, actionKeys, hash) => { hash, error: fakeError, value: fakeData, + query: fakeQuery, }) ); }); @@ -69,6 +80,7 @@ describe('Immutable Reducer', () => { "abc1234", ], "data": Immutable.Map {}, + "query": Immutable.Map {}, } `); }); @@ -88,6 +100,7 @@ describe('Immutable Reducer', () => { "status": 200, }, }, + "query": Immutable.Map {}, } `); }); @@ -101,6 +114,7 @@ describe('Immutable Reducer', () => { }, "loading": Immutable.Set [], "data": Immutable.Map {}, + "query": Immutable.Map {}, } `); }); @@ -112,6 +126,7 @@ describe('Immutable Reducer', () => { "errors": Immutable.Map {}, "loading": Immutable.Set [], "data": Immutable.Map {}, + "query": Immutable.Map {}, } `); }); @@ -123,6 +138,7 @@ describe('Immutable Reducer', () => { "errors": Immutable.Map {}, "loading": Immutable.Set [], "data": Immutable.Map {}, + "query": Immutable.Map {}, } `); }); @@ -136,6 +152,7 @@ describe('Immutable Reducer', () => { }, "loading": Immutable.Set [], "data": Immutable.Map {}, + "query": Immutable.Map {}, } `); }); @@ -155,6 +172,7 @@ describe('Immutable Reducer', () => { "status": 200, }, }, + "query": Immutable.Map {}, } `); }); @@ -177,6 +195,41 @@ describe('Immutable Reducer', () => { "status": 200, }, }, + "query": Immutable.Map {}, + } + `); + }); + it('should reflect query state', () => { + const { dispatch, getState } = store; + createScenario(dispatch, ['setQuery'], 'abc1234'); + expect(getState()).toMatchInlineSnapshot(` + Immutable.Map { + "errors": Immutable.Map {}, + "loading": Immutable.Set [], + "data": Immutable.Map {}, + "query": Immutable.Map { + "abc1234": "query { test }", + }, + } + `); + }); + it('should reflect update data state', () => { + mergeGraphQLResponses.mockReturnValueOnce({ body: { updated: true } }); + const { dispatch, getState } = store; + createScenario(dispatch, ['loadingAction', 'setAction'], 'abc1234'); + createScenario(dispatch, ['updateDataAction'], 'abc1234'); + expect(getState()).toMatchInlineSnapshot(` + Immutable.Map { + "errors": Immutable.Map {}, + "loading": Immutable.Set [], + "data": Immutable.Map { + "abc1234": Object { + "body": Object { + "updated": true, + }, + }, + }, + "query": Immutable.Map {}, } `); }); @@ -188,6 +241,7 @@ describe('Immutable Reducer', () => { "errors": Immutable.Map {}, "loading": Immutable.Set [], "data": Immutable.Map {}, + "query": Immutable.Map {}, } `); }); diff --git a/packages/fetchye-immutable-cache/package.json b/packages/fetchye-immutable-cache/package.json index 60f05ee..65d05fd 100644 --- a/packages/fetchye-immutable-cache/package.json +++ b/packages/fetchye-immutable-cache/package.json @@ -35,7 +35,8 @@ "Ruben Casas (https://github.com/infoxicator)" ], "dependencies": { - "immutable": "^4.0.0-rc.12" + "immutable": "^4.0.0-rc.12", + "lodash.merge": "^4.6.2" }, "devDependencies": { "@babel/cli": "^7.12.1", diff --git a/packages/fetchye-immutable-cache/src/mergeGraphQLResponse.js b/packages/fetchye-immutable-cache/src/mergeGraphQLResponse.js new file mode 100644 index 0000000..c4bd184 --- /dev/null +++ b/packages/fetchye-immutable-cache/src/mergeGraphQLResponse.js @@ -0,0 +1,11 @@ +import merge from 'lodash.merge'; + +export const mergeGraphQLResponses = (existingResponse, newResponse) => { + if (!existingResponse) return newResponse; + + return { + ...existingResponse, + ...newResponse, + body: merge(existingResponse.body, newResponse.body), + }; +}; diff --git a/packages/fetchye-immutable-cache/src/reducer.js b/packages/fetchye-immutable-cache/src/reducer.js index fc699c8..556dd97 100644 --- a/packages/fetchye-immutable-cache/src/reducer.js +++ b/packages/fetchye-immutable-cache/src/reducer.js @@ -20,16 +20,19 @@ import { SET_DATA, DELETE_DATA, ERROR, + UPDATE_DATA, + SET_QUERY, CLEAR_ERROR, ACTION_NAMESPACE, - } from 'fetchye-core'; +import { mergeGraphQLResponses } from './mergeGraphQLResponse'; // eslint-disable-next-line default-param-last -- the first default param value takes care of explicitly calling this function with `undefined` the second param can't be defaulted as it must be provided export function fetchyeReducer(state = iMap({ errors: iMap(), loading: iSet(), data: iMap(), + query: iMap(), }), action) { if (!action.type.startsWith(ACTION_NAMESPACE)) { return state; @@ -55,6 +58,15 @@ export function fetchyeReducer(state = iMap({ .deleteIn(['loading', action.hash]) .deleteIn(['errors', action.hash]) ); + case UPDATE_DATA: + return state.withMutations( + (nextState) => nextState + .updateIn(['data', action.hash], (existingData) => mergeGraphQLResponses(existingData, action.value)) + .deleteIn(['loading', action.hash]) + .deleteIn(['errors', action.hash]) + ); + case SET_QUERY: + return state.setIn(['query', action.hash], action.query); default: return state; } diff --git a/packages/fetchye-one-app/package.json b/packages/fetchye-one-app/package.json index d6374f1..e34c5c1 100644 --- a/packages/fetchye-one-app/package.json +++ b/packages/fetchye-one-app/package.json @@ -40,8 +40,8 @@ ], "dependencies": { "@babel/runtime": "^7.11.2", - "fetchye-immutable-cache": "^1.5.0", - "fetchye-redux-provider": "^1.5.0", + "fetchye-immutable-cache": "file:../fetchye-immutable-cache/fetchye-immutable-cache-v1.5.0.tgz", + "fetchye-redux-provider": "file:../fetchye-redux-provider/fetchye-redux-provider-v1.5.0.tgz", "fetchye-test-utils": "^1.5.0", "invariant": "^2.2.4", "prop-types": "^15.7.2" diff --git a/packages/fetchye-redux-provider/__tests__/__snapshots__/FetchyeReduxProvider.spec.jsx.snap b/packages/fetchye-redux-provider/__tests__/__snapshots__/FetchyeReduxProvider.spec.jsx.snap index eece62a..4465178 100644 --- a/packages/fetchye-redux-provider/__tests__/__snapshots__/FetchyeReduxProvider.spec.jsx.snap +++ b/packages/fetchye-redux-provider/__tests__/__snapshots__/FetchyeReduxProvider.spec.jsx.snap @@ -8,6 +8,7 @@ exports[`FetchyeReduxProvider should have loading state change when data is popu "data": undefined, "error": undefined, "loading": true, + "query": undefined, }, ], Array [ @@ -15,6 +16,7 @@ exports[`FetchyeReduxProvider should have loading state change when data is popu "data": "val1", "error": undefined, "loading": false, + "query": undefined, }, ], Array [ @@ -22,6 +24,7 @@ exports[`FetchyeReduxProvider should have loading state change when data is popu "data": undefined, "error": undefined, "loading": false, + "query": undefined, }, ], Array [ @@ -29,6 +32,7 @@ exports[`FetchyeReduxProvider should have loading state change when data is popu "data": undefined, "error": undefined, "loading": false, + "query": undefined, }, ], ], @@ -61,6 +65,7 @@ exports[`FetchyeReduxProvider should return a stable response from useFetchyeSel "data": "val1", "error": undefined, "loading": false, + "query": undefined, }, ], Array [ @@ -68,6 +73,7 @@ exports[`FetchyeReduxProvider should return a stable response from useFetchyeSel "data": "val1", "error": undefined, "loading": false, + "query": undefined, }, ], Array [ @@ -75,6 +81,7 @@ exports[`FetchyeReduxProvider should return a stable response from useFetchyeSel "data": "val2", "error": undefined, "loading": false, + "query": undefined, }, ], ], diff --git a/packages/fetchye-redux-provider/src/FetchyeReduxProvider.jsx b/packages/fetchye-redux-provider/src/FetchyeReduxProvider.jsx index 1046cc5..d01e24d 100644 --- a/packages/fetchye-redux-provider/src/FetchyeReduxProvider.jsx +++ b/packages/fetchye-redux-provider/src/FetchyeReduxProvider.jsx @@ -21,7 +21,6 @@ import PropTypes from 'prop-types'; import { useDispatch, useStore } from 'react-redux'; import { defaultFetcher, FetchyeContext, defaultEqualityChecker, useSubscription, - } from 'fetchye-core'; const makeUseFetchyeSelector = ({ diff --git a/packages/fetchye/__tests__/FetchyeProvider.spec.jsx b/packages/fetchye/__tests__/FetchyeProvider.spec.jsx index cfa0f4e..9dc5385 100644 --- a/packages/fetchye/__tests__/FetchyeProvider.spec.jsx +++ b/packages/fetchye/__tests__/FetchyeProvider.spec.jsx @@ -17,8 +17,12 @@ import React, { useContext, useEffect } from 'react'; import { act, render } from '@testing-library/react'; import { - FetchyeContext, loadingAction, setAction, deleteAction, errorAction, clearErrorsAction, - + FetchyeContext, + loadingAction, + setAction, + deleteAction, + errorAction, + clearErrorsAction, } from 'fetchye-core'; import PropTypes from 'prop-types'; import FetchyeProvider from '../src/FetchyeProvider'; @@ -79,6 +83,7 @@ describe('FetchyeProvider', () => { "data": undefined, "error": undefined, "loading": true, + "query": undefined, }, } `); @@ -103,6 +108,7 @@ describe('FetchyeProvider', () => { "data": "fakeData", "error": undefined, "loading": false, + "query": undefined, }, } `); @@ -127,6 +133,7 @@ describe('FetchyeProvider', () => { "data": undefined, "error": "fake error", "loading": false, + "query": undefined, }, } `); diff --git a/packages/fetchye/__tests__/__snapshots__/FetchyeProvider.spec.jsx.snap b/packages/fetchye/__tests__/__snapshots__/FetchyeProvider.spec.jsx.snap index 6482bb6..dce7a74 100644 --- a/packages/fetchye/__tests__/__snapshots__/FetchyeProvider.spec.jsx.snap +++ b/packages/fetchye/__tests__/__snapshots__/FetchyeProvider.spec.jsx.snap @@ -8,6 +8,7 @@ exports[`FetchyeProvider should return a stable response from useFetchyeSelector "data": "val1", "error": undefined, "loading": false, + "query": undefined, }, ], Array [ @@ -15,6 +16,7 @@ exports[`FetchyeProvider should return a stable response from useFetchyeSelector "data": "val1", "error": undefined, "loading": false, + "query": undefined, }, ], Array [ @@ -22,6 +24,7 @@ exports[`FetchyeProvider should return a stable response from useFetchyeSelector "data": "val2", "error": undefined, "loading": false, + "query": undefined, }, ], ], diff --git a/packages/fetchye/__tests__/__snapshots__/SimpleCache.spec.js.snap b/packages/fetchye/__tests__/__snapshots__/SimpleCache.spec.js.snap index 12def8a..41ee837 100644 --- a/packages/fetchye/__tests__/__snapshots__/SimpleCache.spec.js.snap +++ b/packages/fetchye/__tests__/__snapshots__/SimpleCache.spec.js.snap @@ -15,6 +15,7 @@ Object { "data": undefined, "error": undefined, "loading": false, + "query": undefined, } `; diff --git a/packages/fetchye/__tests__/computeKey.spec.js b/packages/fetchye/__tests__/computeKey.spec.js index e4e8d50..d423189 100644 --- a/packages/fetchye/__tests__/computeKey.spec.js +++ b/packages/fetchye/__tests__/computeKey.spec.js @@ -29,7 +29,7 @@ describe('computeKey', () => { it('should return an object', () => { expect(computeKey('abcd', {})).toMatchInlineSnapshot(` Object { - "hash": "037ace2918f4083eda9c4be34cccb93de5051b5a", + "hash": "cce033e32599cd4d2447b3d6c7a207ed52129471", "key": "abcd", } `); @@ -37,7 +37,7 @@ describe('computeKey', () => { it('should return an object if passed key function', () => { expect(computeKey(() => 'abcd', {})).toMatchInlineSnapshot(` Object { - "hash": "037ace2918f4083eda9c4be34cccb93de5051b5a", + "hash": "cce033e32599cd4d2447b3d6c7a207ed52129471", "key": "abcd", } `); @@ -45,7 +45,7 @@ describe('computeKey', () => { it('should return an object if no options are passed', () => { expect(computeKey(() => 'abcd')).toMatchInlineSnapshot(` Object { - "hash": "037ace2918f4083eda9c4be34cccb93de5051b5a", + "hash": "cce033e32599cd4d2447b3d6c7a207ed52129471", "key": "abcd", } `); @@ -118,7 +118,10 @@ describe('computeKey', () => { optionKeyMock: 'optionKeyValue', }); expect(computedKey.key).toBe('abcd'); - expect(computeHash).toHaveBeenCalledWith(['ABCD-optionKeyValue', { optionKeyMock: 'optionKeyValue' }], { respectType: false }); + expect(computeHash).toHaveBeenCalledWith( + ['ABCD-optionKeyValue', { optionKeyMock: 'optionKeyValue' }], + { respectType: false } + ); }); it('should return false if mapKeyToCacheKey throws error', () => { @@ -136,8 +139,8 @@ describe('computeKey', () => { }); it('should throw an error if mapKeyToCacheKey is defined and not a function', () => { - expect(() => computeKey(() => 'abcd', - { mapKeyToCacheKey: 'string' } - )).toThrow('mapKeyToCacheKey must be a function'); + expect(() => computeKey(() => 'abcd', { mapKeyToCacheKey: 'string' })).toThrow( + 'mapKeyToCacheKey must be a function' + ); }); }); diff --git a/packages/fetchye/__tests__/defaultMapOptionsToKey.spec.js b/packages/fetchye/__tests__/defaultMapOptionsToKey.spec.js index 744a83d..9c2b9af 100644 --- a/packages/fetchye/__tests__/defaultMapOptionsToKey.spec.js +++ b/packages/fetchye/__tests__/defaultMapOptionsToKey.spec.js @@ -18,11 +18,30 @@ import { defaultMapOptionsToKey } from '../src/defaultMapOptionsToKey'; describe('defaultMapOptionsToKey', () => { it('should return an object without passed signal, defer, or mapOptionsToKey', () => { - expect(defaultMapOptionsToKey({ - signal: {}, defer: true, mapOptionsToKey: () => { }, method: 'POST', - })) - .toMatchInlineSnapshot(` + expect( + defaultMapOptionsToKey({ + signal: {}, + defer: true, + mapOptionsToKey: () => {}, + method: 'POST', + }) + ).toMatchInlineSnapshot(` Object { + "body": undefined, + "method": "POST", + } + `); + }); + it('should return an object with body as argsList if isGraphQL and body.query', () => { + expect( + defaultMapOptionsToKey({ + isGraphQL: true, + body: { query: 'query { test }' }, + method: 'POST', + }) + ).toMatchInlineSnapshot(` + Object { + "body": Array [], "method": "POST", } `); diff --git a/packages/fetchye/__tests__/getCacheByKey.spec.js b/packages/fetchye/__tests__/getCacheByKey.spec.js index a51464c..176a886 100644 --- a/packages/fetchye/__tests__/getCacheByKey.spec.js +++ b/packages/fetchye/__tests__/getCacheByKey.spec.js @@ -44,6 +44,7 @@ describe('getCacheByKey', () => { "data": "Some Data", "error": "Some Error", "loading": true, + "query": undefined, } `); }); @@ -53,6 +54,7 @@ describe('getCacheByKey', () => { "data": undefined, "error": undefined, "loading": false, + "query": undefined, } `); }); @@ -62,6 +64,7 @@ describe('getCacheByKey', () => { "data": "Some Data", "error": "Some Error", "loading": true, + "query": undefined, } `); }); @@ -72,6 +75,7 @@ describe('getCacheByKey', () => { "data": "Some Data", "error": "Some Error", "loading": false, + "query": undefined, } `); }); diff --git a/packages/fetchye/__tests__/getGraphQLArgs.spec.js b/packages/fetchye/__tests__/getGraphQLArgs.spec.js new file mode 100644 index 0000000..d307961 --- /dev/null +++ b/packages/fetchye/__tests__/getGraphQLArgs.spec.js @@ -0,0 +1,27 @@ +// write tests for getGraphQLArgs + +import { getGraphQLArgs } from '../src/getGraphQLArgs'; + +describe('getGraphQLArgs', () => { + it('should return args list from query', () => { + expect( + getGraphQLArgs({ + query: { + __args: { id: '1' }, + __name: 'test', + }, + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "__args": Object { + "id": "1", + }, + }, + Object { + "__name": "test", + }, + ] + `); + }); +}); diff --git a/packages/fetchye/__tests__/handleGraphQLRequest.spec.js b/packages/fetchye/__tests__/handleGraphQLRequest.spec.js new file mode 100644 index 0000000..9706773 --- /dev/null +++ b/packages/fetchye/__tests__/handleGraphQLRequest.spec.js @@ -0,0 +1,175 @@ +// write tests for handleGraphQLRequest + +import { handleGraphQLRequest } from '../src/handleGraphQLRequest'; + +describe('handleGraphQLRequest', () => { + it('should add existing query and merged query', () => { + const existingQuery = { + query: { + __args: { id: '1' }, + __name: 'test', + }, + }; + const options = { + body: { + query: { + __args: { id: '2' }, + __name: 'test', + }, + }, + isGraphQL: true, + }; + expect( + handleGraphQLRequest({ + existingQuery, + options, + }) + ).toMatchInlineSnapshot(` + Object { + "graphQLOptions": Object { + "body": Object { + "query": Object { + "__args": Object { + "id": "2", + }, + "__name": "test", + "query": Object { + "__args": Object { + "id": "1", + }, + "__name": "test", + }, + }, + }, + "existingQuery": Object { + "query": Object { + "__args": Object { + "id": "1", + }, + "__name": "test", + }, + }, + "isGraphQL": true, + }, + "newQuery": true, + } + `); + }); + it('should not add existing query and merged query when query is matched', () => { + const existingQuery = { + __args: { id: '1' }, + __name: 'test', + }; + const options = { + body: { + query: { + __args: { id: '1' }, + __name: 'test', + }, + }, + isGraphQL: true, + }; + expect( + handleGraphQLRequest({ + existingQuery, + options, + }) + ).toMatchInlineSnapshot(` + Object { + "graphQLOptions": null, + "newQuery": false, + } + `); + }); + it('should not add existing query and merged query when isGraphQL is false', () => { + const existingQuery = { + query: { + __args: { id: '1' }, + __name: 'test', + }, + }; + const options = { + body: { + query: { + __args: { id: '2' }, + __name: 'test', + }, + }, + isGraphQL: false, + }; + expect( + handleGraphQLRequest({ + existingQuery, + options, + }) + ).toMatchInlineSnapshot(` + Object { + "graphQLOptions": null, + "newQuery": false, + } + `); + }); + it('should not add existing query and merged query when existingQuery is undefined', () => { + const existingQuery = undefined; + const options = { + body: { + query: { + __args: { id: '2' }, + __name: 'test', + }, + }, + isGraphQL: true, + }; + expect( + handleGraphQLRequest({ + existingQuery, + options, + }) + ).toMatchInlineSnapshot(` + Object { + "graphQLOptions": null, + "newQuery": false, + } + `); + }); + it('should not add existing query and merged query when query is undefined', () => { + const existingQuery = { + query: { + __args: { id: '1' }, + __name: 'test', + }, + }; + const options = { + body: { + query: undefined, + }, + isGraphQL: true, + }; + expect( + handleGraphQLRequest({ + existingQuery, + options, + }) + ).toMatchInlineSnapshot(` + Object { + "graphQLOptions": null, + "newQuery": false, + } + `); + }); + it('should not add existing query and merged query when existingQuery and query is undefined', () => { + const existingQuery = undefined; + const options = undefined; + expect( + handleGraphQLRequest({ + existingQuery, + options, + }) + ).toMatchInlineSnapshot(` + Object { + "graphQLOptions": null, + "newQuery": false, + } + `); + }); +}); diff --git a/packages/fetchye/__tests__/runAsync.spec.js b/packages/fetchye/__tests__/runAsync.spec.js index 593d2fa..041e9a6 100644 --- a/packages/fetchye/__tests__/runAsync.spec.js +++ b/packages/fetchye/__tests__/runAsync.spec.js @@ -111,4 +111,76 @@ describe('runAsync', () => { } `); }); + it('should handle isGraphQL', async () => { + const computedKey = { + hash: '1234', + key: 'http://example.com', + }; + const dispatch = jest.fn(); + const fetchClient = jest.fn(); + const fetcher = async () => ({ + payload: { body: { fake: true } }, + error: undefined, + }); + + const options = { + isGraphQL: true, + body: { + query: { + __args: { id: '1' }, + __name: 'test', + }, + }, + }; + const data = await runAsync({ + dispatch, + computedKey, + fetcher, + fetchClient, + options, + }); + expect(dispatch.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "hash": "1234", + "type": "@fetchye/IS_LOADING", + }, + ], + Array [ + Object { + "hash": "1234", + "query": Object { + "__args": Object { + "id": "1", + }, + "__name": "test", + }, + "type": "@fetchye/SET_QUERY", + }, + ], + Array [ + Object { + "hash": "1234", + "type": "@fetchye/UPDATE_DATA", + "value": Object { + "body": Object { + "fake": true, + }, + }, + }, + ], + ] + `); + expect(data).toMatchInlineSnapshot(` + Object { + "data": Object { + "body": Object { + "fake": true, + }, + }, + "error": undefined, + } + `); + }); }); diff --git a/packages/fetchye/__tests__/useFetchye.spec.jsx b/packages/fetchye/__tests__/useFetchye.spec.jsx index 09453ff..e0fc90d 100644 --- a/packages/fetchye/__tests__/useFetchye.spec.jsx +++ b/packages/fetchye/__tests__/useFetchye.spec.jsx @@ -152,6 +152,44 @@ describe('useFetchye', () => { mapOptionsToKey: expect.any(Function), }); }); + it('should call fetch with graphQL headers when isGraphQL is true', async () => { + let fetchyeRes; + global.fetch = jest.fn(async () => ({ + ...defaultPayload, + })); + render( + + {React.createElement(() => { + fetchyeRes = useFetchye('http://example.com', { + isGraphQL: true, + method: 'POST', + body: { + query: { + query: { + __name: 'MyQuery', + membership: { + __args: { + type: 'test', + id: 'test', + }, + test: true, + }, + }, + }, + }, + }); + return null; + })} + + ); + await waitFor(() => fetchyeRes.isLoading === false); + expect(global.fetch).toHaveBeenCalledTimes(1); + expect(global.fetch).toHaveBeenNthCalledWith(1, 'http://example.com', { + method: 'POST', + body: '{"query":"query MyQuery {\\n membership (type: \\"test\\", id: \\"test\\") {\\n test\\n }\\n}"}', + isGraphQL: true, + }); + }); it('should return data success state when response is empty (204 no content)', async () => { let fetchyeRes; global.fetch = jest.fn(async () => ({ @@ -321,7 +359,7 @@ describe('useFetchye', () => { }); it('should return initialState', async () => { let fetchyeRes; - global.fetch = jest.fn(async () => {}); + global.fetch = jest.fn(async () => { }); render( {React.createElement(() => { diff --git a/packages/fetchye/__tests__/useFetchyeContext.spec.jsx b/packages/fetchye/__tests__/useFetchyeContext.spec.jsx index a3920d7..35aec3b 100644 --- a/packages/fetchye/__tests__/useFetchyeContext.spec.jsx +++ b/packages/fetchye/__tests__/useFetchyeContext.spec.jsx @@ -17,8 +17,11 @@ import React, { useEffect } from 'react'; import { render } from '@testing-library/react'; import { - loadingAction, setAction, deleteAction, errorAction, clearErrorsAction, - + loadingAction, + setAction, + deleteAction, + errorAction, + clearErrorsAction, } from 'fetchye-core'; import { useFetchyeContext } from '../src/useFetchyeContext'; @@ -78,6 +81,7 @@ describe('useFetchyeContext', () => { "data": undefined, "error": undefined, "loading": true, + "query": undefined, }, } `); @@ -102,6 +106,7 @@ describe('useFetchyeContext', () => { "data": "fakeData", "error": undefined, "loading": false, + "query": undefined, }, } `); @@ -126,6 +131,7 @@ describe('useFetchyeContext', () => { "data": undefined, "error": "fake error", "loading": false, + "query": undefined, }, } `); diff --git a/packages/fetchye/package.json b/packages/fetchye/package.json index b1e65a1..5a30ba1 100644 --- a/packages/fetchye/package.json +++ b/packages/fetchye/package.json @@ -53,7 +53,9 @@ ], "dependencies": { "@babel/runtime": "^7.11.2", - "fetchye-core": "^1.5.0", + "deep-equal": "^2.2.3", + "fetchye-core": "file:../fetchye-core/fetchye-core-v1.5.0.tgz", + "lodash.merge": "^4.6.2", "object-hash": "^2.0.3", "prop-types": "^15.7.2" }, diff --git a/packages/fetchye/src/SimpleCache.js b/packages/fetchye/src/SimpleCache.js index e0c12b6..3cab869 100644 --- a/packages/fetchye/src/SimpleCache.js +++ b/packages/fetchye/src/SimpleCache.js @@ -104,7 +104,10 @@ const getCacheByKey = (cache = {}, key = undefined) => { const data = cache.data?.[key]; const loading = !!cache.loading?.[key]; const error = cache.errors?.[key]; - return { data, loading, error }; + const query = cache.query?.[key]; + return { + data, loading, error, query, + }; }; function SimpleCache({ cacheSelector = (state) => state } = {}) { diff --git a/packages/fetchye/src/defaultMapOptionsToKey.js b/packages/fetchye/src/defaultMapOptionsToKey.js index 751eb09..b80d4da 100644 --- a/packages/fetchye/src/defaultMapOptionsToKey.js +++ b/packages/fetchye/src/defaultMapOptionsToKey.js @@ -14,15 +14,23 @@ * permissions and limitations under the License. */ +import { getGraphQLArgs } from './getGraphQLArgs'; + export const defaultMapOptionsToKey = (options) => { const { signal, defer, mapOptionsToKey, initialData, + isGraphQL, + body, ...restOfOptions } = options; - return restOfOptions; + const argsList = (isGraphQL && body?.query) && getGraphQLArgs(body.query); + return { + body: argsList || body, + ...restOfOptions, + }; }; export default defaultMapOptionsToKey; diff --git a/packages/fetchye/src/getCacheByKey.js b/packages/fetchye/src/getCacheByKey.js index 444c42b..080e39f 100644 --- a/packages/fetchye/src/getCacheByKey.js +++ b/packages/fetchye/src/getCacheByKey.js @@ -20,7 +20,10 @@ export const getCacheByKey = (cache = {}, computedKey) => { const data = unpackedCache.data?.[computedKey.hash]; const loading = !!unpackedCache.loading && unpackedCache.loading.includes(computedKey.hash); const error = unpackedCache.errors?.[computedKey.hash]; - return { data, loading, error }; + const query = unpackedCache.query?.[computedKey.hash]; + return { + data, loading, error, query, + }; }; export default getCacheByKey; diff --git a/packages/fetchye/src/getGraphQLArgs.js b/packages/fetchye/src/getGraphQLArgs.js new file mode 100644 index 0000000..a9bbf4a --- /dev/null +++ b/packages/fetchye/src/getGraphQLArgs.js @@ -0,0 +1,14 @@ +const graphQLTerms = ['__args', '__name', '__aliasFor', '__variables', '__directives', '__all_on', '__on', '__typeName', 'mutation']; + +export const getGraphQLArgs = (query, args = []) => { + let argsList = [...args]; + Object.keys(query).forEach((key) => { + if (graphQLTerms.indexOf(key) > -1) { + argsList.push({ [key]: query[key] }); + } + if (typeof query[key] === 'object' && !Array.isArray(query[key]) && query[key] !== null) { + argsList = getGraphQLArgs(query[key], argsList); + } + }); + return argsList; +}; diff --git a/packages/fetchye/src/handleGraphQLRequest.js b/packages/fetchye/src/handleGraphQLRequest.js new file mode 100644 index 0000000..4be1127 --- /dev/null +++ b/packages/fetchye/src/handleGraphQLRequest.js @@ -0,0 +1,36 @@ +import merge from 'lodash.merge'; +import deepEqual from 'deep-equal'; + +export const handleGraphQLRequest = ({ + existingQuery, options, +}) => { + const { + body: { + query, + } = {}, + isGraphQL = false, + } = options || {}; + const mergedQuery = {}; + let newQuery = false; + let queryOptions = null; + if (isGraphQL) { + if (existingQuery && query) { + merge(mergedQuery, existingQuery, query); + if (!deepEqual(existingQuery, mergedQuery)) { + newQuery = true; + queryOptions = { + ...options, + existingQuery, + body: { + ...options.body, + query: mergedQuery, + }, + }; + } + } + } + return { + graphQLOptions: queryOptions, + newQuery, + }; +}; diff --git a/packages/fetchye/src/runAsync.js b/packages/fetchye/src/runAsync.js index 8ed9a77..61bb870 100644 --- a/packages/fetchye/src/runAsync.js +++ b/packages/fetchye/src/runAsync.js @@ -18,20 +18,36 @@ import { loadingAction, setAction, errorAction, - + setQuery, + updateDataAction, } from 'fetchye-core'; import { handleDynamicHeaders } from './handleDynamicHeaders'; export const runAsync = async ({ dispatch, computedKey, fetcher, fetchClient, options, }) => { + const { + body: { + query, + } = {}, + isGraphQL = false, + } = options; dispatch(loadingAction({ hash: computedKey.hash })); + if (isGraphQL) { + dispatch(setQuery({ hash: computedKey.hash, query })); + } const { payload: data, error: requestError, - } = await fetcher(fetchClient, computedKey.key, handleDynamicHeaders(options)); + } = await fetcher( + fetchClient, computedKey.key, handleDynamicHeaders(options) + ); if (!requestError) { - dispatch(setAction({ hash: computedKey.hash, value: data })); + if (isGraphQL) { + dispatch(updateDataAction({ hash: computedKey.hash, value: data })); + } else { + dispatch(setAction({ hash: computedKey.hash, value: data })); + } } else { dispatch(errorAction({ hash: computedKey.hash, error: requestError })); } diff --git a/packages/fetchye/src/useFetchye.js b/packages/fetchye/src/useFetchye.js index e523a08..673798d 100644 --- a/packages/fetchye/src/useFetchye.js +++ b/packages/fetchye/src/useFetchye.js @@ -21,6 +21,7 @@ import { isLoading, } from './queryHelpers'; import { useFetchyeContext } from './useFetchyeContext'; +import { handleGraphQLRequest } from './handleGraphQLRequest'; const passInitialData = (value, initialValue, numOfRenders) => (numOfRenders === 1 ? value || initialValue @@ -40,8 +41,29 @@ const useFetchye = ( // create a render version manager using refs const numOfRenders = useRef(0); numOfRenders.current += 1; + const graphQLOptions = useRef(null); + const newQuery = useRef(false); + const graphQLArgs = options.isGraphQL + ? [JSON.stringify(options), JSON.stringify(selectorState.current.query)] : []; useEffect(() => { + const { + query: existingQuery, + } = selectorState.current; + // Used for GraphQL queries to determine if a new query is being made + ({ graphQLOptions: graphQLOptions.current, newQuery: newQuery.current } = handleGraphQLRequest({ + existingQuery, + options, + })); + }, graphQLArgs); + + useEffect(() => { + const { + loading, + data, + error, + } = selectorState.current; + if (options.defer || !computedKey) { return; } @@ -49,10 +71,13 @@ const useFetchye = ( if (numOfRenders.current === 1 && options.initialData?.data) { return; } - const { loading, data, error } = selectorState.current; - if (!loading && !data && !error) { + if ((!loading && !data && !error) || newQuery.current) { runAsync({ - dispatch, computedKey, fetcher: selectedFetcher, fetchClient, options, + dispatch, + computedKey, + fetcher: selectedFetcher, + fetchClient, + options: graphQLOptions.current || options, }); } }); diff --git a/yarn.lock b/yarn.lock index b996b26..308714a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3046,6 +3046,14 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= +array-buffer-byte-length@^1.0.0: + version "1.0.1" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== + dependencies: + call-bind "^1.0.5" + is-array-buffer "^3.0.4" + array-differ@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-2.1.0.tgz#4b9c1c3f14b906757082925769e8ab904f4801b1" @@ -3196,6 +3204,13 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -3496,6 +3511,17 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" +call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: + version "1.0.7" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + call-me-maybe@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" @@ -4278,6 +4304,30 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= +deep-equal@^2.2.3: + version "2.2.3" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1" + integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.5" + es-get-iterator "^1.1.3" + get-intrinsic "^1.2.2" + is-arguments "^1.1.1" + is-array-buffer "^3.0.2" + is-date-object "^1.0.5" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + isarray "^2.0.5" + object-is "^1.1.5" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.1" + side-channel "^1.0.4" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.13" + deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -4295,6 +4345,15 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -4310,6 +4369,15 @@ define-properties@^1.1.4: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +define-properties@^1.2.1: + version "1.2.1" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" @@ -4716,6 +4784,33 @@ es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19 string.prototype.trimstart "^1.0.5" unbox-primitive "^1.0.2" +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-get-iterator@^1.1.3: + version "1.1.3" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" + integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + has-symbols "^1.0.3" + is-arguments "^1.1.1" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.7" + isarray "^2.0.5" + stop-iteration-iterator "^1.0.0" + es-shim-unscopables@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" @@ -5281,6 +5376,24 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +"fetchye-core@file:packages/fetchye-core/fetchye-core-v1.5.0.tgz": + version "1.5.0" + resolved "file:packages/fetchye-core/fetchye-core-v1.5.0.tgz#a2ef25c669bef933c3ec6daca7ed42a89e0d3c09" + dependencies: + create-shared-react-context "^1.0.3" + +"fetchye-immutable-cache@file:packages/fetchye-immutable-cache/fetchye-immutable-cache-v1.5.0.tgz": + version "1.5.0" + resolved "file:packages/fetchye-immutable-cache/fetchye-immutable-cache-v1.5.0.tgz#464fd999dda91a1b5ca092616cb3b8f41e7022ef" + dependencies: + immutable "^4.0.0-rc.12" + +"fetchye-redux-provider@file:packages/fetchye-redux-provider/fetchye-redux-provider-v1.5.0.tgz": + version "1.5.0" + resolved "file:packages/fetchye-redux-provider/fetchye-redux-provider-v1.5.0.tgz#948d07ee5abb544ebc2c4b48c1c4e80d8301744d" + dependencies: + prop-types "^15.7.2" + figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" @@ -5380,6 +5493,13 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +for-each@^0.3.3: + version "0.3.3" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha1-abRH6IoKXTLD5whPPxcQA0shN24= + dependencies: + is-callable "^1.1.3" + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -5468,6 +5588,11 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + function.prototype.name@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.2.tgz#5cdf79d7c05db401591dfde83e3b70c5123e9a45" @@ -5497,7 +5622,7 @@ functions-have-names@^1.2.0: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.1.tgz#a981ac397fa0c9964551402cdc5533d7a4d52f91" integrity sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA== -functions-have-names@^1.2.2: +functions-have-names@^1.2.2, functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== @@ -5540,6 +5665,17 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.3" +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -5756,6 +5892,13 @@ globby@^9.2.0: pify "^4.0.1" slash "^2.0.0" +gopd@^1.0.1: + version "1.0.1" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" @@ -5823,6 +5966,18 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.1.1" +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1: + version "1.0.3" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + has-symbols@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" @@ -5840,6 +5995,13 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -5883,6 +6045,13 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +hasown@^2.0.0: + version "2.0.2" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -6156,6 +6325,15 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" +internal-slot@^1.0.4: + version "1.0.7" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.0" + side-channel "^1.0.4" + invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -6200,6 +6378,22 @@ is-alphanumerical@^1.0.0: is-alphabetical "^1.0.0" is-decimal "^1.0.0" +is-arguments@^1.1.1: + version "1.1.1" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha1-FbP4j9oB8ql/7ITKdhpWDxI++ps= + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-array-buffer@^3.0.2, is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -6251,6 +6445,11 @@ is-builtin-module@^3.1.0: dependencies: builtin-modules "^3.0.0" +is-callable@^1.1.3: + version "1.2.7" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha1-O8KoXqdC2eNiBdys3XLKH9xRsFU= + is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" @@ -6308,6 +6507,13 @@ is-date-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== +is-date-object@^1.0.5: + version "1.0.5" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha1-CEHVU25yTCVZe/bqYuG9OCmN8x8= + dependencies: + has-tostringtag "^1.0.0" + is-decimal@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" @@ -6411,6 +6617,11 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== +is-map@^2.0.2, is-map@^2.0.3: + version "2.0.3" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + is-negative-zero@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" @@ -6485,6 +6696,11 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-set@^2.0.2, is-set@^2.0.3: + version "2.0.3" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + is-shared-array-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" @@ -6557,6 +6773,11 @@ is-utf8@^0.2.0: resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + is-weakref@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" @@ -6564,6 +6785,14 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" +is-weakset@^2.0.3: + version "2.0.3" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" + integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + is-windows@^1.0.0, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -6581,6 +6810,11 @@ isarray@1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= +isarray@^2.0.5: + version "2.0.5" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha1-ivHkwSISRMxiRZ+vOJQNTmRKVyM= + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -7113,6 +7347,11 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= +json-to-graphql-query@^2.2.5: + version "2.2.5" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/json-to-graphql-query/-/json-to-graphql-query-2.2.5.tgz#56b072a693b50fd4dc981367b60d52e3dc78f426" + integrity sha512-5Nom9inkIMrtY992LMBBG1Zaekrc10JaRhyZgprwHBVMDtRgllTvzl0oBbg13wJsVZoSoFNNMaeIVQs0P04vsA== + json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -8119,6 +8358,14 @@ object-is@^1.0.2, object-is@^1.1.2: define-properties "^1.1.3" es-abstract "^1.18.0-next.1" +object-is@^1.1.5: + version "1.1.6" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" + integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -8151,6 +8398,16 @@ object.assign@^4.1.2: has-symbols "^1.0.1" object-keys "^1.1.1" +object.assign@^4.1.4: + version "4.1.5" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + has-symbols "^1.0.3" + object-keys "^1.1.1" + object.entries@^1.1.1, object.entries@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" @@ -8648,6 +8905,11 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -8658,6 +8920,11 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= +prettier@^2.8.8: + version "2.8.8" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + pretty-format@^26.4.2, pretty-format@^26.6.1: version "26.6.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.1.tgz#af9a2f63493a856acddeeb11ba6bcf61989660a8" @@ -9104,6 +9371,16 @@ regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3: define-properties "^1.1.3" functions-have-names "^1.2.2" +regexp.prototype.flags@^1.5.1: + version "1.5.2" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== + dependencies: + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" + regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" @@ -9464,6 +9741,28 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.1: + version "2.0.2" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -9735,6 +10034,13 @@ stealthy-require@^1.1.1: resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= +stop-iteration-iterator@^1.0.0: + version "1.0.0" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" + integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== + dependencies: + internal-slot "^1.0.4" + stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" @@ -10563,6 +10869,16 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" +which-collection@^1.0.1: + version "1.0.2" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" @@ -10573,6 +10889,17 @@ which-pm-runs@^1.0.0: resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= +which-typed-array@^1.1.13: + version "1.1.15" + resolved "https://artifactory.aexp.com/api/npm/npm-virtual/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.2" + which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"