diff --git a/.changeset/real-donkeys-act.md b/.changeset/real-donkeys-act.md new file mode 100644 index 0000000000..50177fe70a --- /dev/null +++ b/.changeset/real-donkeys-act.md @@ -0,0 +1,7 @@ +--- +'@urql/core': patch +--- + +Replace fetch source implementation with async generator implementation, based on Wonka's `fromAsyncIterable`. +This also further hardens our support for the "Incremental Delivery" specification and +refactors its implementation and covers more edge cases. diff --git a/exchanges/execute/src/execute.test.ts b/exchanges/execute/src/execute.test.ts index d9517a16af..c4070fd165 100644 --- a/exchanges/execute/src/execute.test.ts +++ b/exchanges/execute/src/execute.test.ts @@ -196,6 +196,7 @@ describe('on operation', () => { fetchMock.mockResolvedValue({ status: 200, + headers: { get: () => 'application/json' }, text: vi .fn() .mockResolvedValue(JSON.stringify({ data: mockHttpResponseData })), diff --git a/exchanges/graphcache/src/offlineExchange.test.ts b/exchanges/graphcache/src/offlineExchange.test.ts index bc0d9b7482..a6ffebc98c 100644 --- a/exchanges/graphcache/src/offlineExchange.test.ts +++ b/exchanges/graphcache/src/offlineExchange.test.ts @@ -62,7 +62,11 @@ const storage = { describe('storage', () => { it('should read the metadata and dispatch operations on initialization', () => { - const client = createClient({ url: 'http://0.0.0.0' }); + const client = createClient({ + url: 'http://0.0.0.0', + exchanges: [], + }); + const reexecuteOperation = vi .spyOn(client, 'reexecuteOperation') .mockImplementation(() => undefined); @@ -100,7 +104,11 @@ describe('offline', () => { it('should intercept errored mutations', () => { const onlineSpy = vi.spyOn(navigator, 'onLine', 'get'); - const client = createClient({ url: 'http://0.0.0.0' }); + const client = createClient({ + url: 'http://0.0.0.0', + exchanges: [], + }); + const queryOp = client.createRequestOperation('query', { key: 1, query: queryOne, diff --git a/exchanges/multipart-fetch/src/multipartFetchExchange.test.ts b/exchanges/multipart-fetch/src/multipartFetchExchange.test.ts index 0a4a390117..f336ce68bf 100644 --- a/exchanges/multipart-fetch/src/multipartFetchExchange.test.ts +++ b/exchanges/multipart-fetch/src/multipartFetchExchange.test.ts @@ -1,5 +1,6 @@ -import { Client, OperationResult, makeOperation } from '@urql/core'; -import { empty, fromValue, pipe, Source, subscribe, toPromise } from 'wonka'; +import { Client, OperationResult } from '@urql/core'; +import { empty, fromValue, pipe, Source, toPromise } from 'wonka'; + import { vi, expect, @@ -23,9 +24,6 @@ import { const fetch = (global as any).fetch as Mock; const abort = vi.fn(); -const abortError = new Error(); -abortError.name = 'AbortError'; - beforeAll(() => { (global as any).AbortController = function AbortController() { this.signal = undefined; @@ -61,6 +59,7 @@ describe('on success', () => { beforeEach(() => { fetch.mockResolvedValue({ status: 200, + headers: { get: () => 'application/json' }, text: vi.fn().mockResolvedValue(response), }); }); @@ -130,6 +129,7 @@ describe('on error', () => { beforeEach(() => { fetch.mockResolvedValue({ status: 400, + headers: { get: () => 'application/json' }, text: vi.fn().mockResolvedValue('{}'), }); }); @@ -165,6 +165,7 @@ describe('on error', () => { it('ignores the error when a result is available', async () => { fetch.mockResolvedValue({ status: 400, + headers: { get: () => 'application/json' }, text: vi.fn().mockResolvedValue(response), }); @@ -177,52 +178,3 @@ describe('on error', () => { expect(data.data).toEqual(JSON.parse(response).data); }); }); - -describe('on teardown', () => { - const fail = () => { - expect(true).toEqual(false); - }; - - it('does not start the outgoing request on immediate teardowns', () => { - fetch.mockRejectedValueOnce(abortError); - - const { unsubscribe } = pipe( - fromValue(queryOperation), - multipartFetchExchange(exchangeArgs), - subscribe(fail) - ); - - unsubscribe(); - expect(fetch).toHaveBeenCalledTimes(0); - expect(abort).toHaveBeenCalledTimes(1); - }); - - it('aborts the outgoing request', async () => { - fetch.mockRejectedValueOnce(abortError); - - const { unsubscribe } = pipe( - fromValue(queryOperation), - multipartFetchExchange(exchangeArgs), - subscribe(fail) - ); - - await Promise.resolve(); - - unsubscribe(); - expect(fetch).toHaveBeenCalledTimes(1); - expect(abort).toHaveBeenCalledTimes(1); - }); - - it('does not call the query', () => { - pipe( - fromValue( - makeOperation('teardown', queryOperation, queryOperation.context) - ), - multipartFetchExchange(exchangeArgs), - subscribe(fail) - ); - - expect(fetch).toHaveBeenCalledTimes(0); - expect(abort).toHaveBeenCalledTimes(0); - }); -}); diff --git a/exchanges/persisted-fetch/src/persistedFetchExchange.test.ts b/exchanges/persisted-fetch/src/persistedFetchExchange.test.ts index 6fe67ff700..76b81e06c3 100644 --- a/exchanges/persisted-fetch/src/persistedFetchExchange.test.ts +++ b/exchanges/persisted-fetch/src/persistedFetchExchange.test.ts @@ -36,6 +36,8 @@ it('accepts successful persisted query responses', async () => { }); fetch.mockResolvedValueOnce({ + status: 200, + headers: { get: () => 'application/json' }, text: () => Promise.resolve(expected), }); @@ -63,9 +65,13 @@ it('supports cache-miss persisted query errors', async () => { fetch .mockResolvedValueOnce({ + status: 200, + headers: { get: () => 'application/json' }, text: () => Promise.resolve(expectedMiss), }) .mockResolvedValueOnce({ + status: 200, + headers: { get: () => 'application/json' }, text: () => Promise.resolve(expectedRetry), }); @@ -94,9 +100,13 @@ it('supports GET exclusively for persisted queries', async () => { fetch .mockResolvedValueOnce({ + status: 200, + headers: { get: () => 'application/json' }, text: () => Promise.resolve(expectedMiss), }) .mockResolvedValueOnce({ + status: 200, + headers: { get: () => 'application/json' }, text: () => Promise.resolve(expectedRetry), }); @@ -127,12 +137,18 @@ it('supports unsupported persisted query errors', async () => { fetch .mockResolvedValueOnce({ + status: 200, + headers: { get: () => 'application/json' }, text: () => Promise.resolve(expectedMiss), }) .mockResolvedValueOnce({ + status: 200, + headers: { get: () => 'application/json' }, text: () => Promise.resolve(expectedRetry), }) .mockResolvedValueOnce({ + status: 200, + headers: { get: () => 'application/json' }, text: () => Promise.resolve(expectedRetry), }); diff --git a/packages/core/src/exchanges/fetch.test.ts b/packages/core/src/exchanges/fetch.test.ts index 80b29a8e2a..9b657a6ea3 100755 --- a/packages/core/src/exchanges/fetch.test.ts +++ b/packages/core/src/exchanges/fetch.test.ts @@ -62,6 +62,7 @@ describe('on success', () => { beforeEach(() => { fetch.mockResolvedValue({ status: 200, + headers: { get: () => 'application/json' }, text: vi.fn().mockResolvedValue(response), }); }); @@ -91,6 +92,7 @@ describe('on error', () => { beforeEach(() => { fetch.mockResolvedValue({ status: 400, + headers: { get: () => 'application/json' }, text: vi.fn().mockResolvedValue(JSON.stringify({})), }); }); @@ -126,6 +128,7 @@ describe('on error', () => { it('ignores the error when a result is available', async () => { fetch.mockResolvedValue({ status: 400, + headers: { get: () => 'application/json' }, text: vi.fn().mockResolvedValue(response), }); @@ -143,8 +146,9 @@ describe('on teardown', () => { const fail = () => { expect(true).toEqual(false); }; + it('does not start the outgoing request on immediate teardowns', () => { - fetch.mockRejectedValueOnce(abortError); + fetch.mockResolvedValue(new Response('text', { status: 200 })); const { unsubscribe } = pipe( fromValue(queryOperation), @@ -154,11 +158,11 @@ describe('on teardown', () => { unsubscribe(); expect(fetch).toHaveBeenCalledTimes(0); - expect(abort).toHaveBeenCalledTimes(1); + expect(abort).toHaveBeenCalledTimes(0); }); it('aborts the outgoing request', async () => { - fetch.mockRejectedValueOnce(abortError); + fetch.mockResolvedValue(new Response('text', { status: 200 })); const { unsubscribe } = pipe( fromValue(queryOperation), @@ -169,11 +173,16 @@ describe('on teardown', () => { await Promise.resolve(); unsubscribe(); + + // NOTE: We can only observe the async iterator's final run after a macro tick + await new Promise(resolve => setTimeout(resolve)); expect(fetch).toHaveBeenCalledTimes(1); - expect(abort).toHaveBeenCalledTimes(1); + expect(abort).toHaveBeenCalled(); }); it('does not call the query', () => { + fetch.mockResolvedValue(new Response('text', { status: 200 })); + pipe( fromValue( makeOperation('teardown', queryOperation, queryOperation.context) diff --git a/packages/core/src/internal/__snapshots__/fetchSource.test.ts.snap b/packages/core/src/internal/__snapshots__/fetchSource.test.ts.snap index 3f23a94faa..408344e90a 100644 --- a/packages/core/src/internal/__snapshots__/fetchSource.test.ts.snap +++ b/packages/core/src/internal/__snapshots__/fetchSource.test.ts.snap @@ -606,6 +606,9 @@ exports[`on success > uses the mock fetch if given 1`] = ` { "type": "return", "value": { + "headers": { + "get": [Function], + }, "status": 200, "text": [MockFunction spy] { "calls": [ diff --git a/packages/core/src/internal/fetchSource.test.ts b/packages/core/src/internal/fetchSource.test.ts index 1986983ae9..0c210b8aea 100644 --- a/packages/core/src/internal/fetchSource.test.ts +++ b/packages/core/src/internal/fetchSource.test.ts @@ -19,9 +19,6 @@ import { makeOperation } from '../utils'; const fetch = (global as any).fetch as Mock; const abort = vi.fn(); -const abortError = new Error(); -abortError.name = 'AbortError'; - beforeAll(() => { (global as any).AbortController = function AbortController() { this.signal = undefined; @@ -51,6 +48,7 @@ describe('on success', () => { beforeEach(() => { fetch.mockResolvedValue({ status: 200, + headers: { get: () => 'application/json' }, text: vi.fn().mockResolvedValue(response), }); }); @@ -73,6 +71,7 @@ describe('on success', () => { const fetchOptions = {}; const fetcher = vi.fn().mockResolvedValue({ status: 200, + headers: { get: () => 'application/json' }, text: vi.fn().mockResolvedValue(response), }); @@ -102,6 +101,7 @@ describe('on error', () => { fetch.mockResolvedValue({ status: 400, statusText: 'Forbidden', + headers: { get: () => 'application/json' }, text: vi.fn().mockResolvedValue('{}'), }); }); @@ -165,7 +165,7 @@ describe('on teardown', () => { }; it('does not start the outgoing request on immediate teardowns', () => { - fetch.mockRejectedValue(abortError); + fetch.mockResolvedValue(new Response('text', { status: 200 })); const { unsubscribe } = pipe( makeFetchSource(queryOperation, 'https://test.com/graphql', {}), @@ -174,11 +174,11 @@ describe('on teardown', () => { unsubscribe(); expect(fetch).toHaveBeenCalledTimes(0); - expect(abort).toHaveBeenCalledTimes(1); + expect(abort).toHaveBeenCalledTimes(0); }); it('aborts the outgoing request', async () => { - fetch.mockRejectedValue(abortError); + fetch.mockResolvedValue(new Response('text', { status: 200 })); const { unsubscribe } = pipe( makeFetchSource(queryOperation, 'https://test.com/graphql', {}), @@ -186,8 +186,10 @@ describe('on teardown', () => { ); await Promise.resolve(); - unsubscribe(); + + // NOTE: We can only observe the async iterator's final run after a macro tick + await new Promise(resolve => setTimeout(resolve)); expect(fetch).toHaveBeenCalledTimes(1); expect(abort).toHaveBeenCalledTimes(1); }); diff --git a/packages/core/src/internal/fetchSource.ts b/packages/core/src/internal/fetchSource.ts index da11d59225..b242371fda 100644 --- a/packages/core/src/internal/fetchSource.ts +++ b/packages/core/src/internal/fetchSource.ts @@ -1,12 +1,11 @@ -import { Source, make } from 'wonka'; -import { Operation, OperationResult } from '../types'; +import { Source, fromAsyncIterable } from 'wonka'; +import { Operation, OperationResult, ExecutionResult } from '../types'; import { makeResult, makeErrorResult, mergeResultPatch } from '../utils'; const decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder() : null; -const jsonHeaderRe = /content-type:[^\r\n]*application\/json/i; const boundaryHeaderRe = /boundary="?([^=";]+)"?/i; -type ChunkData = { done: false; value: Buffer | Uint8Array } | { done: true }; +type ChunkData = Buffer | Uint8Array; // NOTE: We're avoiding referencing the `Buffer` global here to prevent // auto-polyfilling in Webpack @@ -15,6 +14,115 @@ const toString = (input: Buffer | ArrayBuffer): string => ? (input as Buffer).toString() : decoder!.decode(input as ArrayBuffer); +async function* streamBody(response: Response): AsyncIterableIterator { + if (response.body![Symbol.asyncIterator]) { + for await (const chunk of response.body! as any) + yield toString(chunk as ChunkData); + } else { + const reader = response.body!.getReader(); + let result: ReadableStreamReadResult; + try { + while (!(result = await reader.read()).done) yield toString(result.value); + } finally { + reader.cancel(); + } + } +} + +async function* parseMultipartMixed( + contentType: string, + response: Response +): AsyncIterableIterator { + const boundaryHeader = contentType.match(boundaryHeaderRe); + const boundary = '--' + (boundaryHeader ? boundaryHeader[1] : '-'); + + let buffer = ''; + let isPreamble = true; + let boundaryIndex: number; + let payload: any; + + chunks: for await (const chunk of streamBody(response)) { + buffer += chunk; + while ((boundaryIndex = buffer.indexOf(boundary)) > -1) { + if (isPreamble) { + isPreamble = false; + } else { + const chunk = buffer.slice( + buffer.indexOf('\r\n\r\n') + 4, + boundaryIndex + ); + + try { + yield (payload = JSON.parse(chunk)); + } catch (error) { + if (!payload) throw error; + } + } + + buffer = buffer.slice(boundaryIndex + boundary.length); + if (buffer.startsWith('--') || (payload && !payload.hasNext)) + break chunks; + } + } + + if (payload && payload.hasNext) yield { hasNext: false }; +} + +async function* fetchOperation( + operation: Operation, + url: string, + fetchOptions: RequestInit +) { + let abortController: AbortController | void; + let result: OperationResult | null = null; + let response: Response; + + try { + if (typeof AbortController !== 'undefined') { + fetchOptions.signal = (abortController = new AbortController()).signal; + } + + // Delay for a tick to give the Client a chance to cancel the request + // if a teardown comes in immediately + await Promise.resolve(); + response = await (operation.context.fetch || fetch)(url, fetchOptions); + const contentType = response.headers.get('Content-Type') || ''; + if (/text\//i.test(contentType)) { + const text = await response.text(); + return yield makeErrorResult(operation, new Error(text), response); + } else if (!/multipart\/mixed/i.test(contentType)) { + const text = await response.text(); + return yield makeResult(operation, JSON.parse(text), response); + } + + const iterator = parseMultipartMixed(contentType, response); + for await (const payload of iterator) { + yield (result = result + ? mergeResultPatch(result, payload, response) + : makeResult(operation, payload, response)); + } + + if (!result) { + yield (result = makeResult(operation, {}, response)); + } + } catch (error: any) { + if (result) { + throw error; + } + + yield makeErrorResult( + operation, + (response!.status < 200 || response!.status >= 300) && + response!.statusText + ? new Error(response!.statusText) + : error, + response! + ); + } finally { + if (abortController) abortController.abort(); + } +} + /** Makes a GraphQL HTTP request to a given API by wrapping around the Fetch API. * * @param operation - The {@link Operation} that should be sent via GraphQL over HTTP. @@ -42,168 +150,10 @@ const toString = (input: Buffer | ArrayBuffer): string => * * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API} for the Fetch API spec. */ -export const makeFetchSource = ( +export function makeFetchSource( operation: Operation, url: string, fetchOptions: RequestInit -): Source => { - const maxStatus = fetchOptions.redirect === 'manual' ? 400 : 300; - const fetcher = operation.context.fetch; - - return make(({ next, complete }) => { - const abortController = - typeof AbortController !== 'undefined' ? new AbortController() : null; - if (abortController) { - fetchOptions.signal = abortController.signal; - } - - let hasResults = false; - // DERIVATIVE: Copyright (c) 2021 Marais Rossouw - // See: https://github.com/maraisr/meros/blob/219fe95/src/browser.ts - const executeIncrementalFetch = ( - onResult: (result: OperationResult) => void, - operation: Operation, - response: Response - ): Promise => { - // NOTE: Guarding against fetch polyfills here - const contentType = - (response.headers && response.headers.get('Content-Type')) || ''; - if (/text\//i.test(contentType)) { - return response.text().then(text => { - onResult(makeErrorResult(operation, new Error(text), response)); - }); - } else if (!/multipart\/mixed/i.test(contentType)) { - return response.text().then(payload => { - onResult(makeResult(operation, JSON.parse(payload), response)); - }); - } - - let boundary = '---'; - const boundaryHeader = contentType.match(boundaryHeaderRe); - if (boundaryHeader) boundary = '--' + boundaryHeader[1]; - - let read: () => Promise; - let cancel = () => { - /*noop*/ - }; - if (response[Symbol.asyncIterator]) { - const iterator = response[Symbol.asyncIterator](); - read = iterator.next.bind(iterator); - } else if ('body' in response && response.body) { - const reader = response.body.getReader(); - cancel = () => reader.cancel(); - read = () => reader.read(); - } else { - throw new TypeError('Streaming requests unsupported'); - } - - let buffer = ''; - let isPreamble = true; - let nextResult: OperationResult | null = null; - let prevResult: OperationResult | null = null; - - function next(data: ChunkData): Promise | void { - if (!data.done) { - const chunk = toString(data.value); - let boundaryIndex = chunk.indexOf(boundary); - if (boundaryIndex > -1) { - boundaryIndex += buffer.length; - } else { - boundaryIndex = buffer.indexOf(boundary); - } - - buffer += chunk; - while (boundaryIndex > -1) { - const current = buffer.slice(0, boundaryIndex); - const next = buffer.slice(boundaryIndex + boundary.length); - - if (isPreamble) { - isPreamble = false; - } else { - const headersEnd = current.indexOf('\r\n\r\n') + 4; - const headers = current.slice(0, headersEnd); - const body = current.slice( - headersEnd, - current.lastIndexOf('\r\n') - ); - - let payload: any; - if (jsonHeaderRe.test(headers)) { - try { - payload = JSON.parse(body); - nextResult = prevResult = prevResult - ? mergeResultPatch(prevResult, payload, response) - : makeResult(operation, payload, response); - } catch (_error) {} - } - - if (next.slice(0, 2) === '--' || (payload && !payload.hasNext)) { - if (!prevResult) - return onResult(makeResult(operation, {}, response)); - break; - } - } - - buffer = next; - boundaryIndex = buffer.indexOf(boundary); - } - } else { - hasResults = true; - } - - if (nextResult) { - onResult(nextResult); - nextResult = null; - } - - if (!data.done && (!prevResult || prevResult.hasNext)) { - return read().then(next); - } - } - - return read().then(next).finally(cancel); - }; - - let ended = false; - let statusNotOk = false; - let response: Response; - - Promise.resolve() - .then(() => { - if (ended) return; - return (fetcher || fetch)(url, fetchOptions); - }) - .then((_response: Response | void) => { - if (!_response) return; - response = _response; - statusNotOk = response.status < 200 || response.status >= maxStatus; - return executeIncrementalFetch(next, operation, response); - }) - .then(complete) - .catch((error: Error) => { - if (hasResults) { - throw error; - } - - const result = makeErrorResult( - operation, - statusNotOk - ? response.statusText - ? new Error(response.statusText) - : error - : error, - response - ); - - next(result); - complete(); - }); - - return () => { - ended = true; - if (abortController) { - abortController.abort(); - } - }; - }); -}; +): Source { + return fromAsyncIterable(fetchOperation(operation, url, fetchOptions)); +} diff --git a/packages/svelte-urql/src/mutationStore.test.ts b/packages/svelte-urql/src/mutationStore.test.ts index 4fb129d5af..3bd80a6048 100644 --- a/packages/svelte-urql/src/mutationStore.test.ts +++ b/packages/svelte-urql/src/mutationStore.test.ts @@ -6,9 +6,14 @@ import { vi, expect, it, describe } from 'vitest'; import { mutationStore } from './mutationStore'; describe('mutationStore', () => { - const client = createClient({ url: 'https://example.com' }); + const client = createClient({ + url: 'noop', + exchanges: [], + }); + const variables = {}; const context = {}; + const query = 'mutation ($input: Example!) { doExample(input: $input) { id } }'; const store = mutationStore({ @@ -26,7 +31,7 @@ describe('mutationStore', () => { it('fills the store with correct values', () => { expect(get(store).operation.kind).toBe('mutation'); - expect(get(store).operation.context.url).toBe('https://example.com'); + expect(get(store).operation.context.url).toBe('noop'); expect(get(store).operation.variables).toBe(variables); expect(print(get(store).operation.query)).toMatchInlineSnapshot(` diff --git a/packages/svelte-urql/src/queryStore.test.ts b/packages/svelte-urql/src/queryStore.test.ts index 02053e348a..de497d7742 100644 --- a/packages/svelte-urql/src/queryStore.test.ts +++ b/packages/svelte-urql/src/queryStore.test.ts @@ -5,7 +5,11 @@ import { get } from 'svelte/store'; import { queryStore } from './queryStore'; describe('queryStore', () => { - const client = createClient({ url: 'https://example.com' }); + const client = createClient({ + url: 'https://example.com', + exchanges: [], + }); + const variables = {}; const context = {}; const query = '{ test }'; diff --git a/packages/svelte-urql/src/subscriptionStore.test.ts b/packages/svelte-urql/src/subscriptionStore.test.ts index f522e50680..c1929f3c86 100644 --- a/packages/svelte-urql/src/subscriptionStore.test.ts +++ b/packages/svelte-urql/src/subscriptionStore.test.ts @@ -5,7 +5,11 @@ import { vi, expect, it, describe } from 'vitest'; import { subscriptionStore } from './subscriptionStore'; describe('subscriptionStore', () => { - const client = createClient({ url: 'https://example.com' }); + const client = createClient({ + url: 'https://example.com', + exchanges: [], + }); + const variables = {}; const context = {}; const query = `subscription ($input: ExampleInput) { exampleSubscribe(input: $input) { data } }`; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 447d3f855a..85e0ab40a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -595,10 +595,10 @@ packages: '@babel/generator': 7.20.4 '@babel/helper-module-transforms': 7.20.2 '@babel/helpers': 7.20.1 - '@babel/parser': 7.20.3 + '@babel/parser': 7.20.15 '@babel/template': 7.18.10 '@babel/traverse': 7.20.1 - '@babel/types': 7.20.2 + '@babel/types': 7.20.7 convert-source-map: 1.7.0 debug: 4.3.4 gensync: 1.0.0-beta.2 @@ -650,7 +650,7 @@ packages: resolution: {integrity: sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA==} dependencies: '@babel/helper-explode-assignable-expression': 7.13.0 - '@babel/types': 7.20.2 + '@babel/types': 7.20.7 /@babel/helper-compilation-targets/7.20.0_@babel+core@7.20.2: resolution: {integrity: sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==} @@ -729,25 +729,25 @@ packages: /@babel/helper-explode-assignable-expression/7.13.0: resolution: {integrity: sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA==} dependencies: - '@babel/types': 7.20.2 + '@babel/types': 7.20.7 /@babel/helper-function-name/7.19.0: resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==} engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.18.10 - '@babel/types': 7.20.2 + '@babel/types': 7.20.7 /@babel/helper-hoist-variables/7.18.6: resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.2 + '@babel/types': 7.20.7 /@babel/helper-member-expression-to-functions/7.13.12: resolution: {integrity: sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==} dependencies: - '@babel/types': 7.20.2 + '@babel/types': 7.20.7 /@babel/helper-module-imports/7.18.6: resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} @@ -773,7 +773,7 @@ packages: /@babel/helper-optimise-call-expression/7.12.13: resolution: {integrity: sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==} dependencies: - '@babel/types': 7.20.2 + '@babel/types': 7.20.7 /@babel/helper-plugin-utils/7.10.4: resolution: {integrity: sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==} @@ -787,7 +787,7 @@ packages: dependencies: '@babel/helper-annotate-as-pure': 7.18.6 '@babel/helper-wrap-function': 7.13.0 - '@babel/types': 7.20.2 + '@babel/types': 7.20.7 transitivePeerDependencies: - supports-color @@ -797,7 +797,7 @@ packages: '@babel/helper-member-expression-to-functions': 7.13.12 '@babel/helper-optimise-call-expression': 7.12.13 '@babel/traverse': 7.20.1 - '@babel/types': 7.20.2 + '@babel/types': 7.20.7 transitivePeerDependencies: - supports-color @@ -805,18 +805,18 @@ packages: resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.2 + '@babel/types': 7.20.7 /@babel/helper-skip-transparent-expression-wrappers/7.12.1: resolution: {integrity: sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==} dependencies: - '@babel/types': 7.20.2 + '@babel/types': 7.20.7 /@babel/helper-split-export-declaration/7.18.6: resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.2 + '@babel/types': 7.20.7 /@babel/helper-string-parser/7.19.4: resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} @@ -836,7 +836,7 @@ packages: '@babel/helper-function-name': 7.19.0 '@babel/template': 7.18.10 '@babel/traverse': 7.20.1 - '@babel/types': 7.20.2 + '@babel/types': 7.20.7 transitivePeerDependencies: - supports-color @@ -864,7 +864,6 @@ packages: hasBin: true dependencies: '@babel/types': 7.20.7 - dev: true /@babel/parser/7.20.3: resolution: {integrity: sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==} @@ -1648,7 +1647,7 @@ packages: '@babel/plugin-transform-unicode-escapes': 7.12.13_@babel+core@7.20.2 '@babel/plugin-transform-unicode-regex': 7.12.13_@babel+core@7.20.2 '@babel/preset-modules': 0.1.4_@babel+core@7.20.2 - '@babel/types': 7.20.2 + '@babel/types': 7.20.7 babel-plugin-polyfill-corejs2: 0.2.0_@babel+core@7.20.2 babel-plugin-polyfill-corejs3: 0.2.0_@babel+core@7.20.2 babel-plugin-polyfill-regenerator: 0.2.0_@babel+core@7.20.2 @@ -1677,7 +1676,7 @@ packages: '@babel/helper-plugin-utils': 7.20.2 '@babel/plugin-proposal-unicode-property-regex': 7.12.13_@babel+core@7.20.2 '@babel/plugin-transform-dotall-regex': 7.12.13_@babel+core@7.20.2 - '@babel/types': 7.20.2 + '@babel/types': 7.20.7 esutils: 2.0.3 /@babel/preset-react/7.13.13_@babel+core@7.20.2: @@ -1797,7 +1796,6 @@ packages: '@babel/helper-string-parser': 7.19.4 '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 - dev: true /@babel/types/7.8.3: resolution: {integrity: sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==}