From 6cc5451ba3bfa51fb045996377bc9e8b3d263b65 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Thu, 13 Jan 2022 14:12:11 +0100 Subject: [PATCH 01/41] fix: handle late resolving promises with promise cancelation --- packages/autocomplete-core/package.json | 3 +- .../src/__tests__/debouncing.test.ts | 113 +++ packages/autocomplete-core/src/createStore.ts | 3 +- .../autocomplete-core/src/getPropGetters.ts | 18 +- packages/autocomplete-core/src/onInput.ts | 88 +- packages/autocomplete-core/src/onKeyDown.ts | 4 +- .../src/types/AutocompleteStore.ts | 4 +- .../createCancelablePromiseList.test.ts | 64 ++ .../createConcurrentSafePromise.test.ts | 39 - .../src/utils/createCancelablePromiseList.ts | 31 + .../src/utils/createConcurrentSafePromise.ts | 56 +- packages/autocomplete-core/src/utils/index.ts | 1 + yarn.lock | 800 +++++++++--------- 13 files changed, 708 insertions(+), 516 deletions(-) create mode 100644 packages/autocomplete-core/src/__tests__/debouncing.test.ts create mode 100644 packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts create mode 100644 packages/autocomplete-core/src/utils/createCancelablePromiseList.ts diff --git a/packages/autocomplete-core/package.json b/packages/autocomplete-core/package.json index 07c7d50c2..ec1324dea 100644 --- a/packages/autocomplete-core/package.json +++ b/packages/autocomplete-core/package.json @@ -31,7 +31,8 @@ "watch": "watch \"yarn on:change\" --ignoreDirectoryPattern \"/dist/\"" }, "dependencies": { - "@algolia/autocomplete-shared": "1.5.1" + "@algolia/autocomplete-shared": "1.5.1", + "cancelable-promise": "4.2.1" }, "devDependencies": { "@algolia/autocomplete-preset-algolia": "1.5.1", diff --git a/packages/autocomplete-core/src/__tests__/debouncing.test.ts b/packages/autocomplete-core/src/__tests__/debouncing.test.ts new file mode 100644 index 000000000..c9ceac963 --- /dev/null +++ b/packages/autocomplete-core/src/__tests__/debouncing.test.ts @@ -0,0 +1,113 @@ +import userEvent from '@testing-library/user-event'; + +import { createAutocomplete, InternalAutocompleteSource } from '..'; +import { createPlayground, createSource, defer } from '../../../../test/utils'; + +type DebouncedSource = InternalAutocompleteSource<{ label: string }>; + +const debounced = debouncePromise( + (items) => Promise.resolve(items), + 100 +); + +describe('debouncing', () => { + test('only submits the final query', async () => { + const onStateChange = jest.fn(); + const getItems = jest.fn(({ query }) => [{ label: query }]); + const { inputElement } = createPlayground(createAutocomplete, { + onStateChange, + getSources: () => debounced([createSource({ getItems })]), + }); + + userEvent.type(inputElement, 'abc'); + + await defer(() => {}, 300); + + expect(getItems).toHaveBeenCalledTimes(1); + expect(onStateChange).toHaveBeenLastCalledWith( + expect.objectContaining({ + state: expect.objectContaining({ + status: 'idle', + isOpen: true, + collections: expect.arrayContaining([ + expect.objectContaining({ + items: [{ __autocomplete_id: 0, label: 'abc' }], + }), + ]), + }), + }) + ); + }); + test('triggers subsequent queries after reopening the panel', async () => { + const onStateChange = jest.fn(); + const getItems = jest.fn(({ query }) => [{ label: query }]); + const { inputElement } = createPlayground(createAutocomplete, { + onStateChange, + getSources: () => debounced([createSource({ getItems })]), + }); + + userEvent.type(inputElement, 'abc'); + + await defer(() => {}, 300); + + expect(onStateChange).toHaveBeenLastCalledWith( + expect.objectContaining({ + state: expect.objectContaining({ + collections: expect.arrayContaining([ + expect.objectContaining({ + items: [{ __autocomplete_id: 0, label: 'abc' }], + }), + ]), + status: 'idle', + isOpen: true, + }), + }) + ); + + userEvent.type(inputElement, '{esc}'); + + expect(onStateChange).toHaveBeenLastCalledWith( + expect.objectContaining({ + state: expect.objectContaining({ + status: 'idle', + isOpen: false, + }), + }) + ); + + userEvent.type(inputElement, 'def'); + + await defer(() => {}, 300); + + expect(onStateChange).toHaveBeenLastCalledWith( + expect.objectContaining({ + state: expect.objectContaining({ + collections: expect.arrayContaining([ + expect.objectContaining({ + items: [{ __autocomplete_id: 0, label: 'abcdef' }], + }), + ]), + status: 'idle', + isOpen: true, + }), + }) + ); + }); +}); + +function debouncePromise( + fn: (...params: TParams) => Promise, + time: number +) { + let timerId: ReturnType | undefined = undefined; + + return function (...args: TParams) { + if (timerId) { + clearTimeout(timerId); + } + + return new Promise((resolve) => { + timerId = setTimeout(() => resolve(fn(...args)), time); + }); + }; +} diff --git a/packages/autocomplete-core/src/createStore.ts b/packages/autocomplete-core/src/createStore.ts index 8761b5e0b..4e3853d07 100644 --- a/packages/autocomplete-core/src/createStore.ts +++ b/packages/autocomplete-core/src/createStore.ts @@ -5,6 +5,7 @@ import { InternalAutocompleteOptions, Reducer, } from './types'; +import { createCancelablePromiseList } from './utils'; type OnStoreStateChange = ({ prevState, @@ -35,6 +36,6 @@ export function createStore( onStoreStateChange({ state, prevState }); }, - shouldSkipPendingUpdate: false, + pendingRequests: createCancelablePromiseList(), }; } diff --git a/packages/autocomplete-core/src/getPropGetters.ts b/packages/autocomplete-core/src/getPropGetters.ts index 24cdbf41f..373c82858 100644 --- a/packages/autocomplete-core/src/getPropGetters.ts +++ b/packages/autocomplete-core/src/getPropGetters.ts @@ -43,13 +43,13 @@ export function getPropGetters< // The `onTouchStart` event shouldn't trigger the `blur` handler when // it's not an interaction with Autocomplete. We detect it with the // following heuristics: - // - the panel is closed AND there are no running requests + // - the panel is closed AND there are no pending requests // (no interaction with the autocomplete, no future state updates) // - OR the touched target is the input element (should open the panel) - const isNotAutocompleteInteraction = - store.getState().isOpen === false && !onInput.isRunning(); + const isAutocompleteInteraction = + store.getState().isOpen === true || !store.pendingRequests.isEmpty(); - if (isNotAutocompleteInteraction || event.target === inputElement) { + if (!isAutocompleteInteraction || event.target === inputElement) { return; } @@ -62,12 +62,12 @@ export function getPropGetters< if (isTargetWithinAutocomplete === false) { store.dispatch('blur', null); - // If requests are still running when the user closes the panel, they + // If requests are still pending when the user closes the panel, they // could reopen the panel once they resolve. // We want to prevent any subsequent query from reopening the panel // because it would result in an unsolicited UI behavior. - if (!props.debug && onInput.isRunning()) { - store.shouldSkipPendingUpdate = true; + if (!props.debug) { + store.pendingRequests.cancelAll(); } } }, @@ -212,8 +212,8 @@ export function getPropGetters< // could reopen the panel once they resolve. // We want to prevent any subsequent query from reopening the panel // because it would result in an unsolicited UI behavior. - if (!props.debug && onInput.isRunning()) { - store.shouldSkipPendingUpdate = true; + if (!props.debug) { + store.pendingRequests.cancelAll(); } } }, diff --git a/packages/autocomplete-core/src/onInput.ts b/packages/autocomplete-core/src/onInput.ts index ba69409a6..b834b839e 100644 --- a/packages/autocomplete-core/src/onInput.ts +++ b/packages/autocomplete-core/src/onInput.ts @@ -1,3 +1,5 @@ +import CancelablePromise, { cancelable } from 'cancelable-promise'; + import { reshape } from './reshape'; import { preResolve, resolve, postResolve } from './resolve'; import { @@ -37,7 +39,7 @@ export function onInput({ refresh, store, ...setters -}: OnInputParams): Promise { +}: OnInputParams): CancelablePromise { if (lastStalledId) { props.environment.clearTimeout(lastStalledId); } @@ -69,7 +71,13 @@ export function onInput({ // promises to keep late resolving promises from "cancelling" the state // updates performed in this code path. // We chain with a void promise to respect `onInput`'s expected return type. - return runConcurrentSafePromise(collections).then(() => Promise.resolve()); + const request = cancelable( + runConcurrentSafePromise(collections).then(() => Promise.resolve()) + ); + + store.pendingRequests.add(request); + + return request; } setStatus('loading'); @@ -84,35 +92,37 @@ export function onInput({ // We don't track nested promises and only rely on the full chain resolution, // meaning we should only ever manipulate the state once this concurrent-safe // promise is resolved. - return runConcurrentSafePromise( - props - .getSources({ - query, - refresh, - state: store.getState(), - ...setters, - }) - .then((sources) => { - return Promise.all( - sources.map((source) => { - return Promise.resolve( - source.getItems({ - query, - refresh, - state: store.getState(), - ...setters, - }) - ).then((itemsOrDescription) => - preResolve(itemsOrDescription, source.sourceId) + const request = cancelable( + runConcurrentSafePromise( + props + .getSources({ + query, + refresh, + state: store.getState(), + ...setters, + }) + .then((sources) => { + return Promise.all( + sources.map((source) => { + return Promise.resolve( + source.getItems({ + query, + refresh, + state: store.getState(), + ...setters, + }) + ).then((itemsOrDescription) => + preResolve(itemsOrDescription, source.sourceId) + ); + }) + ) + .then(resolve) + .then((responses) => postResolve(responses, sources)) + .then((collections) => + reshape({ collections, props, state: store.getState() }) ); - }) - ) - .then(resolve) - .then((responses) => postResolve(responses, sources)) - .then((collections) => - reshape({ collections, props, state: store.getState() }) - ); - }) + }) + ) ) .then((collections) => { // Parameters passed to `onInput` could be stale when the following code @@ -122,14 +132,6 @@ export function onInput({ setStatus('idle'); - if (store.shouldSkipPendingUpdate) { - if (!runConcurrentSafePromise.isRunning()) { - store.shouldSkipPendingUpdate = false; - } - - return; - } - setCollections(collections as any); const isPanelOpen = props.shouldPanelOpen({ state: store.getState() }); @@ -157,10 +159,14 @@ export function onInput({ } }) .finally(() => { + setStatus('idle'); + if (lastStalledId) { props.environment.clearTimeout(lastStalledId); } - }); -} + }, true); + + store.pendingRequests.add(request); -onInput.isRunning = runConcurrentSafePromise.isRunning; + return request; +} diff --git a/packages/autocomplete-core/src/onKeyDown.ts b/packages/autocomplete-core/src/onKeyDown.ts index f4fb79b9a..4bf3d6d4d 100644 --- a/packages/autocomplete-core/src/onKeyDown.ts +++ b/packages/autocomplete-core/src/onKeyDown.ts @@ -104,9 +104,7 @@ export function onKeyDown({ // autocomplete. At this point, we should ignore any requests that are still // running and could reopen the panel once they resolve, because that would // result in an unsolicited UI behavior. - if (onInput.isRunning()) { - store.shouldSkipPendingUpdate = true; - } + store.pendingRequests.cancelAll(); } else if (event.key === 'Enter') { // No active item, so we let the browser handle the native `onSubmit` form // event. diff --git a/packages/autocomplete-core/src/types/AutocompleteStore.ts b/packages/autocomplete-core/src/types/AutocompleteStore.ts index d9beaae0c..5dd47181a 100644 --- a/packages/autocomplete-core/src/types/AutocompleteStore.ts +++ b/packages/autocomplete-core/src/types/AutocompleteStore.ts @@ -1,3 +1,5 @@ +import { CancelablePromiseQueue } from '../utils'; + import { BaseItem } from './AutocompleteApi'; import { InternalAutocompleteOptions } from './AutocompleteOptions'; import { AutocompleteState } from './AutocompleteState'; @@ -5,7 +7,7 @@ import { AutocompleteState } from './AutocompleteState'; export interface AutocompleteStore { getState(): AutocompleteState; dispatch(action: ActionType, payload: any): void; - shouldSkipPendingUpdate: boolean; + pendingRequests: CancelablePromiseQueue; } export type Reducer = ( diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts new file mode 100644 index 000000000..8fba8862f --- /dev/null +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts @@ -0,0 +1,64 @@ +import { CancelablePromise } from 'cancelable-promise'; + +import { createCancelablePromiseList } from '..'; + +describe('createCancelablePromiseList', () => { + test('adds cancelable promises to the list', () => { + const cancelablePromiseList = createCancelablePromiseList(); + const cancelablePromise = new CancelablePromise(() => {}); + + expect(cancelablePromiseList.isEmpty()).toBe(true); + + cancelablePromiseList.add(cancelablePromise); + + expect(cancelablePromiseList.isEmpty()).toBe(false); + }); + test('removes the cancelable promise from the list when it resolves', async () => { + const cancelablePromiseList = createCancelablePromiseList(); + const cancelablePromise = CancelablePromise.resolve(); + + cancelablePromiseList.add(cancelablePromise); + + expect(cancelablePromiseList.isEmpty()).toBe(false); + + await cancelablePromise; + + expect(cancelablePromiseList.isEmpty()).toBe(true); + }); + test('removes the cancelable promise from the list when it rejects', async () => { + const cancelablePromiseList = createCancelablePromiseList(); + const cancelablePromise = CancelablePromise.reject(); + + cancelablePromiseList.add(cancelablePromise); + + expect(cancelablePromiseList.isEmpty()).toBe(false); + + await cancelablePromise.catch(() => {}); + + expect(cancelablePromiseList.isEmpty()).toBe(true); + }); + test('removes the cancelable promise from the list when it is canceled', () => { + const cancelablePromiseList = createCancelablePromiseList(); + const cancelablePromise = CancelablePromise.resolve(); + + cancelablePromiseList.add(cancelablePromise); + + expect(cancelablePromiseList.isEmpty()).toBe(false); + + cancelablePromise.cancel(); + + expect(cancelablePromiseList.isEmpty()).toBe(true); + }); + test('empties the list when all promises are canceled', () => { + const cancelablePromiseList = createCancelablePromiseList(); + const cancelablePromise = CancelablePromise.resolve(); + + cancelablePromiseList.add(cancelablePromise); + + expect(cancelablePromiseList.isEmpty()).toBe(false); + + cancelablePromiseList.cancelAll(); + + expect(cancelablePromiseList.isEmpty()).toBe(true); + }); +}); diff --git a/packages/autocomplete-core/src/utils/__tests__/createConcurrentSafePromise.test.ts b/packages/autocomplete-core/src/utils/__tests__/createConcurrentSafePromise.test.ts index b605842c3..a67b3a40e 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createConcurrentSafePromise.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createConcurrentSafePromise.test.ts @@ -55,43 +55,4 @@ describe('createConcurrentSafePromise', () => { expect(await concurrentSafePromise2).toEqual({ value: 3 }); expect(await concurrentSafePromise3).toEqual({ value: 3 }); }); - - test('returns whether promises are currently running', async () => { - const runConcurrentSafePromise = createConcurrentSafePromise(); - const concurrentSafePromise1 = runConcurrentSafePromise( - defer(() => ({ value: 1 }), 0) - ); - const concurrentSafePromise2 = runConcurrentSafePromise( - defer(() => ({ value: 2 }), 0) - ); - const concurrentSafePromise3 = runConcurrentSafePromise( - defer(() => ({ value: 3 }), 0) - ); - - jest.runAllTimers(); - - expect(runConcurrentSafePromise.isRunning()).toBe(true); - - await concurrentSafePromise1; - await concurrentSafePromise2; - - expect(runConcurrentSafePromise.isRunning()).toBe(true); - - await concurrentSafePromise3; - - expect(runConcurrentSafePromise.isRunning()).toBe(false); - - const concurrentSafePromise4 = runConcurrentSafePromise( - defer(() => Promise.reject(new Error()), 400) - ); - - expect(runConcurrentSafePromise.isRunning()).toBe(true); - - try { - await concurrentSafePromise4; - // eslint-disable-next-line no-empty - } catch (err) {} - - expect(runConcurrentSafePromise.isRunning()).toBe(false); - }); }); diff --git a/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts new file mode 100644 index 000000000..b0d5559d5 --- /dev/null +++ b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts @@ -0,0 +1,31 @@ +import CancelablePromise from 'cancelable-promise'; + +export type CancelablePromiseQueue = { + add(cancelablePromise: CancelablePromise): void; + cancelAll(): void; + isEmpty(): boolean; +}; + +export function createCancelablePromiseList< + TPromise +>(): CancelablePromiseQueue { + let list: Array> = []; + + function remove(cancelablePromise: CancelablePromise) { + list = list.filter((promise) => promise !== cancelablePromise); + } + + return { + add(cancelablePromise) { + list.push(cancelablePromise); + + cancelablePromise.finally(() => remove(cancelablePromise), true); + }, + cancelAll() { + list.forEach((promise) => promise.cancel()); + }, + isEmpty() { + return list.length === 0; + }, + }; +} diff --git a/packages/autocomplete-core/src/utils/createConcurrentSafePromise.ts b/packages/autocomplete-core/src/utils/createConcurrentSafePromise.ts index 6c992be11..ae72460b7 100644 --- a/packages/autocomplete-core/src/utils/createConcurrentSafePromise.ts +++ b/packages/autocomplete-core/src/utils/createConcurrentSafePromise.ts @@ -10,41 +10,35 @@ export function createConcurrentSafePromise() { let basePromiseId = -1; let latestResolvedId = -1; let latestResolvedValue: unknown = undefined; - let runningPromisesCount = 0; - function runConcurrentSafePromise(promise: MaybePromise) { + return function runConcurrentSafePromise( + promise: MaybePromise + ) { basePromiseId++; - runningPromisesCount++; const currentPromiseId = basePromiseId; - return Promise.resolve(promise) - .then((x) => { - // The promise might take too long to resolve and get outdated. This would - // result in resolving stale values. - // When this happens, we ignore the promise value and return the one - // coming from the latest resolved value. - // - // +----------------------------------+ - // | 100ms | - // | run(1) +---> R1 | - // | 300ms | - // | run(2) +-------------> R2 (SKIP) | - // | 200ms | - // | run(3) +--------> R3 | - // +----------------------------------+ - if (latestResolvedValue && currentPromiseId < latestResolvedId) { - return latestResolvedValue as TValue; - } + return Promise.resolve(promise).then((x) => { + // The promise might take too long to resolve and get outdated. This would + // result in resolving stale values. + // When this happens, we ignore the promise value and return the one + // coming from the latest resolved value. + // + // +----------------------------------+ + // | 100ms | + // | run(1) +---> R1 | + // | 300ms | + // | run(2) +-------------> R2 (SKIP) | + // | 200ms | + // | run(3) +--------> R3 | + // +----------------------------------+ + if (latestResolvedValue && currentPromiseId < latestResolvedId) { + return latestResolvedValue as TValue; + } - latestResolvedId = currentPromiseId; - latestResolvedValue = x; + latestResolvedId = currentPromiseId; + latestResolvedValue = x; - return x; - }) - .finally(() => runningPromisesCount--); - } - - runConcurrentSafePromise.isRunning = () => runningPromisesCount > 0; - - return runConcurrentSafePromise; + return x; + }); + }; } diff --git a/packages/autocomplete-core/src/utils/index.ts b/packages/autocomplete-core/src/utils/index.ts index c4245e292..5c61a6c40 100644 --- a/packages/autocomplete-core/src/utils/index.ts +++ b/packages/autocomplete-core/src/utils/index.ts @@ -1,4 +1,5 @@ export * from './createConcurrentSafePromise'; +export * from './createCancelablePromiseList'; export * from './getNextActiveItemId'; export * from './getNormalizedSources'; export * from './getActiveItem'; diff --git a/yarn.lock b/yarn.lock index 16f839bf6..b477af2cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3075,15 +3075,15 @@ "@parcel/utils" "2.0.0-beta.2" astring "^1.6.2" -"@parcel/babel-ast-utils@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/babel-ast-utils/-/babel-ast-utils-2.0.1.tgz#e131e74136af878e0b2355d63acf19abbe1920db" - integrity sha512-adBHMaPAj+w1NjO+oq6SUgtOpO7wmyNIgsiHDsf8cpLf2gT0GcC/afcaC07WhIq1PJvL2hkWQpT/8sj1m/QZSw== +"@parcel/babel-ast-utils@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/babel-ast-utils/-/babel-ast-utils-2.2.0.tgz#8e1ab1f0118c1477ea50dd404d23d861502f7547" + integrity sha512-rIvqRJZ3ocPk9lZMQMlTgau3CO2bCI4fJb7lwiXVwK7E5XkJWcxyB3hplNXkSBCMDd69sQ9PNdZFW6wwzONuNQ== dependencies: "@babel/parser" "^7.0.0" - "@parcel/babylon-walk" "^2.0.1" + "@parcel/babylon-walk" "^2.2.0" "@parcel/source-map" "^2.0.0" - "@parcel/utils" "^2.0.1" + "@parcel/utils" "^2.2.0" astring "^1.6.2" "@parcel/babel-preset-env@2.0.0-beta.2": @@ -3102,10 +3102,10 @@ "@babel/types" "^7.12.13" lodash.clone "^4.5.0" -"@parcel/babylon-walk@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/babylon-walk/-/babylon-walk-2.0.1.tgz#eaedb97e57db3d40d20f6140bc3303d53e103144" - integrity sha512-eXlfG7ZGUuRF81mStZGeaYj4uH7Mgd8yfWB+c/Y13sxdacml+0vinCyZ9BjY7rYuxvKTlVSjp9BJW0Q0DS6THg== +"@parcel/babylon-walk@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/babylon-walk/-/babylon-walk-2.2.0.tgz#822ba7c74272d605a1edef24e7f57492373a2674" + integrity sha512-7H32Ln6hUAMaW46ba1S44l5EZ8l+boqQrV3iOVZEyPUUToOmFUD/TER+51M2CioXShvxdSZLVITKvbZLWRbTig== dependencies: "@babel/types" "^7.12.13" lodash.clone "^4.5.0" @@ -3120,15 +3120,15 @@ "@parcel/utils" "2.0.0-beta.2" nullthrows "^1.1.1" -"@parcel/bundler-default@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/bundler-default/-/bundler-default-2.0.1.tgz#dcffbb92c67716f9c561988b384c9c9a13074fc5" - integrity sha512-4BE86Z26gr7VHeIOCWkaucl5SNntCGS9ltk1ed65mqbZaZloZP8YD/YINxxgPtx9moTWNqQO8Y3bvCAD+VY8mQ== +"@parcel/bundler-default@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/bundler-default/-/bundler-default-2.2.0.tgz#85d5e8ae79641a9d9043ffdc2948ea4919dcf8b7" + integrity sha512-h661kIWbjcym8fh/cpTOosROCAMF/NgGdtQlQggxg9JKzH1Gj2ukxPk2uaJAML7k1MvKOF9B8PN1BRIGY3mxXA== dependencies: - "@parcel/diagnostic" "^2.0.1" - "@parcel/hash" "^2.0.1" - "@parcel/plugin" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/diagnostic" "^2.2.0" + "@parcel/hash" "^2.2.0" + "@parcel/plugin" "^2.2.0" + "@parcel/utils" "^2.2.0" nullthrows "^1.1.1" "@parcel/cache@2.0.0-beta.2": @@ -3139,14 +3139,15 @@ "@parcel/logger" "2.0.0-beta.2" "@parcel/utils" "2.0.0-beta.2" -"@parcel/cache@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/cache/-/cache-2.0.1.tgz#8619d26028124377cbcec59921ba62f57c43c4f5" - integrity sha512-aXWkx6ySwHBdPWvCJ1x6aHGFWlfu9X89iKuN4X/quMHyUDqA2PkKBR0kAvcs47ZnmUAXlKI2J9BR+lEOSAJazA== +"@parcel/cache@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/cache/-/cache-2.2.0.tgz#a3a12b6630e609b22b42b19a0a232656e50f1559" + integrity sha512-vzIuiui+VCIdvttIKIYen+IVNn6JbPv3I6MbJoNGM+UycVeK9N0yNAhSUbtZzGiBeyt1fLhq+46DSs11gV5IfA== dependencies: - "@parcel/logger" "^2.0.1" - "@parcel/utils" "^2.0.1" - lmdb-store "^1.5.5" + "@parcel/fs" "^2.2.0" + "@parcel/logger" "^2.2.0" + "@parcel/utils" "^2.2.0" + lmdb "^2.0.2" "@parcel/codeframe@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3158,22 +3159,22 @@ slice-ansi "^4.0.0" string-width "^4.2.0" -"@parcel/codeframe@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/codeframe/-/codeframe-2.0.1.tgz#b221a9082db059e1b4ae1119133fe4bfe4d923e6" - integrity sha512-NfquLg7qt8TfPmmfXVPlcq5mtEM3CvYjc+s5HLt1w0H461NiZOq7qhAaSS1N/3E+3d3eXOT/2AlCxoGm7KQ8hg== +"@parcel/codeframe@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/codeframe/-/codeframe-2.2.0.tgz#76c6f7c617e7cef692d72950129e0d59a66d9ab6" + integrity sha512-mszI8sRyDvzqcixb82dCXZKGrt/uQu2VVDsjGp9Tra1ZDJcHqWAw4ikcXVrzPnYqV6QxbD/MPt3q8ktgLKZv+A== dependencies: chalk "^4.1.0" emphasize "^4.2.0" slice-ansi "^4.0.0" string-width "^4.2.0" -"@parcel/compressor-raw@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/compressor-raw/-/compressor-raw-2.0.1.tgz#faa2586b0b569f9cf34c3c40206b7dd35fb8255c" - integrity sha512-0VNadPUIIpgx2MCjt7PGOwcz0OXN0BFxCmWzy+ocyEWj0KQ79OBr8ni7I3Be78OxNhE8luTEC22kVJwM0rtP1g== +"@parcel/compressor-raw@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/compressor-raw/-/compressor-raw-2.2.0.tgz#6b93a4697b62eebe1ab7d0622a5651f220e1391b" + integrity sha512-w/SpIKuhlABjWQtWi6NyyHmgDMvEHsjvvGa9OhEycODz67KKEohkQ/YT5TmjBVBuljfY5XAocVKIKNuONV0DYA== dependencies: - "@parcel/plugin" "^2.0.1" + "@parcel/plugin" "^2.2.0" "@parcel/config-default@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3205,40 +3206,40 @@ "@parcel/transformer-react-refresh-wrap" "2.0.0-beta.2" "@parcel/config-default@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/config-default/-/config-default-2.0.1.tgz#86f13d89baa8b062ce690415df2b018c5a87a5ea" - integrity sha512-LavQo5+81wYARmDW+GsgPIV6GPG/rskR73oGHWV1oDr9k3UD2RYdGaH1GDcwqXyUEWVCw3K+nglaZdWFpOEdRQ== - dependencies: - "@parcel/bundler-default" "^2.0.1" - "@parcel/compressor-raw" "^2.0.1" - "@parcel/namer-default" "^2.0.1" - "@parcel/optimizer-cssnano" "^2.0.1" - "@parcel/optimizer-htmlnano" "^2.0.1" - "@parcel/optimizer-image" "^2.0.1" - "@parcel/optimizer-svgo" "^2.0.1" - "@parcel/optimizer-terser" "^2.0.1" - "@parcel/packager-css" "^2.0.1" - "@parcel/packager-html" "^2.0.1" - "@parcel/packager-js" "^2.0.1" - "@parcel/packager-raw" "^2.0.1" - "@parcel/packager-svg" "^2.0.1" - "@parcel/reporter-dev-server" "^2.0.1" - "@parcel/resolver-default" "^2.0.1" - "@parcel/runtime-browser-hmr" "^2.0.1" - "@parcel/runtime-js" "^2.0.1" - "@parcel/runtime-react-refresh" "^2.0.1" - "@parcel/runtime-service-worker" "^2.0.1" - "@parcel/transformer-babel" "^2.0.1" - "@parcel/transformer-css" "^2.0.1" - "@parcel/transformer-html" "^2.0.1" - "@parcel/transformer-image" "^2.0.1" - "@parcel/transformer-js" "^2.0.1" - "@parcel/transformer-json" "^2.0.1" - "@parcel/transformer-postcss" "^2.0.1" - "@parcel/transformer-posthtml" "^2.0.1" - "@parcel/transformer-raw" "^2.0.1" - "@parcel/transformer-react-refresh-wrap" "^2.0.1" - "@parcel/transformer-svg" "^2.0.1" + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/config-default/-/config-default-2.2.0.tgz#fef389dcfd49985de9901fd792a231970a240997" + integrity sha512-Xj4WaYpKxqAf5PEHMdRfOJSjFFslISQjPwRhoXTjHTCgzDHu/js+IsQ3iK1BDLsgq1g0KyLUVbP4xjzjfW/ANw== + dependencies: + "@parcel/bundler-default" "^2.2.0" + "@parcel/compressor-raw" "^2.2.0" + "@parcel/namer-default" "^2.2.0" + "@parcel/optimizer-cssnano" "^2.2.0" + "@parcel/optimizer-htmlnano" "^2.2.0" + "@parcel/optimizer-image" "^2.2.0" + "@parcel/optimizer-svgo" "^2.2.0" + "@parcel/optimizer-terser" "^2.2.0" + "@parcel/packager-css" "^2.2.0" + "@parcel/packager-html" "^2.2.0" + "@parcel/packager-js" "^2.2.0" + "@parcel/packager-raw" "^2.2.0" + "@parcel/packager-svg" "^2.2.0" + "@parcel/reporter-dev-server" "^2.2.0" + "@parcel/resolver-default" "^2.2.0" + "@parcel/runtime-browser-hmr" "^2.2.0" + "@parcel/runtime-js" "^2.2.0" + "@parcel/runtime-react-refresh" "^2.2.0" + "@parcel/runtime-service-worker" "^2.2.0" + "@parcel/transformer-babel" "^2.2.0" + "@parcel/transformer-css" "^2.2.0" + "@parcel/transformer-html" "^2.2.0" + "@parcel/transformer-image" "^2.2.0" + "@parcel/transformer-js" "^2.2.0" + "@parcel/transformer-json" "^2.2.0" + "@parcel/transformer-postcss" "^2.2.0" + "@parcel/transformer-posthtml" "^2.2.0" + "@parcel/transformer-raw" "^2.2.0" + "@parcel/transformer-react-refresh-wrap" "^2.2.0" + "@parcel/transformer-svg" "^2.2.0" "@parcel/core@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3270,23 +3271,23 @@ semver "^5.4.1" "@parcel/core@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/core/-/core-2.0.1.tgz#804a68fc1768dc9b02ea309e919ca6bb3c6bf175" - integrity sha512-Iy5FgUAquc5HjQGiyKbWK0WaaVXerrzWD7cNBTIUOlk1xNeUtOeGu80Kc5xu0qT0/Mc+nsDfPhWcN8p4RVF+PQ== - dependencies: - "@parcel/cache" "^2.0.1" - "@parcel/diagnostic" "^2.0.1" - "@parcel/events" "^2.0.1" - "@parcel/fs" "^2.0.1" - "@parcel/graph" "^2.0.1" - "@parcel/hash" "^2.0.1" - "@parcel/logger" "^2.0.1" - "@parcel/package-manager" "^2.0.1" - "@parcel/plugin" "^2.0.1" + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/core/-/core-2.2.0.tgz#0586b0bbc9dcda48720a62cad799a3b3464b24e7" + integrity sha512-5VvSEIHqfvTSk7aX1dcvyhZTkhSiJcma8SDpQ5wTCDhK0SScInl+y7yH2v8soUaRMDE3LD4uzYLaSoJeJH/BpQ== + dependencies: + "@parcel/cache" "^2.2.0" + "@parcel/diagnostic" "^2.2.0" + "@parcel/events" "^2.2.0" + "@parcel/fs" "^2.2.0" + "@parcel/graph" "^2.2.0" + "@parcel/hash" "^2.2.0" + "@parcel/logger" "^2.2.0" + "@parcel/package-manager" "^2.2.0" + "@parcel/plugin" "^2.2.0" "@parcel/source-map" "^2.0.0" - "@parcel/types" "^2.0.1" - "@parcel/utils" "^2.0.1" - "@parcel/workers" "^2.0.1" + "@parcel/types" "^2.2.0" + "@parcel/utils" "^2.2.0" + "@parcel/workers" "^2.2.0" abortcontroller-polyfill "^1.1.9" base-x "^3.0.8" browserslist "^4.6.6" @@ -3296,8 +3297,9 @@ json-source-map "^0.6.1" json5 "^1.0.1" micromatch "^4.0.2" + msgpackr "^1.5.1" nullthrows "^1.1.1" - semver "^5.4.1" + semver "^5.7.1" "@parcel/diagnostic@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3307,10 +3309,10 @@ json-source-map "^0.6.1" nullthrows "^1.1.1" -"@parcel/diagnostic@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/diagnostic/-/diagnostic-2.0.1.tgz#cc96d94eab0db963f0c4586a2f5bc822eea46e36" - integrity sha512-pC9GmEUUB2UQ9epvE/H2wn0rb6hyF68QlpxppHZ9fxib/RxqGWDG1I3axR0cxZifRRZiMNnbk7HfmUB19KNTtA== +"@parcel/diagnostic@^2.0.1", "@parcel/diagnostic@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/diagnostic/-/diagnostic-2.2.0.tgz#717eba5d34da337c5ec0aa26cd2d2868da600edb" + integrity sha512-NJcuf7e3mbgcsaqaRq7XFLeYjAm4PJ+ZBNQGZhnnMgPDT4PjOAPUqBTBIjWyjbqRyxEosgSMbMxsZs6+U4Tt/g== dependencies: json-source-map "^0.6.1" nullthrows "^1.1.1" @@ -3320,10 +3322,10 @@ resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.0.0-beta.2.tgz#31e73129787422fa19b70d5b1a976169d18a05b7" integrity sha512-kbiFb/Qd8TavhmL84FTg3dN29Zi5Bi8bWqMgzA8hq7E8W5ezXpmw1Tu5wkjsNzHuOTj2YcAtxlTh3l29UEmh2g== -"@parcel/events@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.0.1.tgz#24ffa93353ca63889faac0928b4118ff1774add5" - integrity sha512-JRt5SkFS8/8r37o1DRKVtrWR1OZNN2pL548YsXVKBLN1b2ys36/+yKNObDuGB7DcOcIRngVs7xxv6+oodGyMlQ== +"@parcel/events@^2.0.1", "@parcel/events@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.2.0.tgz#a599aa4237b90672b01f6886934e88d8e1fd4b13" + integrity sha512-d5H9jPnCjVuNErNcDVFwxHK5zEUNr4wTcSH0CIeLshq/Z5mGhDlLC42QN4PuTR+9vHc56LlmEVrEKsLcTY5v8g== "@parcel/fs-search@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3332,10 +3334,10 @@ dependencies: detect-libc "^1.0.3" -"@parcel/fs-search@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/fs-search/-/fs-search-2.0.1.tgz#673f3ff2725c4557672d453be3f2dcfd4909f80e" - integrity sha512-Zyo1PY4opLMunes5YZ2+Q0cMCgdAuepznVvUY+dK3WjW5OzO09G/L8cfNBhgeYA84wu0yyzNohZogvFjS10TZg== +"@parcel/fs-search@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/fs-search/-/fs-search-2.2.0.tgz#de2be8829bb86db650594a1e4f37167afcac7334" + integrity sha512-EsyNFKGgixxGHTNEmfY3xGMEpAAFwEquHbaYn76SlXfQ6CLHjfGLPU5DkodSbiYE2uGwGbDQdChF60kYrHhIeQ== dependencies: detect-libc "^1.0.3" @@ -3349,10 +3351,10 @@ imurmurhash "^0.1.4" readable-stream "1 || 2" -"@parcel/fs-write-stream-atomic@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/fs-write-stream-atomic/-/fs-write-stream-atomic-2.0.1.tgz#700f9f2b3761494af305e71a185117e804b1ae41" - integrity sha512-+CSeXRCnI9f9K4jeBOYzZiOf+qw6t3TvhEstR/zeXenzx0nBMzPv28mjUMZ33vRMy8bQOHAim8qy/AMSIMolEg== +"@parcel/fs-write-stream-atomic@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/fs-write-stream-atomic/-/fs-write-stream-atomic-2.2.0.tgz#1b73f52151973b064208d7db35f8ffec6ccb2a11" + integrity sha512-c3nJJrsmxaFq0qDyRybq1f6hA7pD/gBj5N3FfFUbabMhKcPf/1tA9me4je4qiErtFPU25RmcL2X36mCrF9SvIg== dependencies: graceful-fs "^4.1.2" iferr "^1.0.2" @@ -3375,17 +3377,17 @@ nullthrows "^1.1.1" rimraf "^3.0.2" -"@parcel/fs@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-2.0.1.tgz#908fe8c953f8fb8fb9d61d97733b11f8a150fe19" - integrity sha512-zl8aV9Qp4lB4cQGyBfz3LQM+JkL7WHGoSlj8PjBamT8VmPlr57BUtp3Gc/IvRCCX8B7izNx3X8vCvr5BrziL+g== +"@parcel/fs@^2.0.1", "@parcel/fs@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-2.2.0.tgz#1c9b13025b24f1c117bd712eab80717b7d4ff045" + integrity sha512-dn0TZAH98OYaSQwk5JrpfNmoPXn8tH5lbHKKm8VP8a1RhrG5TdynYbeZ8uu5XQ2FK1+M66/HtAdFLBLlevlWrw== dependencies: - "@parcel/fs-search" "^2.0.1" - "@parcel/fs-write-stream-atomic" "^2.0.1" - "@parcel/types" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/fs-search" "^2.2.0" + "@parcel/fs-write-stream-atomic" "^2.2.0" + "@parcel/types" "^2.2.0" + "@parcel/utils" "^2.2.0" "@parcel/watcher" "^2.0.0" - "@parcel/workers" "^2.0.1" + "@parcel/workers" "^2.2.0" graceful-fs "^4.2.4" mkdirp "^0.5.1" ncp "^2.0.0" @@ -3393,20 +3395,21 @@ rimraf "^3.0.2" utility-types "^3.10.0" -"@parcel/graph@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/graph/-/graph-2.0.1.tgz#800d9d93ea4a1403954ae913096eba4ea96d66b1" - integrity sha512-LESQVWy/Oln1CqTgWTjvm99btNSqHxOcIKEIL7k6Pq2d6vhO6oyAAmMe5sqf6Sr1nNCVjZW7oHRzyIG0kYTgWw== +"@parcel/graph@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/graph/-/graph-2.2.0.tgz#8a13b9e8c17c0074f9acbdffdf9f45ce37a14a47" + integrity sha512-3c1AZWO7ndpHPBxK1R+z+jS2MBBlsu2bKWVfUAPU3I9tGCGXKOhH5i/gO/jURSHx3g5s6ygZbfljAodpZ7W2DQ== dependencies: + "@parcel/utils" "^2.2.0" nullthrows "^1.1.1" -"@parcel/hash@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/hash/-/hash-2.0.1.tgz#46178908dd4c8c3a3f9f46ae62ec0646100400da" - integrity sha512-Zng4i5HhcmOr6NMzQlnCf12ED9isL+HmcFC3XSLc6VYFcCnVg6cEIwJ7KrB/s5wRHLU2TfSZAaLIJlhcPKPPog== +"@parcel/hash@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/hash/-/hash-2.2.0.tgz#12eb373a7fe64c791822a0655bd661e632e45e06" + integrity sha512-lXN4dY3y5ZxUP52jhd+C99kSWJRjIbh0lMpMqMGhkdIRoJUpvv24kQ2aItcgqazWWR9SS1NDZ0/z7+vHPMmEFw== dependencies: detect-libc "^1.0.3" - xxhash-wasm "^0.4.1" + xxhash-wasm "^0.4.2" "@parcel/logger@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3416,13 +3419,13 @@ "@parcel/diagnostic" "2.0.0-beta.2" "@parcel/events" "2.0.0-beta.2" -"@parcel/logger@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-2.0.1.tgz#e379fc171d3b7860bacf363132a983665f01752d" - integrity sha512-gN2mdDnUkbN11hUIDBU+zlREsgp7zm42ZAsc0xwIdmlnsZY7wu2G3lNtkXSMlIPJPdRi6oE6vmaArQJfXjaAOg== +"@parcel/logger@^2.0.1", "@parcel/logger@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-2.2.0.tgz#ceb6783cc9fa807837f8a1b6a17856746c511753" + integrity sha512-HEiPq7EVmttDVZocebNbHDDhTWDUVD8UizLkRrLCtdRQGTcK0+WFitooamhjCQKXG0T/4/dh8ojIfWTQPMCFLg== dependencies: - "@parcel/diagnostic" "^2.0.1" - "@parcel/events" "^2.0.1" + "@parcel/diagnostic" "^2.2.0" + "@parcel/events" "^2.2.0" "@parcel/markdown-ansi@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3431,10 +3434,10 @@ dependencies: chalk "^4.1.0" -"@parcel/markdown-ansi@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/markdown-ansi/-/markdown-ansi-2.0.1.tgz#448eeb7a8d7cc44de4b0d7e74b4ae5b613d5ca30" - integrity sha512-KFUvJoGncCwOml+RSyJl0KfQgle42YC8VJwQrHUqKMR5acyC3KaDNWAx96xkPf3k/hKv+VVEhIsH7SRJ63qwwQ== +"@parcel/markdown-ansi@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/markdown-ansi/-/markdown-ansi-2.2.0.tgz#507c2cd533a09ac27bb799d2905962c67c7b2861" + integrity sha512-N46Yun+jRA97rEhOKyMC3awIULw6KRPsV2JkI9yfc5EiOOA1CYmVNIB6OmgmuuvZYwq9OoHxB+XFkXF+yxiWBA== dependencies: chalk "^4.1.0" @@ -3447,13 +3450,13 @@ "@parcel/plugin" "2.0.0-beta.2" nullthrows "^1.1.1" -"@parcel/namer-default@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/namer-default/-/namer-default-2.0.1.tgz#94181e72fbb4dd6963d92292e2ba76caca989563" - integrity sha512-wF948WojfksHutz023T2lC3b1BWRyOa9KaCh9caYtZ1Lq26kG3X2eaWVjOzw65SUQRLzAAxu3ujRhKEg0N0Ntw== +"@parcel/namer-default@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/namer-default/-/namer-default-2.2.0.tgz#51bee03db280b671677b2324bac118bab4c3369e" + integrity sha512-Iz5MLGTTLd5zQ+CbyV/4VjOiTFaQJaMwwu3W/GfaI2eLou2d9bazFaa786b4UM9u0LjLKYaUUzWDohllSwBwSg== dependencies: - "@parcel/diagnostic" "^2.0.1" - "@parcel/plugin" "^2.0.1" + "@parcel/diagnostic" "^2.2.0" + "@parcel/plugin" "^2.2.0" nullthrows "^1.1.1" "@parcel/node-libs-browser@2.0.0-beta.2": @@ -3484,10 +3487,10 @@ util "^0.12.3" vm-browserify "^1.1.2" -"@parcel/node-libs-browser@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/node-libs-browser/-/node-libs-browser-2.0.1.tgz#4c4c7ae43d7d347bba1f426a9cc01e74bb6bcfce" - integrity sha512-EK6hndQMtW0DJMU4FeDmbDwdIus/IAXz/YjR2kdQ0fLRAvcNWC/34R5bqlLmWdX2NXWVS+1tcDhPa2oEnUzzHA== +"@parcel/node-libs-browser@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/node-libs-browser/-/node-libs-browser-2.2.0.tgz#bf5feabe76378f6504a4a72250a9438ec4892792" + integrity sha512-RtaJG+cdo60+ffq+tD9OwtTAqRG4cAyrFxWbeqYPb+RenpZJBWBJHRy1C5cjtH98sfXPzwMzROayQviFBvRq5w== dependencies: assert "^2.0.0" browserify-zlib "^0.2.0" @@ -3524,14 +3527,14 @@ nullthrows "^1.1.1" querystring "^0.2.0" -"@parcel/node-resolver-core@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/node-resolver-core/-/node-resolver-core-2.0.1.tgz#1469ad7fe19a48453dfa8c574356fa543cfa8d63" - integrity sha512-bZqot9TZKuBpojo9i4LQ/mc+iKKuurcWDy481E/Z9Xp3zfDEZaNzj2f+0MSwv3pbqB134/PIMMtN92tewJ7Piw== +"@parcel/node-resolver-core@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/node-resolver-core/-/node-resolver-core-2.2.0.tgz#8aca3e8904a628e77d62bb0a8d310d0dcfc8960f" + integrity sha512-/YssRfsNLMQAYuNAGMOqThTbiWHQLJbDxsT7V/L387UkinR7TZl0ZKNjAwmb+DMD2oTKZfgLcUnLCcEyg9numg== dependencies: - "@parcel/diagnostic" "^2.0.1" - "@parcel/node-libs-browser" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/diagnostic" "^2.2.0" + "@parcel/node-libs-browser" "^2.2.0" + "@parcel/utils" "^2.2.0" micromatch "^4.0.4" nullthrows "^1.1.1" @@ -3545,12 +3548,12 @@ cssnano "^4.1.10" postcss "^8.0.5" -"@parcel/optimizer-cssnano@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-cssnano/-/optimizer-cssnano-2.0.1.tgz#0b35d2228d372607c591cf73cd7129f886667de5" - integrity sha512-yhuSUyTa4IKsFX+k2K8J6fsClpIWAu0Ng6HcW/fwDSfssZMm+Lfe33+sRo1fwqr8vd/okFrm3vOBQ+NhncsVVw== +"@parcel/optimizer-cssnano@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-cssnano/-/optimizer-cssnano-2.2.0.tgz#7df8d0f2b85ea088c4154167b71680cc0ab678c9" + integrity sha512-YpVFJO9v8TqGZVvonu5OOmUS7AOn1z9t+YaqiuD/ytJGyePVPIBoZ9H6TlL17jLv0gbAXTTv1zoLDmae4/ruZQ== dependencies: - "@parcel/plugin" "^2.0.1" + "@parcel/plugin" "^2.2.0" "@parcel/source-map" "^2.0.0" cssnano "^5.0.5" postcss "^8.3.0" @@ -3566,35 +3569,36 @@ nullthrows "^1.1.1" posthtml "^0.15.1" -"@parcel/optimizer-htmlnano@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.0.1.tgz#6789ebc8140f04a31929a5f7e106cd594afd5771" - integrity sha512-Q2YQt4YnRNGth6RtRw6Q/IanhboKhD2QfrDpUsDwcpBbP3nEirvLcOmVfzuNXDqvYaQG7720ulCRt8jWErZ2WQ== +"@parcel/optimizer-htmlnano@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.2.0.tgz#a12f77bbf9c4de6468d62f73cb8e1a108ede6c3c" + integrity sha512-Lrsjz5sG5uayhIGAcAv5IqOPY8FJnsy4XeT0EUc70cAI9p3NS48HZqqPfWAn9nsMqu3EBhKNyFpiaJ4LY6LNkg== dependencies: - "@parcel/plugin" "^2.0.1" + "@parcel/plugin" "^2.2.0" htmlnano "^1.0.1" nullthrows "^1.1.1" posthtml "^0.16.5" svgo "^2.4.0" -"@parcel/optimizer-image@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-image/-/optimizer-image-2.0.1.tgz#39e6551723573bf04f74dcaa1c3a6092e37049f1" - integrity sha512-tXqrAoFoGT6R2nY88OMj6DxHctyewOA3RW6VFksolX+/eWjy9MsQMUWFJmc1TlsVJCu4xGVvcHM3+6Q3XF8VSA== +"@parcel/optimizer-image@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-image/-/optimizer-image-2.2.0.tgz#d8ebc71bff53cc5ccde5e5cc0e2d2523f5ff0b59" + integrity sha512-bvrR7wX4GbcqR38MzSVImedyL3huFeMxyjdpElq6J4RKIGY3KK/+k4VhVzU0wGqIx+XTuJxNJWJGkJ8i+mESEw== dependencies: - "@parcel/diagnostic" "^2.0.1" - "@parcel/plugin" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/diagnostic" "^2.2.0" + "@parcel/plugin" "^2.2.0" + "@parcel/utils" "^2.2.0" + "@parcel/workers" "^2.2.0" detect-libc "^1.0.3" -"@parcel/optimizer-svgo@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-svgo/-/optimizer-svgo-2.0.1.tgz#66372245677d6e6086cc9ae71bf657c282677b92" - integrity sha512-vdTXQrYjNd7s9ye8NYi7IrcS/oa1Rn1cI9pFeQCocEuL3eoesnFBtkeW0bbA7tNaIBkkR0x9NagRVtWgZJW4uQ== +"@parcel/optimizer-svgo@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-svgo/-/optimizer-svgo-2.2.0.tgz#fa84d90c51571266c96c815494a5321f03df3208" + integrity sha512-gBMQPn4EGOGsEQI0MjK1PoD7w2QrMvoyk7QvSbES1F91lhYh4e3zc1op3G/hdT6IiudlrbNgsSvMZCuxyFp6Uw== dependencies: - "@parcel/diagnostic" "^2.0.1" - "@parcel/plugin" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/diagnostic" "^2.2.0" + "@parcel/plugin" "^2.2.0" + "@parcel/utils" "^2.2.0" svgo "^2.4.0" "@parcel/optimizer-terser@2.0.0-beta.2": @@ -3609,15 +3613,15 @@ nullthrows "^1.1.1" terser "^5.2.0" -"@parcel/optimizer-terser@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-terser/-/optimizer-terser-2.0.1.tgz#c8375fad84668f4c24d3aef9e2452566e8f5ebab" - integrity sha512-iT3gvkZsUKW4PJHRwWn4xqQlIIsrkr4gO2X5XQtPEXkYUn3UlHTE1lguJd1Pj6L3A0dS+ubI6wIfYk/Z59WAjw== +"@parcel/optimizer-terser@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-terser/-/optimizer-terser-2.2.0.tgz#db672ca93c67326de64aa58aacde2c012ae71ba5" + integrity sha512-R0QC9JAFJpoPS4mUltVLHbiLOeyv5G5kmM2MhJN3VYJyJNForMHvFwFBRQLxp8GbJ9pU3P36dN8jRfrlPQffog== dependencies: - "@parcel/diagnostic" "^2.0.1" - "@parcel/plugin" "^2.0.1" + "@parcel/diagnostic" "^2.2.0" + "@parcel/plugin" "^2.2.0" "@parcel/source-map" "^2.0.0" - "@parcel/utils" "^2.0.1" + "@parcel/utils" "^2.2.0" nullthrows "^1.1.1" terser "^5.2.0" @@ -3637,21 +3641,21 @@ semver "^5.4.1" split2 "^3.1.1" -"@parcel/package-manager@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/package-manager/-/package-manager-2.0.1.tgz#98e83b0893d59a2212d859ed95cb93a7710ff92a" - integrity sha512-I8pMP18zIAYIfwnFOhi4Pt+6grKysMxFqNTXAdfobszk4PvoOzbUIjzTk+3Z2IXT2FEdH/R/3Jej70OxpPf0CQ== - dependencies: - "@parcel/diagnostic" "^2.0.1" - "@parcel/fs" "^2.0.1" - "@parcel/logger" "^2.0.1" - "@parcel/types" "^2.0.1" - "@parcel/utils" "^2.0.1" - "@parcel/workers" "^2.0.1" +"@parcel/package-manager@^2.0.1", "@parcel/package-manager@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/package-manager/-/package-manager-2.2.0.tgz#8c37dd1cb5e508b33f2600a5228873d5837b2aa6" + integrity sha512-FTh8/E6AMvRJTbNav7MD8ZULvTuZhUKBpfQu07oL1khE4O6KYyQ8NBGk3W221dfY+vXoM2+b+4BrKBgw8Sn3EA== + dependencies: + "@parcel/diagnostic" "^2.2.0" + "@parcel/fs" "^2.2.0" + "@parcel/logger" "^2.2.0" + "@parcel/types" "^2.2.0" + "@parcel/utils" "^2.2.0" + "@parcel/workers" "^2.2.0" command-exists "^1.2.6" cross-spawn "^6.0.4" nullthrows "^1.1.1" - semver "^5.4.1" + semver "^5.7.1" split2 "^3.1.1" "@parcel/packager-css@2.0.0-beta.2": @@ -3665,14 +3669,14 @@ nullthrows "^1.1.1" postcss "^8.2.1" -"@parcel/packager-css@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/packager-css/-/packager-css-2.0.1.tgz#4e891cd36200c83d2d2c09448a21646309eae1e7" - integrity sha512-oPyouH+6+by3s68xxwYaaePPtrcRhNJ1Tia51eIVagBxp3kAOpB7F4S1Ou8w2qlipk9Wq6HJx2n1u4aZISbkAg== +"@parcel/packager-css@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/packager-css/-/packager-css-2.2.0.tgz#d2c13249f8ecaab571b4d301bb94f64ef065a6a6" + integrity sha512-FUn7HEDb3q6/T4Z0We+kpD5zKYwEkK2ClTyDAEDvnqtEz2A+DIrvxarwtW8NlA+uSKOH+kioMrV7Gdl0RpN5XA== dependencies: - "@parcel/plugin" "^2.0.1" + "@parcel/plugin" "^2.2.0" "@parcel/source-map" "^2.0.0" - "@parcel/utils" "^2.0.1" + "@parcel/utils" "^2.2.0" nullthrows "^1.1.1" postcss "^8.3.0" @@ -3687,14 +3691,14 @@ nullthrows "^1.1.1" posthtml "^0.15.1" -"@parcel/packager-html@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/packager-html/-/packager-html-2.0.1.tgz#d62ffe14d7adfad916ebac8ed9f3776457c2c1ed" - integrity sha512-uGQYjspjz/VF4v+kVWAmPfXoGKCmos8rgTZ7XtXnhuRT4SH/OYHlRKVxzC4sb4zRoeO6Bj82yVw65Xj2gz9K4Q== +"@parcel/packager-html@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/packager-html/-/packager-html-2.2.0.tgz#af3e53d2270e82301c08831c1913595b0e629aef" + integrity sha512-Hbp7z/1TQb5WZcystsO6ICuUVJblkVaWMw60hVDezkLsKTB87Qc81OIsqyST3LMhqM7NipsNqpaXvmg3wF6zrA== dependencies: - "@parcel/plugin" "^2.0.1" - "@parcel/types" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/plugin" "^2.2.0" + "@parcel/types" "^2.2.0" + "@parcel/utils" "^2.2.0" nullthrows "^1.1.1" posthtml "^0.16.5" @@ -3710,16 +3714,16 @@ "@parcel/utils" "2.0.0-beta.2" nullthrows "^1.1.1" -"@parcel/packager-js@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/packager-js/-/packager-js-2.0.1.tgz#f6bb5a93d15f08fbf3fdd35974b954a5e722dcc6" - integrity sha512-eN7BQITwTj2KeYMkW/9KRMBw1SoR7qlFhfX2+hbFA6Kl/b0bKEx33Gm21JJBl8wqqo3QVr9Rhg0JruwkQX1JHg== +"@parcel/packager-js@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/packager-js/-/packager-js-2.2.0.tgz#f0347dd240d77c42bc9f007f26bb174144d9eacc" + integrity sha512-dQmJqRAjy0RxWqGDSkfpyxMcUx3btqltSdK/Z8sUPVvNfss1gHRuz3Ee5CJxNoLLN8On+qM4xAvq2W3y16psbA== dependencies: - "@parcel/diagnostic" "^2.0.1" - "@parcel/hash" "^2.0.1" - "@parcel/plugin" "^2.0.1" + "@parcel/diagnostic" "^2.2.0" + "@parcel/hash" "^2.2.0" + "@parcel/plugin" "^2.2.0" "@parcel/source-map" "^2.0.0" - "@parcel/utils" "^2.0.1" + "@parcel/utils" "^2.2.0" globals "^13.2.0" nullthrows "^1.1.1" @@ -3730,21 +3734,21 @@ dependencies: "@parcel/plugin" "2.0.0-beta.2" -"@parcel/packager-raw@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/packager-raw/-/packager-raw-2.0.1.tgz#08516692e6050debc540fab4b36b078edb8ce492" - integrity sha512-Cr9we+Pf9jl9AhKsZPKg7Da6xzNFxUqPDBRIZmO9GjTm1NZOeddmRPrtporPPZxtTmtQzRuyStRNKe5zBZtg3w== +"@parcel/packager-raw@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/packager-raw/-/packager-raw-2.2.0.tgz#83de58ed88830d49066a6141497d035a201c0f19" + integrity sha512-Iz/rltpChammeEUotDZqzZr5WZcJGr2ro68GBt/8/oMU0HDWItsWUF8EtsqSuPiQWYU+65xcXPguk2cQ1qXObg== dependencies: - "@parcel/plugin" "^2.0.1" + "@parcel/plugin" "^2.2.0" -"@parcel/packager-svg@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/packager-svg/-/packager-svg-2.0.1.tgz#0e9fbbd385f848bff1226eb6e29d459ee25a1fa7" - integrity sha512-UqMYNxoaxLdJN+R3rOAACeMdkT/ONcMNQ+OzEowpt6lWZJyLRRF63akk2KhMVjYNQpV6y4wJZV6H/TWV6eRMjg== +"@parcel/packager-svg@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/packager-svg/-/packager-svg-2.2.0.tgz#7e86a83b385c2d3134444d499f344a6a13f06570" + integrity sha512-APNuRVV6fRg36rL+H4nORCOe/gxzkDjF8UUldQLbxZdsYaL+9YvJA+4vmULce96b9Fcq7ZOkqUeDvQmWw9kLNw== dependencies: - "@parcel/plugin" "^2.0.1" - "@parcel/types" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/plugin" "^2.2.0" + "@parcel/types" "^2.2.0" + "@parcel/utils" "^2.2.0" posthtml "^0.16.4" "@parcel/plugin@2.0.0-beta.2": @@ -3754,12 +3758,12 @@ dependencies: "@parcel/types" "2.0.0-beta.2" -"@parcel/plugin@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/plugin/-/plugin-2.0.1.tgz#de6dad81a98e135dad724b581c209332480c70fc" - integrity sha512-zg9LdUk1fh8UApo9q79ZbG+QCwMioSlBP0+LKYLQqcNketzmjPuhe3rCialR0s2/6QsM1EQbuMUpCmZLSQZ4tA== +"@parcel/plugin@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/plugin/-/plugin-2.2.0.tgz#dfd80fd5c8e9663b2a08b1235297ab7abdadcca4" + integrity sha512-etIxpizU14aZELSV/qEeuufoC5fLgopQ9I0j/Y9ExSmlU3NH79NeQo0JPP1TU49ZOL8Si9o/D6tlhojle6GbzA== dependencies: - "@parcel/types" "^2.0.1" + "@parcel/types" "^2.2.0" "@parcel/reporter-cli@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3778,13 +3782,13 @@ term-size "^2.2.1" "@parcel/reporter-cli@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.0.1.tgz#75406cb9d32130891f8c367112d92eb49a8dd453" - integrity sha512-R4gmEhXH6vQMoSEobVolyCIJWBRV9z9Ju5y4gheUv7X0u3e2tpsHpDq835o8jqNIBG75Dm8Q5f3EE8BdhPzTEg== + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.2.0.tgz#2f220cc2d5bf02674dd1b1c546794b34785422e5" + integrity sha512-FVRnSu5Yba/18X3KWiJEXLfT8KugsNoQmvnfL7FDgVbdxUS/RWPeAwICfXPH9pkqZBl9c7QcMDVtnSD3xNR8FA== dependencies: - "@parcel/plugin" "^2.0.1" - "@parcel/types" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/plugin" "^2.2.0" + "@parcel/types" "^2.2.0" + "@parcel/utils" "^2.2.0" chalk "^4.1.0" filesize "^6.1.0" nullthrows "^1.1.1" @@ -3808,13 +3812,13 @@ serve-handler "^6.0.0" ws "^7.0.0" -"@parcel/reporter-dev-server@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/reporter-dev-server/-/reporter-dev-server-2.0.1.tgz#a122645277d20da94d5fc54d92d9b5ffc8801703" - integrity sha512-dm2zgE8mPgLD5Nkmw9WQENZunrBN29fDRkNZhqnQyq4BBXF7e6Q/J/uamUjdtxAp7Qzobw1ZjybqlFuEh0z2tg== +"@parcel/reporter-dev-server@^2.0.1", "@parcel/reporter-dev-server@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/reporter-dev-server/-/reporter-dev-server-2.2.0.tgz#307d386197f45bd2c7b8e25c5c25142d89400b87" + integrity sha512-r6FRJ1BU/zHZvAxSvG5p0o2ZJgEkYDqU2y99a5tvRhQAIMupNp9Oc3PkAIOcoGtM3aiJvGO5pwDBoOpNwS4hwQ== dependencies: - "@parcel/plugin" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/plugin" "^2.2.0" + "@parcel/utils" "^2.2.0" connect "^3.7.0" ejs "^3.1.6" http-proxy-middleware "^1.0.0" @@ -3830,13 +3834,13 @@ "@parcel/node-resolver-core" "2.0.0-beta.2" "@parcel/plugin" "2.0.0-beta.2" -"@parcel/resolver-default@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/resolver-default/-/resolver-default-2.0.1.tgz#8f6b428f0d65d2f897aad233d2f22ae8f8478e04" - integrity sha512-8+dMgb6pJGaepGAb+44ORLamFv8Ik7T1MyyexI3d9KfWXolU4lhSoFrNGeSEqm4VtPHH0xMYQo2cyIYKZSzuyA== +"@parcel/resolver-default@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/resolver-default/-/resolver-default-2.2.0.tgz#8e45d483e26e6df0698f9c1d93d1d7535f38df5c" + integrity sha512-mDEmFVcNI4AAnKZ2P44AqKGVpG2vt9npmRNRyCw1QZvCnETiTVvHl0AngVtZRP6DVw0MeuPt7JaFWy3DQ+LMnQ== dependencies: - "@parcel/node-resolver-core" "^2.0.1" - "@parcel/plugin" "^2.0.1" + "@parcel/node-resolver-core" "^2.2.0" + "@parcel/plugin" "^2.2.0" "@parcel/runtime-browser-hmr@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3846,13 +3850,13 @@ "@parcel/plugin" "2.0.0-beta.2" "@parcel/utils" "2.0.0-beta.2" -"@parcel/runtime-browser-hmr@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.0.1.tgz#f825bcd143b3cf5414c99d2b6f0cffd5e7abe9d0" - integrity sha512-fHuK3tzfJdDhCuNab7aB0RGrfyPlpmV7l0YJJ6Hvv2FiJ1EP2f0mMYF3/T6BXacL4/HLVo58K/XLYhTb6jU2cA== +"@parcel/runtime-browser-hmr@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.2.0.tgz#458f77b46190de6835457bf3cf5dc1ae554a6dcb" + integrity sha512-Y3K8bpm06xZ2IgzS8l8iBhHoc04CcJzwWsRS60xGHikDxkZWbUMCvxMjgUS6NmDo62aY3dFkD6VVFKZIRnWxCQ== dependencies: - "@parcel/plugin" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/plugin" "^2.2.0" + "@parcel/utils" "^2.2.0" "@parcel/runtime-js@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3863,13 +3867,13 @@ "@parcel/utils" "2.0.0-beta.2" nullthrows "^1.1.1" -"@parcel/runtime-js@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/runtime-js/-/runtime-js-2.0.1.tgz#cb9c00e3b6e6d16ff38dec4378b01c2b8308ad17" - integrity sha512-5syJTEWY4uw+GH8AYwL55fqRgcBjL/tb95FSYHfABKMHSkaU6KbeUzCv88oj2wE5szWHX793LuqjppO465XYvQ== +"@parcel/runtime-js@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/runtime-js/-/runtime-js-2.2.0.tgz#a0ba9345390398c372e3fe2fea944916ad554b62" + integrity sha512-XgI/lmX/7Lp9y9KpnxehryvbHtKs9KVLs6V6MmiMXb7s74S/A6cj4cWq+AhSfz2mZtvMJAhIw3bl5vYNx8qEog== dependencies: - "@parcel/plugin" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/plugin" "^2.2.0" + "@parcel/utils" "^2.2.0" nullthrows "^1.1.1" "@parcel/runtime-react-refresh@2.0.0-beta.2": @@ -3880,22 +3884,22 @@ "@parcel/plugin" "2.0.0-beta.2" react-refresh "^0.9.0" -"@parcel/runtime-react-refresh@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.0.1.tgz#71b8aefa48aa909e8d0ab966f2fe9c9dcecfd53e" - integrity sha512-7j8cmIaoGP0etC2SLrWO1RdxQp+IealRAyZsLODRU22EQxCobGh5uq7Bjdv+m1wZrAdolR00lZe5p+dGrD2QGw== +"@parcel/runtime-react-refresh@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.2.0.tgz#1f4a56a83aa90df1432628560309f1c93c663449" + integrity sha512-bnIW3K37cH2PoGXl5NX8401exe97VGORz5YTAm6BSHiXKWZkOHwE6dTj6/PydQRm4NLAiYlJ6hvxcu5QkF7/jw== dependencies: - "@parcel/plugin" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/plugin" "^2.2.0" + "@parcel/utils" "^2.2.0" react-refresh "^0.9.0" -"@parcel/runtime-service-worker@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/runtime-service-worker/-/runtime-service-worker-2.0.1.tgz#ef8c6ee8f0b039635a80b36a76ceb9c1d6d8a45d" - integrity sha512-B12lgz5LYLhhvjnTryg38R0PryAbq1+GCJE8Inidzr/IYLROUZANokPcUYUxwVB6QJVzYRhkx3lEf9VziAot2g== +"@parcel/runtime-service-worker@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/runtime-service-worker/-/runtime-service-worker-2.2.0.tgz#630f97b8b136287b9825e09242bf1c02331da96d" + integrity sha512-+lwIBdTbhvhkYYNKG5ZxMaQ2wY/eaJqDjVelEpfvuoRzjR+v2YwKyAP4wV8DKzMb/lbtj6fsG+GerqaSwGVdDg== dependencies: - "@parcel/plugin" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/plugin" "^2.2.0" + "@parcel/utils" "^2.2.0" nullthrows "^1.1.1" "@parcel/scope-hoisting@2.0.0-beta.2": @@ -3951,21 +3955,21 @@ nullthrows "^1.1.1" semver "^5.7.0" -"@parcel/transformer-babel@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-babel/-/transformer-babel-2.0.1.tgz#f7c288e02ade16b7744dc04279b45016680b6787" - integrity sha512-TUCTdZi3V7z0WzyFPe3A1dQ0kLxPS8bEa0KgW7sueo9D0LXFvxpwh3Mf93q2H56KGb96o/QOXkz4HY8og+Wy4g== +"@parcel/transformer-babel@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/transformer-babel/-/transformer-babel-2.2.0.tgz#0dda0da9f6f9df59d5e2dcc13085c96a84b6985f" + integrity sha512-amZIncU9Ld/SuIWajntAOXwsgYl6h+5e2qBIPYuFhZX3FrbhHJFJQ27GJxOmRu+C09JOpAQk7vlr6Oi6jLvxNw== dependencies: "@babel/core" "^7.12.0" "@babel/generator" "^7.9.0" "@babel/helper-compilation-targets" "^7.8.4" "@babel/plugin-transform-flow-strip-types" "^7.0.0" "@babel/traverse" "^7.0.0" - "@parcel/babel-ast-utils" "^2.0.1" - "@parcel/diagnostic" "^2.0.1" - "@parcel/plugin" "^2.0.1" + "@parcel/babel-ast-utils" "^2.2.0" + "@parcel/diagnostic" "^2.2.0" + "@parcel/plugin" "^2.2.0" "@parcel/source-map" "^2.0.0" - "@parcel/utils" "^2.0.1" + "@parcel/utils" "^2.2.0" browserslist "^4.6.6" core-js "^3.2.1" json5 "^2.1.0" @@ -3985,18 +3989,21 @@ postcss-value-parser "^4.1.0" semver "^5.4.1" -"@parcel/transformer-css@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-css/-/transformer-css-2.0.1.tgz#1eef65fb10b27288281d1bc4f9a23a9d470b546a" - integrity sha512-sSe8elt3ejTkmZmGk3ahhimGwVoxQL0hUYSjmsgK24a4kUoJWby2hvV8BEZWDZ8zJz5ZOWUw+34fM1frEn87dQ== +"@parcel/transformer-css@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/transformer-css/-/transformer-css-2.2.0.tgz#b06fd42112f375541bcaaa3565a99d7b1d5aefa1" + integrity sha512-90GNysl/E8ScT6VxAqkgmsOi5HxkzX5xuj8YjjyKGls2DO3wZKjIi8q5ZGKjKLcWfKdF5gCzuvuObcg9nyMLug== dependencies: - "@parcel/plugin" "^2.0.1" + "@parcel/hash" "^2.2.0" + "@parcel/plugin" "^2.2.0" "@parcel/source-map" "^2.0.0" - "@parcel/utils" "^2.0.1" + "@parcel/utils" "^2.2.0" + css-modules-loader-core "^1.1.0" nullthrows "^1.1.1" postcss "^8.3.0" + postcss-modules "^3.2.2" postcss-value-parser "^4.1.0" - semver "^5.4.1" + semver "^5.7.1" "@parcel/transformer-html@2.0.0-beta.2": version "2.0.0-beta.2" @@ -4011,26 +4018,27 @@ posthtml-render "^1.4.0" semver "^5.4.1" -"@parcel/transformer-html@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-html/-/transformer-html-2.0.1.tgz#f285e46ab654e32bdb8af506284ecb22a14b6547" - integrity sha512-UkRtBHPnuedSX5UPzrZDzNb5pxWCVqvE5/xTPlxWEtN4een9Aixl4RSOZiJxMp4dxxVtw/fo9Lnx0z1wYxbWRw== +"@parcel/transformer-html@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/transformer-html/-/transformer-html-2.2.0.tgz#2149b6cad3e8d49cf331823abc6ea13b81f0501b" + integrity sha512-Swvo4TUqyGcwN3AsUMRg5dZRaJnS2DUapJlr7wHmf5bL8Q0GOuGDLLZpK5SOmQn9aZSJ6uoof5gu9zoca+s5Ig== dependencies: - "@parcel/hash" "^2.0.1" - "@parcel/plugin" "^2.0.1" + "@parcel/diagnostic" "^2.2.0" + "@parcel/hash" "^2.2.0" + "@parcel/plugin" "^2.2.0" nullthrows "^1.1.1" posthtml "^0.16.5" posthtml-parser "^0.10.1" posthtml-render "^3.0.0" - semver "^5.4.1" + semver "^5.7.1" -"@parcel/transformer-image@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-image/-/transformer-image-2.0.1.tgz#b13eb2a54546059a7ab260153e4009a3df5808f9" - integrity sha512-1xHPdE4W8jzsI0AWi4XWYioG2sDZvxJHprlTYNGK8GE+A2U7bOi7T2aoa44fEfK1pRa+N5GTkoNVTYiv4hza0g== +"@parcel/transformer-image@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/transformer-image/-/transformer-image-2.2.0.tgz#8348f426cadb3cae1d7a7adbca3a3d9bc9ef3d11" + integrity sha512-9MRmgNRi8Dg+GMA1378smh0m/xfzPpTljR23jKvwviNsz54Tf67pC328DGaI6+5eNIw4fYFZxJTXcOSAVVKk2Q== dependencies: - "@parcel/plugin" "^2.0.1" - "@parcel/workers" "^2.0.1" + "@parcel/plugin" "^2.2.0" + "@parcel/workers" "^2.2.0" nullthrows "^1.1.1" "@parcel/transformer-js@2.0.0-beta.2": @@ -4054,22 +4062,23 @@ nullthrows "^1.1.1" semver "^5.4.1" -"@parcel/transformer-js@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-js/-/transformer-js-2.0.1.tgz#784247a16a772fb250dd752a18d7b45e65e78ed4" - integrity sha512-c55qVfPU+jKoFFLV2GhME7CCqBO4Il34lW1EEv0RdYlBivPQQf+8vdcrrRX2FSjlI9cpvw9E4l298HyQDpVyng== +"@parcel/transformer-js@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/transformer-js/-/transformer-js-2.2.0.tgz#06b2b323e6839d35e8e56395d99726fe052c82cb" + integrity sha512-sD9NXeO6j6wEe8zkRcVU235fLGhyBWEhOpAbqd23+YjFgwpYq58aqw0v2tX3Ifv+jFuw8wYvRbN7Eq4hCKdjSw== dependencies: - "@parcel/diagnostic" "^2.0.1" - "@parcel/plugin" "^2.0.1" + "@parcel/diagnostic" "^2.2.0" + "@parcel/plugin" "^2.2.0" "@parcel/source-map" "^2.0.0" - "@parcel/utils" "^2.0.1" + "@parcel/utils" "^2.2.0" + "@parcel/workers" "^2.2.0" "@swc/helpers" "^0.2.11" browserslist "^4.6.6" detect-libc "^1.0.3" micromatch "^4.0.2" nullthrows "^1.1.1" regenerator-runtime "^0.13.7" - semver "^5.4.1" + semver "^5.7.1" "@parcel/transformer-json@2.0.0-beta.2": version "2.0.0-beta.2" @@ -4079,12 +4088,12 @@ "@parcel/plugin" "2.0.0-beta.2" json5 "^2.1.0" -"@parcel/transformer-json@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-json/-/transformer-json-2.0.1.tgz#e33298f3b44d3e6d5067d572ec723c8f806d8c08" - integrity sha512-Nx22PQY5InJdqLKppC+Rq0zwH7mpE2MUvgdyhGBzbwB3qwo+us1uupj+3TGYtBQ8tsUypTZVQ1kWGyQkkGWqHg== +"@parcel/transformer-json@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/transformer-json/-/transformer-json-2.2.0.tgz#f053c0c04e6b6b2b060c4ac28077dbbede8c2ccc" + integrity sha512-SukzfuncSeK0nFahA4cgSNM9JAltp1TZBiIFTi6sufAfpr4g8de04uk8l09dPxEggSkRGFGvxiNeJfxXGqEkMg== dependencies: - "@parcel/plugin" "^2.0.1" + "@parcel/plugin" "^2.2.0" json5 "^2.1.0" "@parcel/transformer-postcss@2.0.0-beta.2": @@ -4100,20 +4109,20 @@ postcss-value-parser "^4.1.0" semver "^5.4.1" -"@parcel/transformer-postcss@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-postcss/-/transformer-postcss-2.0.1.tgz#517929aa81523051c1ae1c555ae036c4e0cf6d40" - integrity sha512-bSmOl1CxE5VD7FoNMz9G5ndh3vkYMJl84nbY2t91lUtGcY/ROJ1LKvZrglCCEEE13j9orFsPproQgCcYG7m1eA== +"@parcel/transformer-postcss@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/transformer-postcss/-/transformer-postcss-2.2.0.tgz#0ddc5455f58dab1fe873307c02a86a334f164ea3" + integrity sha512-u8aZ63oL9N28HXZTQPVz3eYoza+BzI86qXqrpT5nsTEHypnRiskih25ucwkYjWyBrOUavIn+ihLO7qKaVgGixQ== dependencies: - "@parcel/hash" "^2.0.1" - "@parcel/plugin" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/hash" "^2.2.0" + "@parcel/plugin" "^2.2.0" + "@parcel/utils" "^2.2.0" clone "^2.1.1" css-modules-loader-core "^1.1.0" nullthrows "^1.1.1" postcss-modules "^3.2.2" postcss-value-parser "^4.1.0" - semver "^5.4.1" + semver "^5.7.1" "@parcel/transformer-posthtml@2.0.0-beta.2": version "2.0.0-beta.2" @@ -4128,18 +4137,18 @@ posthtml-render "^1.4.0" semver "^5.4.1" -"@parcel/transformer-posthtml@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-posthtml/-/transformer-posthtml-2.0.1.tgz#14a25be291d4b9d56abecd0af6ff0dbe97a27ea0" - integrity sha512-UKGZO5vAZCxnTDF5fT8DzNrUdzahpCnFCrFOa0MFKi0DLKrVrxXmgIgLtoLS+mgwd3WuOW3Vx3KgyVovP5n2JQ== +"@parcel/transformer-posthtml@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/transformer-posthtml/-/transformer-posthtml-2.2.0.tgz#dc85d0e474df2a2cd3a5ed61cb241bc2edd59e64" + integrity sha512-7xtnLh2wrVLZiU0pTkUWNwr2S4v3u/lgDDSr6UNQcjDHMdMUdbN8CIoraYNm3cEiTzb8O6OQEYHyH2ZEc753Xw== dependencies: - "@parcel/plugin" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/plugin" "^2.2.0" + "@parcel/utils" "^2.2.0" nullthrows "^1.1.1" posthtml "^0.16.5" posthtml-parser "^0.10.1" posthtml-render "^3.0.0" - semver "^5.4.1" + semver "^5.7.1" "@parcel/transformer-raw@2.0.0-beta.2": version "2.0.0-beta.2" @@ -4148,12 +4157,12 @@ dependencies: "@parcel/plugin" "2.0.0-beta.2" -"@parcel/transformer-raw@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-raw/-/transformer-raw-2.0.1.tgz#bec9f802cfbda864281056eca88bd8202ff9167c" - integrity sha512-NkwOp2lZX5bNxSj6tMNTEledWZvpIperCMOERm4raToDkdjBH1pDrxDLUBy8VzQ8M08CLz+2KJaF5wRMvj/eQw== +"@parcel/transformer-raw@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/transformer-raw/-/transformer-raw-2.2.0.tgz#f5c623996b0e819bca87ede51d73de9bcc277a57" + integrity sha512-xSSulmsPFNi6T/Ji/GeWIe3UatHEAemIBX3KZsvDDjy0P4olaNmeU/IvL+bb1H4NXKynOAgzyIR0S7bEpIj6Fw== dependencies: - "@parcel/plugin" "^2.0.1" + "@parcel/plugin" "^2.2.0" "@parcel/transformer-react-refresh-babel@2.0.0-beta.2": version "2.0.0-beta.2" @@ -4176,44 +4185,45 @@ react-refresh "^0.9.0" semver "^5.4.1" -"@parcel/transformer-react-refresh-wrap@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.0.1.tgz#4f8c6df15c23e66a61c430615baaf57b5a7791bc" - integrity sha512-zZj2Leh39ODh3C2xDh3eVvp1VnfVqeY5PrNdIcNfWw2DMBli13azcwYmF4Uim8natRqMFIsWsfKNesEY+mGLfA== +"@parcel/transformer-react-refresh-wrap@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.2.0.tgz#f6c6040b9d1edaecd880e8c21f66211cf6ae19fb" + integrity sha512-bPFXG7nB3dleH1pAfvlPXOY02CR5DZCoAGmu+D24VUyXXIEOX6RlB6nGls10FL7FSMLT1Q2iuJCS4DsFMIXLsw== dependencies: - "@parcel/plugin" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/plugin" "^2.2.0" + "@parcel/utils" "^2.2.0" react-refresh "^0.9.0" -"@parcel/transformer-svg@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/transformer-svg/-/transformer-svg-2.0.1.tgz#acee4bbb9b4a9f1a80416180630da5123a8fd452" - integrity sha512-ZctnwpSomOZoh2FdfETLU4WnIr2t5P9W7QX5USATTlq62uD404Qsj1gr93wQgjLjzy9ID6T1Ua4iIdYNSkScNA== +"@parcel/transformer-svg@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/transformer-svg/-/transformer-svg-2.2.0.tgz#f38c8f2809afa162380af0fb98d26e695cb2119a" + integrity sha512-tiGRVgwm/Fc36H/JUkvzcHAs+BPfOVcb171bgu5zUkDhfFj6ty5fvAQ9OThHJ6mV1vv/KGJuPbH4N1+wW/P4zQ== dependencies: - "@parcel/hash" "^2.0.1" - "@parcel/plugin" "^2.0.1" + "@parcel/diagnostic" "^2.2.0" + "@parcel/hash" "^2.2.0" + "@parcel/plugin" "^2.2.0" nullthrows "^1.1.1" posthtml "^0.16.5" posthtml-parser "^0.10.1" posthtml-render "^3.0.0" - semver "^5.4.1" + semver "^5.7.1" "@parcel/types@2.0.0-beta.2": version "2.0.0-beta.2" resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.0.0-beta.2.tgz#39530f885a546ccc8759ba8b970440bb7aadc146" integrity sha512-ri2BPGAFDntQbA5p3m/4QgnEqWYToUMkAtLelXSPbwnTM0KARavTAwSRqz1xwTdXa8gQyv4SSV7xURwaPaZ3GA== -"@parcel/types@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.0.1.tgz#e4f30f29e0f1ea53029a0450794a3d0aee90a9f7" - integrity sha512-em8/GgC7uzkUyEA2ogkzeVDmjaKYQhjf/4EIiC7jXWr22NlSXRQOawhc0CB2o97J9EV2rVXVkWTj0drHTpN2Bw== +"@parcel/types@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.2.0.tgz#781cacacda6ccb02c4f0454463ec408a28f4f9bb" + integrity sha512-QmzC/EowXifXYCRwWZS1/jC5yiWCV1R5YuKDhEj9AgKU6LOAMXAnfBwYB4jRnY+1Zv+n/Pf2LD24sz02sXzScQ== dependencies: - "@parcel/cache" "^2.0.1" - "@parcel/diagnostic" "^2.0.1" - "@parcel/fs" "^2.0.1" - "@parcel/package-manager" "^2.0.1" + "@parcel/cache" "^2.2.0" + "@parcel/diagnostic" "^2.2.0" + "@parcel/fs" "^2.2.0" + "@parcel/package-manager" "^2.2.0" "@parcel/source-map" "^2.0.0" - "@parcel/workers" "^2.0.1" + "@parcel/workers" "^2.2.0" utility-types "^3.10.0" "@parcel/utils@2.0.0-beta.2": @@ -4241,17 +4251,17 @@ nullthrows "^1.1.1" open "^7.0.3" -"@parcel/utils@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-2.0.1.tgz#dd78bfe3562abd2bf3b4f8417758e4e0dbd8350f" - integrity sha512-+XD+LYDq+VKAUfRPzcsOjq9LefeX6tiQ2zH2uCWYAwA+s+sTHIrvWkKoF3QfFOQpPgj2QqnAZMOS6F/xY2phPg== +"@parcel/utils@^2.0.1", "@parcel/utils@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-2.2.0.tgz#92d88f4603f943d10208728369881c667b3b6fd6" + integrity sha512-H1SqGvbhlNIFoiMTLX/aEGCGG0KYWQCc9Nf5BJ+75/lTaznZKCcUgv4pqu5j2PxmKlyl61aNkX69H9lWjdcvMw== dependencies: "@iarna/toml" "^2.2.0" - "@parcel/codeframe" "^2.0.1" - "@parcel/diagnostic" "^2.0.1" - "@parcel/hash" "^2.0.1" - "@parcel/logger" "^2.0.1" - "@parcel/markdown-ansi" "^2.0.1" + "@parcel/codeframe" "^2.2.0" + "@parcel/diagnostic" "^2.2.0" + "@parcel/hash" "^2.2.0" + "@parcel/logger" "^2.2.0" + "@parcel/markdown-ansi" "^2.2.0" "@parcel/source-map" "^2.0.0" ansi-html-community "0.0.8" chalk "^4.1.0" @@ -4263,7 +4273,7 @@ json5 "^1.0.1" lru-cache "^6.0.0" micromatch "^4.0.4" - node-forge "^0.10.0" + node-forge "^1.2.1" nullthrows "^1.1.1" open "^7.0.3" terminal-link "^2.1.1" @@ -4295,15 +4305,15 @@ chrome-trace-event "^1.0.2" nullthrows "^1.1.1" -"@parcel/workers@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-2.0.1.tgz#55e540556b201677591519b26f84c6ec955b9a6c" - integrity sha512-nBBK5QeoWM0l8khyStDiEd432UXaF6mkUa8n2D4Ee6XOFgUCiXWV7VROqA4nhf6OJr5K+trtNaNVGq9oHRuPHw== +"@parcel/workers@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-2.2.0.tgz#12f9774c9dc3c4e6a8427e9c7b79ee59dc20f2b6" + integrity sha512-qJTC+61LMz5eBHvT0lT+auoTKDzh/FemQrWzCdcQZjgECQyLYdD6GtMeCZP745pDfXcYZpflbSGZChIUnt1yDg== dependencies: - "@parcel/diagnostic" "^2.0.1" - "@parcel/logger" "^2.0.1" - "@parcel/types" "^2.0.1" - "@parcel/utils" "^2.0.1" + "@parcel/diagnostic" "^2.2.0" + "@parcel/logger" "^2.2.0" + "@parcel/types" "^2.2.0" + "@parcel/utils" "^2.2.0" chrome-trace-event "^1.0.2" nullthrows "^1.1.1" @@ -6949,6 +6959,11 @@ camelcase@^6.0.0, camelcase@^6.1.0, camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.1.tgz#250fd350cfd555d0d2160b1d51510eaf8326e86e" integrity sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA== +cancelable-promise@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/cancelable-promise/-/cancelable-promise-4.2.1.tgz#b02f79c5dde2704acfff1bc1ac2b4090f55541fe" + integrity sha512-PJZ/000ocWhPZQBAuNewAOMA2WEkJ8RhXI6AxeGLiGdW8EYDmumzo9wKyNgjDgxc1q/HbXuTdlcI+wXrOe/jMw== + caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" @@ -13399,16 +13414,16 @@ listr@^0.14.3: p-map "^2.0.0" rxjs "^6.3.3" -lmdb-store@^1.5.5: - version "1.6.14" - resolved "https://registry.yarnpkg.com/lmdb-store/-/lmdb-store-1.6.14.tgz#8aa5f36fb04195f8639a3b01b32f6696867f2bc9" - integrity sha512-4woZfvfgolMEngjoMJrwePjdLotr3QKGJsDWURlJmKBed5JtE00IfAKo7ryPowl4ksGcs21pcdLkwrPnKomIuA== +lmdb@^2.0.2: + version "2.1.5" + resolved "https://registry.yarnpkg.com/lmdb/-/lmdb-2.1.5.tgz#7863d268c579b4c6bf6042a97784fe1dea6753d8" + integrity sha512-J84gtJYC6DnZvczrtBF20xIyT9dZzY24/p1wi5zHldyoW+nKuckFy+AywoiMnTBRODp/lJkNhmjqw0dQUl0pFg== dependencies: - msgpackr "^1.5.0" + msgpackr "^1.5.2" nan "^2.14.2" node-gyp-build "^4.2.3" - ordered-binary "^1.0.0" - weak-lru-cache "^1.0.0" + ordered-binary "^1.2.3" + weak-lru-cache "^1.2.1" load-json-file@^1.0.0: version "1.1.0" @@ -14307,10 +14322,10 @@ msgpackr-extract@^1.0.14: nan "^2.14.2" node-gyp-build "^4.2.3" -msgpackr@^1.5.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.5.1.tgz#2a8e39d25458406034b8cb50dc7d6a7abd3dfff2" - integrity sha512-I1CXFG8BYYSeIhtDlHpUVMsdDiyvP9JAh1d9QoBnkPx3ETPeH/1lR14hweM9GETs09wCWlaOyhtXxIc9boxAAA== +msgpackr@^1.5.1, msgpackr@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.5.2.tgz#b400c9885642bdec27b284f8bdadbd6570b448b7" + integrity sha512-OCguCkbG34x1ddO4vAzEm/4J1GTo512k9SoxV8K+EGfI/onFdpemRf0HpsVRFpxadXr4JBFgHsQUitgTlw7ZYQ== optionalDependencies: msgpackr-extract "^1.0.14" @@ -14482,6 +14497,11 @@ node-forge@^0.10.0: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== +node-forge@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.2.1.tgz#82794919071ef2eb5c509293325cec8afd0fd53c" + integrity sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w== + node-gyp-build@^4.2.3, node-gyp-build@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" @@ -15052,10 +15072,10 @@ ora@^5.2.0: strip-ansi "^6.0.0" wcwidth "^1.0.1" -ordered-binary@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ordered-binary/-/ordered-binary-1.2.1.tgz#5429c9a8171cc1e5dd55a44568871dcd62531262" - integrity sha512-Zl2RCcj/wRCakW9/yI83gutgNf7JFOPEHrCK72z+boIrU+PWAnIt6HADd1w+3keDQ90GCKbp1BduKZgkeNbz7A== +ordered-binary@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/ordered-binary/-/ordered-binary-1.2.3.tgz#518f637692a74d372e56230effae37b811575e36" + integrity sha512-fEwMk8TNUtzQDjXKYS2ANW3fNZ/gMReCPOAsLHaqw+UDnq/8ddXAcX4lGRpTK7kAghAjkmJs1EXXbcrDbg+ruw== original@^1.0.0: version "1.0.2" @@ -20698,10 +20718,10 @@ wcwidth@^1.0.0, wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -weak-lru-cache@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-1.1.4.tgz#c0b2d5257dcbf4f6989d44f13764698234eb9f78" - integrity sha512-oD0vx3PpnwnGkr3QYn0nGvepmeZPvrM2m9Rq4Hu4IMCGAS3PO1qnCioaJR6ajVK58oABbm76zSh3Kai3Z6BGyw== +weak-lru-cache@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-1.2.1.tgz#6b4f2da7e1701f845e71522417f1df1e39503df8" + integrity sha512-O5ag1F0Xk6ui+Fg5LlosTcVAyHs6DeyiDDbOapNtFCx/KjZ82B3U9stM9hvzbVclKWn9ABPjaINX/nQkGkJkKg== webidl-conversions@^3.0.0: version "3.0.1" @@ -21294,7 +21314,7 @@ xmlchars@^2.1.1, xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xxhash-wasm@^0.4.1: +xxhash-wasm@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz#752398c131a4dd407b5132ba62ad372029be6f79" integrity sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA== From f01de2633e8044f7e249a21b88ee9c01098e12ca Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Fri, 14 Jan 2022 17:33:49 +0100 Subject: [PATCH 02/41] feat: inline cancelable-promise --- packages/autocomplete-core/src/onInput.ts | 9 +- .../src/utils/CancelablePromise.ts | 361 ++++++++++ .../utils/__tests__/CancelablePromise.test.ts | 659 ++++++++++++++++++ .../createCancelablePromiseList.test.ts | 4 +- .../src/utils/createCancelablePromiseList.ts | 2 +- packages/autocomplete-core/src/utils/index.ts | 1 + 6 files changed, 1029 insertions(+), 7 deletions(-) create mode 100644 packages/autocomplete-core/src/utils/CancelablePromise.ts create mode 100644 packages/autocomplete-core/src/utils/__tests__/CancelablePromise.test.ts diff --git a/packages/autocomplete-core/src/onInput.ts b/packages/autocomplete-core/src/onInput.ts index b834b839e..d2e803e0b 100644 --- a/packages/autocomplete-core/src/onInput.ts +++ b/packages/autocomplete-core/src/onInput.ts @@ -1,5 +1,3 @@ -import CancelablePromise, { cancelable } from 'cancelable-promise'; - import { reshape } from './reshape'; import { preResolve, resolve, postResolve } from './resolve'; import { @@ -9,7 +7,12 @@ import { BaseItem, InternalAutocompleteOptions, } from './types'; -import { createConcurrentSafePromise, getActiveItem } from './utils'; +import { + cancelable, + CancelablePromise, + createConcurrentSafePromise, + getActiveItem, +} from './utils'; let lastStalledId: number | null = null; diff --git a/packages/autocomplete-core/src/utils/CancelablePromise.ts b/packages/autocomplete-core/src/utils/CancelablePromise.ts new file mode 100644 index 000000000..07a05a226 --- /dev/null +++ b/packages/autocomplete-core/src/utils/CancelablePromise.ts @@ -0,0 +1,361 @@ +class CancelablePromiseInternal { + #internals: Internals; + #promise: Promise; + + constructor({ + executor = () => {}, + internals = defaultInternals(), + promise = new Promise((resolve, reject) => + executor(resolve, reject, (onCancel) => { + internals.onCancelList.push(onCancel); + }) + ), + }: { + executor?: ( + resolve: (value: T | PromiseLike) => void, + reject: (reason?: any) => void, + onCancel: (cancelHandler: () => void) => void + ) => void; + internals?: Internals; + promise?: Promise; + }) { + this.cancel = this.cancel.bind(this); + this.#internals = internals; + this.#promise = + promise || + new Promise((resolve, reject) => + executor(resolve, reject, (onCancel) => { + internals.onCancelList.push(onCancel); + }) + ); + } + + then( + onfulfilled?: + | (( + value: T + ) => TResult1 | PromiseLike | CancelablePromise) + | undefined + | null, + onrejected?: + | (( + reason: any + ) => TResult2 | PromiseLike | CancelablePromise) + | undefined + | null + ): CancelablePromise { + return makeCancelable( + this.#promise.then( + createCallback(onfulfilled, this.#internals), + createCallback(onrejected, this.#internals) + ), + this.#internals + ); + } + + catch( + onrejected?: + | (( + reason: any + ) => TResult | PromiseLike | CancelablePromise) + | undefined + | null + ): CancelablePromise { + return makeCancelable( + this.#promise.catch(createCallback(onrejected, this.#internals)), + this.#internals + ); + } + + finally( + onfinally?: (() => void) | undefined | null, + runWhenCanceled?: boolean + ): CancelablePromise { + if (runWhenCanceled) { + this.#internals.onCancelList.push(onfinally); + } + return makeCancelable( + this.#promise.finally( + createCallback(() => { + if (onfinally) { + if (runWhenCanceled) { + this.#internals.onCancelList = this.#internals.onCancelList.filter( + (callback) => callback !== onfinally + ); + } + return onfinally(); + } + }, this.#internals) + ), + this.#internals + ); + } + + cancel(): void { + this.#internals.isCanceled = true; + const callbacks = this.#internals.onCancelList; + this.#internals.onCancelList = []; + for (const callback of callbacks) { + if (typeof callback === 'function') { + try { + callback(); + } catch (err) { + console.error(err); + } + } + } + } + + isCanceled(): boolean { + return this.#internals.isCanceled === true; + } +} + +export class CancelablePromise extends CancelablePromiseInternal { + static all = function all(iterable: any) { + return makeAllCancelable(iterable, Promise.all(iterable)); + } as CancelablePromiseOverloads['all']; + + static allSettled = function allSettled(iterable: any) { + return makeAllCancelable(iterable, Promise.allSettled(iterable)); + } as CancelablePromiseOverloads['allSettled']; + + static race = function race(iterable) { + return makeAllCancelable(iterable, Promise.race(iterable)); + } as CancelablePromiseOverloads['race']; + + static resolve = function resolve(value) { + return cancelable(Promise.resolve(value)); + } as CancelablePromiseOverloads['resolve']; + + static reject = function reject(reason) { + return cancelable(Promise.reject(reason)); + } as CancelablePromiseOverloads['reject']; + + static isCancelable = isCancelablePromise; + + constructor( + executor: ( + resolve: (value: T | PromiseLike) => void, + reject: (reason?: any) => void, + onCancel: (cancelHandler: () => void) => void + ) => void + ) { + super({ executor }); + } +} + +export function cancelable(promise: Promise): CancelablePromise { + return makeCancelable(promise, defaultInternals()); +} + +export function isCancelablePromise(promise: any): boolean { + return ( + promise instanceof CancelablePromise || + promise instanceof CancelablePromiseInternal + ); +} + +function createCallback(onResult: any, internals: Internals) { + if (onResult) { + return (arg?: any) => { + if (!internals.isCanceled) { + const result = onResult(arg); + if (isCancelablePromise(result)) { + internals.onCancelList.push(result.cancel); + } + return result; + } + return arg; + }; + } +} + +function makeCancelable(promise: Promise, internals: Internals) { + return new CancelablePromiseInternal({ + internals, + promise, + }) as CancelablePromise; +} + +function makeAllCancelable(iterable: any, promise: Promise) { + const internals = defaultInternals(); + internals.onCancelList.push(() => { + for (const resolvable of iterable) { + if (isCancelablePromise(resolvable)) { + resolvable.cancel(); + } + } + }); + return new CancelablePromiseInternal({ internals, promise }); +} + +function defaultInternals(): Internals { + return { isCanceled: false, onCancelList: [] }; +} + +interface Internals { + isCanceled: boolean; + onCancelList: any[]; +} + +interface CancelablePromiseOverloads { + all( + values: readonly [ + T1 | PromiseLike, + T2 | PromiseLike, + T3 | PromiseLike, + T4 | PromiseLike, + T5 | PromiseLike, + T6 | PromiseLike, + T7 | PromiseLike, + T8 | PromiseLike, + T9 | PromiseLike, + T10 | PromiseLike, + T11 | PromiseLike, + T12 | PromiseLike + ] + ): CancelablePromise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]>; + + all( + values: readonly [ + T1 | PromiseLike, + T2 | PromiseLike, + T3 | PromiseLike, + T4 | PromiseLike, + T5 | PromiseLike, + T6 | PromiseLike, + T7 | PromiseLike, + T8 | PromiseLike, + T9 | PromiseLike, + T10 | PromiseLike, + T11 | PromiseLike + ] + ): CancelablePromise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]>; + + all( + values: readonly [ + T1 | PromiseLike, + T2 | PromiseLike, + T3 | PromiseLike, + T4 | PromiseLike, + T5 | PromiseLike, + T6 | PromiseLike, + T7 | PromiseLike, + T8 | PromiseLike, + T9 | PromiseLike, + T10 | PromiseLike + ] + ): CancelablePromise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>; + + all( + values: readonly [ + T1 | PromiseLike, + T2 | PromiseLike, + T3 | PromiseLike, + T4 | PromiseLike, + T5 | PromiseLike, + T6 | PromiseLike, + T7 | PromiseLike, + T8 | PromiseLike, + T9 | PromiseLike + ] + ): CancelablePromise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>; + + all( + values: readonly [ + T1 | PromiseLike, + T2 | PromiseLike, + T3 | PromiseLike, + T4 | PromiseLike, + T5 | PromiseLike, + T6 | PromiseLike, + T7 | PromiseLike, + T8 | PromiseLike + ] + ): CancelablePromise<[T1, T2, T3, T4, T5, T6, T7, T8]>; + + all( + values: readonly [ + T1 | PromiseLike, + T2 | PromiseLike, + T3 | PromiseLike, + T4 | PromiseLike, + T5 | PromiseLike, + T6 | PromiseLike, + T7 | PromiseLike + ] + ): CancelablePromise<[T1, T2, T3, T4, T5, T6, T7]>; + + all( + values: readonly [ + T1 | PromiseLike, + T2 | PromiseLike, + T3 | PromiseLike, + T4 | PromiseLike, + T5 | PromiseLike, + T6 | PromiseLike + ] + ): CancelablePromise<[T1, T2, T3, T4, T5, T6]>; + + all( + values: readonly [ + T1 | PromiseLike, + T2 | PromiseLike, + T3 | PromiseLike, + T4 | PromiseLike, + T5 | PromiseLike + ] + ): CancelablePromise<[T1, T2, T3, T4, T5]>; + + all( + values: readonly [ + T1 | PromiseLike, + T2 | PromiseLike, + T3 | PromiseLike, + T4 | PromiseLike + ] + ): CancelablePromise<[T1, T2, T3, T4]>; + + all( + values: readonly [ + T1 | PromiseLike, + T2 | PromiseLike, + T3 | PromiseLike + ] + ): CancelablePromise<[T1, T2, T3]>; + + all( + values: readonly [T1 | PromiseLike, T2 | PromiseLike] + ): CancelablePromise<[T1, T2]>; + + all(values: readonly (T | PromiseLike)[]): CancelablePromise; + + allSettled( + values: T + ): CancelablePromise< + { + -readonly [P in keyof T]: PromiseSettledResult< + T[P] extends PromiseLike ? U : T[P] + >; + } + >; + + allSettled( + values: Iterable + ): CancelablePromise< + PromiseSettledResult ? U : T>[] + >; + + race( + values: readonly T[] + ): CancelablePromise ? U : T>; + + resolve(): CancelablePromise; + + resolve( + value: T | PromiseLike | CancelablePromise + ): CancelablePromise; + + reject(reason?: any): CancelablePromise; +} diff --git a/packages/autocomplete-core/src/utils/__tests__/CancelablePromise.test.ts b/packages/autocomplete-core/src/utils/__tests__/CancelablePromise.test.ts new file mode 100644 index 000000000..de82f6f2c --- /dev/null +++ b/packages/autocomplete-core/src/utils/__tests__/CancelablePromise.test.ts @@ -0,0 +1,659 @@ +import { cancelable, CancelablePromise, isCancelablePromise } from '..'; + +const delay = async (timeout = 0, callback?: Function) => { + await new Promise((resolve) => setTimeout(resolve, timeout)); + if (callback) { + return await callback(); + } +}; + +describe('Fulfilled worflow', () => { + const promises: ( + | [string, () => Promise] + | [string, () => CancelablePromise] + )[] = [ + [ + 'cancelable()', + () => + cancelable( + new Promise((resolve) => { + delay(1, resolve); + }) + ), + ], + [ + 'new CancelablePromise()', + () => + new CancelablePromise((resolve) => { + delay(1, resolve); + }), + ], + [ + 'new Promise()', + () => + new Promise((resolve) => { + delay(1, resolve); + }), + ], + ]; + + const expectResolveWorkflow = async ( + promise1: Promise | CancelablePromise + ) => { + const callback = jest.fn(); + const promise2 = promise1.then(callback); + const promise3 = promise1.then(() => { + callback(); + return delay(1); + }); + const promise4 = promise2.then(callback); + const promise5 = promise3.then(() => { + callback(); + return delay(1); + }); + const promise6 = promise5.then().then(callback); + const promise7 = promise6.finally(callback); + await Promise.all([ + promise1, + promise2, + promise3, + promise4, + promise5, + promise6, + promise7, + ]); + expect(callback).toHaveBeenCalledTimes(6); + }; + + for (const [label, createPromise] of promises) { + it(label, async () => { + await expectResolveWorkflow(createPromise()); + }); + } +}); + +describe('Rejected worflow', () => { + const promises: ( + | [string, () => Promise] + | [string, () => CancelablePromise] + )[] = [ + [ + 'cancelable()', + () => + cancelable( + new Promise((resolve, reject) => { + delay(1, () => reject(new Error('native promise error'))); + }) + ), + ], + [ + 'new CancelablePromise()', + () => + new CancelablePromise((resolve, reject) => { + delay(1, () => reject(new Error('cancelable promise error'))); + }), + ], + [ + 'new Promise()', + () => + new Promise((resolve, reject) => { + delay(1, () => reject(new Error('native promise error'))); + }), + ], + ]; + + const expectErrorWorkflow = async ( + promise1: Promise | CancelablePromise + ) => { + const callback = jest.fn(); + const promise2 = promise1.then(callback).catch(() => callback(1)); + const promise3 = promise1.then(callback, () => callback(2)); + const promise4 = promise3.then(() => { + callback(3); + return delay(1, () => Promise.reject(new Error('internal error'))); + }); + const promise5 = promise4.catch(() => callback(4)); + const promise6 = promise4.then(callback, () => callback(5)); + const promise7 = promise6.finally(() => callback(6)); + await Promise.all([promise2, promise3, promise5, promise6, promise7]); + expect(callback).toHaveBeenCalledTimes(6); + expect(callback).toHaveBeenCalledWith(1); + expect(callback).toHaveBeenCalledWith(2); + expect(callback).toHaveBeenCalledWith(3); + expect(callback).toHaveBeenCalledWith(4); + expect(callback).toHaveBeenCalledWith(5); + expect(callback).toHaveBeenCalledWith(6); + }; + + for (const [label, createPromise] of promises) { + it(label, async () => { + await expectErrorWorkflow(createPromise()); + }); + } +}); + +test('Cancel root promise', async () => { + const callback = jest.fn(); + const promise1 = cancelable( + new Promise((resolve) => { + delay(1, resolve); + }) + ); + const promise2 = promise1.then(callback); + promise1.then(callback).then(callback); + promise2.then(callback); + expect(promise1.isCanceled()).toBe(false); + promise1.cancel(); + expect(promise1.isCanceled()).toBe(true); + await delay(10); + expect(callback).toHaveBeenCalledTimes(0); +}); + +test('Cancel a returned promise', async () => { + const callback = jest.fn(); + const promise1 = cancelable( + new Promise((resolve) => { + delay(1, resolve); + }) + ); + const promise2 = promise1.then(callback); + promise1.then(callback).then(callback); + promise2.then(callback); + expect(promise2.isCanceled()).toBe(false); + promise2.cancel(); + expect(promise2.isCanceled()).toBe(true); + await delay(10); + expect(callback).toHaveBeenCalledTimes(0); +}); + +test('Cancel a rejected promise', async () => { + const callback = jest.fn(); + const promise1 = cancelable( + new Promise((resolve, reject) => { + reject(); + }) + ); + promise1.cancel(); + const promise2 = promise1.catch(callback); + promise1.then(callback, callback).then(callback); + promise2.then(callback); + await delay(10); + expect(callback).toHaveBeenCalledTimes(0); +}); + +test('Cancel a promise but finally should not be still executed', async () => { + const callback = jest.fn(); + const promise = cancelable( + new Promise((resolve) => { + delay(5, resolve); + }) + ).finally(callback); + promise.cancel(); + await delay(10); + expect(callback).toHaveBeenCalledTimes(0); +}); + +test('Cancel a promise but finally should be still executed', async () => { + const callback = jest.fn(); + const promise = cancelable( + new Promise((resolve) => { + delay(5, resolve); + }) + ).finally(callback, true); + promise.cancel(); + await delay(10); + expect(callback).toHaveBeenCalledTimes(1); +}); + +test('Cancel a promise but finally should not be executed twice #1', async () => { + const callback = jest.fn(); + const promise = cancelable( + new Promise((resolve) => { + resolve(); + }) + ).finally(callback, true); + await promise; + expect(callback).toHaveBeenCalledTimes(1); + promise.cancel(); + await delay(10); + expect(callback).toHaveBeenCalledTimes(1); +}); + +test('Cancel a promise but finally should not be executed twice #2', async () => { + const callback = jest.fn(); + const promise = cancelable( + new Promise((resolve) => { + delay(10, resolve); + }) + ).finally(callback, true); + await delay(5); + promise.cancel(); + await delay(10); + expect(callback).toHaveBeenCalledTimes(1); +}); + +test('On cancel callbacks should executed in the correct order', async () => { + const callback = jest.fn(); + const p1 = cancelable(Promise.resolve(callback('resolve p1'))); + p1.then(() => { + return new CancelablePromise((resolve, reject, onCancel) => { + delay(10, resolve); + onCancel(() => { + callback('cancel p2'); + }); + }).finally(() => { + callback('finally p2'); + }, true); + }); + p1.then(() => { + return new CancelablePromise((resolve, reject, onCancel) => { + delay(10, resolve); + onCancel(() => { + callback('cancel p3'); + }); + }).finally(() => { + callback('finally p3'); + }, true); + }); + await delay(5); + p1.cancel(); + await delay(10); + expect(callback.mock.calls).toEqual([ + ['resolve p1'], + ['cancel p2'], + ['finally p2'], + ['cancel p3'], + ['finally p3'], + ]); +}); + +test('CancelablePromise.resolve()', async () => { + const callback = jest.fn(); + await new Promise((resolve) => + resolve(CancelablePromise.resolve('ok')) + ).then(callback); + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith('ok'); +}); + +test('CancelablePromise.reject()', async () => { + const callback = jest.fn(); + await new Promise((resolve) => + resolve(CancelablePromise.reject('ko')) + ).catch(callback); + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith('ko'); +}); + +describe('CancelablePromise.all()', () => { + it('should resolve', async () => { + const callback = jest.fn(); + const promise = CancelablePromise.all([ + Promise.resolve('ok1'), + delay(1, () => 'ok2'), + ]).then(callback); + await promise; + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith(['ok1', 'ok2']); + }); + + it('should cancel', async () => { + const callback = jest.fn(); + const promise = CancelablePromise.all([ + CancelablePromise.resolve(), + delay(1), + cancelable(new Promise((resolve) => delay(5, resolve))).then(callback), + new CancelablePromise((resolve) => { + delay(4, resolve); + }).then(callback), + ]).then(callback); + promise.cancel(); + await delay(10); + expect(callback).toHaveBeenCalledTimes(0); + }); +}); + +describe('CancelablePromise.allSettled()', () => { + it('should resolve', async () => { + const callback = jest.fn(); + const promise = CancelablePromise.allSettled([ + Promise.resolve('ok'), + Promise.reject('ko'), + delay(1, () => 'yes'), + ]).then(callback); + await promise; + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith([ + { status: 'fulfilled', value: 'ok' }, + { status: 'rejected', reason: 'ko' }, + { status: 'fulfilled', value: 'yes' }, + ]); + }); + + it('should cancel', async () => { + const callback = jest.fn(); + const promise = CancelablePromise.allSettled([ + Promise.resolve(), + Promise.reject(), + delay(1), + cancelable(new Promise((resolve) => delay(5, resolve))).then(callback), + new CancelablePromise((resolve) => { + delay(4, resolve); + }).then(callback), + ]).then(callback); + promise.cancel(); + await delay(10); + expect(callback).toHaveBeenCalledTimes(0); + }); +}); + +describe('CancelablePromise.race()', () => { + it('should resolve', async () => { + const callback = jest.fn(); + const promise = CancelablePromise.race([ + delay(5), + delay(1, () => 'yes'), + ]).then(callback); + await promise; + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith('yes'); + }); + + it('should cancel', async () => { + const callback = jest.fn(); + const promise = CancelablePromise.race([ + new Promise(() => {}), + delay(1), + cancelable(new Promise((resolve) => delay(5, resolve))).then(callback), + new CancelablePromise((resolve) => { + delay(4, resolve); + }).then(callback), + ]).then(callback); + promise.cancel(); + await delay(10); + expect(callback).toHaveBeenCalledTimes(0); + }); +}); + +describe('Cancelable promises returned by executors', () => { + async function worflow({ + withClass, + withFail, + withCatch, + }: { + withClass?: boolean; + withFail?: boolean; + withCatch?: boolean; + }) { + const callback = jest.fn(); + let promise1: CancelablePromise; + + if (withClass) { + promise1 = new CancelablePromise((resolve, reject, onCancel) => { + callback('start p1'); + const timer = setTimeout(() => { + callback('resolve p1'); + if (withFail) { + reject(); + } else { + resolve(); + } + }, 5); + const abort = () => { + callback('abort p1'); + clearTimeout(timer); + }; + onCancel(abort); + }); + } else { + promise1 = cancelable( + new Promise((resolve, reject) => { + callback('start p1'); + delay(5, () => { + callback('resolve p1'); + if (withFail) { + reject(); + } else { + resolve(); + } + }); + }) + ); + } + + let promise2 = promise1.then( + ...([ + () => { + callback('then p2'); + const promise3 = new CancelablePromise( + (resolve, reject, onCancel) => { + callback('start p3'); + const timer = setTimeout(() => { + callback('resolve p3'); + resolve(); + }, 10); + const abort = () => { + callback('abort p3'); + clearTimeout(timer); + }; + onCancel(abort); + } + ); + return promise3; + }, + !withCatch && + (() => { + callback('error p2'); + const promise3 = new CancelablePromise( + (resolve, reject, onCancel) => { + callback('start p3'); + const timer = setTimeout(() => { + callback('resolve p3'); + resolve(); + }, 10); + const abort = () => { + callback('abort p3'); + clearTimeout(timer); + }; + onCancel(abort); + } + ); + return promise3; + }), + ].filter(Boolean) as [() => CancelablePromise]) + ); + + if (withCatch) { + promise2 = promise2.catch(() => { + callback('catch p2'); + const promise3 = new CancelablePromise( + (resolve, reject, onCancel) => { + callback('start p3'); + const timer = setTimeout(() => { + callback('resolve p3'); + resolve(); + }, 10); + const abort = () => { + callback('abort p3'); + clearTimeout(timer); + }; + onCancel(abort); + } + ); + return promise3; + }); + } + + promise2.then(() => { + callback('then done'); + }); + + await delay(10, () => { + callback('cancel p2'); + promise2.cancel(); + }); + await delay(20); + return callback; + } + + it('should be canceled when fulfilled (with CancelablePromise)', async () => { + const callback = await worflow({ withClass: true, withFail: false }); + expect(callback.mock.calls).toEqual([ + ['start p1'], + ['resolve p1'], + ['then p2'], + ['start p3'], + ['cancel p2'], + ['abort p1'], + ['abort p3'], + ]); + }); + + it('should be canceled when rejected (with CancelablePromise)', async () => { + const callback = await worflow({ + withClass: true, + withFail: true, + withCatch: false, + }); + expect(callback.mock.calls).toEqual([ + ['start p1'], + ['resolve p1'], + ['error p2'], + ['start p3'], + ['cancel p2'], + ['abort p1'], + ['abort p3'], + ]); + }); + + it('should be canceled when rejected and caught (with CancelablePromise)', async () => { + const callback = await worflow({ + withClass: true, + withFail: true, + withCatch: true, + }); + expect(callback.mock.calls).toEqual([ + ['start p1'], + ['resolve p1'], + ['catch p2'], + ['start p3'], + ['cancel p2'], + ['abort p1'], + ['abort p3'], + ]); + }); + + it('should be canceled when fulfilled (with cancelable)', async () => { + const callback = await worflow({ withClass: false, withFail: false }); + expect(callback.mock.calls).toEqual([ + ['start p1'], + ['resolve p1'], + ['then p2'], + ['start p3'], + ['cancel p2'], + ['abort p3'], + ]); + }); + + it('should be canceled when rejected (with cancelable)', async () => { + const callback = await worflow({ + withClass: false, + withFail: true, + withCatch: false, + }); + expect(callback.mock.calls).toEqual([ + ['start p1'], + ['resolve p1'], + ['error p2'], + ['start p3'], + ['cancel p2'], + ['abort p3'], + ]); + }); + + it('should be canceled when rejected and caught (with cancelable)', async () => { + const callback = await worflow({ + withClass: false, + withFail: true, + withCatch: true, + }); + expect(callback.mock.calls).toEqual([ + ['start p1'], + ['resolve p1'], + ['catch p2'], + ['start p3'], + ['cancel p2'], + ['abort p3'], + ]); + }); + + it('should cancel promises deeply', async () => { + const callback = jest.fn(); + const promise1 = cancelable( + new Promise((resolve) => { + setTimeout(resolve, 1); + }) + ).then(() => { + return new CancelablePromise((resolve) => { + setTimeout(resolve, 1); + }).then(() => { + return cancelable( + new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 1); + }) + ).then(() => { + return new CancelablePromise((resolve, reject, onCancel) => { + const timer = setTimeout(() => { + callback('it should not resolve'); + resolve(); + }, 10); + onCancel(() => { + clearTimeout(timer); + }); + }).then(() => { + return cancelable( + new Promise((resolve) => { + setTimeout(() => { + callback('it should not resolve'); + resolve(); + }, 0); + }) + ); + }); + }); + }); + }); + await delay(5); + promise1.cancel(); + await delay(20); + expect(callback).not.toHaveBeenCalled(); + }); +}); + +for (const [label, isCancelable] of [ + ['isCancelablePromise', isCancelablePromise], + ['CancelablePromise.isCancelable', CancelablePromise.isCancelable], +] as [string, typeof isCancelablePromise][]) { + describe(label, () => { + it('should be cancelable', () => { + const p1 = cancelable(new Promise(() => {})); + const p2 = new CancelablePromise(() => {}); + expect(isCancelable(p1)).toBe(true); + expect(isCancelable(p2)).toBe(true); + expect(isCancelable(p1.then(() => {}))).toBe(true); + expect(isCancelable(p2.then(() => {}))).toBe(true); + expect(isCancelable(p1.catch(() => {}))).toBe(true); + expect(isCancelable(p2.catch(() => {}))).toBe(true); + }); + + it('should not be cancelable', () => { + expect(isCancelable(new Promise(() => {}))).toBe(false); + expect(isCancelable(undefined)).toBe(false); + expect(isCancelable(null)).toBe(false); + expect(isCancelable({})).toBe(false); + expect(isCancelable({ cancel() {} })).toBe(false); + }); + }); +} diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts index 8fba8862f..9e54e9f16 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts @@ -1,6 +1,4 @@ -import { CancelablePromise } from 'cancelable-promise'; - -import { createCancelablePromiseList } from '..'; +import { CancelablePromise, createCancelablePromiseList } from '..'; describe('createCancelablePromiseList', () => { test('adds cancelable promises to the list', () => { diff --git a/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts index b0d5559d5..37cdedc68 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts @@ -1,4 +1,4 @@ -import CancelablePromise from 'cancelable-promise'; +import { CancelablePromise } from '.'; export type CancelablePromiseQueue = { add(cancelablePromise: CancelablePromise): void; diff --git a/packages/autocomplete-core/src/utils/index.ts b/packages/autocomplete-core/src/utils/index.ts index 5c61a6c40..a610b0685 100644 --- a/packages/autocomplete-core/src/utils/index.ts +++ b/packages/autocomplete-core/src/utils/index.ts @@ -1,3 +1,4 @@ +export * from './CancelablePromise'; export * from './createConcurrentSafePromise'; export * from './createCancelablePromiseList'; export * from './getNextActiveItemId'; From 00526a381269e0481ab3c06f7f6251925c264039 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Fri, 14 Jan 2022 17:35:16 +0100 Subject: [PATCH 03/41] chore: remove dependency --- packages/autocomplete-core/package.json | 3 +-- yarn.lock | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/autocomplete-core/package.json b/packages/autocomplete-core/package.json index ec1324dea..07c7d50c2 100644 --- a/packages/autocomplete-core/package.json +++ b/packages/autocomplete-core/package.json @@ -31,8 +31,7 @@ "watch": "watch \"yarn on:change\" --ignoreDirectoryPattern \"/dist/\"" }, "dependencies": { - "@algolia/autocomplete-shared": "1.5.1", - "cancelable-promise": "4.2.1" + "@algolia/autocomplete-shared": "1.5.1" }, "devDependencies": { "@algolia/autocomplete-preset-algolia": "1.5.1", diff --git a/yarn.lock b/yarn.lock index b477af2cb..f186ce34f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6959,11 +6959,6 @@ camelcase@^6.0.0, camelcase@^6.1.0, camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.1.tgz#250fd350cfd555d0d2160b1d51510eaf8326e86e" integrity sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA== -cancelable-promise@4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/cancelable-promise/-/cancelable-promise-4.2.1.tgz#b02f79c5dde2704acfff1bc1ac2b4090f55541fe" - integrity sha512-PJZ/000ocWhPZQBAuNewAOMA2WEkJ8RhXI6AxeGLiGdW8EYDmumzo9wKyNgjDgxc1q/HbXuTdlcI+wXrOe/jMw== - caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" From d40c06a307404c7148ce43d7efd9afcae33d0271 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Fri, 14 Jan 2022 17:45:17 +0100 Subject: [PATCH 04/41] feat: drop unused methods --- .../src/utils/CancelablePromise.ts | 24 ----- .../utils/__tests__/CancelablePromise.test.ts | 90 ------------------- 2 files changed, 114 deletions(-) diff --git a/packages/autocomplete-core/src/utils/CancelablePromise.ts b/packages/autocomplete-core/src/utils/CancelablePromise.ts index 07a05a226..2854d393f 100644 --- a/packages/autocomplete-core/src/utils/CancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/CancelablePromise.ts @@ -112,18 +112,6 @@ class CancelablePromiseInternal { } export class CancelablePromise extends CancelablePromiseInternal { - static all = function all(iterable: any) { - return makeAllCancelable(iterable, Promise.all(iterable)); - } as CancelablePromiseOverloads['all']; - - static allSettled = function allSettled(iterable: any) { - return makeAllCancelable(iterable, Promise.allSettled(iterable)); - } as CancelablePromiseOverloads['allSettled']; - - static race = function race(iterable) { - return makeAllCancelable(iterable, Promise.race(iterable)); - } as CancelablePromiseOverloads['race']; - static resolve = function resolve(value) { return cancelable(Promise.resolve(value)); } as CancelablePromiseOverloads['resolve']; @@ -178,18 +166,6 @@ function makeCancelable(promise: Promise, internals: Internals) { }) as CancelablePromise; } -function makeAllCancelable(iterable: any, promise: Promise) { - const internals = defaultInternals(); - internals.onCancelList.push(() => { - for (const resolvable of iterable) { - if (isCancelablePromise(resolvable)) { - resolvable.cancel(); - } - } - }); - return new CancelablePromiseInternal({ internals, promise }); -} - function defaultInternals(): Internals { return { isCanceled: false, onCancelList: [] }; } diff --git a/packages/autocomplete-core/src/utils/__tests__/CancelablePromise.test.ts b/packages/autocomplete-core/src/utils/__tests__/CancelablePromise.test.ts index de82f6f2c..78ad3af5c 100644 --- a/packages/autocomplete-core/src/utils/__tests__/CancelablePromise.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/CancelablePromise.test.ts @@ -285,96 +285,6 @@ test('CancelablePromise.reject()', async () => { expect(callback).toHaveBeenCalledWith('ko'); }); -describe('CancelablePromise.all()', () => { - it('should resolve', async () => { - const callback = jest.fn(); - const promise = CancelablePromise.all([ - Promise.resolve('ok1'), - delay(1, () => 'ok2'), - ]).then(callback); - await promise; - expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledWith(['ok1', 'ok2']); - }); - - it('should cancel', async () => { - const callback = jest.fn(); - const promise = CancelablePromise.all([ - CancelablePromise.resolve(), - delay(1), - cancelable(new Promise((resolve) => delay(5, resolve))).then(callback), - new CancelablePromise((resolve) => { - delay(4, resolve); - }).then(callback), - ]).then(callback); - promise.cancel(); - await delay(10); - expect(callback).toHaveBeenCalledTimes(0); - }); -}); - -describe('CancelablePromise.allSettled()', () => { - it('should resolve', async () => { - const callback = jest.fn(); - const promise = CancelablePromise.allSettled([ - Promise.resolve('ok'), - Promise.reject('ko'), - delay(1, () => 'yes'), - ]).then(callback); - await promise; - expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledWith([ - { status: 'fulfilled', value: 'ok' }, - { status: 'rejected', reason: 'ko' }, - { status: 'fulfilled', value: 'yes' }, - ]); - }); - - it('should cancel', async () => { - const callback = jest.fn(); - const promise = CancelablePromise.allSettled([ - Promise.resolve(), - Promise.reject(), - delay(1), - cancelable(new Promise((resolve) => delay(5, resolve))).then(callback), - new CancelablePromise((resolve) => { - delay(4, resolve); - }).then(callback), - ]).then(callback); - promise.cancel(); - await delay(10); - expect(callback).toHaveBeenCalledTimes(0); - }); -}); - -describe('CancelablePromise.race()', () => { - it('should resolve', async () => { - const callback = jest.fn(); - const promise = CancelablePromise.race([ - delay(5), - delay(1, () => 'yes'), - ]).then(callback); - await promise; - expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledWith('yes'); - }); - - it('should cancel', async () => { - const callback = jest.fn(); - const promise = CancelablePromise.race([ - new Promise(() => {}), - delay(1), - cancelable(new Promise((resolve) => delay(5, resolve))).then(callback), - new CancelablePromise((resolve) => { - delay(4, resolve); - }).then(callback), - ]).then(callback); - promise.cancel(); - await delay(10); - expect(callback).toHaveBeenCalledTimes(0); - }); -}); - describe('Cancelable promises returned by executors', () => { async function worflow({ withClass, From 244c0dbcb862cfad856c7a5ae9fb6d22974859e8 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Tue, 18 Jan 2022 11:37:46 +0100 Subject: [PATCH 05/41] feat: use functions instead of classes --- .../src/utils/CancelablePromise.ts | 418 ++++++------------ .../utils/__tests__/CancelablePromise.test.ts | 48 +- .../createCancelablePromiseList.test.ts | 12 +- 3 files changed, 169 insertions(+), 309 deletions(-) diff --git a/packages/autocomplete-core/src/utils/CancelablePromise.ts b/packages/autocomplete-core/src/utils/CancelablePromise.ts index 2854d393f..997d055e3 100644 --- a/packages/autocomplete-core/src/utils/CancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/CancelablePromise.ts @@ -1,58 +1,38 @@ -class CancelablePromiseInternal { - #internals: Internals; - #promise: Promise; - - constructor({ - executor = () => {}, - internals = defaultInternals(), - promise = new Promise((resolve, reject) => - executor(resolve, reject, (onCancel) => { - internals.onCancelList.push(onCancel); - }) - ), - }: { - executor?: ( - resolve: (value: T | PromiseLike) => void, - reject: (reason?: any) => void, - onCancel: (cancelHandler: () => void) => void - ) => void; - internals?: Internals; - promise?: Promise; - }) { - this.cancel = this.cancel.bind(this); - this.#internals = internals; - this.#promise = - promise || - new Promise((resolve, reject) => - executor(resolve, reject, (onCancel) => { - internals.onCancelList.push(onCancel); - }) - ); - } - - then( +import { noop } from '@algolia/autocomplete-shared'; + +type PromiseExecutor = ( + resolve: (value: TValue | PromiseLike) => void, + reject: (reason?: any) => void, + onCancel: (handler: () => void) => void +) => void; + +type CreateInternalCancelablePromiseParams = { + executor?: PromiseExecutor; + promise?: Promise; + initialState?: InternalState; +}; + +type InternalCancelablePromise = { + then( onfulfilled?: | (( - value: T - ) => TResult1 | PromiseLike | CancelablePromise) + value: TValue + ) => + | TFulfilledResult + | PromiseLike + | CancelablePromise) | undefined | null, onrejected?: | (( reason: any - ) => TResult2 | PromiseLike | CancelablePromise) + ) => + | TRejectedResult + | PromiseLike + | CancelablePromise) | undefined | null - ): CancelablePromise { - return makeCancelable( - this.#promise.then( - createCallback(onfulfilled, this.#internals), - createCallback(onrejected, this.#internals) - ), - this.#internals - ); - } - + ): CancelablePromise; catch( onrejected?: | (( @@ -60,278 +40,150 @@ class CancelablePromiseInternal { ) => TResult | PromiseLike | CancelablePromise) | undefined | null - ): CancelablePromise { - return makeCancelable( - this.#promise.catch(createCallback(onrejected, this.#internals)), - this.#internals - ); - } - + ): CancelablePromise; finally( onfinally?: (() => void) | undefined | null, runWhenCanceled?: boolean - ): CancelablePromise { - if (runWhenCanceled) { - this.#internals.onCancelList.push(onfinally); - } - return makeCancelable( - this.#promise.finally( - createCallback(() => { - if (onfinally) { - if (runWhenCanceled) { - this.#internals.onCancelList = this.#internals.onCancelList.filter( - (callback) => callback !== onfinally - ); + ): CancelablePromise; + cancel(): void; + isCanceled(): boolean; +}; + +export function createInternalCancelablePromise({ + executor = noop, + initialState = createInitialState(), + promise = new Promise((resolve, reject) => { + return executor(resolve, reject, (onCancel) => { + initialState.onCancelList.push(onCancel); + }); + }), +}: CreateInternalCancelablePromiseParams): InternalCancelablePromise { + const state = initialState; + + return { + then(onfulfilled, onrejected) { + return createCancelable( + promise.then( + createCallback(onfulfilled, state), + createCallback(onrejected, state) + ), + state + ); + }, + catch(onrejected) { + return createCancelable( + promise.catch(createCallback(onrejected, state)), + state + ); + }, + finally(onfinally, runWhenCanceled) { + if (runWhenCanceled) { + state.onCancelList.push(onfinally); + } + + return createCancelable( + promise.finally( + createCallback(() => { + if (onfinally) { + if (runWhenCanceled) { + state.onCancelList = state.onCancelList.filter( + (callback) => callback !== onfinally + ); + } + return onfinally(); } - return onfinally(); + }, state) + ), + state + ); + }, + cancel() { + state.isCanceled = true; + const callbacks = state.onCancelList; + state.onCancelList = []; + + for (const callback of callbacks) { + if (typeof callback === 'function') { + try { + callback(); + } catch (err) { + console.error(err); } - }, this.#internals) - ), - this.#internals - ); - } - - cancel(): void { - this.#internals.isCanceled = true; - const callbacks = this.#internals.onCancelList; - this.#internals.onCancelList = []; - for (const callback of callbacks) { - if (typeof callback === 'function') { - try { - callback(); - } catch (err) { - console.error(err); } } - } - } - - isCanceled(): boolean { - return this.#internals.isCanceled === true; - } + }, + isCanceled() { + return state.isCanceled === true; + }, + }; } -export class CancelablePromise extends CancelablePromiseInternal { - static resolve = function resolve(value) { - return cancelable(Promise.resolve(value)); - } as CancelablePromiseOverloads['resolve']; - - static reject = function reject(reason) { - return cancelable(Promise.reject(reason)); - } as CancelablePromiseOverloads['reject']; +export type CancelablePromise = InternalCancelablePromise; - static isCancelable = isCancelablePromise; +export function createCancelablePromise( + executor: PromiseExecutor +): CancelablePromise { + return createInternalCancelablePromise({ executor }); +} - constructor( - executor: ( - resolve: (value: T | PromiseLike) => void, - reject: (reason?: any) => void, - onCancel: (cancelHandler: () => void) => void - ) => void - ) { - super({ executor }); - } +createCancelablePromise.resolve = ((value) => + cancelable(Promise.resolve(value))) as CancelablePromiseOverloads['resolve']; +createCancelablePromise.reject = ((reason) => + cancelable(Promise.reject(reason))) as CancelablePromiseOverloads['reject']; +createCancelablePromise.isCancelable = isCancelablePromise; + +function createCancelable( + promise: Promise, + initialState: InternalState +): CancelablePromise { + return createInternalCancelablePromise({ promise, initialState }); } -export function cancelable(promise: Promise): CancelablePromise { - return makeCancelable(promise, defaultInternals()); +export function cancelable( + promise: Promise +): CancelablePromise { + return createCancelable(promise, createInitialState()); } -export function isCancelablePromise(promise: any): boolean { - return ( - promise instanceof CancelablePromise || - promise instanceof CancelablePromiseInternal - ); +export function isCancelablePromise( + promise: Promise | CancelablePromise +): boolean { + return promise?.hasOwnProperty('cancel') || false; } -function createCallback(onResult: any, internals: Internals) { +function createCallback( + onResult: (...args: any[]) => any, + state: InternalState +): void | ((...args: any[]) => any) { if (onResult) { return (arg?: any) => { - if (!internals.isCanceled) { + if (!state.isCanceled) { const result = onResult(arg); + if (isCancelablePromise(result)) { - internals.onCancelList.push(result.cancel); + state.onCancelList.push(result.cancel); } + return result; } + return arg; }; } } -function makeCancelable(promise: Promise, internals: Internals) { - return new CancelablePromiseInternal({ - internals, - promise, - }) as CancelablePromise; -} - -function defaultInternals(): Internals { +function createInitialState(): InternalState { return { isCanceled: false, onCancelList: [] }; } -interface Internals { +interface InternalState { isCanceled: boolean; onCancelList: any[]; } interface CancelablePromiseOverloads { - all( - values: readonly [ - T1 | PromiseLike, - T2 | PromiseLike, - T3 | PromiseLike, - T4 | PromiseLike, - T5 | PromiseLike, - T6 | PromiseLike, - T7 | PromiseLike, - T8 | PromiseLike, - T9 | PromiseLike, - T10 | PromiseLike, - T11 | PromiseLike, - T12 | PromiseLike - ] - ): CancelablePromise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]>; - - all( - values: readonly [ - T1 | PromiseLike, - T2 | PromiseLike, - T3 | PromiseLike, - T4 | PromiseLike, - T5 | PromiseLike, - T6 | PromiseLike, - T7 | PromiseLike, - T8 | PromiseLike, - T9 | PromiseLike, - T10 | PromiseLike, - T11 | PromiseLike - ] - ): CancelablePromise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]>; - - all( - values: readonly [ - T1 | PromiseLike, - T2 | PromiseLike, - T3 | PromiseLike, - T4 | PromiseLike, - T5 | PromiseLike, - T6 | PromiseLike, - T7 | PromiseLike, - T8 | PromiseLike, - T9 | PromiseLike, - T10 | PromiseLike - ] - ): CancelablePromise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>; - - all( - values: readonly [ - T1 | PromiseLike, - T2 | PromiseLike, - T3 | PromiseLike, - T4 | PromiseLike, - T5 | PromiseLike, - T6 | PromiseLike, - T7 | PromiseLike, - T8 | PromiseLike, - T9 | PromiseLike - ] - ): CancelablePromise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>; - - all( - values: readonly [ - T1 | PromiseLike, - T2 | PromiseLike, - T3 | PromiseLike, - T4 | PromiseLike, - T5 | PromiseLike, - T6 | PromiseLike, - T7 | PromiseLike, - T8 | PromiseLike - ] - ): CancelablePromise<[T1, T2, T3, T4, T5, T6, T7, T8]>; - - all( - values: readonly [ - T1 | PromiseLike, - T2 | PromiseLike, - T3 | PromiseLike, - T4 | PromiseLike, - T5 | PromiseLike, - T6 | PromiseLike, - T7 | PromiseLike - ] - ): CancelablePromise<[T1, T2, T3, T4, T5, T6, T7]>; - - all( - values: readonly [ - T1 | PromiseLike, - T2 | PromiseLike, - T3 | PromiseLike, - T4 | PromiseLike, - T5 | PromiseLike, - T6 | PromiseLike - ] - ): CancelablePromise<[T1, T2, T3, T4, T5, T6]>; - - all( - values: readonly [ - T1 | PromiseLike, - T2 | PromiseLike, - T3 | PromiseLike, - T4 | PromiseLike, - T5 | PromiseLike - ] - ): CancelablePromise<[T1, T2, T3, T4, T5]>; - - all( - values: readonly [ - T1 | PromiseLike, - T2 | PromiseLike, - T3 | PromiseLike, - T4 | PromiseLike - ] - ): CancelablePromise<[T1, T2, T3, T4]>; - - all( - values: readonly [ - T1 | PromiseLike, - T2 | PromiseLike, - T3 | PromiseLike - ] - ): CancelablePromise<[T1, T2, T3]>; - - all( - values: readonly [T1 | PromiseLike, T2 | PromiseLike] - ): CancelablePromise<[T1, T2]>; - - all(values: readonly (T | PromiseLike)[]): CancelablePromise; - - allSettled( - values: T - ): CancelablePromise< - { - -readonly [P in keyof T]: PromiseSettledResult< - T[P] extends PromiseLike ? U : T[P] - >; - } - >; - - allSettled( - values: Iterable - ): CancelablePromise< - PromiseSettledResult ? U : T>[] - >; - - race( - values: readonly T[] - ): CancelablePromise ? U : T>; - - resolve(): CancelablePromise; - - resolve( - value: T | PromiseLike | CancelablePromise - ): CancelablePromise; - - reject(reason?: any): CancelablePromise; + resolve( + value?: TValue | PromiseLike | CancelablePromise + ): CancelablePromise; + reject(reason?: any): CancelablePromise; } diff --git a/packages/autocomplete-core/src/utils/__tests__/CancelablePromise.test.ts b/packages/autocomplete-core/src/utils/__tests__/CancelablePromise.test.ts index 78ad3af5c..92f3c7fe3 100644 --- a/packages/autocomplete-core/src/utils/__tests__/CancelablePromise.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/CancelablePromise.test.ts @@ -1,7 +1,13 @@ -import { cancelable, CancelablePromise, isCancelablePromise } from '..'; +import { + cancelable, + CancelablePromise, + createCancelablePromise, + isCancelablePromise, +} from '..'; const delay = async (timeout = 0, callback?: Function) => { await new Promise((resolve) => setTimeout(resolve, timeout)); + if (callback) { return await callback(); } @@ -22,9 +28,9 @@ describe('Fulfilled worflow', () => { ), ], [ - 'new CancelablePromise()', + 'createCancelablePromise', () => - new CancelablePromise((resolve) => { + createCancelablePromise((resolve) => { delay(1, resolve); }), ], @@ -87,9 +93,9 @@ describe('Rejected worflow', () => { ), ], [ - 'new CancelablePromise()', + 'createCancelablePromise', () => - new CancelablePromise((resolve, reject) => { + createCancelablePromise((resolve, reject) => { delay(1, () => reject(new Error('cancelable promise error'))); }), ], @@ -236,7 +242,7 @@ test('On cancel callbacks should executed in the correct order', async () => { const callback = jest.fn(); const p1 = cancelable(Promise.resolve(callback('resolve p1'))); p1.then(() => { - return new CancelablePromise((resolve, reject, onCancel) => { + return createCancelablePromise((resolve, reject, onCancel) => { delay(10, resolve); onCancel(() => { callback('cancel p2'); @@ -246,7 +252,7 @@ test('On cancel callbacks should executed in the correct order', async () => { }, true); }); p1.then(() => { - return new CancelablePromise((resolve, reject, onCancel) => { + return createCancelablePromise((resolve, reject, onCancel) => { delay(10, resolve); onCancel(() => { callback('cancel p3'); @@ -267,19 +273,19 @@ test('On cancel callbacks should executed in the correct order', async () => { ]); }); -test('CancelablePromise.resolve()', async () => { +test('createCancelablePromise.resolve()', async () => { const callback = jest.fn(); await new Promise((resolve) => - resolve(CancelablePromise.resolve('ok')) + resolve(createCancelablePromise.resolve('ok')) ).then(callback); expect(callback).toHaveBeenCalledTimes(1); expect(callback).toHaveBeenCalledWith('ok'); }); -test('CancelablePromise.reject()', async () => { +test('createCancelablePromise.reject()', async () => { const callback = jest.fn(); await new Promise((resolve) => - resolve(CancelablePromise.reject('ko')) + resolve(createCancelablePromise.reject('ko')) ).catch(callback); expect(callback).toHaveBeenCalledTimes(1); expect(callback).toHaveBeenCalledWith('ko'); @@ -299,7 +305,7 @@ describe('Cancelable promises returned by executors', () => { let promise1: CancelablePromise; if (withClass) { - promise1 = new CancelablePromise((resolve, reject, onCancel) => { + promise1 = createCancelablePromise((resolve, reject, onCancel) => { callback('start p1'); const timer = setTimeout(() => { callback('resolve p1'); @@ -335,7 +341,7 @@ describe('Cancelable promises returned by executors', () => { ...([ () => { callback('then p2'); - const promise3 = new CancelablePromise( + const promise3 = createCancelablePromise( (resolve, reject, onCancel) => { callback('start p3'); const timer = setTimeout(() => { @@ -354,7 +360,7 @@ describe('Cancelable promises returned by executors', () => { !withCatch && (() => { callback('error p2'); - const promise3 = new CancelablePromise( + const promise3 = createCancelablePromise( (resolve, reject, onCancel) => { callback('start p3'); const timer = setTimeout(() => { @@ -376,7 +382,7 @@ describe('Cancelable promises returned by executors', () => { if (withCatch) { promise2 = promise2.catch(() => { callback('catch p2'); - const promise3 = new CancelablePromise( + const promise3 = createCancelablePromise( (resolve, reject, onCancel) => { callback('start p3'); const timer = setTimeout(() => { @@ -504,7 +510,7 @@ describe('Cancelable promises returned by executors', () => { setTimeout(resolve, 1); }) ).then(() => { - return new CancelablePromise((resolve) => { + return createCancelablePromise((resolve) => { setTimeout(resolve, 1); }).then(() => { return cancelable( @@ -514,7 +520,7 @@ describe('Cancelable promises returned by executors', () => { }, 1); }) ).then(() => { - return new CancelablePromise((resolve, reject, onCancel) => { + return createCancelablePromise((resolve, reject, onCancel) => { const timer = setTimeout(() => { callback('it should not resolve'); resolve(); @@ -544,12 +550,15 @@ describe('Cancelable promises returned by executors', () => { for (const [label, isCancelable] of [ ['isCancelablePromise', isCancelablePromise], - ['CancelablePromise.isCancelable', CancelablePromise.isCancelable], + [ + 'createCancelablePromise.isCancelable', + createCancelablePromise.isCancelable, + ], ] as [string, typeof isCancelablePromise][]) { describe(label, () => { it('should be cancelable', () => { const p1 = cancelable(new Promise(() => {})); - const p2 = new CancelablePromise(() => {}); + const p2 = createCancelablePromise(() => {}); expect(isCancelable(p1)).toBe(true); expect(isCancelable(p2)).toBe(true); expect(isCancelable(p1.then(() => {}))).toBe(true); @@ -563,7 +572,6 @@ for (const [label, isCancelable] of [ expect(isCancelable(undefined)).toBe(false); expect(isCancelable(null)).toBe(false); expect(isCancelable({})).toBe(false); - expect(isCancelable({ cancel() {} })).toBe(false); }); }); } diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts index 9e54e9f16..20f9f8542 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts @@ -1,9 +1,9 @@ -import { CancelablePromise, createCancelablePromiseList } from '..'; +import { createCancelablePromise, createCancelablePromiseList } from '..'; describe('createCancelablePromiseList', () => { test('adds cancelable promises to the list', () => { const cancelablePromiseList = createCancelablePromiseList(); - const cancelablePromise = new CancelablePromise(() => {}); + const cancelablePromise = createCancelablePromise(() => {}); expect(cancelablePromiseList.isEmpty()).toBe(true); @@ -13,7 +13,7 @@ describe('createCancelablePromiseList', () => { }); test('removes the cancelable promise from the list when it resolves', async () => { const cancelablePromiseList = createCancelablePromiseList(); - const cancelablePromise = CancelablePromise.resolve(); + const cancelablePromise = createCancelablePromise.resolve(); cancelablePromiseList.add(cancelablePromise); @@ -25,7 +25,7 @@ describe('createCancelablePromiseList', () => { }); test('removes the cancelable promise from the list when it rejects', async () => { const cancelablePromiseList = createCancelablePromiseList(); - const cancelablePromise = CancelablePromise.reject(); + const cancelablePromise = createCancelablePromise.reject(); cancelablePromiseList.add(cancelablePromise); @@ -37,7 +37,7 @@ describe('createCancelablePromiseList', () => { }); test('removes the cancelable promise from the list when it is canceled', () => { const cancelablePromiseList = createCancelablePromiseList(); - const cancelablePromise = CancelablePromise.resolve(); + const cancelablePromise = createCancelablePromise.resolve(); cancelablePromiseList.add(cancelablePromise); @@ -49,7 +49,7 @@ describe('createCancelablePromiseList', () => { }); test('empties the list when all promises are canceled', () => { const cancelablePromiseList = createCancelablePromiseList(); - const cancelablePromise = CancelablePromise.resolve(); + const cancelablePromise = createCancelablePromise.resolve(); cancelablePromiseList.add(cancelablePromise); From 7610dcc6c1122147ed57cf7091170b9055ef513b Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Tue, 18 Jan 2022 11:39:01 +0100 Subject: [PATCH 06/41] refactor: rename file --- .../utils/{CancelablePromise.ts => createCancelablePromise.ts} | 0 packages/autocomplete-core/src/utils/index.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/autocomplete-core/src/utils/{CancelablePromise.ts => createCancelablePromise.ts} (100%) diff --git a/packages/autocomplete-core/src/utils/CancelablePromise.ts b/packages/autocomplete-core/src/utils/createCancelablePromise.ts similarity index 100% rename from packages/autocomplete-core/src/utils/CancelablePromise.ts rename to packages/autocomplete-core/src/utils/createCancelablePromise.ts diff --git a/packages/autocomplete-core/src/utils/index.ts b/packages/autocomplete-core/src/utils/index.ts index a610b0685..e6f79a003 100644 --- a/packages/autocomplete-core/src/utils/index.ts +++ b/packages/autocomplete-core/src/utils/index.ts @@ -1,4 +1,4 @@ -export * from './CancelablePromise'; +export * from './createCancelablePromise'; export * from './createConcurrentSafePromise'; export * from './createCancelablePromiseList'; export * from './getNextActiveItemId'; From a8d9b3e497f443612694dbcc34522fa5927c4d86 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Thu, 20 Jan 2022 20:53:02 +0100 Subject: [PATCH 07/41] fix: fix types --- ...est.ts => createCancelablePromise.test.ts} | 0 .../src/utils/createCancelablePromise.ts | 57 +++++++++---------- 2 files changed, 28 insertions(+), 29 deletions(-) rename packages/autocomplete-core/src/utils/__tests__/{CancelablePromise.test.ts => createCancelablePromise.test.ts} (100%) diff --git a/packages/autocomplete-core/src/utils/__tests__/CancelablePromise.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts similarity index 100% rename from packages/autocomplete-core/src/utils/__tests__/CancelablePromise.test.ts rename to packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts diff --git a/packages/autocomplete-core/src/utils/createCancelablePromise.ts b/packages/autocomplete-core/src/utils/createCancelablePromise.ts index 997d055e3..68a34c8ce 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromise.ts @@ -12,27 +12,21 @@ type CreateInternalCancelablePromiseParams = { initialState?: InternalState; }; -type InternalCancelablePromise = { - then( +export type CancelablePromise = { + then( onfulfilled?: | (( value: TValue - ) => - | TFulfilledResult - | PromiseLike - | CancelablePromise) + ) => TResult1 | PromiseLike | CancelablePromise) | undefined | null, onrejected?: | (( reason: any - ) => - | TRejectedResult - | PromiseLike - | CancelablePromise) + ) => TResult2 | PromiseLike | CancelablePromise) | undefined | null - ): CancelablePromise; + ): CancelablePromise; catch( onrejected?: | (( @@ -57,22 +51,22 @@ export function createInternalCancelablePromise({ initialState.onCancelList.push(onCancel); }); }), -}: CreateInternalCancelablePromiseParams): InternalCancelablePromise { +}: CreateInternalCancelablePromiseParams): CancelablePromise { const state = initialState; return { then(onfulfilled, onrejected) { return createCancelable( promise.then( - createCallback(onfulfilled, state), - createCallback(onrejected, state) + createCallback(onfulfilled, state, promise), + createCallback(onrejected, state, promise) ), state ); }, catch(onrejected) { return createCancelable( - promise.catch(createCallback(onrejected, state)), + promise.catch(createCallback(onrejected, state, promise)), state ); }, @@ -83,16 +77,20 @@ export function createInternalCancelablePromise({ return createCancelable( promise.finally( - createCallback(() => { - if (onfinally) { - if (runWhenCanceled) { - state.onCancelList = state.onCancelList.filter( - (callback) => callback !== onfinally - ); + createCallback( + () => { + if (onfinally) { + if (runWhenCanceled) { + state.onCancelList = state.onCancelList.filter( + (callback) => callback !== onfinally + ); + } + return onfinally(); } - return onfinally(); - } - }, state) + }, + state, + promise + ) ), state ); @@ -118,8 +116,6 @@ export function createInternalCancelablePromise({ }; } -export type CancelablePromise = InternalCancelablePromise; - export function createCancelablePromise( executor: PromiseExecutor ): CancelablePromise { @@ -152,9 +148,10 @@ export function isCancelablePromise( } function createCallback( - onResult: (...args: any[]) => any, - state: InternalState -): void | ((...args: any[]) => any) { + onResult: ((...args: any[]) => any) | null | undefined, + state: InternalState, + fallback: any +) { if (onResult) { return (arg?: any) => { if (!state.isCanceled) { @@ -170,6 +167,8 @@ function createCallback( return arg; }; } + + return fallback; } function createInitialState(): InternalState { From 65a94835613270b9ee55f92b649c71a3997a2967 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Thu, 20 Jan 2022 21:17:28 +0100 Subject: [PATCH 08/41] fix: fix inconsistent behavior --- .../src/utils/createCancelablePromise.ts | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/packages/autocomplete-core/src/utils/createCancelablePromise.ts b/packages/autocomplete-core/src/utils/createCancelablePromise.ts index 68a34c8ce..119f5467b 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromise.ts @@ -1,32 +1,38 @@ import { noop } from '@algolia/autocomplete-shared'; -type PromiseExecutor = ( +type PromiseExecutor = ( resolve: (value: TValue | PromiseLike) => void, reject: (reason?: any) => void, - onCancel: (handler: () => void) => void + onCancel: (handler: (...args: any[]) => any) => void ) => void; -type CreateInternalCancelablePromiseParams = { +type CreateCancelablePromiseParams = { executor?: PromiseExecutor; promise?: Promise; initialState?: InternalState; }; export type CancelablePromise = { - then( + then( onfulfilled?: | (( value: TValue - ) => TResult1 | PromiseLike | CancelablePromise) + ) => + | TResultFulfilled + | PromiseLike + | CancelablePromise) | undefined | null, onrejected?: | (( reason: any - ) => TResult2 | PromiseLike | CancelablePromise) + ) => + | TResultRejected + | PromiseLike + | CancelablePromise) | undefined | null - ): CancelablePromise; + ): CancelablePromise; catch( onrejected?: | (( @@ -43,7 +49,7 @@ export type CancelablePromise = { isCanceled(): boolean; }; -export function createInternalCancelablePromise({ +export function createInternalCancelablePromise({ executor = noop, initialState = createInitialState(), promise = new Promise((resolve, reject) => { @@ -51,7 +57,7 @@ export function createInternalCancelablePromise({ initialState.onCancelList.push(onCancel); }); }), -}: CreateInternalCancelablePromiseParams): CancelablePromise { +}: CreateCancelablePromiseParams): CancelablePromise { const state = initialState; return { @@ -78,16 +84,15 @@ export function createInternalCancelablePromise({ return createCancelable( promise.finally( createCallback( - () => { - if (onfinally) { + onfinally && + (() => { if (runWhenCanceled) { state.onCancelList = state.onCancelList.filter( (callback) => callback !== onfinally ); } return onfinally(); - } - }, + }), state, promise ) @@ -102,11 +107,7 @@ export function createInternalCancelablePromise({ for (const callback of callbacks) { if (typeof callback === 'function') { - try { - callback(); - } catch (err) { - console.error(err); - } + callback(); } } }, @@ -116,7 +117,7 @@ export function createInternalCancelablePromise({ }; } -export function createCancelablePromise( +export function createCancelablePromise( executor: PromiseExecutor ): CancelablePromise { return createInternalCancelablePromise({ executor }); @@ -128,20 +129,20 @@ createCancelablePromise.reject = ((reason) => cancelable(Promise.reject(reason))) as CancelablePromiseOverloads['reject']; createCancelablePromise.isCancelable = isCancelablePromise; -function createCancelable( +function createCancelable( promise: Promise, initialState: InternalState ): CancelablePromise { return createInternalCancelablePromise({ promise, initialState }); } -export function cancelable( +export function cancelable( promise: Promise ): CancelablePromise { return createCancelable(promise, createInitialState()); } -export function isCancelablePromise( +export function isCancelablePromise( promise: Promise | CancelablePromise ): boolean { return promise?.hasOwnProperty('cancel') || false; @@ -177,7 +178,7 @@ function createInitialState(): InternalState { interface InternalState { isCanceled: boolean; - onCancelList: any[]; + onCancelList: Array<((...args: any[]) => any) | null | undefined>; } interface CancelablePromiseOverloads { From 46468ff21027bad9044c9616713079ccf43cdceb Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Thu, 20 Jan 2022 21:48:43 +0100 Subject: [PATCH 09/41] refactor: rename type --- .../autocomplete-core/src/utils/createCancelablePromise.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/autocomplete-core/src/utils/createCancelablePromise.ts b/packages/autocomplete-core/src/utils/createCancelablePromise.ts index 119f5467b..7b8686ec8 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromise.ts @@ -6,7 +6,7 @@ type PromiseExecutor = ( onCancel: (handler: (...args: any[]) => any) => void ) => void; -type CreateCancelablePromiseParams = { +type CreateInternalCancelablePromiseParams = { executor?: PromiseExecutor; promise?: Promise; initialState?: InternalState; @@ -57,7 +57,7 @@ export function createInternalCancelablePromise({ initialState.onCancelList.push(onCancel); }); }), -}: CreateCancelablePromiseParams): CancelablePromise { +}: CreateInternalCancelablePromiseParams): CancelablePromise { const state = initialState; return { From 9ad300018947df36c783ce9d76bfa1e7cf4b8350 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Fri, 21 Jan 2022 00:17:01 +0100 Subject: [PATCH 10/41] test: update tests --- .../__tests__/createCancelablePromise.test.ts | 1049 ++++++++--------- .../src/utils/createCancelablePromise.ts | 2 +- 2 files changed, 521 insertions(+), 530 deletions(-) diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts index 92f3c7fe3..7b1c5c65b 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts @@ -1,577 +1,568 @@ +import { noop } from '@algolia/autocomplete-shared'; import { cancelable, CancelablePromise, createCancelablePromise, isCancelablePromise, } from '..'; +import { defer, runAllMicroTasks } from '../../../../../test/utils'; + +describe('createCancelablePromise', () => { + test('returns an immediately resolved cancelable promise', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + + await createCancelablePromise + .resolve('ok') + .then(onFulfilled) + .catch(onRejected) + .finally(onFinally); + + expect(onFulfilled).toHaveBeenCalledTimes(1); + expect(onFulfilled).toHaveBeenCalledWith('ok'); + expect(onRejected).not.toHaveBeenCalled(); + expect(onFinally).toHaveBeenCalledTimes(1); + expect(onFinally).toHaveBeenCalledWith(); + }); -const delay = async (timeout = 0, callback?: Function) => { - await new Promise((resolve) => setTimeout(resolve, timeout)); - - if (callback) { - return await callback(); - } -}; - -describe('Fulfilled worflow', () => { - const promises: ( - | [string, () => Promise] - | [string, () => CancelablePromise] - )[] = [ - [ - 'cancelable()', - () => - cancelable( - new Promise((resolve) => { - delay(1, resolve); - }) - ), - ], - [ - 'createCancelablePromise', - () => - createCancelablePromise((resolve) => { - delay(1, resolve); - }), - ], - [ - 'new Promise()', - () => - new Promise((resolve) => { - delay(1, resolve); - }), - ], - ]; - - const expectResolveWorkflow = async ( - promise1: Promise | CancelablePromise - ) => { - const callback = jest.fn(); - const promise2 = promise1.then(callback); - const promise3 = promise1.then(() => { - callback(); - return delay(1); - }); - const promise4 = promise2.then(callback); - const promise5 = promise3.then(() => { - callback(); - return delay(1); - }); - const promise6 = promise5.then().then(callback); - const promise7 = promise6.finally(callback); - await Promise.all([ - promise1, - promise2, - promise3, - promise4, - promise5, - promise6, - promise7, - ]); - expect(callback).toHaveBeenCalledTimes(6); - }; - - for (const [label, createPromise] of promises) { - it(label, async () => { - await expectResolveWorkflow(createPromise()); - }); - } -}); + test('returns an immediately rejected cancelable promise', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + + await createCancelablePromise + .reject(new Error()) + .then(onFulfilled) + .catch(onRejected) + .finally(onFinally); + + expect(onRejected).toHaveBeenCalledTimes(1); + expect(onRejected).toHaveBeenCalledWith(new Error()); + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onFinally).toHaveBeenCalledTimes(1); + expect(onFinally).toHaveBeenCalledWith(); + }); -describe('Rejected worflow', () => { - const promises: ( - | [string, () => Promise] - | [string, () => CancelablePromise] - )[] = [ - [ - 'cancelable()', - () => - cancelable( - new Promise((resolve, reject) => { - delay(1, () => reject(new Error('native promise error'))); - }) - ), - ], - [ - 'createCancelablePromise', - () => - createCancelablePromise((resolve, reject) => { - delay(1, () => reject(new Error('cancelable promise error'))); - }), - ], - [ - 'new Promise()', - () => - new Promise((resolve, reject) => { - delay(1, () => reject(new Error('native promise error'))); - }), - ], - ]; - - const expectErrorWorkflow = async ( - promise1: Promise | CancelablePromise - ) => { - const callback = jest.fn(); - const promise2 = promise1.then(callback).catch(() => callback(1)); - const promise3 = promise1.then(callback, () => callback(2)); - const promise4 = promise3.then(() => { - callback(3); - return delay(1, () => Promise.reject(new Error('internal error'))); - }); - const promise5 = promise4.catch(() => callback(4)); - const promise6 = promise4.then(callback, () => callback(5)); - const promise7 = promise6.finally(() => callback(6)); - await Promise.all([promise2, promise3, promise5, promise6, promise7]); - expect(callback).toHaveBeenCalledTimes(6); - expect(callback).toHaveBeenCalledWith(1); - expect(callback).toHaveBeenCalledWith(2); - expect(callback).toHaveBeenCalledWith(3); - expect(callback).toHaveBeenCalledWith(4); - expect(callback).toHaveBeenCalledWith(5); - expect(callback).toHaveBeenCalledWith(6); - }; - - for (const [label, createPromise] of promises) { - it(label, async () => { - await expectErrorWorkflow(createPromise()); - }); - } -}); + test('triggers callbacks when the cancelable promise resolves', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + const onCancelSpy = jest.fn(); -test('Cancel root promise', async () => { - const callback = jest.fn(); - const promise1 = cancelable( - new Promise((resolve) => { - delay(1, resolve); + await createCancelablePromise((resolve, _, onCancel) => { + resolve('ok'); + onCancel(onCancelSpy); }) - ); - const promise2 = promise1.then(callback); - promise1.then(callback).then(callback); - promise2.then(callback); - expect(promise1.isCanceled()).toBe(false); - promise1.cancel(); - expect(promise1.isCanceled()).toBe(true); - await delay(10); - expect(callback).toHaveBeenCalledTimes(0); -}); + .then(onFulfilled) + .catch(onRejected) + .finally(onFinally); + + expect(onFulfilled).toHaveBeenCalledTimes(1); + expect(onFulfilled).toHaveBeenCalledWith('ok'); + expect(onRejected).not.toHaveBeenCalled(); + expect(onFinally).toHaveBeenCalledTimes(1); + expect(onFinally).toHaveBeenCalledWith(); + expect(onCancelSpy).toHaveBeenCalledTimes(0); + }); -test('Cancel a returned promise', async () => { - const callback = jest.fn(); - const promise1 = cancelable( - new Promise((resolve) => { - delay(1, resolve); - }) - ); - const promise2 = promise1.then(callback); - promise1.then(callback).then(callback); - promise2.then(callback); - expect(promise2.isCanceled()).toBe(false); - promise2.cancel(); - expect(promise2.isCanceled()).toBe(true); - await delay(10); - expect(callback).toHaveBeenCalledTimes(0); -}); + test('triggers callbacks when the cancelable promise rejects', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + const onCancelSpy = jest.fn(); -test('Cancel a rejected promise', async () => { - const callback = jest.fn(); - const promise1 = cancelable( - new Promise((resolve, reject) => { - reject(); + await createCancelablePromise((_, reject, onCancel) => { + reject(new Error()); + onCancel(onCancelSpy); }) - ); - promise1.cancel(); - const promise2 = promise1.catch(callback); - promise1.then(callback, callback).then(callback); - promise2.then(callback); - await delay(10); - expect(callback).toHaveBeenCalledTimes(0); -}); + .then(onFulfilled) + .catch(onRejected) + .finally(onFinally); + + expect(onRejected).toHaveBeenCalledTimes(1); + expect(onRejected).toHaveBeenCalledWith(new Error()); + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onFinally).toHaveBeenCalledTimes(1); + expect(onFinally).toHaveBeenCalledWith(); + expect(onCancelSpy).toHaveBeenCalledTimes(0); + }); -test('Cancel a promise but finally should not be still executed', async () => { - const callback = jest.fn(); - const promise = cancelable( - new Promise((resolve) => { - delay(5, resolve); - }) - ).finally(callback); - promise.cancel(); - await delay(10); - expect(callback).toHaveBeenCalledTimes(0); -}); + test('does not trigger callbacks when the cancelable promise is canceled and it resolves', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + const onCancelSpy = jest.fn(); + const cancelablePromise = createCancelablePromise( + (resolve, _, onCancel) => { + resolve('ok'); + onCancel(onCancelSpy); + } + ); -test('Cancel a promise but finally should be still executed', async () => { - const callback = jest.fn(); - const promise = cancelable( - new Promise((resolve) => { - delay(5, resolve); - }) - ).finally(callback, true); - promise.cancel(); - await delay(10); - expect(callback).toHaveBeenCalledTimes(1); -}); + cancelablePromise.then(onFulfilled).catch(onRejected).finally(onFinally); -test('Cancel a promise but finally should not be executed twice #1', async () => { - const callback = jest.fn(); - const promise = cancelable( - new Promise((resolve) => { - resolve(); - }) - ).finally(callback, true); - await promise; - expect(callback).toHaveBeenCalledTimes(1); - promise.cancel(); - await delay(10); - expect(callback).toHaveBeenCalledTimes(1); -}); + expect(cancelablePromise.isCanceled()).toBe(false); -test('Cancel a promise but finally should not be executed twice #2', async () => { - const callback = jest.fn(); - const promise = cancelable( - new Promise((resolve) => { - delay(10, resolve); - }) - ).finally(callback, true); - await delay(5); - promise.cancel(); - await delay(10); - expect(callback).toHaveBeenCalledTimes(1); -}); + cancelablePromise.cancel(); -test('On cancel callbacks should executed in the correct order', async () => { - const callback = jest.fn(); - const p1 = cancelable(Promise.resolve(callback('resolve p1'))); - p1.then(() => { - return createCancelablePromise((resolve, reject, onCancel) => { - delay(10, resolve); - onCancel(() => { - callback('cancel p2'); - }); - }).finally(() => { - callback('finally p2'); - }, true); - }); - p1.then(() => { - return createCancelablePromise((resolve, reject, onCancel) => { - delay(10, resolve); - onCancel(() => { - callback('cancel p3'); - }); - }).finally(() => { - callback('finally p3'); - }, true); + expect(cancelablePromise.isCanceled()).toBe(true); + + await runAllMicroTasks(); + + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onRejected).not.toHaveBeenCalled(); + expect(onFinally).not.toHaveBeenCalled(); + expect(onCancelSpy).toHaveBeenCalledTimes(1); + expect(onCancelSpy).toHaveBeenCalledWith(); }); - await delay(5); - p1.cancel(); - await delay(10); - expect(callback.mock.calls).toEqual([ - ['resolve p1'], - ['cancel p2'], - ['finally p2'], - ['cancel p3'], - ['finally p3'], - ]); -}); -test('createCancelablePromise.resolve()', async () => { - const callback = jest.fn(); - await new Promise((resolve) => - resolve(createCancelablePromise.resolve('ok')) - ).then(callback); - expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledWith('ok'); -}); + test('triggers `finally` handler callback with `runWhenCanceled=true` when the cancelable promise is canceled and it resolves', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + const onCancelSpy = jest.fn(); + const cancelablePromise = createCancelablePromise( + (resolve, _, onCancel) => { + resolve('ok'); + onCancel(onCancelSpy); + } + ); -test('createCancelablePromise.reject()', async () => { - const callback = jest.fn(); - await new Promise((resolve) => - resolve(createCancelablePromise.reject('ko')) - ).catch(callback); - expect(callback).toHaveBeenCalledTimes(1); - expect(callback).toHaveBeenCalledWith('ko'); -}); + cancelablePromise + .then(onFulfilled) + .catch(onRejected) + .finally(onFinally, true); -describe('Cancelable promises returned by executors', () => { - async function worflow({ - withClass, - withFail, - withCatch, - }: { - withClass?: boolean; - withFail?: boolean; - withCatch?: boolean; - }) { - const callback = jest.fn(); - let promise1: CancelablePromise; - - if (withClass) { - promise1 = createCancelablePromise((resolve, reject, onCancel) => { - callback('start p1'); - const timer = setTimeout(() => { - callback('resolve p1'); - if (withFail) { - reject(); - } else { - resolve(); - } - }, 5); - const abort = () => { - callback('abort p1'); - clearTimeout(timer); - }; - onCancel(abort); - }); - } else { - promise1 = cancelable( - new Promise((resolve, reject) => { - callback('start p1'); - delay(5, () => { - callback('resolve p1'); - if (withFail) { - reject(); - } else { - resolve(); - } - }); - }) - ); - } - - let promise2 = promise1.then( - ...([ - () => { - callback('then p2'); - const promise3 = createCancelablePromise( - (resolve, reject, onCancel) => { - callback('start p3'); - const timer = setTimeout(() => { - callback('resolve p3'); - resolve(); - }, 10); - const abort = () => { - callback('abort p3'); - clearTimeout(timer); - }; - onCancel(abort); - } - ); - return promise3; - }, - !withCatch && - (() => { - callback('error p2'); - const promise3 = createCancelablePromise( - (resolve, reject, onCancel) => { - callback('start p3'); - const timer = setTimeout(() => { - callback('resolve p3'); - resolve(); - }, 10); - const abort = () => { - callback('abort p3'); - clearTimeout(timer); - }; - onCancel(abort); - } - ); - return promise3; - }), - ].filter(Boolean) as [() => CancelablePromise]) + expect(cancelablePromise.isCanceled()).toBe(false); + + cancelablePromise.cancel(); + + expect(cancelablePromise.isCanceled()).toBe(true); + + await runAllMicroTasks(); + + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onRejected).not.toHaveBeenCalled(); + expect(onFinally).toHaveBeenCalledTimes(1); + expect(onFinally).toHaveBeenCalledWith(); + expect(onCancelSpy).toHaveBeenCalledTimes(1); + expect(onCancelSpy).toHaveBeenCalledWith(); + }); + + test('triggers `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it resolves', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + const onCancelSpy = jest.fn(); + const cancelablePromise = createCancelablePromise( + (resolve, _, onCancel) => { + resolve('ok'); + onCancel(onCancelSpy); + } ); - if (withCatch) { - promise2 = promise2.catch(() => { - callback('catch p2'); - const promise3 = createCancelablePromise( - (resolve, reject, onCancel) => { - callback('start p3'); - const timer = setTimeout(() => { - callback('resolve p3'); - resolve(); - }, 10); - const abort = () => { - callback('abort p3'); - clearTimeout(timer); - }; - onCancel(abort); - } - ); - return promise3; - }); - } - - promise2.then(() => { - callback('then done'); - }); + cancelablePromise + .then(onFulfilled) + .catch(onRejected) + .finally(onFinally, false); + + expect(cancelablePromise.isCanceled()).toBe(false); + + cancelablePromise.cancel(); + + expect(cancelablePromise.isCanceled()).toBe(true); - await delay(10, () => { - callback('cancel p2'); - promise2.cancel(); + await runAllMicroTasks(); + + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onRejected).not.toHaveBeenCalled(); + expect(onFinally).not.toHaveBeenCalled(); + expect(onCancelSpy).toHaveBeenCalledTimes(1); + expect(onCancelSpy).toHaveBeenCalledWith(); + }); + + test('does not trigger callbacks when the cancelable promise is canceled and it rejects', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + const onCancelSpy = jest.fn(); + const cancelablePromise = createCancelablePromise((_, reject, onCancel) => { + reject(); + onCancel(onCancelSpy); }); - await delay(20); - return callback; - } - - it('should be canceled when fulfilled (with CancelablePromise)', async () => { - const callback = await worflow({ withClass: true, withFail: false }); - expect(callback.mock.calls).toEqual([ - ['start p1'], - ['resolve p1'], - ['then p2'], - ['start p3'], - ['cancel p2'], - ['abort p1'], - ['abort p3'], - ]); + + cancelablePromise.then(onFulfilled).catch(onRejected).finally(onFinally); + + expect(cancelablePromise.isCanceled()).toBe(false); + + cancelablePromise.cancel(); + + expect(cancelablePromise.isCanceled()).toBe(true); + + await runAllMicroTasks(); + + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onRejected).not.toHaveBeenCalled(); + expect(onFinally).not.toHaveBeenCalled(); + expect(onCancelSpy).toHaveBeenCalledTimes(1); + expect(onCancelSpy).toHaveBeenCalledWith(); }); - it('should be canceled when rejected (with CancelablePromise)', async () => { - const callback = await worflow({ - withClass: true, - withFail: true, - withCatch: false, + test('triggers `finally` handler callback with `runWhenCanceled=true` when the cancelable promise is canceled and it rejects', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + const onCancelSpy = jest.fn(); + const cancelablePromise = createCancelablePromise((_, reject, onCancel) => { + reject(); + onCancel(onCancelSpy); }); - expect(callback.mock.calls).toEqual([ - ['start p1'], - ['resolve p1'], - ['error p2'], - ['start p3'], - ['cancel p2'], - ['abort p1'], - ['abort p3'], - ]); + + cancelablePromise + .then(onFulfilled) + .catch(onRejected) + .finally(onFinally, true); + + expect(cancelablePromise.isCanceled()).toBe(false); + + cancelablePromise.cancel(); + + expect(cancelablePromise.isCanceled()).toBe(true); + + await runAllMicroTasks(); + + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onRejected).not.toHaveBeenCalled(); + expect(onFinally).toHaveBeenCalledTimes(1); + expect(onFinally).toHaveBeenCalledWith(); + expect(onCancelSpy).toHaveBeenCalledTimes(1); + expect(onCancelSpy).toHaveBeenCalledWith(); }); - it('should be canceled when rejected and caught (with CancelablePromise)', async () => { - const callback = await worflow({ - withClass: true, - withFail: true, - withCatch: true, + test('triggers `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it resolves', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + const onCancelSpy = jest.fn(); + const cancelablePromise = createCancelablePromise((_, reject, onCancel) => { + reject(); + onCancel(onCancelSpy); }); - expect(callback.mock.calls).toEqual([ - ['start p1'], - ['resolve p1'], - ['catch p2'], - ['start p3'], - ['cancel p2'], - ['abort p1'], - ['abort p3'], - ]); + + cancelablePromise + .then(onFulfilled) + .catch(onRejected) + .finally(onFinally, false); + + expect(cancelablePromise.isCanceled()).toBe(false); + + cancelablePromise.cancel(); + + expect(cancelablePromise.isCanceled()).toBe(true); + + await runAllMicroTasks(); + + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onRejected).not.toHaveBeenCalled(); + expect(onFinally).not.toHaveBeenCalled(); + expect(onCancelSpy).toHaveBeenCalledTimes(1); + expect(onCancelSpy).toHaveBeenCalledWith(); }); - it('should be canceled when fulfilled (with cancelable)', async () => { - const callback = await worflow({ withClass: false, withFail: false }); - expect(callback.mock.calls).toEqual([ - ['start p1'], - ['resolve p1'], - ['then p2'], - ['start p3'], - ['cancel p2'], - ['abort p3'], - ]); + test('deeply cancels nested cancelable promises', async () => { + const onFulfilled = jest.fn(); + const onCancelSpy = jest.fn(); + + const cancelablePromise = createCancelablePromise( + (resolve, _, onCancel) => { + resolve('ok'); + onCancel(onCancelSpy); + } + ).then(() => + createCancelablePromise((resolve) => { + resolve('ok'); + onFulfilled(); + }).then(() => + createCancelablePromise((resolve) => { + resolve('ok'); + onFulfilled(); + }).then(onFulfilled) + ) + ); + + expect(cancelablePromise.isCanceled()).toBe(false); + + cancelablePromise.cancel(); + + expect(cancelablePromise.isCanceled()).toBe(true); + + await runAllMicroTasks(); + + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onCancelSpy).toHaveBeenCalledTimes(1); + expect(onCancelSpy).toHaveBeenCalledWith(); }); +}); - it('should be canceled when rejected (with cancelable)', async () => { - const callback = await worflow({ - withClass: false, - withFail: true, - withCatch: false, - }); - expect(callback.mock.calls).toEqual([ - ['start p1'], - ['resolve p1'], - ['error p2'], - ['start p3'], - ['cancel p2'], - ['abort p3'], - ]); +describe('cancelable', () => { + test('triggers callbacks when the cancelable promise resolves', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + + await cancelable( + new Promise((resolve) => { + resolve('ok'); + }) + ) + .then(onFulfilled) + .catch(onRejected) + .finally(onFinally); + + expect(onFulfilled).toHaveBeenCalledTimes(1); + expect(onFulfilled).toHaveBeenCalledWith('ok'); + expect(onRejected).not.toHaveBeenCalled(); + expect(onFinally).toHaveBeenCalledTimes(1); + expect(onFinally).toHaveBeenCalledWith(); }); - it('should be canceled when rejected and caught (with cancelable)', async () => { - const callback = await worflow({ - withClass: false, - withFail: true, - withCatch: true, - }); - expect(callback.mock.calls).toEqual([ - ['start p1'], - ['resolve p1'], - ['catch p2'], - ['start p3'], - ['cancel p2'], - ['abort p3'], - ]); + test('triggers callbacks when the cancelable promise rejects', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + + await cancelable( + new Promise((_, reject) => { + reject(new Error()); + }) + ) + .then(onFulfilled) + .catch(onRejected) + .finally(onFinally); + + expect(onRejected).toHaveBeenCalledTimes(1); + expect(onRejected).toHaveBeenCalledWith(new Error()); + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onFinally).toHaveBeenCalledTimes(1); + expect(onFinally).toHaveBeenCalledWith(); + }); + + test('does not trigger callbacks when the cancelable promise is canceled and it resolves', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + const cancelablePromise = cancelable( + new Promise((resolve) => { + resolve('ok'); + }) + ); + + cancelablePromise.then(onFulfilled).catch(onRejected).finally(onFinally); + + expect(cancelablePromise.isCanceled()).toBe(false); + + cancelablePromise.cancel(); + + expect(cancelablePromise.isCanceled()).toBe(true); + + await runAllMicroTasks(); + + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onRejected).not.toHaveBeenCalled(); + expect(onFinally).not.toHaveBeenCalled(); + }); + + test('triggers `finally` handler callback with `runWhenCanceled=true` when the cancelable promise is canceled and it resolves', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + const cancelablePromise = cancelable( + new Promise((resolve) => { + resolve('ok'); + }) + ); + + cancelablePromise + .then(onFulfilled) + .catch(onRejected) + .finally(onFinally, true); + + expect(cancelablePromise.isCanceled()).toBe(false); + + cancelablePromise.cancel(); + + expect(cancelablePromise.isCanceled()).toBe(true); + + await runAllMicroTasks(); + + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onRejected).not.toHaveBeenCalled(); + expect(onFinally).toHaveBeenCalledTimes(1); + expect(onFinally).toHaveBeenCalledWith(); + }); + + test('triggers `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it resolves', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + const cancelablePromise = cancelable( + new Promise((resolve) => { + resolve('ok'); + }) + ); + + cancelablePromise + .then(onFulfilled) + .catch(onRejected) + .finally(onFinally, false); + + expect(cancelablePromise.isCanceled()).toBe(false); + + cancelablePromise.cancel(); + + expect(cancelablePromise.isCanceled()).toBe(true); + + await runAllMicroTasks(); + + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onRejected).not.toHaveBeenCalled(); + expect(onFinally).not.toHaveBeenCalled(); + }); + + test('does not trigger callbacks when the cancelable promise is canceled and it rejects', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + const cancelablePromise = cancelable( + new Promise((_, reject) => { + reject(); + }) + ); + + cancelablePromise.then(onFulfilled).catch(onRejected).finally(onFinally); + + expect(cancelablePromise.isCanceled()).toBe(false); + + cancelablePromise.cancel(); + + expect(cancelablePromise.isCanceled()).toBe(true); + + await runAllMicroTasks(); + + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onRejected).not.toHaveBeenCalled(); + expect(onFinally).not.toHaveBeenCalled(); }); - it('should cancel promises deeply', async () => { - const callback = jest.fn(); - const promise1 = cancelable( + test('triggers `finally` handler callback with `runWhenCanceled=true` when the cancelable promise is canceled and it rejects', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + const cancelablePromise = cancelable( + new Promise((_, reject) => { + reject(); + }) + ); + + cancelablePromise + .then(onFulfilled) + .catch(onRejected) + .finally(onFinally, true); + + expect(cancelablePromise.isCanceled()).toBe(false); + + cancelablePromise.cancel(); + + expect(cancelablePromise.isCanceled()).toBe(true); + + await runAllMicroTasks(); + + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onRejected).not.toHaveBeenCalled(); + expect(onFinally).toHaveBeenCalledTimes(1); + expect(onFinally).toHaveBeenCalledWith(); + }); + + test('triggers `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it rejects', async () => { + const onFulfilled = jest.fn(); + const onRejected = jest.fn(); + const onFinally = jest.fn(); + const cancelablePromise = cancelable( + new Promise((_, reject) => { + reject(); + }) + ); + + cancelablePromise + .then(onFulfilled) + .catch(onRejected) + .finally(onFinally, false); + + expect(cancelablePromise.isCanceled()).toBe(false); + + cancelablePromise.cancel(); + + expect(cancelablePromise.isCanceled()).toBe(true); + + await runAllMicroTasks(); + + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onRejected).not.toHaveBeenCalled(); + expect(onFinally).not.toHaveBeenCalled(); + }); + + test('deeply cancels nested cancelable promises', async () => { + const onFulfilled = jest.fn(); + + const cancelablePromise = cancelable( new Promise((resolve) => { - setTimeout(resolve, 1); + resolve('ok'); }) - ).then(() => { - return createCancelablePromise((resolve) => { - setTimeout(resolve, 1); - }).then(() => { - return cancelable( - new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, 1); + ).then(() => + cancelable( + new Promise((resolve) => { + resolve('ok'); + onFulfilled(); + }) + ).then(() => + cancelable( + new Promise((resolve) => { + resolve('ok'); + onFulfilled(); }) - ).then(() => { - return createCancelablePromise((resolve, reject, onCancel) => { - const timer = setTimeout(() => { - callback('it should not resolve'); - resolve(); - }, 10); - onCancel(() => { - clearTimeout(timer); - }); - }).then(() => { - return cancelable( - new Promise((resolve) => { - setTimeout(() => { - callback('it should not resolve'); - resolve(); - }, 0); - }) - ); - }); - }); - }); - }); - await delay(5); - promise1.cancel(); - await delay(20); - expect(callback).not.toHaveBeenCalled(); + ).then(onFulfilled) + ) + ); + + expect(cancelablePromise.isCanceled()).toBe(false); + + cancelablePromise.cancel(); + + expect(cancelablePromise.isCanceled()).toBe(true); + + await runAllMicroTasks(); + + expect(onFulfilled).not.toHaveBeenCalled(); }); }); -for (const [label, isCancelable] of [ - ['isCancelablePromise', isCancelablePromise], - [ - 'createCancelablePromise.isCancelable', - createCancelablePromise.isCancelable, - ], -] as [string, typeof isCancelablePromise][]) { - describe(label, () => { - it('should be cancelable', () => { - const p1 = cancelable(new Promise(() => {})); - const p2 = createCancelablePromise(() => {}); - expect(isCancelable(p1)).toBe(true); - expect(isCancelable(p2)).toBe(true); - expect(isCancelable(p1.then(() => {}))).toBe(true); - expect(isCancelable(p2.then(() => {}))).toBe(true); - expect(isCancelable(p1.catch(() => {}))).toBe(true); - expect(isCancelable(p2.catch(() => {}))).toBe(true); - }); +describe('isCancelablePromise', () => { + test('should be cancelable', () => { + const p1 = cancelable(new Promise(noop)); + const p2 = createCancelablePromise(noop); + + expect(isCancelablePromise(p1)).toBe(true); + expect(isCancelablePromise(p2)).toBe(true); + expect(isCancelablePromise(p1.then(noop))).toBe(true); + expect(isCancelablePromise(p2.then(noop))).toBe(true); + expect(isCancelablePromise(p1.catch(noop))).toBe(true); + expect(isCancelablePromise(p2.catch(noop))).toBe(true); + }); - it('should not be cancelable', () => { - expect(isCancelable(new Promise(() => {}))).toBe(false); - expect(isCancelable(undefined)).toBe(false); - expect(isCancelable(null)).toBe(false); - expect(isCancelable({})).toBe(false); - }); + test('should not be cancelable', () => { + expect(isCancelablePromise(new Promise(noop))).toBe(false); + expect(isCancelablePromise(undefined)).toBe(false); + expect(isCancelablePromise(null)).toBe(false); }); -} +}); diff --git a/packages/autocomplete-core/src/utils/createCancelablePromise.ts b/packages/autocomplete-core/src/utils/createCancelablePromise.ts index 7b8686ec8..ab4465299 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromise.ts @@ -49,7 +49,7 @@ export type CancelablePromise = { isCanceled(): boolean; }; -export function createInternalCancelablePromise({ +function createInternalCancelablePromise({ executor = noop, initialState = createInitialState(), promise = new Promise((resolve, reject) => { From 516875c2e22fc456419b1dd3d4bae06cd6cbc775 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Fri, 21 Jan 2022 17:22:18 +0100 Subject: [PATCH 11/41] build: bump bundle size --- bundlesize.config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundlesize.config.json b/bundlesize.config.json index e71598ae0..8d3545209 100644 --- a/bundlesize.config.json +++ b/bundlesize.config.json @@ -2,11 +2,11 @@ "files": [ { "path": "packages/autocomplete-core/dist/umd/index.production.js", - "maxSize": "6 kB" + "maxSize": "6.5 kB" }, { "path": "packages/autocomplete-js/dist/umd/index.production.js", - "maxSize": "16.5 kB" + "maxSize": "17 kB" }, { "path": "packages/autocomplete-preset-algolia/dist/umd/index.production.js", From 8e2f05a2d5275ba3bc00c329101f98d0c62b220a Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Fri, 21 Jan 2022 17:40:19 +0100 Subject: [PATCH 12/41] style: lint --- .../__tests__/createCancelablePromise.test.ts | 42 ++++++------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts index 7b1c5c65b..223bdd92b 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts @@ -1,11 +1,7 @@ import { noop } from '@algolia/autocomplete-shared'; -import { - cancelable, - CancelablePromise, - createCancelablePromise, - isCancelablePromise, -} from '..'; -import { defer, runAllMicroTasks } from '../../../../../test/utils'; + +import { cancelable, createCancelablePromise, isCancelablePromise } from '..'; +import { runAllMicroTasks } from '../../../../../test/utils'; describe('createCancelablePromise', () => { test('returns an immediately resolved cancelable promise', async () => { @@ -188,7 +184,7 @@ describe('createCancelablePromise', () => { const onFinally = jest.fn(); const onCancelSpy = jest.fn(); const cancelablePromise = createCancelablePromise((_, reject, onCancel) => { - reject(); + reject(new Error()); onCancel(onCancelSpy); }); @@ -215,7 +211,7 @@ describe('createCancelablePromise', () => { const onFinally = jest.fn(); const onCancelSpy = jest.fn(); const cancelablePromise = createCancelablePromise((_, reject, onCancel) => { - reject(); + reject(new Error()); onCancel(onCancelSpy); }); @@ -240,13 +236,13 @@ describe('createCancelablePromise', () => { expect(onCancelSpy).toHaveBeenCalledWith(); }); - test('triggers `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it resolves', async () => { + test('triggers `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); const onCancelSpy = jest.fn(); const cancelablePromise = createCancelablePromise((_, reject, onCancel) => { - reject(); + reject(new Error()); onCancel(onCancelSpy); }); @@ -270,7 +266,7 @@ describe('createCancelablePromise', () => { expect(onCancelSpy).toHaveBeenCalledWith(); }); - test('deeply cancels nested cancelable promises', async () => { + test('cancels nested cancelable promises', async () => { const onFulfilled = jest.fn(); const onCancelSpy = jest.fn(); @@ -283,12 +279,7 @@ describe('createCancelablePromise', () => { createCancelablePromise((resolve) => { resolve('ok'); onFulfilled(); - }).then(() => - createCancelablePromise((resolve) => { - resolve('ok'); - onFulfilled(); - }).then(onFulfilled) - ) + }) ); expect(cancelablePromise.isCanceled()).toBe(false); @@ -436,7 +427,7 @@ describe('cancelable', () => { const onFinally = jest.fn(); const cancelablePromise = cancelable( new Promise((_, reject) => { - reject(); + reject(new Error()); }) ); @@ -461,7 +452,7 @@ describe('cancelable', () => { const onFinally = jest.fn(); const cancelablePromise = cancelable( new Promise((_, reject) => { - reject(); + reject(new Error()); }) ); @@ -490,7 +481,7 @@ describe('cancelable', () => { const onFinally = jest.fn(); const cancelablePromise = cancelable( new Promise((_, reject) => { - reject(); + reject(new Error()); }) ); @@ -512,7 +503,7 @@ describe('cancelable', () => { expect(onFinally).not.toHaveBeenCalled(); }); - test('deeply cancels nested cancelable promises', async () => { + test('cancels nested cancelable promises', async () => { const onFulfilled = jest.fn(); const cancelablePromise = cancelable( @@ -525,13 +516,6 @@ describe('cancelable', () => { resolve('ok'); onFulfilled(); }) - ).then(() => - cancelable( - new Promise((resolve) => { - resolve('ok'); - onFulfilled(); - }) - ).then(onFulfilled) ) ); From 9702ecf2c19368baacb426527abe9d030562bdc3 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Fri, 21 Jan 2022 17:51:19 +0100 Subject: [PATCH 13/41] refactor: move things around --- .../src/utils/createCancelablePromise.ts | 102 +++++++++--------- 1 file changed, 48 insertions(+), 54 deletions(-) diff --git a/packages/autocomplete-core/src/utils/createCancelablePromise.ts b/packages/autocomplete-core/src/utils/createCancelablePromise.ts index ab4465299..297eebd8f 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromise.ts @@ -1,5 +1,10 @@ import { noop } from '@algolia/autocomplete-shared'; +type InternalState = { + isCanceled: boolean; + onCancelList: Array<((...args: any[]) => any) | null | undefined>; +}; + type PromiseExecutor = ( resolve: (value: TValue | PromiseLike) => void, reject: (reason?: any) => void, @@ -12,43 +17,6 @@ type CreateInternalCancelablePromiseParams = { initialState?: InternalState; }; -export type CancelablePromise = { - then( - onfulfilled?: - | (( - value: TValue - ) => - | TResultFulfilled - | PromiseLike - | CancelablePromise) - | undefined - | null, - onrejected?: - | (( - reason: any - ) => - | TResultRejected - | PromiseLike - | CancelablePromise) - | undefined - | null - ): CancelablePromise; - catch( - onrejected?: - | (( - reason: any - ) => TResult | PromiseLike | CancelablePromise) - | undefined - | null - ): CancelablePromise; - finally( - onfinally?: (() => void) | undefined | null, - runWhenCanceled?: boolean - ): CancelablePromise; - cancel(): void; - isCanceled(): boolean; -}; - function createInternalCancelablePromise({ executor = noop, initialState = createInitialState(), @@ -117,17 +85,55 @@ function createInternalCancelablePromise({ }; } +export type CancelablePromise = { + then( + onfulfilled?: + | (( + value: TValue + ) => + | TResultFulfilled + | PromiseLike + | CancelablePromise) + | undefined + | null, + onrejected?: + | (( + reason: any + ) => + | TResultRejected + | PromiseLike + | CancelablePromise) + | undefined + | null + ): CancelablePromise; + catch( + onrejected?: + | (( + reason: any + ) => TResult | PromiseLike | CancelablePromise) + | undefined + | null + ): CancelablePromise; + finally( + onfinally?: (() => void) | undefined | null, + runWhenCanceled?: boolean + ): CancelablePromise; + cancel(): void; + isCanceled(): boolean; +}; + export function createCancelablePromise( executor: PromiseExecutor ): CancelablePromise { return createInternalCancelablePromise({ executor }); } -createCancelablePromise.resolve = ((value) => - cancelable(Promise.resolve(value))) as CancelablePromiseOverloads['resolve']; -createCancelablePromise.reject = ((reason) => - cancelable(Promise.reject(reason))) as CancelablePromiseOverloads['reject']; -createCancelablePromise.isCancelable = isCancelablePromise; +createCancelablePromise.resolve = ( + value?: TValue | PromiseLike | CancelablePromise +) => cancelable(Promise.resolve(value)); + +createCancelablePromise.reject = (reason?: any) => + cancelable(Promise.reject(reason)); function createCancelable( promise: Promise, @@ -175,15 +181,3 @@ function createCallback( function createInitialState(): InternalState { return { isCanceled: false, onCancelList: [] }; } - -interface InternalState { - isCanceled: boolean; - onCancelList: Array<((...args: any[]) => any) | null | undefined>; -} - -interface CancelablePromiseOverloads { - resolve( - value?: TValue | PromiseLike | CancelablePromise - ): CancelablePromise; - reject(reason?: any): CancelablePromise; -} From 7f484a7f2320f2ba2af67eeeb7ba8dfe4533eb37 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Fri, 21 Jan 2022 18:59:25 +0100 Subject: [PATCH 14/41] feat: remove onCancel handler --- .../__tests__/createCancelablePromise.test.ts | 72 +++++-------------- .../src/utils/createCancelablePromise.ts | 8 +-- 2 files changed, 19 insertions(+), 61 deletions(-) diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts index 223bdd92b..437460922 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts @@ -44,11 +44,9 @@ describe('createCancelablePromise', () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); - const onCancelSpy = jest.fn(); - await createCancelablePromise((resolve, _, onCancel) => { + await createCancelablePromise((resolve) => { resolve('ok'); - onCancel(onCancelSpy); }) .then(onFulfilled) .catch(onRejected) @@ -59,7 +57,6 @@ describe('createCancelablePromise', () => { expect(onRejected).not.toHaveBeenCalled(); expect(onFinally).toHaveBeenCalledTimes(1); expect(onFinally).toHaveBeenCalledWith(); - expect(onCancelSpy).toHaveBeenCalledTimes(0); }); test('triggers callbacks when the cancelable promise rejects', async () => { @@ -88,13 +85,9 @@ describe('createCancelablePromise', () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); - const onCancelSpy = jest.fn(); - const cancelablePromise = createCancelablePromise( - (resolve, _, onCancel) => { - resolve('ok'); - onCancel(onCancelSpy); - } - ); + const cancelablePromise = createCancelablePromise((resolve) => { + resolve('ok'); + }); cancelablePromise.then(onFulfilled).catch(onRejected).finally(onFinally); @@ -109,21 +102,15 @@ describe('createCancelablePromise', () => { expect(onFulfilled).not.toHaveBeenCalled(); expect(onRejected).not.toHaveBeenCalled(); expect(onFinally).not.toHaveBeenCalled(); - expect(onCancelSpy).toHaveBeenCalledTimes(1); - expect(onCancelSpy).toHaveBeenCalledWith(); }); test('triggers `finally` handler callback with `runWhenCanceled=true` when the cancelable promise is canceled and it resolves', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); - const onCancelSpy = jest.fn(); - const cancelablePromise = createCancelablePromise( - (resolve, _, onCancel) => { - resolve('ok'); - onCancel(onCancelSpy); - } - ); + const cancelablePromise = createCancelablePromise((resolve) => { + resolve('ok'); + }); cancelablePromise .then(onFulfilled) @@ -142,21 +129,15 @@ describe('createCancelablePromise', () => { expect(onRejected).not.toHaveBeenCalled(); expect(onFinally).toHaveBeenCalledTimes(1); expect(onFinally).toHaveBeenCalledWith(); - expect(onCancelSpy).toHaveBeenCalledTimes(1); - expect(onCancelSpy).toHaveBeenCalledWith(); }); test('triggers `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it resolves', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); - const onCancelSpy = jest.fn(); - const cancelablePromise = createCancelablePromise( - (resolve, _, onCancel) => { - resolve('ok'); - onCancel(onCancelSpy); - } - ); + const cancelablePromise = createCancelablePromise((resolve) => { + resolve('ok'); + }); cancelablePromise .then(onFulfilled) @@ -174,18 +155,14 @@ describe('createCancelablePromise', () => { expect(onFulfilled).not.toHaveBeenCalled(); expect(onRejected).not.toHaveBeenCalled(); expect(onFinally).not.toHaveBeenCalled(); - expect(onCancelSpy).toHaveBeenCalledTimes(1); - expect(onCancelSpy).toHaveBeenCalledWith(); }); test('does not trigger callbacks when the cancelable promise is canceled and it rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); - const onCancelSpy = jest.fn(); - const cancelablePromise = createCancelablePromise((_, reject, onCancel) => { + const cancelablePromise = createCancelablePromise((_, reject) => { reject(new Error()); - onCancel(onCancelSpy); }); cancelablePromise.then(onFulfilled).catch(onRejected).finally(onFinally); @@ -201,18 +178,14 @@ describe('createCancelablePromise', () => { expect(onFulfilled).not.toHaveBeenCalled(); expect(onRejected).not.toHaveBeenCalled(); expect(onFinally).not.toHaveBeenCalled(); - expect(onCancelSpy).toHaveBeenCalledTimes(1); - expect(onCancelSpy).toHaveBeenCalledWith(); }); test('triggers `finally` handler callback with `runWhenCanceled=true` when the cancelable promise is canceled and it rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); - const onCancelSpy = jest.fn(); - const cancelablePromise = createCancelablePromise((_, reject, onCancel) => { + const cancelablePromise = createCancelablePromise((_, reject) => { reject(new Error()); - onCancel(onCancelSpy); }); cancelablePromise @@ -232,18 +205,14 @@ describe('createCancelablePromise', () => { expect(onRejected).not.toHaveBeenCalled(); expect(onFinally).toHaveBeenCalledTimes(1); expect(onFinally).toHaveBeenCalledWith(); - expect(onCancelSpy).toHaveBeenCalledTimes(1); - expect(onCancelSpy).toHaveBeenCalledWith(); }); test('triggers `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); - const onCancelSpy = jest.fn(); - const cancelablePromise = createCancelablePromise((_, reject, onCancel) => { + const cancelablePromise = createCancelablePromise((_, reject) => { reject(new Error()); - onCancel(onCancelSpy); }); cancelablePromise @@ -262,20 +231,13 @@ describe('createCancelablePromise', () => { expect(onFulfilled).not.toHaveBeenCalled(); expect(onRejected).not.toHaveBeenCalled(); expect(onFinally).not.toHaveBeenCalled(); - expect(onCancelSpy).toHaveBeenCalledTimes(1); - expect(onCancelSpy).toHaveBeenCalledWith(); }); test('cancels nested cancelable promises', async () => { const onFulfilled = jest.fn(); - const onCancelSpy = jest.fn(); - - const cancelablePromise = createCancelablePromise( - (resolve, _, onCancel) => { - resolve('ok'); - onCancel(onCancelSpy); - } - ).then(() => + const cancelablePromise = createCancelablePromise((resolve) => { + resolve('ok'); + }).then(() => createCancelablePromise((resolve) => { resolve('ok'); onFulfilled(); @@ -291,8 +253,6 @@ describe('createCancelablePromise', () => { await runAllMicroTasks(); expect(onFulfilled).not.toHaveBeenCalled(); - expect(onCancelSpy).toHaveBeenCalledTimes(1); - expect(onCancelSpy).toHaveBeenCalledWith(); }); }); diff --git a/packages/autocomplete-core/src/utils/createCancelablePromise.ts b/packages/autocomplete-core/src/utils/createCancelablePromise.ts index 297eebd8f..dd1fd84c0 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromise.ts @@ -7,8 +7,7 @@ type InternalState = { type PromiseExecutor = ( resolve: (value: TValue | PromiseLike) => void, - reject: (reason?: any) => void, - onCancel: (handler: (...args: any[]) => any) => void + reject: (reason?: any) => void ) => void; type CreateInternalCancelablePromiseParams = { @@ -21,9 +20,7 @@ function createInternalCancelablePromise({ executor = noop, initialState = createInitialState(), promise = new Promise((resolve, reject) => { - return executor(resolve, reject, (onCancel) => { - initialState.onCancelList.push(onCancel); - }); + return executor(resolve, reject); }), }: CreateInternalCancelablePromiseParams): CancelablePromise { const state = initialState; @@ -59,6 +56,7 @@ function createInternalCancelablePromise({ (callback) => callback !== onfinally ); } + return onfinally(); }), state, From 1eb49807152fd3a4dbb7cf15762978c88d9eb097 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Fri, 21 Jan 2022 19:00:33 +0100 Subject: [PATCH 15/41] refactor: rename type --- .../src/utils/createCancelablePromise.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/autocomplete-core/src/utils/createCancelablePromise.ts b/packages/autocomplete-core/src/utils/createCancelablePromise.ts index dd1fd84c0..289cd9136 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromise.ts @@ -1,6 +1,6 @@ import { noop } from '@algolia/autocomplete-shared'; -type InternalState = { +type CancelablePromiseInternalState = { isCanceled: boolean; onCancelList: Array<((...args: any[]) => any) | null | undefined>; }; @@ -13,7 +13,7 @@ type PromiseExecutor = ( type CreateInternalCancelablePromiseParams = { executor?: PromiseExecutor; promise?: Promise; - initialState?: InternalState; + initialState?: CancelablePromiseInternalState; }; function createInternalCancelablePromise({ @@ -135,7 +135,7 @@ createCancelablePromise.reject = (reason?: any) => function createCancelable( promise: Promise, - initialState: InternalState + initialState: CancelablePromiseInternalState ): CancelablePromise { return createInternalCancelablePromise({ promise, initialState }); } @@ -154,7 +154,7 @@ export function isCancelablePromise( function createCallback( onResult: ((...args: any[]) => any) | null | undefined, - state: InternalState, + state: CancelablePromiseInternalState, fallback: any ) { if (onResult) { @@ -176,6 +176,6 @@ function createCallback( return fallback; } -function createInitialState(): InternalState { +function createInitialState(): CancelablePromiseInternalState { return { isCanceled: false, onCancelList: [] }; } From 4bf6c0e55f733ce8f131f743f952d3369706ae3c Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Fri, 21 Jan 2022 19:19:06 +0100 Subject: [PATCH 16/41] fix: simplify code by narrowing type --- .../src/utils/createCancelablePromise.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/autocomplete-core/src/utils/createCancelablePromise.ts b/packages/autocomplete-core/src/utils/createCancelablePromise.ts index 289cd9136..613ae02f3 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromise.ts @@ -2,7 +2,7 @@ import { noop } from '@algolia/autocomplete-shared'; type CancelablePromiseInternalState = { isCanceled: boolean; - onCancelList: Array<((...args: any[]) => any) | null | undefined>; + onCancelList: Array<(...args: any[]) => any>; }; type PromiseExecutor = ( @@ -42,7 +42,7 @@ function createInternalCancelablePromise({ ); }, finally(onfinally, runWhenCanceled) { - if (runWhenCanceled) { + if (runWhenCanceled && onfinally) { state.onCancelList.push(onfinally); } @@ -72,9 +72,7 @@ function createInternalCancelablePromise({ state.onCancelList = []; for (const callback of callbacks) { - if (typeof callback === 'function') { - callback(); - } + callback(); } }, isCanceled() { From a60e0d8bad0033c80984390327a287cd7b52c9a2 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Fri, 21 Jan 2022 22:52:55 +0100 Subject: [PATCH 17/41] refactor: further simplify --- .../__tests__/createCancelablePromise.test.ts | 102 +++++++++++++----- .../src/utils/createCancelablePromise.ts | 60 ++++------- 2 files changed, 98 insertions(+), 64 deletions(-) diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts index 437460922..0f8159c6d 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts @@ -1,6 +1,6 @@ import { noop } from '@algolia/autocomplete-shared'; -import { cancelable, createCancelablePromise, isCancelablePromise } from '..'; +import { cancelable, createCancelablePromise } from '..'; import { runAllMicroTasks } from '../../../../../test/utils'; describe('createCancelablePromise', () => { @@ -131,7 +131,7 @@ describe('createCancelablePromise', () => { expect(onFinally).toHaveBeenCalledWith(); }); - test('triggers `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it resolves', async () => { + test('does not trigger `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it resolves', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -157,6 +157,23 @@ describe('createCancelablePromise', () => { expect(onFinally).not.toHaveBeenCalled(); }); + test('only triggers `finally` handler callback once when the cancelable promise is canceled and it resolves', async () => { + const onFulfilled = jest.fn(); + const cancelablePromise = createCancelablePromise((resolve) => { + resolve('ok'); + }).finally(onFulfilled, true); + + await cancelablePromise; + + expect(onFulfilled).toHaveBeenCalledTimes(1); + + cancelablePromise.cancel(); + + await runAllMicroTasks(); + + expect(onFulfilled).toHaveBeenCalledTimes(1); + }); + test('does not trigger callbacks when the cancelable promise is canceled and it rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); @@ -207,7 +224,7 @@ describe('createCancelablePromise', () => { expect(onFinally).toHaveBeenCalledWith(); }); - test('triggers `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it rejects', async () => { + test('does not trigger `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -233,6 +250,23 @@ describe('createCancelablePromise', () => { expect(onFinally).not.toHaveBeenCalled(); }); + test('only triggers `finally` handler callback once when the cancelable promise is canceled and it rejects', async () => { + const onFulfilled = jest.fn(); + const cancelablePromise = createCancelablePromise((_, reject) => { + reject(new Error()); + }).finally(onFulfilled, true); + + await cancelablePromise.catch(noop); + + expect(onFulfilled).toHaveBeenCalledTimes(1); + + cancelablePromise.cancel(); + + await runAllMicroTasks(); + + expect(onFulfilled).toHaveBeenCalledTimes(1); + }); + test('cancels nested cancelable promises', async () => { const onFulfilled = jest.fn(); const cancelablePromise = createCancelablePromise((resolve) => { @@ -353,7 +387,7 @@ describe('cancelable', () => { expect(onFinally).toHaveBeenCalledWith(); }); - test('triggers `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it resolves', async () => { + test('does not trigger `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it resolves', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -381,6 +415,25 @@ describe('cancelable', () => { expect(onFinally).not.toHaveBeenCalled(); }); + test('only triggers `finally` handler callback once when the cancelable promise is canceled and it resolves', async () => { + const onFulfilled = jest.fn(); + const cancelablePromise = cancelable( + new Promise((resolve) => { + resolve('ok'); + }) + ).finally(onFulfilled, true); + + await cancelablePromise; + + expect(onFulfilled).toHaveBeenCalledTimes(1); + + cancelablePromise.cancel(); + + await runAllMicroTasks(); + + expect(onFulfilled).toHaveBeenCalledTimes(1); + }); + test('does not trigger callbacks when the cancelable promise is canceled and it rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); @@ -435,7 +488,7 @@ describe('cancelable', () => { expect(onFinally).toHaveBeenCalledWith(); }); - test('triggers `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it rejects', async () => { + test('does not trigger `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -463,6 +516,25 @@ describe('cancelable', () => { expect(onFinally).not.toHaveBeenCalled(); }); + test('only triggers `finally` handler callback once when the cancelable promise is canceled and it rejects', async () => { + const onFulfilled = jest.fn(); + const cancelablePromise = cancelable( + new Promise((_, reject) => { + reject(new Error()); + }) + ).finally(onFulfilled, true); + + await cancelablePromise.catch(noop); + + expect(onFulfilled).toHaveBeenCalledTimes(1); + + cancelablePromise.cancel(); + + await runAllMicroTasks(); + + expect(onFulfilled).toHaveBeenCalledTimes(1); + }); + test('cancels nested cancelable promises', async () => { const onFulfilled = jest.fn(); @@ -490,23 +562,3 @@ describe('cancelable', () => { expect(onFulfilled).not.toHaveBeenCalled(); }); }); - -describe('isCancelablePromise', () => { - test('should be cancelable', () => { - const p1 = cancelable(new Promise(noop)); - const p2 = createCancelablePromise(noop); - - expect(isCancelablePromise(p1)).toBe(true); - expect(isCancelablePromise(p2)).toBe(true); - expect(isCancelablePromise(p1.then(noop))).toBe(true); - expect(isCancelablePromise(p2.then(noop))).toBe(true); - expect(isCancelablePromise(p1.catch(noop))).toBe(true); - expect(isCancelablePromise(p2.catch(noop))).toBe(true); - }); - - test('should not be cancelable', () => { - expect(isCancelablePromise(new Promise(noop))).toBe(false); - expect(isCancelablePromise(undefined)).toBe(false); - expect(isCancelablePromise(null)).toBe(false); - }); -}); diff --git a/packages/autocomplete-core/src/utils/createCancelablePromise.ts b/packages/autocomplete-core/src/utils/createCancelablePromise.ts index 613ae02f3..e271994fc 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromise.ts @@ -1,6 +1,6 @@ import { noop } from '@algolia/autocomplete-shared'; -type CancelablePromiseInternalState = { +type CancelablePromiseState = { isCanceled: boolean; onCancelList: Array<(...args: any[]) => any>; }; @@ -13,7 +13,7 @@ type PromiseExecutor = ( type CreateInternalCancelablePromiseParams = { executor?: PromiseExecutor; promise?: Promise; - initialState?: CancelablePromiseInternalState; + initialState?: CancelablePromiseState; }; function createInternalCancelablePromise({ @@ -52,9 +52,7 @@ function createInternalCancelablePromise({ onfinally && (() => { if (runWhenCanceled) { - state.onCancelList = state.onCancelList.filter( - (callback) => callback !== onfinally - ); + state.onCancelList = []; } return onfinally(); @@ -68,12 +66,10 @@ function createInternalCancelablePromise({ }, cancel() { state.isCanceled = true; - const callbacks = state.onCancelList; - state.onCancelList = []; - for (const callback of callbacks) { + state.onCancelList.forEach((callback) => { callback(); - } + }); }, isCanceled() { return state.isCanceled === true; @@ -131,49 +127,35 @@ createCancelablePromise.resolve = ( createCancelablePromise.reject = (reason?: any) => cancelable(Promise.reject(reason)); +export function cancelable(promise: Promise) { + return createCancelable(promise); +} + function createCancelable( promise: Promise, - initialState: CancelablePromiseInternalState -): CancelablePromise { + initialState: CancelablePromiseState = createInitialState() +) { return createInternalCancelablePromise({ promise, initialState }); } -export function cancelable( - promise: Promise -): CancelablePromise { - return createCancelable(promise, createInitialState()); -} - -export function isCancelablePromise( - promise: Promise | CancelablePromise -): boolean { - return promise?.hasOwnProperty('cancel') || false; -} - function createCallback( onResult: ((...args: any[]) => any) | null | undefined, - state: CancelablePromiseInternalState, + state: CancelablePromiseState, fallback: any ) { - if (onResult) { - return (arg?: any) => { - if (!state.isCanceled) { - const result = onResult(arg); - - if (isCancelablePromise(result)) { - state.onCancelList.push(result.cancel); - } - - return result; - } + if (!onResult) { + return fallback; + } + return function callback(arg?: any) { + if (state.isCanceled) { return arg; - }; - } + } - return fallback; + return onResult(arg); + }; } -function createInitialState(): CancelablePromiseInternalState { +function createInitialState(): CancelablePromiseState { return { isCanceled: false, onCancelList: [] }; } From 6e633fd8d8c28b40fca48bc39ecb66155f7cb7be Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Fri, 21 Jan 2022 23:37:12 +0100 Subject: [PATCH 18/41] build: adjust bundlesize --- bundlesize.config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundlesize.config.json b/bundlesize.config.json index 8d3545209..24dc47d47 100644 --- a/bundlesize.config.json +++ b/bundlesize.config.json @@ -2,11 +2,11 @@ "files": [ { "path": "packages/autocomplete-core/dist/umd/index.production.js", - "maxSize": "6.5 kB" + "maxSize": "6.25 kB" }, { "path": "packages/autocomplete-js/dist/umd/index.production.js", - "maxSize": "17 kB" + "maxSize": "16.75 kB" }, { "path": "packages/autocomplete-preset-algolia/dist/umd/index.production.js", From 00b9cd0ea764c52831636be359c7c45b0b2150f5 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Fri, 21 Jan 2022 23:57:45 +0100 Subject: [PATCH 19/41] build: drop yarn.lock changes --- yarn.lock | 795 +++++++++++++++++++++++++++--------------------------- 1 file changed, 390 insertions(+), 405 deletions(-) diff --git a/yarn.lock b/yarn.lock index f186ce34f..16f839bf6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3075,15 +3075,15 @@ "@parcel/utils" "2.0.0-beta.2" astring "^1.6.2" -"@parcel/babel-ast-utils@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/babel-ast-utils/-/babel-ast-utils-2.2.0.tgz#8e1ab1f0118c1477ea50dd404d23d861502f7547" - integrity sha512-rIvqRJZ3ocPk9lZMQMlTgau3CO2bCI4fJb7lwiXVwK7E5XkJWcxyB3hplNXkSBCMDd69sQ9PNdZFW6wwzONuNQ== +"@parcel/babel-ast-utils@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/babel-ast-utils/-/babel-ast-utils-2.0.1.tgz#e131e74136af878e0b2355d63acf19abbe1920db" + integrity sha512-adBHMaPAj+w1NjO+oq6SUgtOpO7wmyNIgsiHDsf8cpLf2gT0GcC/afcaC07WhIq1PJvL2hkWQpT/8sj1m/QZSw== dependencies: "@babel/parser" "^7.0.0" - "@parcel/babylon-walk" "^2.2.0" + "@parcel/babylon-walk" "^2.0.1" "@parcel/source-map" "^2.0.0" - "@parcel/utils" "^2.2.0" + "@parcel/utils" "^2.0.1" astring "^1.6.2" "@parcel/babel-preset-env@2.0.0-beta.2": @@ -3102,10 +3102,10 @@ "@babel/types" "^7.12.13" lodash.clone "^4.5.0" -"@parcel/babylon-walk@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/babylon-walk/-/babylon-walk-2.2.0.tgz#822ba7c74272d605a1edef24e7f57492373a2674" - integrity sha512-7H32Ln6hUAMaW46ba1S44l5EZ8l+boqQrV3iOVZEyPUUToOmFUD/TER+51M2CioXShvxdSZLVITKvbZLWRbTig== +"@parcel/babylon-walk@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/babylon-walk/-/babylon-walk-2.0.1.tgz#eaedb97e57db3d40d20f6140bc3303d53e103144" + integrity sha512-eXlfG7ZGUuRF81mStZGeaYj4uH7Mgd8yfWB+c/Y13sxdacml+0vinCyZ9BjY7rYuxvKTlVSjp9BJW0Q0DS6THg== dependencies: "@babel/types" "^7.12.13" lodash.clone "^4.5.0" @@ -3120,15 +3120,15 @@ "@parcel/utils" "2.0.0-beta.2" nullthrows "^1.1.1" -"@parcel/bundler-default@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/bundler-default/-/bundler-default-2.2.0.tgz#85d5e8ae79641a9d9043ffdc2948ea4919dcf8b7" - integrity sha512-h661kIWbjcym8fh/cpTOosROCAMF/NgGdtQlQggxg9JKzH1Gj2ukxPk2uaJAML7k1MvKOF9B8PN1BRIGY3mxXA== +"@parcel/bundler-default@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/bundler-default/-/bundler-default-2.0.1.tgz#dcffbb92c67716f9c561988b384c9c9a13074fc5" + integrity sha512-4BE86Z26gr7VHeIOCWkaucl5SNntCGS9ltk1ed65mqbZaZloZP8YD/YINxxgPtx9moTWNqQO8Y3bvCAD+VY8mQ== dependencies: - "@parcel/diagnostic" "^2.2.0" - "@parcel/hash" "^2.2.0" - "@parcel/plugin" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/diagnostic" "^2.0.1" + "@parcel/hash" "^2.0.1" + "@parcel/plugin" "^2.0.1" + "@parcel/utils" "^2.0.1" nullthrows "^1.1.1" "@parcel/cache@2.0.0-beta.2": @@ -3139,15 +3139,14 @@ "@parcel/logger" "2.0.0-beta.2" "@parcel/utils" "2.0.0-beta.2" -"@parcel/cache@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/cache/-/cache-2.2.0.tgz#a3a12b6630e609b22b42b19a0a232656e50f1559" - integrity sha512-vzIuiui+VCIdvttIKIYen+IVNn6JbPv3I6MbJoNGM+UycVeK9N0yNAhSUbtZzGiBeyt1fLhq+46DSs11gV5IfA== +"@parcel/cache@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/cache/-/cache-2.0.1.tgz#8619d26028124377cbcec59921ba62f57c43c4f5" + integrity sha512-aXWkx6ySwHBdPWvCJ1x6aHGFWlfu9X89iKuN4X/quMHyUDqA2PkKBR0kAvcs47ZnmUAXlKI2J9BR+lEOSAJazA== dependencies: - "@parcel/fs" "^2.2.0" - "@parcel/logger" "^2.2.0" - "@parcel/utils" "^2.2.0" - lmdb "^2.0.2" + "@parcel/logger" "^2.0.1" + "@parcel/utils" "^2.0.1" + lmdb-store "^1.5.5" "@parcel/codeframe@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3159,22 +3158,22 @@ slice-ansi "^4.0.0" string-width "^4.2.0" -"@parcel/codeframe@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/codeframe/-/codeframe-2.2.0.tgz#76c6f7c617e7cef692d72950129e0d59a66d9ab6" - integrity sha512-mszI8sRyDvzqcixb82dCXZKGrt/uQu2VVDsjGp9Tra1ZDJcHqWAw4ikcXVrzPnYqV6QxbD/MPt3q8ktgLKZv+A== +"@parcel/codeframe@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/codeframe/-/codeframe-2.0.1.tgz#b221a9082db059e1b4ae1119133fe4bfe4d923e6" + integrity sha512-NfquLg7qt8TfPmmfXVPlcq5mtEM3CvYjc+s5HLt1w0H461NiZOq7qhAaSS1N/3E+3d3eXOT/2AlCxoGm7KQ8hg== dependencies: chalk "^4.1.0" emphasize "^4.2.0" slice-ansi "^4.0.0" string-width "^4.2.0" -"@parcel/compressor-raw@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/compressor-raw/-/compressor-raw-2.2.0.tgz#6b93a4697b62eebe1ab7d0622a5651f220e1391b" - integrity sha512-w/SpIKuhlABjWQtWi6NyyHmgDMvEHsjvvGa9OhEycODz67KKEohkQ/YT5TmjBVBuljfY5XAocVKIKNuONV0DYA== +"@parcel/compressor-raw@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/compressor-raw/-/compressor-raw-2.0.1.tgz#faa2586b0b569f9cf34c3c40206b7dd35fb8255c" + integrity sha512-0VNadPUIIpgx2MCjt7PGOwcz0OXN0BFxCmWzy+ocyEWj0KQ79OBr8ni7I3Be78OxNhE8luTEC22kVJwM0rtP1g== dependencies: - "@parcel/plugin" "^2.2.0" + "@parcel/plugin" "^2.0.1" "@parcel/config-default@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3206,40 +3205,40 @@ "@parcel/transformer-react-refresh-wrap" "2.0.0-beta.2" "@parcel/config-default@^2.0.1": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/config-default/-/config-default-2.2.0.tgz#fef389dcfd49985de9901fd792a231970a240997" - integrity sha512-Xj4WaYpKxqAf5PEHMdRfOJSjFFslISQjPwRhoXTjHTCgzDHu/js+IsQ3iK1BDLsgq1g0KyLUVbP4xjzjfW/ANw== - dependencies: - "@parcel/bundler-default" "^2.2.0" - "@parcel/compressor-raw" "^2.2.0" - "@parcel/namer-default" "^2.2.0" - "@parcel/optimizer-cssnano" "^2.2.0" - "@parcel/optimizer-htmlnano" "^2.2.0" - "@parcel/optimizer-image" "^2.2.0" - "@parcel/optimizer-svgo" "^2.2.0" - "@parcel/optimizer-terser" "^2.2.0" - "@parcel/packager-css" "^2.2.0" - "@parcel/packager-html" "^2.2.0" - "@parcel/packager-js" "^2.2.0" - "@parcel/packager-raw" "^2.2.0" - "@parcel/packager-svg" "^2.2.0" - "@parcel/reporter-dev-server" "^2.2.0" - "@parcel/resolver-default" "^2.2.0" - "@parcel/runtime-browser-hmr" "^2.2.0" - "@parcel/runtime-js" "^2.2.0" - "@parcel/runtime-react-refresh" "^2.2.0" - "@parcel/runtime-service-worker" "^2.2.0" - "@parcel/transformer-babel" "^2.2.0" - "@parcel/transformer-css" "^2.2.0" - "@parcel/transformer-html" "^2.2.0" - "@parcel/transformer-image" "^2.2.0" - "@parcel/transformer-js" "^2.2.0" - "@parcel/transformer-json" "^2.2.0" - "@parcel/transformer-postcss" "^2.2.0" - "@parcel/transformer-posthtml" "^2.2.0" - "@parcel/transformer-raw" "^2.2.0" - "@parcel/transformer-react-refresh-wrap" "^2.2.0" - "@parcel/transformer-svg" "^2.2.0" + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/config-default/-/config-default-2.0.1.tgz#86f13d89baa8b062ce690415df2b018c5a87a5ea" + integrity sha512-LavQo5+81wYARmDW+GsgPIV6GPG/rskR73oGHWV1oDr9k3UD2RYdGaH1GDcwqXyUEWVCw3K+nglaZdWFpOEdRQ== + dependencies: + "@parcel/bundler-default" "^2.0.1" + "@parcel/compressor-raw" "^2.0.1" + "@parcel/namer-default" "^2.0.1" + "@parcel/optimizer-cssnano" "^2.0.1" + "@parcel/optimizer-htmlnano" "^2.0.1" + "@parcel/optimizer-image" "^2.0.1" + "@parcel/optimizer-svgo" "^2.0.1" + "@parcel/optimizer-terser" "^2.0.1" + "@parcel/packager-css" "^2.0.1" + "@parcel/packager-html" "^2.0.1" + "@parcel/packager-js" "^2.0.1" + "@parcel/packager-raw" "^2.0.1" + "@parcel/packager-svg" "^2.0.1" + "@parcel/reporter-dev-server" "^2.0.1" + "@parcel/resolver-default" "^2.0.1" + "@parcel/runtime-browser-hmr" "^2.0.1" + "@parcel/runtime-js" "^2.0.1" + "@parcel/runtime-react-refresh" "^2.0.1" + "@parcel/runtime-service-worker" "^2.0.1" + "@parcel/transformer-babel" "^2.0.1" + "@parcel/transformer-css" "^2.0.1" + "@parcel/transformer-html" "^2.0.1" + "@parcel/transformer-image" "^2.0.1" + "@parcel/transformer-js" "^2.0.1" + "@parcel/transformer-json" "^2.0.1" + "@parcel/transformer-postcss" "^2.0.1" + "@parcel/transformer-posthtml" "^2.0.1" + "@parcel/transformer-raw" "^2.0.1" + "@parcel/transformer-react-refresh-wrap" "^2.0.1" + "@parcel/transformer-svg" "^2.0.1" "@parcel/core@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3271,23 +3270,23 @@ semver "^5.4.1" "@parcel/core@^2.0.1": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/core/-/core-2.2.0.tgz#0586b0bbc9dcda48720a62cad799a3b3464b24e7" - integrity sha512-5VvSEIHqfvTSk7aX1dcvyhZTkhSiJcma8SDpQ5wTCDhK0SScInl+y7yH2v8soUaRMDE3LD4uzYLaSoJeJH/BpQ== - dependencies: - "@parcel/cache" "^2.2.0" - "@parcel/diagnostic" "^2.2.0" - "@parcel/events" "^2.2.0" - "@parcel/fs" "^2.2.0" - "@parcel/graph" "^2.2.0" - "@parcel/hash" "^2.2.0" - "@parcel/logger" "^2.2.0" - "@parcel/package-manager" "^2.2.0" - "@parcel/plugin" "^2.2.0" + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/core/-/core-2.0.1.tgz#804a68fc1768dc9b02ea309e919ca6bb3c6bf175" + integrity sha512-Iy5FgUAquc5HjQGiyKbWK0WaaVXerrzWD7cNBTIUOlk1xNeUtOeGu80Kc5xu0qT0/Mc+nsDfPhWcN8p4RVF+PQ== + dependencies: + "@parcel/cache" "^2.0.1" + "@parcel/diagnostic" "^2.0.1" + "@parcel/events" "^2.0.1" + "@parcel/fs" "^2.0.1" + "@parcel/graph" "^2.0.1" + "@parcel/hash" "^2.0.1" + "@parcel/logger" "^2.0.1" + "@parcel/package-manager" "^2.0.1" + "@parcel/plugin" "^2.0.1" "@parcel/source-map" "^2.0.0" - "@parcel/types" "^2.2.0" - "@parcel/utils" "^2.2.0" - "@parcel/workers" "^2.2.0" + "@parcel/types" "^2.0.1" + "@parcel/utils" "^2.0.1" + "@parcel/workers" "^2.0.1" abortcontroller-polyfill "^1.1.9" base-x "^3.0.8" browserslist "^4.6.6" @@ -3297,9 +3296,8 @@ json-source-map "^0.6.1" json5 "^1.0.1" micromatch "^4.0.2" - msgpackr "^1.5.1" nullthrows "^1.1.1" - semver "^5.7.1" + semver "^5.4.1" "@parcel/diagnostic@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3309,10 +3307,10 @@ json-source-map "^0.6.1" nullthrows "^1.1.1" -"@parcel/diagnostic@^2.0.1", "@parcel/diagnostic@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/diagnostic/-/diagnostic-2.2.0.tgz#717eba5d34da337c5ec0aa26cd2d2868da600edb" - integrity sha512-NJcuf7e3mbgcsaqaRq7XFLeYjAm4PJ+ZBNQGZhnnMgPDT4PjOAPUqBTBIjWyjbqRyxEosgSMbMxsZs6+U4Tt/g== +"@parcel/diagnostic@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/diagnostic/-/diagnostic-2.0.1.tgz#cc96d94eab0db963f0c4586a2f5bc822eea46e36" + integrity sha512-pC9GmEUUB2UQ9epvE/H2wn0rb6hyF68QlpxppHZ9fxib/RxqGWDG1I3axR0cxZifRRZiMNnbk7HfmUB19KNTtA== dependencies: json-source-map "^0.6.1" nullthrows "^1.1.1" @@ -3322,10 +3320,10 @@ resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.0.0-beta.2.tgz#31e73129787422fa19b70d5b1a976169d18a05b7" integrity sha512-kbiFb/Qd8TavhmL84FTg3dN29Zi5Bi8bWqMgzA8hq7E8W5ezXpmw1Tu5wkjsNzHuOTj2YcAtxlTh3l29UEmh2g== -"@parcel/events@^2.0.1", "@parcel/events@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.2.0.tgz#a599aa4237b90672b01f6886934e88d8e1fd4b13" - integrity sha512-d5H9jPnCjVuNErNcDVFwxHK5zEUNr4wTcSH0CIeLshq/Z5mGhDlLC42QN4PuTR+9vHc56LlmEVrEKsLcTY5v8g== +"@parcel/events@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.0.1.tgz#24ffa93353ca63889faac0928b4118ff1774add5" + integrity sha512-JRt5SkFS8/8r37o1DRKVtrWR1OZNN2pL548YsXVKBLN1b2ys36/+yKNObDuGB7DcOcIRngVs7xxv6+oodGyMlQ== "@parcel/fs-search@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3334,10 +3332,10 @@ dependencies: detect-libc "^1.0.3" -"@parcel/fs-search@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/fs-search/-/fs-search-2.2.0.tgz#de2be8829bb86db650594a1e4f37167afcac7334" - integrity sha512-EsyNFKGgixxGHTNEmfY3xGMEpAAFwEquHbaYn76SlXfQ6CLHjfGLPU5DkodSbiYE2uGwGbDQdChF60kYrHhIeQ== +"@parcel/fs-search@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/fs-search/-/fs-search-2.0.1.tgz#673f3ff2725c4557672d453be3f2dcfd4909f80e" + integrity sha512-Zyo1PY4opLMunes5YZ2+Q0cMCgdAuepznVvUY+dK3WjW5OzO09G/L8cfNBhgeYA84wu0yyzNohZogvFjS10TZg== dependencies: detect-libc "^1.0.3" @@ -3351,10 +3349,10 @@ imurmurhash "^0.1.4" readable-stream "1 || 2" -"@parcel/fs-write-stream-atomic@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/fs-write-stream-atomic/-/fs-write-stream-atomic-2.2.0.tgz#1b73f52151973b064208d7db35f8ffec6ccb2a11" - integrity sha512-c3nJJrsmxaFq0qDyRybq1f6hA7pD/gBj5N3FfFUbabMhKcPf/1tA9me4je4qiErtFPU25RmcL2X36mCrF9SvIg== +"@parcel/fs-write-stream-atomic@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/fs-write-stream-atomic/-/fs-write-stream-atomic-2.0.1.tgz#700f9f2b3761494af305e71a185117e804b1ae41" + integrity sha512-+CSeXRCnI9f9K4jeBOYzZiOf+qw6t3TvhEstR/zeXenzx0nBMzPv28mjUMZ33vRMy8bQOHAim8qy/AMSIMolEg== dependencies: graceful-fs "^4.1.2" iferr "^1.0.2" @@ -3377,17 +3375,17 @@ nullthrows "^1.1.1" rimraf "^3.0.2" -"@parcel/fs@^2.0.1", "@parcel/fs@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-2.2.0.tgz#1c9b13025b24f1c117bd712eab80717b7d4ff045" - integrity sha512-dn0TZAH98OYaSQwk5JrpfNmoPXn8tH5lbHKKm8VP8a1RhrG5TdynYbeZ8uu5XQ2FK1+M66/HtAdFLBLlevlWrw== +"@parcel/fs@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-2.0.1.tgz#908fe8c953f8fb8fb9d61d97733b11f8a150fe19" + integrity sha512-zl8aV9Qp4lB4cQGyBfz3LQM+JkL7WHGoSlj8PjBamT8VmPlr57BUtp3Gc/IvRCCX8B7izNx3X8vCvr5BrziL+g== dependencies: - "@parcel/fs-search" "^2.2.0" - "@parcel/fs-write-stream-atomic" "^2.2.0" - "@parcel/types" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/fs-search" "^2.0.1" + "@parcel/fs-write-stream-atomic" "^2.0.1" + "@parcel/types" "^2.0.1" + "@parcel/utils" "^2.0.1" "@parcel/watcher" "^2.0.0" - "@parcel/workers" "^2.2.0" + "@parcel/workers" "^2.0.1" graceful-fs "^4.2.4" mkdirp "^0.5.1" ncp "^2.0.0" @@ -3395,21 +3393,20 @@ rimraf "^3.0.2" utility-types "^3.10.0" -"@parcel/graph@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/graph/-/graph-2.2.0.tgz#8a13b9e8c17c0074f9acbdffdf9f45ce37a14a47" - integrity sha512-3c1AZWO7ndpHPBxK1R+z+jS2MBBlsu2bKWVfUAPU3I9tGCGXKOhH5i/gO/jURSHx3g5s6ygZbfljAodpZ7W2DQ== +"@parcel/graph@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/graph/-/graph-2.0.1.tgz#800d9d93ea4a1403954ae913096eba4ea96d66b1" + integrity sha512-LESQVWy/Oln1CqTgWTjvm99btNSqHxOcIKEIL7k6Pq2d6vhO6oyAAmMe5sqf6Sr1nNCVjZW7oHRzyIG0kYTgWw== dependencies: - "@parcel/utils" "^2.2.0" nullthrows "^1.1.1" -"@parcel/hash@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/hash/-/hash-2.2.0.tgz#12eb373a7fe64c791822a0655bd661e632e45e06" - integrity sha512-lXN4dY3y5ZxUP52jhd+C99kSWJRjIbh0lMpMqMGhkdIRoJUpvv24kQ2aItcgqazWWR9SS1NDZ0/z7+vHPMmEFw== +"@parcel/hash@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/hash/-/hash-2.0.1.tgz#46178908dd4c8c3a3f9f46ae62ec0646100400da" + integrity sha512-Zng4i5HhcmOr6NMzQlnCf12ED9isL+HmcFC3XSLc6VYFcCnVg6cEIwJ7KrB/s5wRHLU2TfSZAaLIJlhcPKPPog== dependencies: detect-libc "^1.0.3" - xxhash-wasm "^0.4.2" + xxhash-wasm "^0.4.1" "@parcel/logger@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3419,13 +3416,13 @@ "@parcel/diagnostic" "2.0.0-beta.2" "@parcel/events" "2.0.0-beta.2" -"@parcel/logger@^2.0.1", "@parcel/logger@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-2.2.0.tgz#ceb6783cc9fa807837f8a1b6a17856746c511753" - integrity sha512-HEiPq7EVmttDVZocebNbHDDhTWDUVD8UizLkRrLCtdRQGTcK0+WFitooamhjCQKXG0T/4/dh8ojIfWTQPMCFLg== +"@parcel/logger@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-2.0.1.tgz#e379fc171d3b7860bacf363132a983665f01752d" + integrity sha512-gN2mdDnUkbN11hUIDBU+zlREsgp7zm42ZAsc0xwIdmlnsZY7wu2G3lNtkXSMlIPJPdRi6oE6vmaArQJfXjaAOg== dependencies: - "@parcel/diagnostic" "^2.2.0" - "@parcel/events" "^2.2.0" + "@parcel/diagnostic" "^2.0.1" + "@parcel/events" "^2.0.1" "@parcel/markdown-ansi@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3434,10 +3431,10 @@ dependencies: chalk "^4.1.0" -"@parcel/markdown-ansi@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/markdown-ansi/-/markdown-ansi-2.2.0.tgz#507c2cd533a09ac27bb799d2905962c67c7b2861" - integrity sha512-N46Yun+jRA97rEhOKyMC3awIULw6KRPsV2JkI9yfc5EiOOA1CYmVNIB6OmgmuuvZYwq9OoHxB+XFkXF+yxiWBA== +"@parcel/markdown-ansi@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/markdown-ansi/-/markdown-ansi-2.0.1.tgz#448eeb7a8d7cc44de4b0d7e74b4ae5b613d5ca30" + integrity sha512-KFUvJoGncCwOml+RSyJl0KfQgle42YC8VJwQrHUqKMR5acyC3KaDNWAx96xkPf3k/hKv+VVEhIsH7SRJ63qwwQ== dependencies: chalk "^4.1.0" @@ -3450,13 +3447,13 @@ "@parcel/plugin" "2.0.0-beta.2" nullthrows "^1.1.1" -"@parcel/namer-default@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/namer-default/-/namer-default-2.2.0.tgz#51bee03db280b671677b2324bac118bab4c3369e" - integrity sha512-Iz5MLGTTLd5zQ+CbyV/4VjOiTFaQJaMwwu3W/GfaI2eLou2d9bazFaa786b4UM9u0LjLKYaUUzWDohllSwBwSg== +"@parcel/namer-default@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/namer-default/-/namer-default-2.0.1.tgz#94181e72fbb4dd6963d92292e2ba76caca989563" + integrity sha512-wF948WojfksHutz023T2lC3b1BWRyOa9KaCh9caYtZ1Lq26kG3X2eaWVjOzw65SUQRLzAAxu3ujRhKEg0N0Ntw== dependencies: - "@parcel/diagnostic" "^2.2.0" - "@parcel/plugin" "^2.2.0" + "@parcel/diagnostic" "^2.0.1" + "@parcel/plugin" "^2.0.1" nullthrows "^1.1.1" "@parcel/node-libs-browser@2.0.0-beta.2": @@ -3487,10 +3484,10 @@ util "^0.12.3" vm-browserify "^1.1.2" -"@parcel/node-libs-browser@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/node-libs-browser/-/node-libs-browser-2.2.0.tgz#bf5feabe76378f6504a4a72250a9438ec4892792" - integrity sha512-RtaJG+cdo60+ffq+tD9OwtTAqRG4cAyrFxWbeqYPb+RenpZJBWBJHRy1C5cjtH98sfXPzwMzROayQviFBvRq5w== +"@parcel/node-libs-browser@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/node-libs-browser/-/node-libs-browser-2.0.1.tgz#4c4c7ae43d7d347bba1f426a9cc01e74bb6bcfce" + integrity sha512-EK6hndQMtW0DJMU4FeDmbDwdIus/IAXz/YjR2kdQ0fLRAvcNWC/34R5bqlLmWdX2NXWVS+1tcDhPa2oEnUzzHA== dependencies: assert "^2.0.0" browserify-zlib "^0.2.0" @@ -3527,14 +3524,14 @@ nullthrows "^1.1.1" querystring "^0.2.0" -"@parcel/node-resolver-core@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/node-resolver-core/-/node-resolver-core-2.2.0.tgz#8aca3e8904a628e77d62bb0a8d310d0dcfc8960f" - integrity sha512-/YssRfsNLMQAYuNAGMOqThTbiWHQLJbDxsT7V/L387UkinR7TZl0ZKNjAwmb+DMD2oTKZfgLcUnLCcEyg9numg== +"@parcel/node-resolver-core@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/node-resolver-core/-/node-resolver-core-2.0.1.tgz#1469ad7fe19a48453dfa8c574356fa543cfa8d63" + integrity sha512-bZqot9TZKuBpojo9i4LQ/mc+iKKuurcWDy481E/Z9Xp3zfDEZaNzj2f+0MSwv3pbqB134/PIMMtN92tewJ7Piw== dependencies: - "@parcel/diagnostic" "^2.2.0" - "@parcel/node-libs-browser" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/diagnostic" "^2.0.1" + "@parcel/node-libs-browser" "^2.0.1" + "@parcel/utils" "^2.0.1" micromatch "^4.0.4" nullthrows "^1.1.1" @@ -3548,12 +3545,12 @@ cssnano "^4.1.10" postcss "^8.0.5" -"@parcel/optimizer-cssnano@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-cssnano/-/optimizer-cssnano-2.2.0.tgz#7df8d0f2b85ea088c4154167b71680cc0ab678c9" - integrity sha512-YpVFJO9v8TqGZVvonu5OOmUS7AOn1z9t+YaqiuD/ytJGyePVPIBoZ9H6TlL17jLv0gbAXTTv1zoLDmae4/ruZQ== +"@parcel/optimizer-cssnano@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-cssnano/-/optimizer-cssnano-2.0.1.tgz#0b35d2228d372607c591cf73cd7129f886667de5" + integrity sha512-yhuSUyTa4IKsFX+k2K8J6fsClpIWAu0Ng6HcW/fwDSfssZMm+Lfe33+sRo1fwqr8vd/okFrm3vOBQ+NhncsVVw== dependencies: - "@parcel/plugin" "^2.2.0" + "@parcel/plugin" "^2.0.1" "@parcel/source-map" "^2.0.0" cssnano "^5.0.5" postcss "^8.3.0" @@ -3569,36 +3566,35 @@ nullthrows "^1.1.1" posthtml "^0.15.1" -"@parcel/optimizer-htmlnano@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.2.0.tgz#a12f77bbf9c4de6468d62f73cb8e1a108ede6c3c" - integrity sha512-Lrsjz5sG5uayhIGAcAv5IqOPY8FJnsy4XeT0EUc70cAI9p3NS48HZqqPfWAn9nsMqu3EBhKNyFpiaJ4LY6LNkg== +"@parcel/optimizer-htmlnano@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.0.1.tgz#6789ebc8140f04a31929a5f7e106cd594afd5771" + integrity sha512-Q2YQt4YnRNGth6RtRw6Q/IanhboKhD2QfrDpUsDwcpBbP3nEirvLcOmVfzuNXDqvYaQG7720ulCRt8jWErZ2WQ== dependencies: - "@parcel/plugin" "^2.2.0" + "@parcel/plugin" "^2.0.1" htmlnano "^1.0.1" nullthrows "^1.1.1" posthtml "^0.16.5" svgo "^2.4.0" -"@parcel/optimizer-image@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-image/-/optimizer-image-2.2.0.tgz#d8ebc71bff53cc5ccde5e5cc0e2d2523f5ff0b59" - integrity sha512-bvrR7wX4GbcqR38MzSVImedyL3huFeMxyjdpElq6J4RKIGY3KK/+k4VhVzU0wGqIx+XTuJxNJWJGkJ8i+mESEw== +"@parcel/optimizer-image@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-image/-/optimizer-image-2.0.1.tgz#39e6551723573bf04f74dcaa1c3a6092e37049f1" + integrity sha512-tXqrAoFoGT6R2nY88OMj6DxHctyewOA3RW6VFksolX+/eWjy9MsQMUWFJmc1TlsVJCu4xGVvcHM3+6Q3XF8VSA== dependencies: - "@parcel/diagnostic" "^2.2.0" - "@parcel/plugin" "^2.2.0" - "@parcel/utils" "^2.2.0" - "@parcel/workers" "^2.2.0" + "@parcel/diagnostic" "^2.0.1" + "@parcel/plugin" "^2.0.1" + "@parcel/utils" "^2.0.1" detect-libc "^1.0.3" -"@parcel/optimizer-svgo@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-svgo/-/optimizer-svgo-2.2.0.tgz#fa84d90c51571266c96c815494a5321f03df3208" - integrity sha512-gBMQPn4EGOGsEQI0MjK1PoD7w2QrMvoyk7QvSbES1F91lhYh4e3zc1op3G/hdT6IiudlrbNgsSvMZCuxyFp6Uw== +"@parcel/optimizer-svgo@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-svgo/-/optimizer-svgo-2.0.1.tgz#66372245677d6e6086cc9ae71bf657c282677b92" + integrity sha512-vdTXQrYjNd7s9ye8NYi7IrcS/oa1Rn1cI9pFeQCocEuL3eoesnFBtkeW0bbA7tNaIBkkR0x9NagRVtWgZJW4uQ== dependencies: - "@parcel/diagnostic" "^2.2.0" - "@parcel/plugin" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/diagnostic" "^2.0.1" + "@parcel/plugin" "^2.0.1" + "@parcel/utils" "^2.0.1" svgo "^2.4.0" "@parcel/optimizer-terser@2.0.0-beta.2": @@ -3613,15 +3609,15 @@ nullthrows "^1.1.1" terser "^5.2.0" -"@parcel/optimizer-terser@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-terser/-/optimizer-terser-2.2.0.tgz#db672ca93c67326de64aa58aacde2c012ae71ba5" - integrity sha512-R0QC9JAFJpoPS4mUltVLHbiLOeyv5G5kmM2MhJN3VYJyJNForMHvFwFBRQLxp8GbJ9pU3P36dN8jRfrlPQffog== +"@parcel/optimizer-terser@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-terser/-/optimizer-terser-2.0.1.tgz#c8375fad84668f4c24d3aef9e2452566e8f5ebab" + integrity sha512-iT3gvkZsUKW4PJHRwWn4xqQlIIsrkr4gO2X5XQtPEXkYUn3UlHTE1lguJd1Pj6L3A0dS+ubI6wIfYk/Z59WAjw== dependencies: - "@parcel/diagnostic" "^2.2.0" - "@parcel/plugin" "^2.2.0" + "@parcel/diagnostic" "^2.0.1" + "@parcel/plugin" "^2.0.1" "@parcel/source-map" "^2.0.0" - "@parcel/utils" "^2.2.0" + "@parcel/utils" "^2.0.1" nullthrows "^1.1.1" terser "^5.2.0" @@ -3641,21 +3637,21 @@ semver "^5.4.1" split2 "^3.1.1" -"@parcel/package-manager@^2.0.1", "@parcel/package-manager@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/package-manager/-/package-manager-2.2.0.tgz#8c37dd1cb5e508b33f2600a5228873d5837b2aa6" - integrity sha512-FTh8/E6AMvRJTbNav7MD8ZULvTuZhUKBpfQu07oL1khE4O6KYyQ8NBGk3W221dfY+vXoM2+b+4BrKBgw8Sn3EA== - dependencies: - "@parcel/diagnostic" "^2.2.0" - "@parcel/fs" "^2.2.0" - "@parcel/logger" "^2.2.0" - "@parcel/types" "^2.2.0" - "@parcel/utils" "^2.2.0" - "@parcel/workers" "^2.2.0" +"@parcel/package-manager@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/package-manager/-/package-manager-2.0.1.tgz#98e83b0893d59a2212d859ed95cb93a7710ff92a" + integrity sha512-I8pMP18zIAYIfwnFOhi4Pt+6grKysMxFqNTXAdfobszk4PvoOzbUIjzTk+3Z2IXT2FEdH/R/3Jej70OxpPf0CQ== + dependencies: + "@parcel/diagnostic" "^2.0.1" + "@parcel/fs" "^2.0.1" + "@parcel/logger" "^2.0.1" + "@parcel/types" "^2.0.1" + "@parcel/utils" "^2.0.1" + "@parcel/workers" "^2.0.1" command-exists "^1.2.6" cross-spawn "^6.0.4" nullthrows "^1.1.1" - semver "^5.7.1" + semver "^5.4.1" split2 "^3.1.1" "@parcel/packager-css@2.0.0-beta.2": @@ -3669,14 +3665,14 @@ nullthrows "^1.1.1" postcss "^8.2.1" -"@parcel/packager-css@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/packager-css/-/packager-css-2.2.0.tgz#d2c13249f8ecaab571b4d301bb94f64ef065a6a6" - integrity sha512-FUn7HEDb3q6/T4Z0We+kpD5zKYwEkK2ClTyDAEDvnqtEz2A+DIrvxarwtW8NlA+uSKOH+kioMrV7Gdl0RpN5XA== +"@parcel/packager-css@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/packager-css/-/packager-css-2.0.1.tgz#4e891cd36200c83d2d2c09448a21646309eae1e7" + integrity sha512-oPyouH+6+by3s68xxwYaaePPtrcRhNJ1Tia51eIVagBxp3kAOpB7F4S1Ou8w2qlipk9Wq6HJx2n1u4aZISbkAg== dependencies: - "@parcel/plugin" "^2.2.0" + "@parcel/plugin" "^2.0.1" "@parcel/source-map" "^2.0.0" - "@parcel/utils" "^2.2.0" + "@parcel/utils" "^2.0.1" nullthrows "^1.1.1" postcss "^8.3.0" @@ -3691,14 +3687,14 @@ nullthrows "^1.1.1" posthtml "^0.15.1" -"@parcel/packager-html@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/packager-html/-/packager-html-2.2.0.tgz#af3e53d2270e82301c08831c1913595b0e629aef" - integrity sha512-Hbp7z/1TQb5WZcystsO6ICuUVJblkVaWMw60hVDezkLsKTB87Qc81OIsqyST3LMhqM7NipsNqpaXvmg3wF6zrA== +"@parcel/packager-html@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/packager-html/-/packager-html-2.0.1.tgz#d62ffe14d7adfad916ebac8ed9f3776457c2c1ed" + integrity sha512-uGQYjspjz/VF4v+kVWAmPfXoGKCmos8rgTZ7XtXnhuRT4SH/OYHlRKVxzC4sb4zRoeO6Bj82yVw65Xj2gz9K4Q== dependencies: - "@parcel/plugin" "^2.2.0" - "@parcel/types" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/plugin" "^2.0.1" + "@parcel/types" "^2.0.1" + "@parcel/utils" "^2.0.1" nullthrows "^1.1.1" posthtml "^0.16.5" @@ -3714,16 +3710,16 @@ "@parcel/utils" "2.0.0-beta.2" nullthrows "^1.1.1" -"@parcel/packager-js@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/packager-js/-/packager-js-2.2.0.tgz#f0347dd240d77c42bc9f007f26bb174144d9eacc" - integrity sha512-dQmJqRAjy0RxWqGDSkfpyxMcUx3btqltSdK/Z8sUPVvNfss1gHRuz3Ee5CJxNoLLN8On+qM4xAvq2W3y16psbA== +"@parcel/packager-js@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/packager-js/-/packager-js-2.0.1.tgz#f6bb5a93d15f08fbf3fdd35974b954a5e722dcc6" + integrity sha512-eN7BQITwTj2KeYMkW/9KRMBw1SoR7qlFhfX2+hbFA6Kl/b0bKEx33Gm21JJBl8wqqo3QVr9Rhg0JruwkQX1JHg== dependencies: - "@parcel/diagnostic" "^2.2.0" - "@parcel/hash" "^2.2.0" - "@parcel/plugin" "^2.2.0" + "@parcel/diagnostic" "^2.0.1" + "@parcel/hash" "^2.0.1" + "@parcel/plugin" "^2.0.1" "@parcel/source-map" "^2.0.0" - "@parcel/utils" "^2.2.0" + "@parcel/utils" "^2.0.1" globals "^13.2.0" nullthrows "^1.1.1" @@ -3734,21 +3730,21 @@ dependencies: "@parcel/plugin" "2.0.0-beta.2" -"@parcel/packager-raw@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/packager-raw/-/packager-raw-2.2.0.tgz#83de58ed88830d49066a6141497d035a201c0f19" - integrity sha512-Iz/rltpChammeEUotDZqzZr5WZcJGr2ro68GBt/8/oMU0HDWItsWUF8EtsqSuPiQWYU+65xcXPguk2cQ1qXObg== +"@parcel/packager-raw@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/packager-raw/-/packager-raw-2.0.1.tgz#08516692e6050debc540fab4b36b078edb8ce492" + integrity sha512-Cr9we+Pf9jl9AhKsZPKg7Da6xzNFxUqPDBRIZmO9GjTm1NZOeddmRPrtporPPZxtTmtQzRuyStRNKe5zBZtg3w== dependencies: - "@parcel/plugin" "^2.2.0" + "@parcel/plugin" "^2.0.1" -"@parcel/packager-svg@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/packager-svg/-/packager-svg-2.2.0.tgz#7e86a83b385c2d3134444d499f344a6a13f06570" - integrity sha512-APNuRVV6fRg36rL+H4nORCOe/gxzkDjF8UUldQLbxZdsYaL+9YvJA+4vmULce96b9Fcq7ZOkqUeDvQmWw9kLNw== +"@parcel/packager-svg@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/packager-svg/-/packager-svg-2.0.1.tgz#0e9fbbd385f848bff1226eb6e29d459ee25a1fa7" + integrity sha512-UqMYNxoaxLdJN+R3rOAACeMdkT/ONcMNQ+OzEowpt6lWZJyLRRF63akk2KhMVjYNQpV6y4wJZV6H/TWV6eRMjg== dependencies: - "@parcel/plugin" "^2.2.0" - "@parcel/types" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/plugin" "^2.0.1" + "@parcel/types" "^2.0.1" + "@parcel/utils" "^2.0.1" posthtml "^0.16.4" "@parcel/plugin@2.0.0-beta.2": @@ -3758,12 +3754,12 @@ dependencies: "@parcel/types" "2.0.0-beta.2" -"@parcel/plugin@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/plugin/-/plugin-2.2.0.tgz#dfd80fd5c8e9663b2a08b1235297ab7abdadcca4" - integrity sha512-etIxpizU14aZELSV/qEeuufoC5fLgopQ9I0j/Y9ExSmlU3NH79NeQo0JPP1TU49ZOL8Si9o/D6tlhojle6GbzA== +"@parcel/plugin@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/plugin/-/plugin-2.0.1.tgz#de6dad81a98e135dad724b581c209332480c70fc" + integrity sha512-zg9LdUk1fh8UApo9q79ZbG+QCwMioSlBP0+LKYLQqcNketzmjPuhe3rCialR0s2/6QsM1EQbuMUpCmZLSQZ4tA== dependencies: - "@parcel/types" "^2.2.0" + "@parcel/types" "^2.0.1" "@parcel/reporter-cli@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3782,13 +3778,13 @@ term-size "^2.2.1" "@parcel/reporter-cli@^2.0.1": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.2.0.tgz#2f220cc2d5bf02674dd1b1c546794b34785422e5" - integrity sha512-FVRnSu5Yba/18X3KWiJEXLfT8KugsNoQmvnfL7FDgVbdxUS/RWPeAwICfXPH9pkqZBl9c7QcMDVtnSD3xNR8FA== + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.0.1.tgz#75406cb9d32130891f8c367112d92eb49a8dd453" + integrity sha512-R4gmEhXH6vQMoSEobVolyCIJWBRV9z9Ju5y4gheUv7X0u3e2tpsHpDq835o8jqNIBG75Dm8Q5f3EE8BdhPzTEg== dependencies: - "@parcel/plugin" "^2.2.0" - "@parcel/types" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/plugin" "^2.0.1" + "@parcel/types" "^2.0.1" + "@parcel/utils" "^2.0.1" chalk "^4.1.0" filesize "^6.1.0" nullthrows "^1.1.1" @@ -3812,13 +3808,13 @@ serve-handler "^6.0.0" ws "^7.0.0" -"@parcel/reporter-dev-server@^2.0.1", "@parcel/reporter-dev-server@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/reporter-dev-server/-/reporter-dev-server-2.2.0.tgz#307d386197f45bd2c7b8e25c5c25142d89400b87" - integrity sha512-r6FRJ1BU/zHZvAxSvG5p0o2ZJgEkYDqU2y99a5tvRhQAIMupNp9Oc3PkAIOcoGtM3aiJvGO5pwDBoOpNwS4hwQ== +"@parcel/reporter-dev-server@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/reporter-dev-server/-/reporter-dev-server-2.0.1.tgz#a122645277d20da94d5fc54d92d9b5ffc8801703" + integrity sha512-dm2zgE8mPgLD5Nkmw9WQENZunrBN29fDRkNZhqnQyq4BBXF7e6Q/J/uamUjdtxAp7Qzobw1ZjybqlFuEh0z2tg== dependencies: - "@parcel/plugin" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/plugin" "^2.0.1" + "@parcel/utils" "^2.0.1" connect "^3.7.0" ejs "^3.1.6" http-proxy-middleware "^1.0.0" @@ -3834,13 +3830,13 @@ "@parcel/node-resolver-core" "2.0.0-beta.2" "@parcel/plugin" "2.0.0-beta.2" -"@parcel/resolver-default@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/resolver-default/-/resolver-default-2.2.0.tgz#8e45d483e26e6df0698f9c1d93d1d7535f38df5c" - integrity sha512-mDEmFVcNI4AAnKZ2P44AqKGVpG2vt9npmRNRyCw1QZvCnETiTVvHl0AngVtZRP6DVw0MeuPt7JaFWy3DQ+LMnQ== +"@parcel/resolver-default@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/resolver-default/-/resolver-default-2.0.1.tgz#8f6b428f0d65d2f897aad233d2f22ae8f8478e04" + integrity sha512-8+dMgb6pJGaepGAb+44ORLamFv8Ik7T1MyyexI3d9KfWXolU4lhSoFrNGeSEqm4VtPHH0xMYQo2cyIYKZSzuyA== dependencies: - "@parcel/node-resolver-core" "^2.2.0" - "@parcel/plugin" "^2.2.0" + "@parcel/node-resolver-core" "^2.0.1" + "@parcel/plugin" "^2.0.1" "@parcel/runtime-browser-hmr@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3850,13 +3846,13 @@ "@parcel/plugin" "2.0.0-beta.2" "@parcel/utils" "2.0.0-beta.2" -"@parcel/runtime-browser-hmr@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.2.0.tgz#458f77b46190de6835457bf3cf5dc1ae554a6dcb" - integrity sha512-Y3K8bpm06xZ2IgzS8l8iBhHoc04CcJzwWsRS60xGHikDxkZWbUMCvxMjgUS6NmDo62aY3dFkD6VVFKZIRnWxCQ== +"@parcel/runtime-browser-hmr@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.0.1.tgz#f825bcd143b3cf5414c99d2b6f0cffd5e7abe9d0" + integrity sha512-fHuK3tzfJdDhCuNab7aB0RGrfyPlpmV7l0YJJ6Hvv2FiJ1EP2f0mMYF3/T6BXacL4/HLVo58K/XLYhTb6jU2cA== dependencies: - "@parcel/plugin" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/plugin" "^2.0.1" + "@parcel/utils" "^2.0.1" "@parcel/runtime-js@2.0.0-beta.2": version "2.0.0-beta.2" @@ -3867,13 +3863,13 @@ "@parcel/utils" "2.0.0-beta.2" nullthrows "^1.1.1" -"@parcel/runtime-js@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/runtime-js/-/runtime-js-2.2.0.tgz#a0ba9345390398c372e3fe2fea944916ad554b62" - integrity sha512-XgI/lmX/7Lp9y9KpnxehryvbHtKs9KVLs6V6MmiMXb7s74S/A6cj4cWq+AhSfz2mZtvMJAhIw3bl5vYNx8qEog== +"@parcel/runtime-js@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/runtime-js/-/runtime-js-2.0.1.tgz#cb9c00e3b6e6d16ff38dec4378b01c2b8308ad17" + integrity sha512-5syJTEWY4uw+GH8AYwL55fqRgcBjL/tb95FSYHfABKMHSkaU6KbeUzCv88oj2wE5szWHX793LuqjppO465XYvQ== dependencies: - "@parcel/plugin" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/plugin" "^2.0.1" + "@parcel/utils" "^2.0.1" nullthrows "^1.1.1" "@parcel/runtime-react-refresh@2.0.0-beta.2": @@ -3884,22 +3880,22 @@ "@parcel/plugin" "2.0.0-beta.2" react-refresh "^0.9.0" -"@parcel/runtime-react-refresh@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.2.0.tgz#1f4a56a83aa90df1432628560309f1c93c663449" - integrity sha512-bnIW3K37cH2PoGXl5NX8401exe97VGORz5YTAm6BSHiXKWZkOHwE6dTj6/PydQRm4NLAiYlJ6hvxcu5QkF7/jw== +"@parcel/runtime-react-refresh@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.0.1.tgz#71b8aefa48aa909e8d0ab966f2fe9c9dcecfd53e" + integrity sha512-7j8cmIaoGP0etC2SLrWO1RdxQp+IealRAyZsLODRU22EQxCobGh5uq7Bjdv+m1wZrAdolR00lZe5p+dGrD2QGw== dependencies: - "@parcel/plugin" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/plugin" "^2.0.1" + "@parcel/utils" "^2.0.1" react-refresh "^0.9.0" -"@parcel/runtime-service-worker@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/runtime-service-worker/-/runtime-service-worker-2.2.0.tgz#630f97b8b136287b9825e09242bf1c02331da96d" - integrity sha512-+lwIBdTbhvhkYYNKG5ZxMaQ2wY/eaJqDjVelEpfvuoRzjR+v2YwKyAP4wV8DKzMb/lbtj6fsG+GerqaSwGVdDg== +"@parcel/runtime-service-worker@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/runtime-service-worker/-/runtime-service-worker-2.0.1.tgz#ef8c6ee8f0b039635a80b36a76ceb9c1d6d8a45d" + integrity sha512-B12lgz5LYLhhvjnTryg38R0PryAbq1+GCJE8Inidzr/IYLROUZANokPcUYUxwVB6QJVzYRhkx3lEf9VziAot2g== dependencies: - "@parcel/plugin" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/plugin" "^2.0.1" + "@parcel/utils" "^2.0.1" nullthrows "^1.1.1" "@parcel/scope-hoisting@2.0.0-beta.2": @@ -3955,21 +3951,21 @@ nullthrows "^1.1.1" semver "^5.7.0" -"@parcel/transformer-babel@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-babel/-/transformer-babel-2.2.0.tgz#0dda0da9f6f9df59d5e2dcc13085c96a84b6985f" - integrity sha512-amZIncU9Ld/SuIWajntAOXwsgYl6h+5e2qBIPYuFhZX3FrbhHJFJQ27GJxOmRu+C09JOpAQk7vlr6Oi6jLvxNw== +"@parcel/transformer-babel@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/transformer-babel/-/transformer-babel-2.0.1.tgz#f7c288e02ade16b7744dc04279b45016680b6787" + integrity sha512-TUCTdZi3V7z0WzyFPe3A1dQ0kLxPS8bEa0KgW7sueo9D0LXFvxpwh3Mf93q2H56KGb96o/QOXkz4HY8og+Wy4g== dependencies: "@babel/core" "^7.12.0" "@babel/generator" "^7.9.0" "@babel/helper-compilation-targets" "^7.8.4" "@babel/plugin-transform-flow-strip-types" "^7.0.0" "@babel/traverse" "^7.0.0" - "@parcel/babel-ast-utils" "^2.2.0" - "@parcel/diagnostic" "^2.2.0" - "@parcel/plugin" "^2.2.0" + "@parcel/babel-ast-utils" "^2.0.1" + "@parcel/diagnostic" "^2.0.1" + "@parcel/plugin" "^2.0.1" "@parcel/source-map" "^2.0.0" - "@parcel/utils" "^2.2.0" + "@parcel/utils" "^2.0.1" browserslist "^4.6.6" core-js "^3.2.1" json5 "^2.1.0" @@ -3989,21 +3985,18 @@ postcss-value-parser "^4.1.0" semver "^5.4.1" -"@parcel/transformer-css@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-css/-/transformer-css-2.2.0.tgz#b06fd42112f375541bcaaa3565a99d7b1d5aefa1" - integrity sha512-90GNysl/E8ScT6VxAqkgmsOi5HxkzX5xuj8YjjyKGls2DO3wZKjIi8q5ZGKjKLcWfKdF5gCzuvuObcg9nyMLug== +"@parcel/transformer-css@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/transformer-css/-/transformer-css-2.0.1.tgz#1eef65fb10b27288281d1bc4f9a23a9d470b546a" + integrity sha512-sSe8elt3ejTkmZmGk3ahhimGwVoxQL0hUYSjmsgK24a4kUoJWby2hvV8BEZWDZ8zJz5ZOWUw+34fM1frEn87dQ== dependencies: - "@parcel/hash" "^2.2.0" - "@parcel/plugin" "^2.2.0" + "@parcel/plugin" "^2.0.1" "@parcel/source-map" "^2.0.0" - "@parcel/utils" "^2.2.0" - css-modules-loader-core "^1.1.0" + "@parcel/utils" "^2.0.1" nullthrows "^1.1.1" postcss "^8.3.0" - postcss-modules "^3.2.2" postcss-value-parser "^4.1.0" - semver "^5.7.1" + semver "^5.4.1" "@parcel/transformer-html@2.0.0-beta.2": version "2.0.0-beta.2" @@ -4018,27 +4011,26 @@ posthtml-render "^1.4.0" semver "^5.4.1" -"@parcel/transformer-html@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-html/-/transformer-html-2.2.0.tgz#2149b6cad3e8d49cf331823abc6ea13b81f0501b" - integrity sha512-Swvo4TUqyGcwN3AsUMRg5dZRaJnS2DUapJlr7wHmf5bL8Q0GOuGDLLZpK5SOmQn9aZSJ6uoof5gu9zoca+s5Ig== +"@parcel/transformer-html@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/transformer-html/-/transformer-html-2.0.1.tgz#f285e46ab654e32bdb8af506284ecb22a14b6547" + integrity sha512-UkRtBHPnuedSX5UPzrZDzNb5pxWCVqvE5/xTPlxWEtN4een9Aixl4RSOZiJxMp4dxxVtw/fo9Lnx0z1wYxbWRw== dependencies: - "@parcel/diagnostic" "^2.2.0" - "@parcel/hash" "^2.2.0" - "@parcel/plugin" "^2.2.0" + "@parcel/hash" "^2.0.1" + "@parcel/plugin" "^2.0.1" nullthrows "^1.1.1" posthtml "^0.16.5" posthtml-parser "^0.10.1" posthtml-render "^3.0.0" - semver "^5.7.1" + semver "^5.4.1" -"@parcel/transformer-image@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-image/-/transformer-image-2.2.0.tgz#8348f426cadb3cae1d7a7adbca3a3d9bc9ef3d11" - integrity sha512-9MRmgNRi8Dg+GMA1378smh0m/xfzPpTljR23jKvwviNsz54Tf67pC328DGaI6+5eNIw4fYFZxJTXcOSAVVKk2Q== +"@parcel/transformer-image@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/transformer-image/-/transformer-image-2.0.1.tgz#b13eb2a54546059a7ab260153e4009a3df5808f9" + integrity sha512-1xHPdE4W8jzsI0AWi4XWYioG2sDZvxJHprlTYNGK8GE+A2U7bOi7T2aoa44fEfK1pRa+N5GTkoNVTYiv4hza0g== dependencies: - "@parcel/plugin" "^2.2.0" - "@parcel/workers" "^2.2.0" + "@parcel/plugin" "^2.0.1" + "@parcel/workers" "^2.0.1" nullthrows "^1.1.1" "@parcel/transformer-js@2.0.0-beta.2": @@ -4062,23 +4054,22 @@ nullthrows "^1.1.1" semver "^5.4.1" -"@parcel/transformer-js@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-js/-/transformer-js-2.2.0.tgz#06b2b323e6839d35e8e56395d99726fe052c82cb" - integrity sha512-sD9NXeO6j6wEe8zkRcVU235fLGhyBWEhOpAbqd23+YjFgwpYq58aqw0v2tX3Ifv+jFuw8wYvRbN7Eq4hCKdjSw== +"@parcel/transformer-js@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/transformer-js/-/transformer-js-2.0.1.tgz#784247a16a772fb250dd752a18d7b45e65e78ed4" + integrity sha512-c55qVfPU+jKoFFLV2GhME7CCqBO4Il34lW1EEv0RdYlBivPQQf+8vdcrrRX2FSjlI9cpvw9E4l298HyQDpVyng== dependencies: - "@parcel/diagnostic" "^2.2.0" - "@parcel/plugin" "^2.2.0" + "@parcel/diagnostic" "^2.0.1" + "@parcel/plugin" "^2.0.1" "@parcel/source-map" "^2.0.0" - "@parcel/utils" "^2.2.0" - "@parcel/workers" "^2.2.0" + "@parcel/utils" "^2.0.1" "@swc/helpers" "^0.2.11" browserslist "^4.6.6" detect-libc "^1.0.3" micromatch "^4.0.2" nullthrows "^1.1.1" regenerator-runtime "^0.13.7" - semver "^5.7.1" + semver "^5.4.1" "@parcel/transformer-json@2.0.0-beta.2": version "2.0.0-beta.2" @@ -4088,12 +4079,12 @@ "@parcel/plugin" "2.0.0-beta.2" json5 "^2.1.0" -"@parcel/transformer-json@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-json/-/transformer-json-2.2.0.tgz#f053c0c04e6b6b2b060c4ac28077dbbede8c2ccc" - integrity sha512-SukzfuncSeK0nFahA4cgSNM9JAltp1TZBiIFTi6sufAfpr4g8de04uk8l09dPxEggSkRGFGvxiNeJfxXGqEkMg== +"@parcel/transformer-json@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/transformer-json/-/transformer-json-2.0.1.tgz#e33298f3b44d3e6d5067d572ec723c8f806d8c08" + integrity sha512-Nx22PQY5InJdqLKppC+Rq0zwH7mpE2MUvgdyhGBzbwB3qwo+us1uupj+3TGYtBQ8tsUypTZVQ1kWGyQkkGWqHg== dependencies: - "@parcel/plugin" "^2.2.0" + "@parcel/plugin" "^2.0.1" json5 "^2.1.0" "@parcel/transformer-postcss@2.0.0-beta.2": @@ -4109,20 +4100,20 @@ postcss-value-parser "^4.1.0" semver "^5.4.1" -"@parcel/transformer-postcss@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-postcss/-/transformer-postcss-2.2.0.tgz#0ddc5455f58dab1fe873307c02a86a334f164ea3" - integrity sha512-u8aZ63oL9N28HXZTQPVz3eYoza+BzI86qXqrpT5nsTEHypnRiskih25ucwkYjWyBrOUavIn+ihLO7qKaVgGixQ== +"@parcel/transformer-postcss@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/transformer-postcss/-/transformer-postcss-2.0.1.tgz#517929aa81523051c1ae1c555ae036c4e0cf6d40" + integrity sha512-bSmOl1CxE5VD7FoNMz9G5ndh3vkYMJl84nbY2t91lUtGcY/ROJ1LKvZrglCCEEE13j9orFsPproQgCcYG7m1eA== dependencies: - "@parcel/hash" "^2.2.0" - "@parcel/plugin" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/hash" "^2.0.1" + "@parcel/plugin" "^2.0.1" + "@parcel/utils" "^2.0.1" clone "^2.1.1" css-modules-loader-core "^1.1.0" nullthrows "^1.1.1" postcss-modules "^3.2.2" postcss-value-parser "^4.1.0" - semver "^5.7.1" + semver "^5.4.1" "@parcel/transformer-posthtml@2.0.0-beta.2": version "2.0.0-beta.2" @@ -4137,18 +4128,18 @@ posthtml-render "^1.4.0" semver "^5.4.1" -"@parcel/transformer-posthtml@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-posthtml/-/transformer-posthtml-2.2.0.tgz#dc85d0e474df2a2cd3a5ed61cb241bc2edd59e64" - integrity sha512-7xtnLh2wrVLZiU0pTkUWNwr2S4v3u/lgDDSr6UNQcjDHMdMUdbN8CIoraYNm3cEiTzb8O6OQEYHyH2ZEc753Xw== +"@parcel/transformer-posthtml@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/transformer-posthtml/-/transformer-posthtml-2.0.1.tgz#14a25be291d4b9d56abecd0af6ff0dbe97a27ea0" + integrity sha512-UKGZO5vAZCxnTDF5fT8DzNrUdzahpCnFCrFOa0MFKi0DLKrVrxXmgIgLtoLS+mgwd3WuOW3Vx3KgyVovP5n2JQ== dependencies: - "@parcel/plugin" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/plugin" "^2.0.1" + "@parcel/utils" "^2.0.1" nullthrows "^1.1.1" posthtml "^0.16.5" posthtml-parser "^0.10.1" posthtml-render "^3.0.0" - semver "^5.7.1" + semver "^5.4.1" "@parcel/transformer-raw@2.0.0-beta.2": version "2.0.0-beta.2" @@ -4157,12 +4148,12 @@ dependencies: "@parcel/plugin" "2.0.0-beta.2" -"@parcel/transformer-raw@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-raw/-/transformer-raw-2.2.0.tgz#f5c623996b0e819bca87ede51d73de9bcc277a57" - integrity sha512-xSSulmsPFNi6T/Ji/GeWIe3UatHEAemIBX3KZsvDDjy0P4olaNmeU/IvL+bb1H4NXKynOAgzyIR0S7bEpIj6Fw== +"@parcel/transformer-raw@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/transformer-raw/-/transformer-raw-2.0.1.tgz#bec9f802cfbda864281056eca88bd8202ff9167c" + integrity sha512-NkwOp2lZX5bNxSj6tMNTEledWZvpIperCMOERm4raToDkdjBH1pDrxDLUBy8VzQ8M08CLz+2KJaF5wRMvj/eQw== dependencies: - "@parcel/plugin" "^2.2.0" + "@parcel/plugin" "^2.0.1" "@parcel/transformer-react-refresh-babel@2.0.0-beta.2": version "2.0.0-beta.2" @@ -4185,45 +4176,44 @@ react-refresh "^0.9.0" semver "^5.4.1" -"@parcel/transformer-react-refresh-wrap@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.2.0.tgz#f6c6040b9d1edaecd880e8c21f66211cf6ae19fb" - integrity sha512-bPFXG7nB3dleH1pAfvlPXOY02CR5DZCoAGmu+D24VUyXXIEOX6RlB6nGls10FL7FSMLT1Q2iuJCS4DsFMIXLsw== +"@parcel/transformer-react-refresh-wrap@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.0.1.tgz#4f8c6df15c23e66a61c430615baaf57b5a7791bc" + integrity sha512-zZj2Leh39ODh3C2xDh3eVvp1VnfVqeY5PrNdIcNfWw2DMBli13azcwYmF4Uim8natRqMFIsWsfKNesEY+mGLfA== dependencies: - "@parcel/plugin" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/plugin" "^2.0.1" + "@parcel/utils" "^2.0.1" react-refresh "^0.9.0" -"@parcel/transformer-svg@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-svg/-/transformer-svg-2.2.0.tgz#f38c8f2809afa162380af0fb98d26e695cb2119a" - integrity sha512-tiGRVgwm/Fc36H/JUkvzcHAs+BPfOVcb171bgu5zUkDhfFj6ty5fvAQ9OThHJ6mV1vv/KGJuPbH4N1+wW/P4zQ== +"@parcel/transformer-svg@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/transformer-svg/-/transformer-svg-2.0.1.tgz#acee4bbb9b4a9f1a80416180630da5123a8fd452" + integrity sha512-ZctnwpSomOZoh2FdfETLU4WnIr2t5P9W7QX5USATTlq62uD404Qsj1gr93wQgjLjzy9ID6T1Ua4iIdYNSkScNA== dependencies: - "@parcel/diagnostic" "^2.2.0" - "@parcel/hash" "^2.2.0" - "@parcel/plugin" "^2.2.0" + "@parcel/hash" "^2.0.1" + "@parcel/plugin" "^2.0.1" nullthrows "^1.1.1" posthtml "^0.16.5" posthtml-parser "^0.10.1" posthtml-render "^3.0.0" - semver "^5.7.1" + semver "^5.4.1" "@parcel/types@2.0.0-beta.2": version "2.0.0-beta.2" resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.0.0-beta.2.tgz#39530f885a546ccc8759ba8b970440bb7aadc146" integrity sha512-ri2BPGAFDntQbA5p3m/4QgnEqWYToUMkAtLelXSPbwnTM0KARavTAwSRqz1xwTdXa8gQyv4SSV7xURwaPaZ3GA== -"@parcel/types@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.2.0.tgz#781cacacda6ccb02c4f0454463ec408a28f4f9bb" - integrity sha512-QmzC/EowXifXYCRwWZS1/jC5yiWCV1R5YuKDhEj9AgKU6LOAMXAnfBwYB4jRnY+1Zv+n/Pf2LD24sz02sXzScQ== +"@parcel/types@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.0.1.tgz#e4f30f29e0f1ea53029a0450794a3d0aee90a9f7" + integrity sha512-em8/GgC7uzkUyEA2ogkzeVDmjaKYQhjf/4EIiC7jXWr22NlSXRQOawhc0CB2o97J9EV2rVXVkWTj0drHTpN2Bw== dependencies: - "@parcel/cache" "^2.2.0" - "@parcel/diagnostic" "^2.2.0" - "@parcel/fs" "^2.2.0" - "@parcel/package-manager" "^2.2.0" + "@parcel/cache" "^2.0.1" + "@parcel/diagnostic" "^2.0.1" + "@parcel/fs" "^2.0.1" + "@parcel/package-manager" "^2.0.1" "@parcel/source-map" "^2.0.0" - "@parcel/workers" "^2.2.0" + "@parcel/workers" "^2.0.1" utility-types "^3.10.0" "@parcel/utils@2.0.0-beta.2": @@ -4251,17 +4241,17 @@ nullthrows "^1.1.1" open "^7.0.3" -"@parcel/utils@^2.0.1", "@parcel/utils@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-2.2.0.tgz#92d88f4603f943d10208728369881c667b3b6fd6" - integrity sha512-H1SqGvbhlNIFoiMTLX/aEGCGG0KYWQCc9Nf5BJ+75/lTaznZKCcUgv4pqu5j2PxmKlyl61aNkX69H9lWjdcvMw== +"@parcel/utils@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-2.0.1.tgz#dd78bfe3562abd2bf3b4f8417758e4e0dbd8350f" + integrity sha512-+XD+LYDq+VKAUfRPzcsOjq9LefeX6tiQ2zH2uCWYAwA+s+sTHIrvWkKoF3QfFOQpPgj2QqnAZMOS6F/xY2phPg== dependencies: "@iarna/toml" "^2.2.0" - "@parcel/codeframe" "^2.2.0" - "@parcel/diagnostic" "^2.2.0" - "@parcel/hash" "^2.2.0" - "@parcel/logger" "^2.2.0" - "@parcel/markdown-ansi" "^2.2.0" + "@parcel/codeframe" "^2.0.1" + "@parcel/diagnostic" "^2.0.1" + "@parcel/hash" "^2.0.1" + "@parcel/logger" "^2.0.1" + "@parcel/markdown-ansi" "^2.0.1" "@parcel/source-map" "^2.0.0" ansi-html-community "0.0.8" chalk "^4.1.0" @@ -4273,7 +4263,7 @@ json5 "^1.0.1" lru-cache "^6.0.0" micromatch "^4.0.4" - node-forge "^1.2.1" + node-forge "^0.10.0" nullthrows "^1.1.1" open "^7.0.3" terminal-link "^2.1.1" @@ -4305,15 +4295,15 @@ chrome-trace-event "^1.0.2" nullthrows "^1.1.1" -"@parcel/workers@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-2.2.0.tgz#12f9774c9dc3c4e6a8427e9c7b79ee59dc20f2b6" - integrity sha512-qJTC+61LMz5eBHvT0lT+auoTKDzh/FemQrWzCdcQZjgECQyLYdD6GtMeCZP745pDfXcYZpflbSGZChIUnt1yDg== +"@parcel/workers@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-2.0.1.tgz#55e540556b201677591519b26f84c6ec955b9a6c" + integrity sha512-nBBK5QeoWM0l8khyStDiEd432UXaF6mkUa8n2D4Ee6XOFgUCiXWV7VROqA4nhf6OJr5K+trtNaNVGq9oHRuPHw== dependencies: - "@parcel/diagnostic" "^2.2.0" - "@parcel/logger" "^2.2.0" - "@parcel/types" "^2.2.0" - "@parcel/utils" "^2.2.0" + "@parcel/diagnostic" "^2.0.1" + "@parcel/logger" "^2.0.1" + "@parcel/types" "^2.0.1" + "@parcel/utils" "^2.0.1" chrome-trace-event "^1.0.2" nullthrows "^1.1.1" @@ -13409,16 +13399,16 @@ listr@^0.14.3: p-map "^2.0.0" rxjs "^6.3.3" -lmdb@^2.0.2: - version "2.1.5" - resolved "https://registry.yarnpkg.com/lmdb/-/lmdb-2.1.5.tgz#7863d268c579b4c6bf6042a97784fe1dea6753d8" - integrity sha512-J84gtJYC6DnZvczrtBF20xIyT9dZzY24/p1wi5zHldyoW+nKuckFy+AywoiMnTBRODp/lJkNhmjqw0dQUl0pFg== +lmdb-store@^1.5.5: + version "1.6.14" + resolved "https://registry.yarnpkg.com/lmdb-store/-/lmdb-store-1.6.14.tgz#8aa5f36fb04195f8639a3b01b32f6696867f2bc9" + integrity sha512-4woZfvfgolMEngjoMJrwePjdLotr3QKGJsDWURlJmKBed5JtE00IfAKo7ryPowl4ksGcs21pcdLkwrPnKomIuA== dependencies: - msgpackr "^1.5.2" + msgpackr "^1.5.0" nan "^2.14.2" node-gyp-build "^4.2.3" - ordered-binary "^1.2.3" - weak-lru-cache "^1.2.1" + ordered-binary "^1.0.0" + weak-lru-cache "^1.0.0" load-json-file@^1.0.0: version "1.1.0" @@ -14317,10 +14307,10 @@ msgpackr-extract@^1.0.14: nan "^2.14.2" node-gyp-build "^4.2.3" -msgpackr@^1.5.1, msgpackr@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.5.2.tgz#b400c9885642bdec27b284f8bdadbd6570b448b7" - integrity sha512-OCguCkbG34x1ddO4vAzEm/4J1GTo512k9SoxV8K+EGfI/onFdpemRf0HpsVRFpxadXr4JBFgHsQUitgTlw7ZYQ== +msgpackr@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.5.1.tgz#2a8e39d25458406034b8cb50dc7d6a7abd3dfff2" + integrity sha512-I1CXFG8BYYSeIhtDlHpUVMsdDiyvP9JAh1d9QoBnkPx3ETPeH/1lR14hweM9GETs09wCWlaOyhtXxIc9boxAAA== optionalDependencies: msgpackr-extract "^1.0.14" @@ -14492,11 +14482,6 @@ node-forge@^0.10.0: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== -node-forge@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.2.1.tgz#82794919071ef2eb5c509293325cec8afd0fd53c" - integrity sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w== - node-gyp-build@^4.2.3, node-gyp-build@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" @@ -15067,10 +15052,10 @@ ora@^5.2.0: strip-ansi "^6.0.0" wcwidth "^1.0.1" -ordered-binary@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/ordered-binary/-/ordered-binary-1.2.3.tgz#518f637692a74d372e56230effae37b811575e36" - integrity sha512-fEwMk8TNUtzQDjXKYS2ANW3fNZ/gMReCPOAsLHaqw+UDnq/8ddXAcX4lGRpTK7kAghAjkmJs1EXXbcrDbg+ruw== +ordered-binary@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ordered-binary/-/ordered-binary-1.2.1.tgz#5429c9a8171cc1e5dd55a44568871dcd62531262" + integrity sha512-Zl2RCcj/wRCakW9/yI83gutgNf7JFOPEHrCK72z+boIrU+PWAnIt6HADd1w+3keDQ90GCKbp1BduKZgkeNbz7A== original@^1.0.0: version "1.0.2" @@ -20713,10 +20698,10 @@ wcwidth@^1.0.0, wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -weak-lru-cache@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-1.2.1.tgz#6b4f2da7e1701f845e71522417f1df1e39503df8" - integrity sha512-O5ag1F0Xk6ui+Fg5LlosTcVAyHs6DeyiDDbOapNtFCx/KjZ82B3U9stM9hvzbVclKWn9ABPjaINX/nQkGkJkKg== +weak-lru-cache@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-1.1.4.tgz#c0b2d5257dcbf4f6989d44f13764698234eb9f78" + integrity sha512-oD0vx3PpnwnGkr3QYn0nGvepmeZPvrM2m9Rq4Hu4IMCGAS3PO1qnCioaJR6ajVK58oABbm76zSh3Kai3Z6BGyw== webidl-conversions@^3.0.0: version "3.0.1" @@ -21309,7 +21294,7 @@ xmlchars@^2.1.1, xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xxhash-wasm@^0.4.2: +xxhash-wasm@^0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz#752398c131a4dd407b5132ba62ad372029be6f79" integrity sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA== From 511d7e32072ed532841a7f320e06bd733999c93e Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Fri, 21 Jan 2022 23:58:57 +0100 Subject: [PATCH 20/41] refactor: alphabetize --- packages/autocomplete-core/src/utils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/autocomplete-core/src/utils/index.ts b/packages/autocomplete-core/src/utils/index.ts index e6f79a003..d732b3ee1 100644 --- a/packages/autocomplete-core/src/utils/index.ts +++ b/packages/autocomplete-core/src/utils/index.ts @@ -1,6 +1,6 @@ export * from './createCancelablePromise'; -export * from './createConcurrentSafePromise'; export * from './createCancelablePromiseList'; +export * from './createConcurrentSafePromise'; export * from './getNextActiveItemId'; export * from './getNormalizedSources'; export * from './getActiveItem'; From 467ac44d57cad2665f32ba14703e2fd93736944e Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Mon, 24 Jan 2022 14:33:00 +0100 Subject: [PATCH 21/41] test: clean up tests --- .../src/utils/__tests__/createCancelablePromise.test.ts | 5 +---- .../src/utils/__tests__/createCancelablePromiseList.test.ts | 6 ++++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts index 0f8159c6d..b98ad1626 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts @@ -63,11 +63,9 @@ describe('createCancelablePromise', () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); - const onCancelSpy = jest.fn(); - await createCancelablePromise((_, reject, onCancel) => { + await createCancelablePromise((_, reject) => { reject(new Error()); - onCancel(onCancelSpy); }) .then(onFulfilled) .catch(onRejected) @@ -78,7 +76,6 @@ describe('createCancelablePromise', () => { expect(onFulfilled).not.toHaveBeenCalled(); expect(onFinally).toHaveBeenCalledTimes(1); expect(onFinally).toHaveBeenCalledWith(); - expect(onCancelSpy).toHaveBeenCalledTimes(0); }); test('does not trigger callbacks when the cancelable promise is canceled and it resolves', async () => { diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts index 20f9f8542..eaf5f7161 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts @@ -1,9 +1,11 @@ +import { noop } from '@algolia/autocomplete-shared'; + import { createCancelablePromise, createCancelablePromiseList } from '..'; describe('createCancelablePromiseList', () => { test('adds cancelable promises to the list', () => { const cancelablePromiseList = createCancelablePromiseList(); - const cancelablePromise = createCancelablePromise(() => {}); + const cancelablePromise = createCancelablePromise(noop); expect(cancelablePromiseList.isEmpty()).toBe(true); @@ -31,7 +33,7 @@ describe('createCancelablePromiseList', () => { expect(cancelablePromiseList.isEmpty()).toBe(false); - await cancelablePromise.catch(() => {}); + await cancelablePromise.catch(noop); expect(cancelablePromiseList.isEmpty()).toBe(true); }); From 14a70d4b8d9b314e347f2a41ec53bf3ab062bedd Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Mon, 24 Jan 2022 14:33:26 +0100 Subject: [PATCH 22/41] feat: catch in promise consumer to avoid unhandled promises --- .../src/utils/createCancelablePromiseList.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts index 37cdedc68..56326c909 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts @@ -1,3 +1,5 @@ +import { noop } from '@algolia/autocomplete-shared'; + import { CancelablePromise } from '.'; export type CancelablePromiseQueue = { @@ -19,7 +21,9 @@ export function createCancelablePromiseList< add(cancelablePromise) { list.push(cancelablePromise); - cancelablePromise.finally(() => remove(cancelablePromise), true); + cancelablePromise + .catch(noop) + .finally(() => remove(cancelablePromise), true); }, cancelAll() { list.forEach((promise) => promise.cancel()); From 494eb7c417c318462576df086a898fa6efca5b2b Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Mon, 24 Jan 2022 15:09:39 +0100 Subject: [PATCH 23/41] chore: rename type and generic --- .../autocomplete-core/src/types/AutocompleteStore.ts | 4 ++-- .../src/utils/createCancelablePromiseList.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/autocomplete-core/src/types/AutocompleteStore.ts b/packages/autocomplete-core/src/types/AutocompleteStore.ts index 5dd47181a..e29914a9f 100644 --- a/packages/autocomplete-core/src/types/AutocompleteStore.ts +++ b/packages/autocomplete-core/src/types/AutocompleteStore.ts @@ -1,4 +1,4 @@ -import { CancelablePromiseQueue } from '../utils'; +import { CancelablePromiseList } from '../utils'; import { BaseItem } from './AutocompleteApi'; import { InternalAutocompleteOptions } from './AutocompleteOptions'; @@ -7,7 +7,7 @@ import { AutocompleteState } from './AutocompleteState'; export interface AutocompleteStore { getState(): AutocompleteState; dispatch(action: ActionType, payload: any): void; - pendingRequests: CancelablePromiseQueue; + pendingRequests: CancelablePromiseList; } export type Reducer = ( diff --git a/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts index 56326c909..3100f9780 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts @@ -2,18 +2,18 @@ import { noop } from '@algolia/autocomplete-shared'; import { CancelablePromise } from '.'; -export type CancelablePromiseQueue = { - add(cancelablePromise: CancelablePromise): void; +export type CancelablePromiseList = { + add(cancelablePromise: CancelablePromise): void; cancelAll(): void; isEmpty(): boolean; }; export function createCancelablePromiseList< - TPromise ->(): CancelablePromiseQueue { - let list: Array> = []; + TValue +>(): CancelablePromiseList { + let list: Array> = []; - function remove(cancelablePromise: CancelablePromise) { + function remove(cancelablePromise: CancelablePromise) { list = list.filter((promise) => promise !== cancelablePromise); } From 84a259f7a41fc0ae2ffe94cae04822e993cebb1c Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Mon, 24 Jan 2022 15:18:46 +0100 Subject: [PATCH 24/41] test: use terser descriptions --- .../__tests__/createCancelablePromise.test.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts index b98ad1626..21cb7448a 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts @@ -78,7 +78,7 @@ describe('createCancelablePromise', () => { expect(onFinally).toHaveBeenCalledWith(); }); - test('does not trigger callbacks when the cancelable promise is canceled and it resolves', async () => { + test('does not trigger callbacks when the cancelable promise is canceled then resolves', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -101,7 +101,7 @@ describe('createCancelablePromise', () => { expect(onFinally).not.toHaveBeenCalled(); }); - test('triggers `finally` handler callback with `runWhenCanceled=true` when the cancelable promise is canceled and it resolves', async () => { + test('triggers `finally` handler with `runWhenCanceled=true` when the cancelable promise is canceled then resolves', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -128,7 +128,7 @@ describe('createCancelablePromise', () => { expect(onFinally).toHaveBeenCalledWith(); }); - test('does not trigger `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it resolves', async () => { + test('does not trigger `finally` handler with `runWhenCanceled=false` when the cancelable promise is canceled then resolves', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -154,7 +154,7 @@ describe('createCancelablePromise', () => { expect(onFinally).not.toHaveBeenCalled(); }); - test('only triggers `finally` handler callback once when the cancelable promise is canceled and it resolves', async () => { + test('only triggers `finally` handler once when the cancelable promise is canceled then resolves', async () => { const onFulfilled = jest.fn(); const cancelablePromise = createCancelablePromise((resolve) => { resolve('ok'); @@ -171,7 +171,7 @@ describe('createCancelablePromise', () => { expect(onFulfilled).toHaveBeenCalledTimes(1); }); - test('does not trigger callbacks when the cancelable promise is canceled and it rejects', async () => { + test('does not trigger callbacks when the cancelable promise is canceled then rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -194,7 +194,7 @@ describe('createCancelablePromise', () => { expect(onFinally).not.toHaveBeenCalled(); }); - test('triggers `finally` handler callback with `runWhenCanceled=true` when the cancelable promise is canceled and it rejects', async () => { + test('triggers `finally` handler with `runWhenCanceled=true` when the cancelable promise is canceled then rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -221,7 +221,7 @@ describe('createCancelablePromise', () => { expect(onFinally).toHaveBeenCalledWith(); }); - test('does not trigger `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it rejects', async () => { + test('does not trigger `finally` handler with `runWhenCanceled=false` when the cancelable promise is canceled then rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -247,7 +247,7 @@ describe('createCancelablePromise', () => { expect(onFinally).not.toHaveBeenCalled(); }); - test('only triggers `finally` handler callback once when the cancelable promise is canceled and it rejects', async () => { + test('only triggers `finally` handler once when the cancelable promise is canceled then rejects', async () => { const onFulfilled = jest.fn(); const cancelablePromise = createCancelablePromise((_, reject) => { reject(new Error()); @@ -330,7 +330,7 @@ describe('cancelable', () => { expect(onFinally).toHaveBeenCalledWith(); }); - test('does not trigger callbacks when the cancelable promise is canceled and it resolves', async () => { + test('does not trigger callbacks when the cancelable promise is canceled then resolves', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -355,7 +355,7 @@ describe('cancelable', () => { expect(onFinally).not.toHaveBeenCalled(); }); - test('triggers `finally` handler callback with `runWhenCanceled=true` when the cancelable promise is canceled and it resolves', async () => { + test('triggers `finally` handler with `runWhenCanceled=true` when the cancelable promise is canceled then resolves', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -384,7 +384,7 @@ describe('cancelable', () => { expect(onFinally).toHaveBeenCalledWith(); }); - test('does not trigger `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it resolves', async () => { + test('does not trigger `finally` handler with `runWhenCanceled=false` when the cancelable promise is canceled then resolves', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -412,7 +412,7 @@ describe('cancelable', () => { expect(onFinally).not.toHaveBeenCalled(); }); - test('only triggers `finally` handler callback once when the cancelable promise is canceled and it resolves', async () => { + test('only triggers `finally` handler once when the cancelable promise is canceled then resolves', async () => { const onFulfilled = jest.fn(); const cancelablePromise = cancelable( new Promise((resolve) => { @@ -431,7 +431,7 @@ describe('cancelable', () => { expect(onFulfilled).toHaveBeenCalledTimes(1); }); - test('does not trigger callbacks when the cancelable promise is canceled and it rejects', async () => { + test('does not trigger callbacks when the cancelable promise is canceled then rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -456,7 +456,7 @@ describe('cancelable', () => { expect(onFinally).not.toHaveBeenCalled(); }); - test('triggers `finally` handler callback with `runWhenCanceled=true` when the cancelable promise is canceled and it rejects', async () => { + test('triggers `finally` handler with `runWhenCanceled=true` when the cancelable promise is canceled then rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -485,7 +485,7 @@ describe('cancelable', () => { expect(onFinally).toHaveBeenCalledWith(); }); - test('does not trigger `finally` handler callback with `runWhenCanceled=false` when the cancelable promise is canceled and it rejects', async () => { + test('does not trigger `finally` handler with `runWhenCanceled=false` when the cancelable promise is canceled then rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -513,7 +513,7 @@ describe('cancelable', () => { expect(onFinally).not.toHaveBeenCalled(); }); - test('only triggers `finally` handler callback once when the cancelable promise is canceled and it rejects', async () => { + test('only triggers `finally` handler once when the cancelable promise is canceled then rejects', async () => { const onFulfilled = jest.fn(); const cancelablePromise = cancelable( new Promise((_, reject) => { From 24e2a6682a5e061b0fa53d26f39ef85fb7897bf2 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Mon, 24 Jan 2022 15:22:18 +0100 Subject: [PATCH 25/41] test: better descriptions --- .../src/utils/__tests__/createCancelablePromise.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts index 21cb7448a..37cca480b 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts @@ -154,7 +154,7 @@ describe('createCancelablePromise', () => { expect(onFinally).not.toHaveBeenCalled(); }); - test('only triggers `finally` handler once when the cancelable promise is canceled then resolves', async () => { + test('only triggers `finally` handler once when the cancelable promise is canceled after resolving', async () => { const onFulfilled = jest.fn(); const cancelablePromise = createCancelablePromise((resolve) => { resolve('ok'); @@ -247,7 +247,7 @@ describe('createCancelablePromise', () => { expect(onFinally).not.toHaveBeenCalled(); }); - test('only triggers `finally` handler once when the cancelable promise is canceled then rejects', async () => { + test('only triggers `finally` handler once when the cancelable promise is canceled after rejecting', async () => { const onFulfilled = jest.fn(); const cancelablePromise = createCancelablePromise((_, reject) => { reject(new Error()); @@ -412,7 +412,7 @@ describe('cancelable', () => { expect(onFinally).not.toHaveBeenCalled(); }); - test('only triggers `finally` handler once when the cancelable promise is canceled then resolves', async () => { + test('only triggers `finally` handler once when the cancelable promise is canceled after resolving', async () => { const onFulfilled = jest.fn(); const cancelablePromise = cancelable( new Promise((resolve) => { @@ -513,7 +513,7 @@ describe('cancelable', () => { expect(onFinally).not.toHaveBeenCalled(); }); - test('only triggers `finally` handler once when the cancelable promise is canceled then rejects', async () => { + test('only triggers `finally` handler once when the cancelable promise is canceled after rejecting', async () => { const onFulfilled = jest.fn(); const cancelablePromise = cancelable( new Promise((_, reject) => { From cef48a03200630077cd37319e2025cf3060d5c81 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Mon, 24 Jan 2022 15:42:30 +0100 Subject: [PATCH 26/41] test: make the test more exhaustive --- .../createCancelablePromiseList.test.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts index eaf5f7161..0efba1e53 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts @@ -49,16 +49,26 @@ describe('createCancelablePromiseList', () => { expect(cancelablePromiseList.isEmpty()).toBe(true); }); - test('empties the list when all promises are canceled', () => { + test('cancels all promises and empties the list', () => { const cancelablePromiseList = createCancelablePromiseList(); - const cancelablePromise = createCancelablePromise.resolve(); + const cancelablePromise1 = createCancelablePromise.resolve(); + const cancelablePromise2 = createCancelablePromise.reject(); + const cancelablePromise3 = createCancelablePromise(noop); - cancelablePromiseList.add(cancelablePromise); + cancelablePromiseList.add(cancelablePromise1); + cancelablePromiseList.add(cancelablePromise2); + cancelablePromiseList.add(cancelablePromise3); + expect(cancelablePromise1.isCanceled()).toBe(false); + expect(cancelablePromise2.isCanceled()).toBe(false); + expect(cancelablePromise3.isCanceled()).toBe(false); expect(cancelablePromiseList.isEmpty()).toBe(false); cancelablePromiseList.cancelAll(); + expect(cancelablePromise1.isCanceled()).toBe(true); + expect(cancelablePromise2.isCanceled()).toBe(true); + expect(cancelablePromise3.isCanceled()).toBe(true); expect(cancelablePromiseList.isEmpty()).toBe(true); }); }); From aedb8c26f4e501383070fdab5a6cac44bfa4b4a2 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Mon, 24 Jan 2022 15:44:55 +0100 Subject: [PATCH 27/41] refactor: rename internal parameter --- .../autocomplete-core/src/utils/createCancelablePromiseList.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts index 3100f9780..39ee12598 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts @@ -14,7 +14,7 @@ export function createCancelablePromiseList< let list: Array> = []; function remove(cancelablePromise: CancelablePromise) { - list = list.filter((promise) => promise !== cancelablePromise); + list = list.filter((item) => item !== cancelablePromise); } return { From 267b700767ec7c2c68551509abbe4ceb3d12cd9b Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Mon, 24 Jan 2022 15:54:02 +0100 Subject: [PATCH 28/41] refactor: rename type --- packages/autocomplete-core/src/__tests__/debouncing.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/autocomplete-core/src/__tests__/debouncing.test.ts b/packages/autocomplete-core/src/__tests__/debouncing.test.ts index c9ceac963..48a7824c7 100644 --- a/packages/autocomplete-core/src/__tests__/debouncing.test.ts +++ b/packages/autocomplete-core/src/__tests__/debouncing.test.ts @@ -3,9 +3,9 @@ import userEvent from '@testing-library/user-event'; import { createAutocomplete, InternalAutocompleteSource } from '..'; import { createPlayground, createSource, defer } from '../../../../test/utils'; -type DebouncedSource = InternalAutocompleteSource<{ label: string }>; +type Source = InternalAutocompleteSource<{ label: string }>; -const debounced = debouncePromise( +const debounced = debouncePromise( (items) => Promise.resolve(items), 100 ); From a431e71023db9d81d5278d31a0ae1379df67ed26 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Mon, 24 Jan 2022 16:01:14 +0100 Subject: [PATCH 29/41] test: refactor test --- .../src/__tests__/debouncing.test.ts | 30 +++++-------------- .../createCancelablePromiseList.test.ts | 4 +++ 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/packages/autocomplete-core/src/__tests__/debouncing.test.ts b/packages/autocomplete-core/src/__tests__/debouncing.test.ts index 48a7824c7..f4bd90e08 100644 --- a/packages/autocomplete-core/src/__tests__/debouncing.test.ts +++ b/packages/autocomplete-core/src/__tests__/debouncing.test.ts @@ -1,3 +1,4 @@ +import { noop } from '@algolia/autocomplete-shared'; import userEvent from '@testing-library/user-event'; import { createAutocomplete, InternalAutocompleteSource } from '..'; @@ -5,9 +6,11 @@ import { createPlayground, createSource, defer } from '../../../../test/utils'; type Source = InternalAutocompleteSource<{ label: string }>; +const delay = 10; + const debounced = debouncePromise( (items) => Promise.resolve(items), - 100 + delay ); describe('debouncing', () => { @@ -21,7 +24,7 @@ describe('debouncing', () => { userEvent.type(inputElement, 'abc'); - await defer(() => {}, 300); + await defer(noop, delay); expect(getItems).toHaveBeenCalledTimes(1); expect(onStateChange).toHaveBeenLastCalledWith( @@ -38,6 +41,7 @@ describe('debouncing', () => { }) ); }); + test('triggers subsequent queries after reopening the panel', async () => { const onStateChange = jest.fn(); const getItems = jest.fn(({ query }) => [{ label: query }]); @@ -46,25 +50,7 @@ describe('debouncing', () => { getSources: () => debounced([createSource({ getItems })]), }); - userEvent.type(inputElement, 'abc'); - - await defer(() => {}, 300); - - expect(onStateChange).toHaveBeenLastCalledWith( - expect.objectContaining({ - state: expect.objectContaining({ - collections: expect.arrayContaining([ - expect.objectContaining({ - items: [{ __autocomplete_id: 0, label: 'abc' }], - }), - ]), - status: 'idle', - isOpen: true, - }), - }) - ); - - userEvent.type(inputElement, '{esc}'); + userEvent.type(inputElement, 'abc{esc}'); expect(onStateChange).toHaveBeenLastCalledWith( expect.objectContaining({ @@ -77,7 +63,7 @@ describe('debouncing', () => { userEvent.type(inputElement, 'def'); - await defer(() => {}, 300); + await defer(noop, delay); expect(onStateChange).toHaveBeenLastCalledWith( expect.objectContaining({ diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts index 0efba1e53..cca3fa9e2 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts @@ -13,6 +13,7 @@ describe('createCancelablePromiseList', () => { expect(cancelablePromiseList.isEmpty()).toBe(false); }); + test('removes the cancelable promise from the list when it resolves', async () => { const cancelablePromiseList = createCancelablePromiseList(); const cancelablePromise = createCancelablePromise.resolve(); @@ -25,6 +26,7 @@ describe('createCancelablePromiseList', () => { expect(cancelablePromiseList.isEmpty()).toBe(true); }); + test('removes the cancelable promise from the list when it rejects', async () => { const cancelablePromiseList = createCancelablePromiseList(); const cancelablePromise = createCancelablePromise.reject(); @@ -37,6 +39,7 @@ describe('createCancelablePromiseList', () => { expect(cancelablePromiseList.isEmpty()).toBe(true); }); + test('removes the cancelable promise from the list when it is canceled', () => { const cancelablePromiseList = createCancelablePromiseList(); const cancelablePromise = createCancelablePromise.resolve(); @@ -49,6 +52,7 @@ describe('createCancelablePromiseList', () => { expect(cancelablePromiseList.isEmpty()).toBe(true); }); + test('cancels all promises and empties the list', () => { const cancelablePromiseList = createCancelablePromiseList(); const cancelablePromise1 = createCancelablePromise.resolve(); From dcc7d2320db1c7a72d9c5caed1037e9309649d8d Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Mon, 24 Jan 2022 16:21:15 +0100 Subject: [PATCH 30/41] test: further test concurrency behavior --- .../src/__tests__/concurrency.test.ts | 112 +++++++++++++++--- 1 file changed, 98 insertions(+), 14 deletions(-) diff --git a/packages/autocomplete-core/src/__tests__/concurrency.test.ts b/packages/autocomplete-core/src/__tests__/concurrency.test.ts index 8284887ea..de7dde305 100644 --- a/packages/autocomplete-core/src/__tests__/concurrency.test.ts +++ b/packages/autocomplete-core/src/__tests__/concurrency.test.ts @@ -1,7 +1,13 @@ +import { noop } from '@algolia/autocomplete-shared'; import userEvent from '@testing-library/user-event'; import { AutocompleteState } from '..'; -import { createPlayground, createSource, defer } from '../../../../test/utils'; +import { + createPlayground, + createSource, + defer, + runAllMicroTasks, +} from '../../../../test/utils'; import { createAutocomplete } from '../createAutocomplete'; type Item = { @@ -31,7 +37,7 @@ describe('concurrency', () => { userEvent.type(input, 'b'); userEvent.type(input, 'c'); - await defer(() => {}, timeout); + await defer(noop, timeout); let stateHistory: Array< AutocompleteState @@ -57,7 +63,7 @@ describe('concurrency', () => { userEvent.type(input, '{backspace}'.repeat(3)); - await defer(() => {}, timeout); + await defer(noop, timeout); stateHistory = onStateChange.mock.calls.flatMap((x) => x[0].state); @@ -88,19 +94,44 @@ describe('concurrency', () => { getSources, }); - userEvent.type(inputElement, 'ab{esc}'); + userEvent.type(inputElement, 'ab'); - await defer(() => {}, timeout); + // The search request is triggered + expect(onStateChange).toHaveBeenLastCalledWith( + expect.objectContaining({ + state: expect.objectContaining({ + status: 'loading', + query: 'ab', + }), + }) + ); + userEvent.type(inputElement, '{esc}'); + + // The status is immediately set to "idle" and the panel is closed expect(onStateChange).toHaveBeenLastCalledWith( expect.objectContaining({ state: expect.objectContaining({ + status: 'idle', isOpen: false, + query: '', + }), + }) + ); + + await defer(noop, timeout); + + // Once the request is settled, the state remains unchanged + expect(onStateChange).toHaveBeenLastCalledWith( + expect.objectContaining({ + state: expect.objectContaining({ status: 'idle', + isOpen: false, }), }) ); - expect(getSources).toHaveBeenCalledTimes(2); + + expect(getSources).toHaveBeenCalledTimes(3); }); test('keeps the panel closed on blur', async () => { @@ -115,19 +146,46 @@ describe('concurrency', () => { getSources, }); - userEvent.type(inputElement, 'a{enter}'); + userEvent.type(inputElement, 'a'); + + await runAllMicroTasks(); + + // The search request is triggered + expect(onStateChange).toHaveBeenLastCalledWith( + expect.objectContaining({ + state: expect.objectContaining({ + status: 'loading', + query: 'a', + }), + }) + ); - await defer(() => {}, timeout); + userEvent.type(inputElement, '{enter}'); + // The status is immediately set to "idle" and the panel is closed expect(onStateChange).toHaveBeenLastCalledWith( expect.objectContaining({ state: expect.objectContaining({ + status: 'idle', isOpen: false, + query: 'a', + }), + }) + ); + + await defer(noop, timeout); + + // Once the request is settled, the state remains unchanged + expect(onStateChange).toHaveBeenLastCalledWith( + expect.objectContaining({ + state: expect.objectContaining({ status: 'idle', + isOpen: false, }), }) ); - expect(getSources).toHaveBeenCalledTimes(1); + + expect(getSources).toHaveBeenCalledTimes(2); }); test('keeps the panel closed on touchstart blur', async () => { @@ -156,19 +214,45 @@ describe('concurrency', () => { window.addEventListener('touchstart', onTouchStart); userEvent.type(inputElement, 'a'); + + await runAllMicroTasks(); + + // The search request is triggered + expect(onStateChange).toHaveBeenLastCalledWith( + expect.objectContaining({ + state: expect.objectContaining({ + status: 'loading', + query: 'a', + }), + }) + ); + const customEvent = new CustomEvent('touchstart', { bubbles: true }); window.document.dispatchEvent(customEvent); - await defer(() => {}, timeout); - + // The status is immediately set to "idle" and the panel is closed expect(onStateChange).toHaveBeenLastCalledWith( expect.objectContaining({ state: expect.objectContaining({ + status: 'idle', isOpen: false, + query: 'a', + }), + }) + ); + + await defer(noop, timeout); + + // Once the request is settled, the state remains unchanged + expect(onStateChange).toHaveBeenLastCalledWith( + expect.objectContaining({ + state: expect.objectContaining({ status: 'idle', + isOpen: false, }), }) ); + expect(getSources).toHaveBeenCalledTimes(1); window.removeEventListener('touchstart', onTouchStart); @@ -197,7 +281,7 @@ describe('concurrency', () => { userEvent.type(inputElement, 'a{esc}'); - await defer(() => {}, delay); + await defer(noop, delay); expect(onStateChange).toHaveBeenLastCalledWith( expect.objectContaining({ @@ -229,7 +313,7 @@ describe('concurrency', () => { userEvent.type(inputElement, 'a{enter}'); - await defer(() => {}, delay); + await defer(noop, delay); expect(onStateChange).toHaveBeenLastCalledWith( expect.objectContaining({ @@ -276,7 +360,7 @@ describe('concurrency', () => { const customEvent = new CustomEvent('touchstart', { bubbles: true }); window.document.dispatchEvent(customEvent); - await defer(() => {}, delay); + await defer(noop, delay); expect(onStateChange).toHaveBeenLastCalledWith( expect.objectContaining({ From 971eeb018b114c063c67ab5d788c31fc096123e7 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Mon, 24 Jan 2022 17:34:57 +0100 Subject: [PATCH 31/41] refactor: inline item removal --- .../src/utils/createCancelablePromiseList.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts index 39ee12598..fac74bd11 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts @@ -13,17 +13,13 @@ export function createCancelablePromiseList< >(): CancelablePromiseList { let list: Array> = []; - function remove(cancelablePromise: CancelablePromise) { - list = list.filter((item) => item !== cancelablePromise); - } - return { add(cancelablePromise) { list.push(cancelablePromise); - cancelablePromise - .catch(noop) - .finally(() => remove(cancelablePromise), true); + cancelablePromise.catch(noop).finally(() => { + list = list.filter((item) => item !== cancelablePromise); + }, true); }, cancelAll() { list.forEach((promise) => promise.cancel()); From d68de50779d5f39bb645d5da092ad3ab1c7fc471 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Mon, 24 Jan 2022 17:46:36 +0100 Subject: [PATCH 32/41] feat: remove runWhenCanceled option --- packages/autocomplete-core/src/onInput.ts | 2 +- .../__tests__/createCancelablePromise.test.ts | 272 +----------------- .../src/utils/createCancelablePromise.ts | 11 +- .../src/utils/createCancelablePromiseList.ts | 2 +- 4 files changed, 16 insertions(+), 271 deletions(-) diff --git a/packages/autocomplete-core/src/onInput.ts b/packages/autocomplete-core/src/onInput.ts index d2e803e0b..b358656d9 100644 --- a/packages/autocomplete-core/src/onInput.ts +++ b/packages/autocomplete-core/src/onInput.ts @@ -167,7 +167,7 @@ export function onInput({ if (lastStalledId) { props.environment.clearTimeout(lastStalledId); } - }, true); + }); store.pendingRequests.add(request); diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts index 37cca480b..dcc5a3c1d 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts @@ -78,7 +78,7 @@ describe('createCancelablePromise', () => { expect(onFinally).toHaveBeenCalledWith(); }); - test('does not trigger callbacks when the cancelable promise is canceled then resolves', async () => { + test('only triggers `finally` handler when the cancelable promise is canceled then resolves', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -96,82 +96,13 @@ describe('createCancelablePromise', () => { await runAllMicroTasks(); - expect(onFulfilled).not.toHaveBeenCalled(); - expect(onRejected).not.toHaveBeenCalled(); - expect(onFinally).not.toHaveBeenCalled(); - }); - - test('triggers `finally` handler with `runWhenCanceled=true` when the cancelable promise is canceled then resolves', async () => { - const onFulfilled = jest.fn(); - const onRejected = jest.fn(); - const onFinally = jest.fn(); - const cancelablePromise = createCancelablePromise((resolve) => { - resolve('ok'); - }); - - cancelablePromise - .then(onFulfilled) - .catch(onRejected) - .finally(onFinally, true); - - expect(cancelablePromise.isCanceled()).toBe(false); - - cancelablePromise.cancel(); - - expect(cancelablePromise.isCanceled()).toBe(true); - - await runAllMicroTasks(); - expect(onFulfilled).not.toHaveBeenCalled(); expect(onRejected).not.toHaveBeenCalled(); expect(onFinally).toHaveBeenCalledTimes(1); expect(onFinally).toHaveBeenCalledWith(); }); - test('does not trigger `finally` handler with `runWhenCanceled=false` when the cancelable promise is canceled then resolves', async () => { - const onFulfilled = jest.fn(); - const onRejected = jest.fn(); - const onFinally = jest.fn(); - const cancelablePromise = createCancelablePromise((resolve) => { - resolve('ok'); - }); - - cancelablePromise - .then(onFulfilled) - .catch(onRejected) - .finally(onFinally, false); - - expect(cancelablePromise.isCanceled()).toBe(false); - - cancelablePromise.cancel(); - - expect(cancelablePromise.isCanceled()).toBe(true); - - await runAllMicroTasks(); - - expect(onFulfilled).not.toHaveBeenCalled(); - expect(onRejected).not.toHaveBeenCalled(); - expect(onFinally).not.toHaveBeenCalled(); - }); - - test('only triggers `finally` handler once when the cancelable promise is canceled after resolving', async () => { - const onFulfilled = jest.fn(); - const cancelablePromise = createCancelablePromise((resolve) => { - resolve('ok'); - }).finally(onFulfilled, true); - - await cancelablePromise; - - expect(onFulfilled).toHaveBeenCalledTimes(1); - - cancelablePromise.cancel(); - - await runAllMicroTasks(); - - expect(onFulfilled).toHaveBeenCalledTimes(1); - }); - - test('does not trigger callbacks when the cancelable promise is canceled then rejects', async () => { + test('only triggers `finally` handler when the cancelable promise is canceled then rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -189,71 +120,19 @@ describe('createCancelablePromise', () => { await runAllMicroTasks(); - expect(onFulfilled).not.toHaveBeenCalled(); - expect(onRejected).not.toHaveBeenCalled(); - expect(onFinally).not.toHaveBeenCalled(); - }); - - test('triggers `finally` handler with `runWhenCanceled=true` when the cancelable promise is canceled then rejects', async () => { - const onFulfilled = jest.fn(); - const onRejected = jest.fn(); - const onFinally = jest.fn(); - const cancelablePromise = createCancelablePromise((_, reject) => { - reject(new Error()); - }); - - cancelablePromise - .then(onFulfilled) - .catch(onRejected) - .finally(onFinally, true); - - expect(cancelablePromise.isCanceled()).toBe(false); - - cancelablePromise.cancel(); - - expect(cancelablePromise.isCanceled()).toBe(true); - - await runAllMicroTasks(); - expect(onFulfilled).not.toHaveBeenCalled(); expect(onRejected).not.toHaveBeenCalled(); expect(onFinally).toHaveBeenCalledTimes(1); expect(onFinally).toHaveBeenCalledWith(); }); - test('does not trigger `finally` handler with `runWhenCanceled=false` when the cancelable promise is canceled then rejects', async () => { - const onFulfilled = jest.fn(); - const onRejected = jest.fn(); - const onFinally = jest.fn(); - const cancelablePromise = createCancelablePromise((_, reject) => { - reject(new Error()); - }); - - cancelablePromise - .then(onFulfilled) - .catch(onRejected) - .finally(onFinally, false); - - expect(cancelablePromise.isCanceled()).toBe(false); - - cancelablePromise.cancel(); - - expect(cancelablePromise.isCanceled()).toBe(true); - - await runAllMicroTasks(); - - expect(onFulfilled).not.toHaveBeenCalled(); - expect(onRejected).not.toHaveBeenCalled(); - expect(onFinally).not.toHaveBeenCalled(); - }); - - test('only triggers `finally` handler once when the cancelable promise is canceled after rejecting', async () => { + test('only triggers `finally` handler once when the cancelable promise is canceled after resolving', async () => { const onFulfilled = jest.fn(); - const cancelablePromise = createCancelablePromise((_, reject) => { - reject(new Error()); - }).finally(onFulfilled, true); + const cancelablePromise = createCancelablePromise((resolve) => { + resolve('ok'); + }).finally(onFulfilled); - await cancelablePromise.catch(noop); + await cancelablePromise; expect(onFulfilled).toHaveBeenCalledTimes(1); @@ -330,7 +209,7 @@ describe('cancelable', () => { expect(onFinally).toHaveBeenCalledWith(); }); - test('does not trigger callbacks when the cancelable promise is canceled then resolves', async () => { + test('only triggers `finally` handler when the cancelable promise is canceled then resolves', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -350,88 +229,13 @@ describe('cancelable', () => { await runAllMicroTasks(); - expect(onFulfilled).not.toHaveBeenCalled(); - expect(onRejected).not.toHaveBeenCalled(); - expect(onFinally).not.toHaveBeenCalled(); - }); - - test('triggers `finally` handler with `runWhenCanceled=true` when the cancelable promise is canceled then resolves', async () => { - const onFulfilled = jest.fn(); - const onRejected = jest.fn(); - const onFinally = jest.fn(); - const cancelablePromise = cancelable( - new Promise((resolve) => { - resolve('ok'); - }) - ); - - cancelablePromise - .then(onFulfilled) - .catch(onRejected) - .finally(onFinally, true); - - expect(cancelablePromise.isCanceled()).toBe(false); - - cancelablePromise.cancel(); - - expect(cancelablePromise.isCanceled()).toBe(true); - - await runAllMicroTasks(); - expect(onFulfilled).not.toHaveBeenCalled(); expect(onRejected).not.toHaveBeenCalled(); expect(onFinally).toHaveBeenCalledTimes(1); expect(onFinally).toHaveBeenCalledWith(); }); - test('does not trigger `finally` handler with `runWhenCanceled=false` when the cancelable promise is canceled then resolves', async () => { - const onFulfilled = jest.fn(); - const onRejected = jest.fn(); - const onFinally = jest.fn(); - const cancelablePromise = cancelable( - new Promise((resolve) => { - resolve('ok'); - }) - ); - - cancelablePromise - .then(onFulfilled) - .catch(onRejected) - .finally(onFinally, false); - - expect(cancelablePromise.isCanceled()).toBe(false); - - cancelablePromise.cancel(); - - expect(cancelablePromise.isCanceled()).toBe(true); - - await runAllMicroTasks(); - - expect(onFulfilled).not.toHaveBeenCalled(); - expect(onRejected).not.toHaveBeenCalled(); - expect(onFinally).not.toHaveBeenCalled(); - }); - - test('only triggers `finally` handler once when the cancelable promise is canceled after resolving', async () => { - const onFulfilled = jest.fn(); - const cancelablePromise = cancelable( - new Promise((resolve) => { - resolve('ok'); - }) - ).finally(onFulfilled, true); - - await cancelablePromise; - - expect(onFulfilled).toHaveBeenCalledTimes(1); - - cancelablePromise.cancel(); - - await runAllMicroTasks(); - - expect(onFulfilled).toHaveBeenCalledTimes(1); - }); - - test('does not trigger callbacks when the cancelable promise is canceled then rejects', async () => { + test('only triggers `finally` handler when the cancelable promise is canceled then rejects', async () => { const onFulfilled = jest.fn(); const onRejected = jest.fn(); const onFinally = jest.fn(); @@ -451,75 +255,19 @@ describe('cancelable', () => { await runAllMicroTasks(); - expect(onFulfilled).not.toHaveBeenCalled(); - expect(onRejected).not.toHaveBeenCalled(); - expect(onFinally).not.toHaveBeenCalled(); - }); - - test('triggers `finally` handler with `runWhenCanceled=true` when the cancelable promise is canceled then rejects', async () => { - const onFulfilled = jest.fn(); - const onRejected = jest.fn(); - const onFinally = jest.fn(); - const cancelablePromise = cancelable( - new Promise((_, reject) => { - reject(new Error()); - }) - ); - - cancelablePromise - .then(onFulfilled) - .catch(onRejected) - .finally(onFinally, true); - - expect(cancelablePromise.isCanceled()).toBe(false); - - cancelablePromise.cancel(); - - expect(cancelablePromise.isCanceled()).toBe(true); - - await runAllMicroTasks(); - expect(onFulfilled).not.toHaveBeenCalled(); expect(onRejected).not.toHaveBeenCalled(); expect(onFinally).toHaveBeenCalledTimes(1); expect(onFinally).toHaveBeenCalledWith(); }); - test('does not trigger `finally` handler with `runWhenCanceled=false` when the cancelable promise is canceled then rejects', async () => { - const onFulfilled = jest.fn(); - const onRejected = jest.fn(); - const onFinally = jest.fn(); - const cancelablePromise = cancelable( - new Promise((_, reject) => { - reject(new Error()); - }) - ); - - cancelablePromise - .then(onFulfilled) - .catch(onRejected) - .finally(onFinally, false); - - expect(cancelablePromise.isCanceled()).toBe(false); - - cancelablePromise.cancel(); - - expect(cancelablePromise.isCanceled()).toBe(true); - - await runAllMicroTasks(); - - expect(onFulfilled).not.toHaveBeenCalled(); - expect(onRejected).not.toHaveBeenCalled(); - expect(onFinally).not.toHaveBeenCalled(); - }); - test('only triggers `finally` handler once when the cancelable promise is canceled after rejecting', async () => { const onFulfilled = jest.fn(); const cancelablePromise = cancelable( new Promise((_, reject) => { reject(new Error()); }) - ).finally(onFulfilled, true); + ).finally(onFulfilled); await cancelablePromise.catch(noop); diff --git a/packages/autocomplete-core/src/utils/createCancelablePromise.ts b/packages/autocomplete-core/src/utils/createCancelablePromise.ts index e271994fc..572fd45d2 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromise.ts @@ -41,8 +41,8 @@ function createInternalCancelablePromise({ state ); }, - finally(onfinally, runWhenCanceled) { - if (runWhenCanceled && onfinally) { + finally(onfinally) { + if (onfinally) { state.onCancelList.push(onfinally); } @@ -51,9 +51,7 @@ function createInternalCancelablePromise({ createCallback( onfinally && (() => { - if (runWhenCanceled) { - state.onCancelList = []; - } + state.onCancelList = []; return onfinally(); }), @@ -107,8 +105,7 @@ export type CancelablePromise = { | null ): CancelablePromise; finally( - onfinally?: (() => void) | undefined | null, - runWhenCanceled?: boolean + onfinally?: (() => void) | undefined | null ): CancelablePromise; cancel(): void; isCanceled(): boolean; diff --git a/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts index fac74bd11..8be605793 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts @@ -19,7 +19,7 @@ export function createCancelablePromiseList< cancelablePromise.catch(noop).finally(() => { list = list.filter((item) => item !== cancelablePromise); - }, true); + }); }, cancelAll() { list.forEach((promise) => promise.cancel()); From 3d65e256cd33c6b3eac26d3e9953eca0aef187ee Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Mon, 24 Jan 2022 21:54:18 +0100 Subject: [PATCH 33/41] feat: clear callbacks once triggered --- .../__tests__/createCancelablePromise.test.ts | 34 +++++++++++++++++++ .../src/utils/createCancelablePromise.ts | 4 ++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts index dcc5a3c1d..a0c484120 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts @@ -143,6 +143,22 @@ describe('createCancelablePromise', () => { expect(onFulfilled).toHaveBeenCalledTimes(1); }); + test('only triggers `finally` handler once when calling `cancel` several times', async () => { + const onFinally = jest.fn(); + const cancelablePromise = createCancelablePromise((resolve) => { + resolve('ok'); + }); + + cancelablePromise.finally(onFinally); + + cancelablePromise.cancel(); + cancelablePromise.cancel(); + + await runAllMicroTasks(); + + expect(onFinally).toHaveBeenCalledTimes(1); + }); + test('cancels nested cancelable promises', async () => { const onFulfilled = jest.fn(); const cancelablePromise = createCancelablePromise((resolve) => { @@ -280,6 +296,24 @@ describe('cancelable', () => { expect(onFulfilled).toHaveBeenCalledTimes(1); }); + test('only triggers `finally` handler once when calling `cancel` several times', async () => { + const onFinally = jest.fn(); + const cancelablePromise = cancelable( + new Promise((resolve) => { + resolve('ok'); + }) + ); + + cancelablePromise.finally(onFinally); + + cancelablePromise.cancel(); + cancelablePromise.cancel(); + + await runAllMicroTasks(); + + expect(onFinally).toHaveBeenCalledTimes(1); + }); + test('cancels nested cancelable promises', async () => { const onFulfilled = jest.fn(); diff --git a/packages/autocomplete-core/src/utils/createCancelablePromise.ts b/packages/autocomplete-core/src/utils/createCancelablePromise.ts index 572fd45d2..354730a29 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromise.ts @@ -64,8 +64,10 @@ function createInternalCancelablePromise({ }, cancel() { state.isCanceled = true; + const callbacks = state.onCancelList; + state.onCancelList = []; - state.onCancelList.forEach((callback) => { + callbacks.forEach((callback) => { callback(); }); }, From 5163e7d7635f0c6c0f98e7d70eed86ec4cf6f480 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Mon, 24 Jan 2022 21:55:01 +0100 Subject: [PATCH 34/41] test: add missing test in both suites --- .../__tests__/createCancelablePromise.test.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts index a0c484120..2dfc49d4f 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromise.test.ts @@ -143,6 +143,23 @@ describe('createCancelablePromise', () => { expect(onFulfilled).toHaveBeenCalledTimes(1); }); + test('only triggers `finally` handler once when the cancelable promise is canceled after rejecting', async () => { + const onFulfilled = jest.fn(); + const cancelablePromise = createCancelablePromise((_, reject) => { + reject(new Error()); + }).finally(onFulfilled); + + await cancelablePromise.catch(noop); + + expect(onFulfilled).toHaveBeenCalledTimes(1); + + cancelablePromise.cancel(); + + await runAllMicroTasks(); + + expect(onFulfilled).toHaveBeenCalledTimes(1); + }); + test('only triggers `finally` handler once when calling `cancel` several times', async () => { const onFinally = jest.fn(); const cancelablePromise = createCancelablePromise((resolve) => { @@ -277,6 +294,25 @@ describe('cancelable', () => { expect(onFinally).toHaveBeenCalledWith(); }); + test('only triggers `finally` handler once when the cancelable promise is canceled after resolving', async () => { + const onFulfilled = jest.fn(); + const cancelablePromise = cancelable( + new Promise((resolve) => { + resolve('ok'); + }) + ).finally(onFulfilled); + + await cancelablePromise; + + expect(onFulfilled).toHaveBeenCalledTimes(1); + + cancelablePromise.cancel(); + + await runAllMicroTasks(); + + expect(onFulfilled).toHaveBeenCalledTimes(1); + }); + test('only triggers `finally` handler once when the cancelable promise is canceled after rejecting', async () => { const onFulfilled = jest.fn(); const cancelablePromise = cancelable( From de4a205c60b2cea547d3bc4f9a174696fb8ccb55 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Tue, 25 Jan 2022 14:31:23 +0100 Subject: [PATCH 35/41] fix: s/running/pending/ --- packages/autocomplete-core/src/getPropGetters.ts | 2 +- packages/autocomplete-core/src/onKeyDown.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/autocomplete-core/src/getPropGetters.ts b/packages/autocomplete-core/src/getPropGetters.ts index 373c82858..941ba96c1 100644 --- a/packages/autocomplete-core/src/getPropGetters.ts +++ b/packages/autocomplete-core/src/getPropGetters.ts @@ -208,7 +208,7 @@ export function getPropGetters< if (!isTouchDevice) { store.dispatch('blur', null); - // If requests are still running when the user closes the panel, they + // If requests are still pending when the user closes the panel, they // could reopen the panel once they resolve. // We want to prevent any subsequent query from reopening the panel // because it would result in an unsolicited UI behavior. diff --git a/packages/autocomplete-core/src/onKeyDown.ts b/packages/autocomplete-core/src/onKeyDown.ts index 4bf3d6d4d..65b9135c3 100644 --- a/packages/autocomplete-core/src/onKeyDown.ts +++ b/packages/autocomplete-core/src/onKeyDown.ts @@ -102,7 +102,7 @@ export function onKeyDown({ // Hitting the `Escape` key signals the end of a user interaction with the // autocomplete. At this point, we should ignore any requests that are still - // running and could reopen the panel once they resolve, because that would + // pending and could reopen the panel once they resolve, because that would // result in an unsolicited UI behavior. store.pendingRequests.cancelAll(); } else if (event.key === 'Enter') { From ffd16fe49affb6c802368aff76057b8648912f2b Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Tue, 25 Jan 2022 16:36:34 +0100 Subject: [PATCH 36/41] refactor: always pass all parameters and avoid defaults --- .../src/utils/createCancelablePromise.ts | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/autocomplete-core/src/utils/createCancelablePromise.ts b/packages/autocomplete-core/src/utils/createCancelablePromise.ts index 354730a29..b83d4503d 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromise.ts @@ -1,5 +1,3 @@ -import { noop } from '@algolia/autocomplete-shared'; - type CancelablePromiseState = { isCanceled: boolean; onCancelList: Array<(...args: any[]) => any>; @@ -11,17 +9,13 @@ type PromiseExecutor = ( ) => void; type CreateInternalCancelablePromiseParams = { - executor?: PromiseExecutor; - promise?: Promise; - initialState?: CancelablePromiseState; + promise: Promise; + initialState: CancelablePromiseState; }; function createInternalCancelablePromise({ - executor = noop, - initialState = createInitialState(), - promise = new Promise((resolve, reject) => { - return executor(resolve, reject); - }), + initialState, + promise, }: CreateInternalCancelablePromiseParams): CancelablePromise { const state = initialState; @@ -116,7 +110,12 @@ export type CancelablePromise = { export function createCancelablePromise( executor: PromiseExecutor ): CancelablePromise { - return createInternalCancelablePromise({ executor }); + return createInternalCancelablePromise({ + initialState: createInitialState(), + promise: new Promise((resolve, reject) => { + return executor(resolve, reject); + }), + }); } createCancelablePromise.resolve = ( @@ -134,7 +133,10 @@ function createCancelable( promise: Promise, initialState: CancelablePromiseState = createInitialState() ) { - return createInternalCancelablePromise({ promise, initialState }); + return createInternalCancelablePromise({ + promise, + initialState, + }); } function createCallback( From c14844cb0df7e4b6e048dcc6e239df543170c092 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Tue, 25 Jan 2022 22:30:50 +0100 Subject: [PATCH 37/41] fix: avoid swallowing errors and return promise chain instead --- packages/autocomplete-core/src/onInput.ts | 4 +--- .../__tests__/createCancelablePromiseList.test.ts | 8 ++++---- .../src/utils/createCancelablePromiseList.ts | 6 ++---- .../autocomplete-js/src/__tests__/autocomplete.test.ts | 10 ++++++++-- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/autocomplete-core/src/onInput.ts b/packages/autocomplete-core/src/onInput.ts index b358656d9..b1bc80ba9 100644 --- a/packages/autocomplete-core/src/onInput.ts +++ b/packages/autocomplete-core/src/onInput.ts @@ -169,7 +169,5 @@ export function onInput({ } }); - store.pendingRequests.add(request); - - return request; + return store.pendingRequests.add(request); } diff --git a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts index cca3fa9e2..a0ccc7064 100644 --- a/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/createCancelablePromiseList.test.ts @@ -31,7 +31,7 @@ describe('createCancelablePromiseList', () => { const cancelablePromiseList = createCancelablePromiseList(); const cancelablePromise = createCancelablePromise.reject(); - cancelablePromiseList.add(cancelablePromise); + cancelablePromiseList.add(cancelablePromise).catch(noop); expect(cancelablePromiseList.isEmpty()).toBe(false); @@ -59,9 +59,9 @@ describe('createCancelablePromiseList', () => { const cancelablePromise2 = createCancelablePromise.reject(); const cancelablePromise3 = createCancelablePromise(noop); - cancelablePromiseList.add(cancelablePromise1); - cancelablePromiseList.add(cancelablePromise2); - cancelablePromiseList.add(cancelablePromise3); + cancelablePromiseList.add(cancelablePromise1).catch(noop); + cancelablePromiseList.add(cancelablePromise2).catch(noop); + cancelablePromiseList.add(cancelablePromise3).catch(noop); expect(cancelablePromise1.isCanceled()).toBe(false); expect(cancelablePromise2.isCanceled()).toBe(false); diff --git a/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts index 8be605793..d48d3f4e2 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromiseList.ts @@ -1,9 +1,7 @@ -import { noop } from '@algolia/autocomplete-shared'; - import { CancelablePromise } from '.'; export type CancelablePromiseList = { - add(cancelablePromise: CancelablePromise): void; + add(cancelablePromise: CancelablePromise): CancelablePromise; cancelAll(): void; isEmpty(): boolean; }; @@ -17,7 +15,7 @@ export function createCancelablePromiseList< add(cancelablePromise) { list.push(cancelablePromise); - cancelablePromise.catch(noop).finally(() => { + return cancelablePromise.finally(() => { list = list.filter((item) => item !== cancelablePromise); }); }, diff --git a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts index 6af5bd29c..4b01c21bc 100644 --- a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts +++ b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts @@ -1,7 +1,11 @@ import * as autocompleteShared from '@algolia/autocomplete-shared'; import { fireEvent, waitFor } from '@testing-library/dom'; -import { castToJestMock, createMatchMedia } from '../../../../test/utils'; +import { + castToJestMock, + createMatchMedia, + runAllMicroTasks, +} from '../../../../test/utils'; import { autocomplete } from '../autocomplete'; jest.mock('@algolia/autocomplete-shared', () => { @@ -524,7 +528,7 @@ describe('autocomplete-js', () => { expect(input).toHaveValue('Query'); }); - test('renders on input', () => { + test('renders on input', async () => { const container = document.createElement('div'); autocomplete<{ label: string }>({ id: 'autocomplete', @@ -554,6 +558,8 @@ describe('autocomplete-js', () => { fireEvent.input(input, { target: { value: 'a' } }); + await runAllMicroTasks(); + expect(input).toHaveValue('a'); }); }); From 6038735a0fa698d5282561c45ae3f41fbf0bfcdc Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Wed, 26 Jan 2022 11:28:56 +0100 Subject: [PATCH 38/41] fix: apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: François Chalifour --- packages/autocomplete-core/src/getPropGetters.ts | 2 +- packages/autocomplete-core/src/onInput.ts | 4 +--- .../autocomplete-core/src/utils/createCancelablePromise.ts | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/autocomplete-core/src/getPropGetters.ts b/packages/autocomplete-core/src/getPropGetters.ts index 941ba96c1..0944a2ab1 100644 --- a/packages/autocomplete-core/src/getPropGetters.ts +++ b/packages/autocomplete-core/src/getPropGetters.ts @@ -47,7 +47,7 @@ export function getPropGetters< // (no interaction with the autocomplete, no future state updates) // - OR the touched target is the input element (should open the panel) const isAutocompleteInteraction = - store.getState().isOpen === true || !store.pendingRequests.isEmpty(); + store.getState().isOpen || !store.pendingRequests.isEmpty(); if (!isAutocompleteInteraction || event.target === inputElement) { return; diff --git a/packages/autocomplete-core/src/onInput.ts b/packages/autocomplete-core/src/onInput.ts index b1bc80ba9..043c1fb34 100644 --- a/packages/autocomplete-core/src/onInput.ts +++ b/packages/autocomplete-core/src/onInput.ts @@ -78,9 +78,7 @@ export function onInput({ runConcurrentSafePromise(collections).then(() => Promise.resolve()) ); - store.pendingRequests.add(request); - - return request; + return store.pendingRequests.add(request); } setStatus('loading'); diff --git a/packages/autocomplete-core/src/utils/createCancelablePromise.ts b/packages/autocomplete-core/src/utils/createCancelablePromise.ts index b83d4503d..5ba84e549 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromise.ts @@ -126,12 +126,12 @@ createCancelablePromise.reject = (reason?: any) => cancelable(Promise.reject(reason)); export function cancelable(promise: Promise) { - return createCancelable(promise); + return createCancelable(promise, createInitialState()); } function createCancelable( promise: Promise, - initialState: CancelablePromiseState = createInitialState() + initialState: CancelablePromiseState ) { return createInternalCancelablePromise({ promise, From d6b9d646a0f5a8fa54d57b3b966c2caac8e0356c Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Wed, 26 Jan 2022 11:41:55 +0100 Subject: [PATCH 39/41] build: adjust bundlesize --- bundlesize.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundlesize.config.json b/bundlesize.config.json index 24dc47d47..47a33b267 100644 --- a/bundlesize.config.json +++ b/bundlesize.config.json @@ -2,7 +2,7 @@ "files": [ { "path": "packages/autocomplete-core/dist/umd/index.production.js", - "maxSize": "6.25 kB" + "maxSize": "6 kB" }, { "path": "packages/autocomplete-js/dist/umd/index.production.js", From 5735c23d045de01cdcfc3d41a5900d858a3ce1a8 Mon Sep 17 00:00:00 2001 From: Haroen Viaene Date: Wed, 26 Jan 2022 11:56:00 +0100 Subject: [PATCH 40/41] chore(cancelable): simplify code (#876) --- .../src/utils/createCancelablePromise.ts | 50 +++++++------------ 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/packages/autocomplete-core/src/utils/createCancelablePromise.ts b/packages/autocomplete-core/src/utils/createCancelablePromise.ts index 5ba84e549..8c770ae92 100644 --- a/packages/autocomplete-core/src/utils/createCancelablePromise.ts +++ b/packages/autocomplete-core/src/utils/createCancelablePromise.ts @@ -1,27 +1,22 @@ -type CancelablePromiseState = { - isCanceled: boolean; - onCancelList: Array<(...args: any[]) => any>; -}; - type PromiseExecutor = ( resolve: (value: TValue | PromiseLike) => void, reject: (reason?: any) => void ) => void; -type CreateInternalCancelablePromiseParams = { - promise: Promise; - initialState: CancelablePromiseState; +type CancelablePromiseState = { + isCanceled: boolean; + onCancelList: Array<(...args: any[]) => any>; }; -function createInternalCancelablePromise({ - initialState, - promise, -}: CreateInternalCancelablePromiseParams): CancelablePromise { +function createInternalCancelablePromise( + promise: Promise, + initialState: CancelablePromiseState +): CancelablePromise { const state = initialState; return { then(onfulfilled, onrejected) { - return createCancelable( + return createInternalCancelablePromise( promise.then( createCallback(onfulfilled, state, promise), createCallback(onrejected, state, promise) @@ -30,7 +25,7 @@ function createInternalCancelablePromise({ ); }, catch(onrejected) { - return createCancelable( + return createInternalCancelablePromise( promise.catch(createCallback(onrejected, state, promise)), state ); @@ -40,7 +35,7 @@ function createInternalCancelablePromise({ state.onCancelList.push(onfinally); } - return createCancelable( + return createInternalCancelablePromise( promise.finally( createCallback( onfinally && @@ -110,12 +105,12 @@ export type CancelablePromise = { export function createCancelablePromise( executor: PromiseExecutor ): CancelablePromise { - return createInternalCancelablePromise({ - initialState: createInitialState(), - promise: new Promise((resolve, reject) => { + return createInternalCancelablePromise( + new Promise((resolve, reject) => { return executor(resolve, reject); }), - }); + { isCanceled: false, onCancelList: [] } + ); } createCancelablePromise.resolve = ( @@ -126,16 +121,9 @@ createCancelablePromise.reject = (reason?: any) => cancelable(Promise.reject(reason)); export function cancelable(promise: Promise) { - return createCancelable(promise, createInitialState()); -} - -function createCancelable( - promise: Promise, - initialState: CancelablePromiseState -) { - return createInternalCancelablePromise({ - promise, - initialState, + return createInternalCancelablePromise(promise, { + isCanceled: false, + onCancelList: [], }); } @@ -156,7 +144,3 @@ function createCallback( return onResult(arg); }; } - -function createInitialState(): CancelablePromiseState { - return { isCanceled: false, onCancelList: [] }; -} From 4fc760af7072fb5446e67d37f58052edd784de3c Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Wed, 26 Jan 2022 11:56:35 +0100 Subject: [PATCH 41/41] build: adjust bundlesize --- bundlesize.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundlesize.config.json b/bundlesize.config.json index 47a33b267..e71598ae0 100644 --- a/bundlesize.config.json +++ b/bundlesize.config.json @@ -6,7 +6,7 @@ }, { "path": "packages/autocomplete-js/dist/umd/index.production.js", - "maxSize": "16.75 kB" + "maxSize": "16.5 kB" }, { "path": "packages/autocomplete-preset-algolia/dist/umd/index.production.js",