Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.

feat(core): Reimplement fetch source as async generator (#3043)

Changed files
+214 -255
.changeset
exchanges
execute
graphcache
multipart-fetch
persisted-fetch
packages
+7
.changeset/real-donkeys-act.md
···
+
---
+
'@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.
+1
exchanges/execute/src/execute.test.ts
···
fetchMock.mockResolvedValue({
status: 200,
+
headers: { get: () => 'application/json' },
text: vi
.fn()
.mockResolvedValue(JSON.stringify({ data: mockHttpResponseData })),
+10 -2
exchanges/graphcache/src/offlineExchange.test.ts
···
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);
···
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,
+6 -54
exchanges/multipart-fetch/src/multipartFetchExchange.test.ts
···
-
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,
···
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() {
···
beforeEach(() => {
fetch.mockResolvedValue({
status: 200,
+
headers: { get: () => 'application/json' },
text: vi.fn().mockResolvedValue(response),
});
});
···
beforeEach(() => {
fetch.mockResolvedValue({
status: 400,
+
headers: { get: () => 'application/json' },
text: vi.fn().mockResolvedValue('{}'),
});
});
···
it('ignores the error when a result is available', async () => {
fetch.mockResolvedValue({
status: 400,
+
headers: { get: () => 'application/json' },
text: vi.fn().mockResolvedValue(response),
});
···
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);
-
});
-
});
+16
exchanges/persisted-fetch/src/persistedFetchExchange.test.ts
···
});
fetch.mockResolvedValueOnce({
+
status: 200,
+
headers: { get: () => 'application/json' },
text: () => Promise.resolve(expected),
});
···
fetch
.mockResolvedValueOnce({
+
status: 200,
+
headers: { get: () => 'application/json' },
text: () => Promise.resolve(expectedMiss),
})
.mockResolvedValueOnce({
+
status: 200,
+
headers: { get: () => 'application/json' },
text: () => Promise.resolve(expectedRetry),
});
···
fetch
.mockResolvedValueOnce({
+
status: 200,
+
headers: { get: () => 'application/json' },
text: () => Promise.resolve(expectedMiss),
})
.mockResolvedValueOnce({
+
status: 200,
+
headers: { get: () => 'application/json' },
text: () => Promise.resolve(expectedRetry),
});
···
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),
});
+13 -4
packages/core/src/exchanges/fetch.test.ts
···
beforeEach(() => {
fetch.mockResolvedValue({
status: 200,
+
headers: { get: () => 'application/json' },
text: vi.fn().mockResolvedValue(response),
});
});
···
beforeEach(() => {
fetch.mockResolvedValue({
status: 400,
+
headers: { get: () => 'application/json' },
text: vi.fn().mockResolvedValue(JSON.stringify({})),
});
});
···
it('ignores the error when a result is available', async () => {
fetch.mockResolvedValue({
status: 400,
+
headers: { get: () => 'application/json' },
text: vi.fn().mockResolvedValue(response),
});
···
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),
···
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),
···
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)
+3
packages/core/src/internal/__snapshots__/fetchSource.test.ts.snap
···
{
"type": "return",
"value": {
+
"headers": {
+
"get": [Function],
+
},
"status": 200,
"text": [MockFunction spy] {
"calls": [
+9 -7
packages/core/src/internal/fetchSource.test.ts
···
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;
···
beforeEach(() => {
fetch.mockResolvedValue({
status: 200,
+
headers: { get: () => 'application/json' },
text: vi.fn().mockResolvedValue(response),
});
});
···
const fetchOptions = {};
const fetcher = vi.fn().mockResolvedValue({
status: 200,
+
headers: { get: () => 'application/json' },
text: vi.fn().mockResolvedValue(response),
});
···
fetch.mockResolvedValue({
status: 400,
statusText: 'Forbidden',
+
headers: { get: () => 'application/json' },
text: vi.fn().mockResolvedValue('{}'),
});
});
···
};
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', {}),
···
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', {}),
···
);
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);
});
+116 -166
packages/core/src/internal/fetchSource.ts
···
-
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
···
? (input as Buffer).toString()
: decoder!.decode(input as ArrayBuffer);
+
async function* streamBody(response: Response): AsyncIterableIterator<string> {
+
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<ChunkData>;
+
try {
+
while (!(result = await reader.read()).done) yield toString(result.value);
+
} finally {
+
reader.cancel();
+
}
+
}
+
}
+
+
async function* parseMultipartMixed(
+
contentType: string,
+
response: Response
+
): AsyncIterableIterator<ExecutionResult> {
+
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.
···
*
* @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<OperationResult> => {
-
const maxStatus = fetchOptions.redirect === 'manual' ? 400 : 300;
-
const fetcher = operation.context.fetch;
-
-
return make<OperationResult>(({ 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 <hi@marais.io>
-
// See: https://github.com/maraisr/meros/blob/219fe95/src/browser.ts
-
const executeIncrementalFetch = (
-
onResult: (result: OperationResult) => void,
-
operation: Operation,
-
response: Response
-
): Promise<void> => {
-
// 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<ChunkData>;
-
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> | 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<OperationResult> {
+
return fromAsyncIterable(fetchOperation(operation, url, fetchOptions));
+
}
+7 -2
packages/svelte-urql/src/mutationStore.test.ts
···
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({
···
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(`
+5 -1
packages/svelte-urql/src/queryStore.test.ts
···
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 }';
+5 -1
packages/svelte-urql/src/subscriptionStore.test.ts
···
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 } }`;
+16 -18
pnpm-lock.yaml
···
'@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
···
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==}
···
/@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==}
···
/@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==}
···
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
···
'@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
···
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==}
···
'@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
···
hasBin: true
dependencies:
'@babel/types': 7.20.7
-
dev: true
/@babel/parser/7.20.3:
resolution: {integrity: sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==}
···
'@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
···
'@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:
···
'@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==}