From bdd48c9a3d59fa2f73531c66a3b9351cc2b78b9c Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 23 May 2021 13:48:34 +0200 Subject: [PATCH 001/117] made Collection Item primaryKey optional with warning --- README.md | 5 ++++- packages/core/src/collection/index.ts | 6 +++--- .../tests/unit/collection/collection.test.ts | 20 ++++++++++++++----- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 48b2d9ac..3a1ae771 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ const App = new Agile(); const MY_FIRST_STATE = App.createState("Hello Friend!"); -// -- myComponent.whatever ------------------------------------------ +// -- MyComponent.whatever ------------------------------------------ // 3️⃣ Bind initialized State to desired UI-Component // And wolla, it's reactive. Everytime the State mutates the Component rerenders @@ -82,8 +82,10 @@ Besides States, AgileTs offers some other powerful APIs that make your life easi The philosophy behind AgileTs is simple: ### 🚅 Straightforward + Write minimalistic, boilerplate-free code that captures your intent. ```ts +const MY_STATE = App.createState('frank'); // Create State MY_STATE.set('jeff'); // Update State value MY_STATE.undo(); // Undo latest State value change MY_STATE.is({hello: "jeff"}); // Check if State has the value '{hello: "jeff"}' @@ -101,6 +103,7 @@ MY_STATE.watch((value) => {console.log(value);}); // Watch on State changes const MY_COLLECTION = App.createCollection(); MY_COLLECTION.collect({id: 1, name: "Frank"}); MY_COLLECTION.collect({id: 2, name: "Dieter"}); + MY_COLLECTION.update(1, {name: "Jeff"}); ``` - Compute State depending on other States ```ts diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index ca8bff15..2d277e17 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -1178,10 +1178,10 @@ export class Collection { } if (!Object.prototype.hasOwnProperty.call(_data, primaryKey)) { - Agile.logger.error( - `Collection '${this._key}' Item Data has to contain a primaryKey property called '${this.config.primaryKey}'!` + Agile.logger.warn( + `Collection '${this._key}' Item Data should contain a primaryKey property called '${this.config.primaryKey}'!` ); - return false; + _data[this.config.primaryKey] = generateId(); } const itemKey = _data[primaryKey]; diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index cfced39f..560c2c16 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -2330,15 +2330,25 @@ describe('Collection Tests', () => { expect(collection.size).toBe(1); }); - it("shouldn't create new Item if passed Data has no primaryKey", () => { + it('should create new Item with random primaryKey if passed Data has no primaryKey', () => { + jest.spyOn(Utils, 'generateId').mockReturnValueOnce('randomDummyId'); + const response = collection.setData({ name: 'Frank' } as any); - expect(console.error).toHaveBeenCalledWith( - `Agile Error: Collection '${collection._key}' Item Data has to contain a primaryKey property called '${collection.config.primaryKey}'!` + expect(console.warn).toHaveBeenCalledWith( + `Agile Warn: Collection '${collection._key}' Item Data should contain a primaryKey property called '${collection.config.primaryKey}'!` ); - expect(response).toBeFalsy(); - expect(collection.size).toBe(1); + expect(response).toBeTruthy(); + expect(response).toBeTruthy(); + expect(collection.data).toHaveProperty('dummyItem1'); + expect(collection.data).toHaveProperty('randomDummyId'); + expect(collection.data['randomDummyId']).toBeInstanceOf(Item); + expect(collection.data['randomDummyId']._value).toStrictEqual({ + id: 'randomDummyId', + name: 'Frank', + }); + expect(collection.size).toBe(2); }); it("should update Item with valid Data, shouldn't rebuild Groups and shouldn't increase size (default config)", () => { From cd45a8741090eb6f7bb1231271aa3f98fd4faa2c Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 24 May 2021 12:51:51 +0200 Subject: [PATCH 002/117] started outsourcing logs --- packages/core/src/agile.ts | 9 +- .../src/collection/collection.persistent.ts | 34 ++-- packages/core/src/collection/group.ts | 2 +- packages/core/src/collection/index.ts | 62 +++---- packages/core/src/collection/item.ts | 2 +- packages/core/src/collection/selector.ts | 2 +- packages/core/src/computed/index.ts | 19 +- packages/core/src/integrations/index.ts | 12 +- packages/core/src/internal.ts | 1 + packages/core/src/loggingHandler.ts | 173 ++++++++++++++++++ packages/core/src/runtime/index.ts | 23 +-- packages/core/src/runtime/observer.ts | 5 +- .../runtime/subscription/sub.controller.ts | 73 +++----- packages/core/src/state/index.ts | 77 ++++---- packages/core/src/state/state.persistent.ts | 30 +-- packages/core/src/storages/index.ts | 65 +++---- packages/core/src/storages/persistent.ts | 43 ++--- packages/core/src/storages/storage.ts | 11 +- packages/proxytree/src/branch.ts | 4 +- packages/proxytree/src/proxytree.ts | 4 +- 20 files changed, 381 insertions(+), 270 deletions(-) create mode 100644 packages/core/src/loggingHandler.ts diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index 7489de46..ed03fb38 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -20,6 +20,7 @@ import { StateConfigInterface, flatMerge, Group, + LoggingHandler, } from './internal'; export class Agile { @@ -76,15 +77,13 @@ export class Agile { Agile.logger = new Logger(config.logConfig); // Logging - Agile.logger.success('Created new AgileInstance ', this, Agile.logger); + LoggingHandler.logs.createdAgileInstanceSuccess(this, Agile.logger); // Create global instance of Agile // Why? getAgileInstance() returns the global AgileInstance if it couldn't find the Agile Instance in the passed Instance if (config.bindGlobal) { if (!globalBind(Agile.globalKey, this)) - Agile.logger.warn( - 'Be careful with binding multiple Agile Instances globally in one Application!' - ); + LoggingHandler.logs.multipleGlobalBoundAgileInstancesWarning(); } } @@ -124,7 +123,7 @@ export class Agile { * Collection - Class that holds a List of Objects with key and causes rerender on subscribed Components * @param config - Config */ - public createCollection( + public createCollection( config?: CollectionConfig ): Collection { return new Collection(this, config); diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index be9be6f1..e2acd7ef 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -14,7 +14,7 @@ import { } from '../internal'; export class CollectionPersistent< - DataType extends object = DefaultItem + DataType extends Object = DefaultItem > extends Persistent { public collection: () => Collection; @@ -101,18 +101,18 @@ export class CollectionPersistent< /** * @internal * Loads Collection from Storage - * @param storageKey - Prefix Key of Persisted Instances (default PersistentKey) + * @param storageItemKey - Prefix Key of Persisted Instances (default PersistentKey) * @return Success? */ public async loadPersistedValue( - storageKey?: PersistentKey + storageItemKey?: PersistentKey ): Promise { if (!this.ready) return false; - const _storageKey = storageKey || this._key; + const _storageItemKey = storageItemKey || this._key; // Check if Collection is Persisted const isPersisted = await this.agileInstance().storages.get( - _storageKey, + _storageItemKey, this.config.defaultStorageKey as any ); if (!isPersisted) return false; @@ -138,7 +138,7 @@ export class CollectionPersistent< for (const itemKey of defaultGroup._value) { const itemStorageKey = CollectionPersistent.getItemStorageKey( itemKey, - _storageKey + _storageItemKey ); // Get Storage Value @@ -156,7 +156,7 @@ export class CollectionPersistent< const success = await loadValuesIntoCollection(); // Persist Collection, so that the Storage Value updates dynamically if the Collection updates - if (success) await this.persistValue(_storageKey); + if (success) await this.persistValue(_storageItemKey); return success; } @@ -167,19 +167,19 @@ export class CollectionPersistent< /** * @internal * Sets everything up so that the Collection gets saved in the Storage - * @param storageKey - Prefix Key of Persisted Instances (default PersistentKey) + * @param storageItemKey - Prefix Key of Persisted Instances (default PersistentKey) * @return Success? */ - public async persistValue(storageKey?: PersistentKey): Promise { + public async persistValue(storageItemKey?: PersistentKey): Promise { if (!this.ready) return false; - const _storageKey = storageKey || this._key; + const _storageItemKey = storageItemKey || this._key; const defaultGroup = this.collection().getGroup( this.collection().config.defaultGroupKey ); if (!defaultGroup) return false; // Set Collection to Persisted (in Storage) - this.agileInstance().storages.set(_storageKey, true, this.storageKeys); + this.agileInstance().storages.set(_storageItemKey, true, this.storageKeys); // Persist default Group if (!defaultGroup.isPersisted) @@ -188,7 +188,7 @@ export class CollectionPersistent< // Add sideEffect to default Group which adds and removes Items from the Storage depending on the Group Value defaultGroup.addSideEffect( CollectionPersistent.defaultGroupSideEffectKey, - () => this.rebuildStorageSideEffect(defaultGroup, _storageKey), + () => this.rebuildStorageSideEffect(defaultGroup, _storageItemKey), { weight: 0 } ); @@ -197,7 +197,7 @@ export class CollectionPersistent< const item = this.collection().getItem(itemKey); const itemStorageKey = CollectionPersistent.getItemStorageKey( itemKey, - _storageKey + _storageItemKey ); item?.persist(itemStorageKey); } @@ -212,21 +212,21 @@ export class CollectionPersistent< /** * @internal * Removes Collection from the Storage - * @param storageKey - Prefix Key of Persisted Instances (default PersistentKey) + * @param storageItemKey - Prefix Key of Persisted Instances (default PersistentKey) * @return Success? */ public async removePersistedValue( - storageKey?: PersistentKey + storageItemKey?: PersistentKey ): Promise { if (!this.ready) return false; - const _storageKey = storageKey || this._key; + const _storageItemKey = storageItemKey || this._key; const defaultGroup = this.collection().getGroup( this.collection().config.defaultGroupKey ); if (!defaultGroup) return false; // Set Collection to not Persisted - this.agileInstance().storages.remove(_storageKey, this.storageKeys); + this.agileInstance().storages.remove(_storageItemKey, this.storageKeys); // Remove default Group from Storage defaultGroup.persistent?.removePersistedValue(); diff --git a/packages/core/src/collection/group.ts b/packages/core/src/collection/group.ts index 25895404..691ad9e7 100644 --- a/packages/core/src/collection/group.ts +++ b/packages/core/src/collection/group.ts @@ -18,7 +18,7 @@ import { removeProperties, } from '../internal'; -export class Group extends State< +export class Group extends State< Array > { static rebuildGroupSideEffectKey = 'rebuildGroup'; diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 2d277e17..96bb36e2 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -18,9 +18,10 @@ import { SideEffectConfigInterface, SelectorConfigInterface, removeProperties, + LoggingHandler, } from '../internal'; -export class Collection { +export class Collection { public agileInstance: () => Agile; public config: CollectionConfigInterface; @@ -126,14 +127,8 @@ export class Collection { config: GroupConfigInterface = {} ): Group { if (this.isInstantiated) { - const key = config?.key || generateId(); - Agile.logger.warn( - "After the instantiation we recommend using 'MY_COLLECTION.createGroup' instead of 'MY_COLLECTION.Group'" - ); - if (config?.key == null) - Agile.logger.warn( - `Failed to find key for creation of Group. Group with random key '${key}' got created!` - ); + const key = config?.key ?? generateId(); + LoggingHandler.logs.useCreateGroupAfterInstantiationWarning(); return this.createGroup(key, initialItems); } @@ -154,14 +149,8 @@ export class Collection { config: SelectorConfigInterface = {} ): Selector { if (this.isInstantiated) { - const key = config?.key || generateId(); - Agile.logger.warn( - "After the instantiation we recommend using 'MY_COLLECTION.createSelector' instead of 'MY_COLLECTION.Selector'" - ); - if (config?.key == null) - Agile.logger.warn( - `Failed to find key for creation of Selector. Selector with random key '${key}' got created!` - ); + const key = config?.key ?? generateId(); + LoggingHandler.logs.useCreateSelectorAfterInstantiationWarning(); return this.createSelector(key, initialKey); } @@ -311,14 +300,16 @@ export class Collection { }); if (!item) { - Agile.logger.error( - `Item with key/name '${itemKey}' doesn't exist in Collection '${this._key}'!` + LoggingHandler.logs.itemAtKeyDoesNotExistInCollectionError( + itemKey, + this._key ); return undefined; } if (!isValidObject(changes)) { - Agile.logger.error( - `You have to pass an valid Changes Object to update '${itemKey}' in '${this._key}'!` + LoggingHandler.logs.validObjectRequiredToUpdateCollectionItemError( + itemKey, + this._key ); return undefined; } @@ -356,10 +347,7 @@ export class Collection { // To make sure that the primaryKey doesn't differ from the changes object primaryKey if (changes[this.config.primaryKey] !== itemKey) { changes[this.config.primaryKey] = itemKey; - Agile.logger.warn( - `By overwriting the whole Item don't forget passing the correct primaryKey!`, - changes - ); + LoggingHandler.logs.overwriteWholeItemWarning(changes); } // Apply changes to Item @@ -386,16 +374,13 @@ export class Collection { ): Group { let group = this.getGroup(groupKey, { notExisting: true }); - if (!this.isInstantiated) { - Agile.logger.warn( - "We recommend to use 'MY_COLLECTION.Group' instead of 'MY_COLLECTION.createGroup' in the Collection config!" - ); - } + if (!this.isInstantiated) + LoggingHandler.logs.useGroupMethodBeforeInstantiationWarning(); // Check if Group already exists if (group) { if (!group.isPlaceholder) { - Agile.logger.warn(`Group with the name '${groupKey}' already exists!`); + LoggingHandler.logs.xAlreadyExistsAtKeyYError('Group', groupKey); return group; } group.set(initialItems, { overwrite: true }); @@ -498,7 +483,7 @@ export class Collection { */ public removeGroup(groupKey: GroupKey): this { if (!this.groups[groupKey]) { - Agile.logger.warn(`Group with the key/name '${groupKey}' doesn't exist!`); + LoggingHandler.logs.xDoesNotExistsAtKeyYError('Group', groupKey); return this; } delete this.groups[groupKey]; @@ -520,18 +505,13 @@ export class Collection { ): Selector { let selector = this.getSelector(selectorKey, { notExisting: true }); - if (!this.isInstantiated) { - Agile.logger.warn( - "We recommend to use 'MY_COLLECTION.Selector' instead of 'MY_COLLECTION.createSelector' in the Collection config!" - ); - } + if (!this.isInstantiated) + LoggingHandler.logs.useSelectorMethodBeforeInstantiationWarning(); // Check if Selector already exists if (selector) { if (!selector.isPlaceholder) { - Agile.logger.warn( - `Selector with the name '${selectorKey}' already exists!` - ); + LoggingHandler.logs.xAlreadyExistsAtKeyYError('Selector', selectorKey); return selector; } selector.select(itemKey, { overwrite: true }); @@ -1347,7 +1327,7 @@ export interface SetDataConfigInterface { background?: boolean; } -export type CollectionConfig = +export type CollectionConfig = | CreateCollectionConfigInterface | (( collection: Collection diff --git a/packages/core/src/collection/item.ts b/packages/core/src/collection/item.ts index 0930064e..65e329a9 100644 --- a/packages/core/src/collection/item.ts +++ b/packages/core/src/collection/item.ts @@ -7,7 +7,7 @@ import { defineConfig, } from '../internal'; -export class Item extends State< +export class Item extends State< DataType > { static updateGroupSideEffectKey = 'rebuildGroup'; diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index fc2d5144..0a7acce2 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -9,7 +9,7 @@ import { StateRuntimeJobConfigInterface, } from '../internal'; -export class Selector extends State< +export class Selector extends State< DataType | undefined > { static dummyItemKey = 'unknown'; diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index 2af67c45..72f645aa 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -9,6 +9,7 @@ import { extractObservers, StateIngestConfigInterface, removeProperties, + LoggingHandler, } from '../internal'; export class Computed extends State< @@ -149,17 +150,29 @@ export class Computed extends State< //========================================================================================================= public patch() { - Agile.logger.error("You can't use patch method on ComputedState!"); + LoggingHandler.logs.canNotUseMethodXOnClassX( + 'patch', + 'Computed', + "The Computed has a dynamic value which shouldn't be changed manually." + ); return this; } public persist(): this { - Agile.logger.error("You can't use persist method on ComputedState!"); + LoggingHandler.logs.canNotUseMethodXOnClassX( + 'persist', + 'Computed', + "Dynamic values shouldn't be persisted. Consider persisting the values the Computed depends on." + ); return this; } public invert(): this { - Agile.logger.error("You can't use invert method on ComputedState!"); + LoggingHandler.logs.canNotUseMethodXOnClassX( + 'invert', + 'Computed', + "The Computed has a dynamic value which shouldn't be changed manually." + ); return this; } } diff --git a/packages/core/src/integrations/index.ts b/packages/core/src/integrations/index.ts index f3ad804b..d2b4b21e 100644 --- a/packages/core/src/integrations/index.ts +++ b/packages/core/src/integrations/index.ts @@ -1,4 +1,4 @@ -import { Agile, Integration } from '../internal'; +import { Agile, Integration, LoggingHandler } from '../internal'; export class Integrations { public agileInstance: () => Agile; @@ -30,10 +30,7 @@ export class Integrations { public async integrate(integration: Integration): Promise { // Check if Integration is valid if (!integration._key) { - Agile.logger.error( - 'Failed to integrate framework! Invalid Integration!', - integration._key - ); + LoggingHandler.logs.failedToIntegrateFrameworkError(integration); return false; } @@ -46,8 +43,7 @@ export class Integrations { this.integrations.add(integration); integration.integrated = true; - // Logging - Agile.logger.success(`Integrated '${integration._key}' into AgileTs`); + LoggingHandler.logs.integratedFrameworkSuccess(integration); return true; } @@ -65,7 +61,7 @@ export class Integrations { public update(componentInstance: any, updatedData: Object): void { this.integrations.forEach((integration) => { if (!integration.ready) { - Agile.logger.warn(`Integration '${integration.key}' isn't ready yet!`); + LoggingHandler.logs.notReadyIntegrationWarning(integration); return; } if (integration.methods.updateMethod) diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index d0d7af8f..1e53fb0d 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -6,6 +6,7 @@ // Logger export * from '@agile-ts/logger'; +export * from './loggingHandler'; // Utils export * from './utils'; diff --git a/packages/core/src/loggingHandler.ts b/packages/core/src/loggingHandler.ts new file mode 100644 index 00000000..6a3f2c28 --- /dev/null +++ b/packages/core/src/loggingHandler.ts @@ -0,0 +1,173 @@ +import { Agile } from './internal'; + +const l = Agile.logger; + +export class LoggingHandler { + public static logs = { + // Agile + createdAgileInstanceSuccess: (agileInstance: any, loggerInstance: any) => + l.success('Created new AgileInstance ', agileInstance, loggerInstance), + multipleGlobalBoundAgileInstancesWarning: () => + l.warn( + 'Be careful with binding multiple Agile Instances globally in one Application!' + ), + + // Storages + localStorageNotAvailableWarning: () => + l.warn( + `The 'Local Storage' is in your current environment not available. + To use the .persist() functionality please provide a custom Storage!` + ), + firstAssignedStorageHasToBeDefaultStorageWarning: () => + l.warn( + 'The first allocated Storage for AgileTs must be set as the default Storage!' + ), + storageAtKeyNotReadyError: (key: any) => + l.error(`Storage with the key/name '${key}' isn't ready yet!`), + noStorageFoundError: () => + l.error('No Storage found! Please provide at least one Storage.'), + + // Persistent + noPersistKeyFoundError: () => + l.error( + 'No valid persist Key found! Please provide a Key or assign one to the parent instance.' + ), + noPersistStorageKeyFoundError: () => + l.error( + 'No persist Storage Key found! Please provide at least one Storage Key.' + ), + + // Storage + noValidStorageMethodError: (methodName: string) => + l.error(`Invalid ${methodName}() method as StorageMethod provided!`), + normalGetInAsyncStorageWarning: () => + l.warn( + 'Using normalGet() in a async based Storage might lead to a unexpected return value. Instead of an resolved value it returns an Promise!' + ), + + // Runtime + createdRuntimeJobInfo: (job: any) => + l.if.tag(['runtime']).info(`Created Job '${job._key}'`, job), + completedRuntimeJobInfo: (job: any) => + l.if.tag(['runtime']).info(`Completed Job '${job._key}'`, job), + notReadySubscriptionContainerWarning: (subscriptionContainer: any) => + l.warn( + "SubscriptionContainer/Component isn't ready to rerender!", + subscriptionContainer + ), + removedJobExpiredJobFromRuntimeWarning: ( + subscriptionContainer: any, + triesOfUpdating + ) => + l.warn( + `Job with not ready SubscriptionContainer/Component was removed from the runtime after ${triesOfUpdating} tries to avoid an overflow.`, + subscriptionContainer + ), + updatedSubscriptionsInfo: (subscriptionsToUpdate: any) => + l.if + .tag(['runtime']) + .info('Updated/Rerendered Subscriptions', subscriptionsToUpdate), + + // SubController + unregisteredSubscriptionInfo: (subscriptionInstance: any, based: string) => + l.if + .tag(['runtime', 'subscription']) + .info( + `Unregistered '${based}' based Subscription.`, + subscriptionInstance + ), + registeredSubscriptionInfo: (subscriptionInstance: any, based: string) => + l.if + .tag(['runtime', 'subscription']) + .info( + `Registered '${based}' based Subscription.`, + subscriptionInstance + ), + + // Integrations + failedToIntegrateFrameworkError: (integration: any) => + l.error( + `Failed to integrate Framework '${integration._key}'!`, + integration + ), + integratedFrameworkSuccess: (integration: any) => + l.success(`Integrated '${integration._key}' into AgileTs`, integration), + notReadyIntegrationWarning: (integration: any) => + l.warn(`Integration '${integration.key}' isn't ready yet!`), + + // State + incorrectTypeProvided: (type: string, messageType: 'error' | 'warn') => + l[messageType](`Incorrect type (${type}) was provided!`), + notSupportedTypeError: (type: string) => + l.error( + `'${type}' is not supported! Supported types: String, Boolean, Array, Object, Number` + ), + noPatchMethodOnNonObjectStateError: () => + l.error('The patch() method works only on States of the type object!'), + onlyOneIntervalAtOnceError: () => + l.error(`Only one Interval can be active at once!`), + onlyInvertBooleanBasedStatesError: () => + l.error('Only boolean based States can be inverted!'), + + // Collection + useCreateGroupAfterInstantiationWarning: () => + l.warn( + "We recommend using 'createGroup()' instead of 'Group()' outside the Collection configuration object" + ), + useCreateSelectorAfterInstantiationWarning: () => + l.warn( + "We recommend using 'createSelector()' instead of 'Selector()' outside the Collection configuration object" + ), + itemAtKeyDoesNotExistInCollectionError: ( + itemKey: any, + collectionKey: any + ) => + l.error( + `Item with key/name '${itemKey}' doesn't exist in Collection '${collectionKey}'!` + ), + validObjectRequiredToUpdateCollectionItemError: ( + itemKey: any, + collectionKey: any + ) => + l.error( + `You have to pass an valid Changes Object to update '${itemKey}' in '${collectionKey}'!` + ), + overwriteWholeItemWarning: (changes: any) => + Agile.logger.warn( + `By overwriting the whole Item you have to pass the correct itemKey into the changes object!`, + changes + ), + useGroupMethodBeforeInstantiationWarning: () => + l.warn( + "We recommend using 'Group()' instead of 'createGroup()' in the Collection configuration object!" + ), + useSelectorMethodBeforeInstantiationWarning: () => + l.warn( + "We recommend using 'Selector()' instead of 'createSelector()' in the Collection configuration object!" + ), + + // Utils + classMethodXNotSet: (methodName: string, className: string) => + l.error( + `${methodName}() isn't set in ${className} but need to be set! ${className} is no stand alone class.` + ), + canNotUseMethodXOnClassX: ( + methodName: string, + className: string, + reason: string + ) => + l.error( + `We can't use the '${methodName}()' in the ${className}! ${reason}` + ), + xAlreadyExistsAtKeyYError: (x: any, y: any) => + l.error(`${x} with the key/name '${y}' already exists!`), + xDoesNotExistsAtKeyYError: (x: any, y: any) => + l.error(`${x} with the key/name '${y}' doesn't exists!`), + xHasToBeOfTheTypeYError: (x: any, y: any) => + l.error(`${x} has to be of the type ${y}!`), + }; + + constructor() { + // empty + } +} diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index 9da95968..ad8e2b4e 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -7,6 +7,7 @@ import { defineConfig, notEqual, isValidObject, + LoggingHandler, } from '../internal'; export class Runtime { @@ -43,8 +44,7 @@ export class Runtime { this.jobQueue.push(job); - // Logging - Agile.logger.if.tag(['runtime']).info(`Created Job '${job._key}'`, job); + LoggingHandler.logs.createdRuntimeJobInfo(job); // Perform Job if (config.perform) { @@ -76,8 +76,7 @@ export class Runtime { if (job.rerender) this.jobsToRerender.push(job); this.currentJob = null; - // Logging - Agile.logger.if.tag(['runtime']).info(`Completed Job '${job._key}'`, job); + LoggingHandler.logs.completedRuntimeJobInfo(job); // Perform Jobs as long as Jobs are left in queue, if no job left update/rerender Subscribers of jobsToRerender if (this.jobQueue.length > 0) { @@ -137,16 +136,13 @@ export class Runtime { job.triesToUpdate++; this.notReadyJobsToRerender.add(job); - // Logging - Agile.logger.warn( - "SubscriptionContainer/Component isn't ready to rerender!", + LoggingHandler.logs.notReadySubscriptionContainerWarning( subscriptionContainer ); } else { - // Logging - Agile.logger.warn( - `Job with not ready SubscriptionContainer/Component was removed from the runtime after ${job.config.numberOfTriesToUpdate} tries to avoid an overflow.`, - subscriptionContainer + LoggingHandler.logs.removedJobExpiredJobFromRuntimeWarning( + subscriptionContainer, + job.config.numberOfTriesToUpdate ); } return; @@ -184,10 +180,7 @@ export class Runtime { ); }); - // Logging - Agile.logger.if - .tag(['runtime']) - .info('Updated/Rerendered Subscriptions', subscriptionsToUpdate); + LoggingHandler.logs.updatedSubscriptionsInfo(subscriptionsToUpdate); return true; } diff --git a/packages/core/src/runtime/observer.ts b/packages/core/src/runtime/observer.ts index b54d65e1..e8d426fe 100644 --- a/packages/core/src/runtime/observer.ts +++ b/packages/core/src/runtime/observer.ts @@ -6,6 +6,7 @@ import { defineConfig, IngestConfigInterface, CreateRuntimeJobConfigInterface, + LoggingHandler, } from '../internal'; export type ObserverKey = string | number; @@ -101,9 +102,7 @@ export class Observer { * @param job - Job that gets performed */ public perform(job: RuntimeJob): void { - Agile.logger.warn( - "Perform function isn't Set in Observer! Be aware that Observer is no stand alone class!" - ); + LoggingHandler.logs.classMethodXNotSet('perform', 'Observer'); } //========================================================================================================= diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index 327d48f3..3e1d00b5 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -8,6 +8,7 @@ import { SubscriptionContainerConfigInterface, defineConfig, removeProperties, + LoggingHandler, } from '../../internal'; export class SubController { @@ -127,13 +128,10 @@ export class SubController { unsub(subscriptionInstance); this.callbackSubs.delete(subscriptionInstance); - // Logging - Agile.logger.if - .tag(['runtime', 'subscription']) - .info( - 'Unregistered Callback based Subscription ', - subscriptionInstance - ); + LoggingHandler.logs.unregisteredSubscriptionInfo( + subscriptionInstance, + 'Callback' + ); return; } @@ -142,13 +140,10 @@ export class SubController { unsub(subscriptionInstance); this.componentSubs.delete(subscriptionInstance); - // Logging - Agile.logger.if - .tag(['runtime', 'subscription']) - .info( - 'Unregistered Component based Subscription ', - subscriptionInstance - ); + LoggingHandler.logs.unregisteredSubscriptionInfo( + subscriptionInstance, + 'Component' + ); return; } @@ -161,13 +156,10 @@ export class SubController { subscriptionInstance.componentSubscriptionContainer ); - // Logging - Agile.logger.if - .tag(['runtime', 'subscription']) - .info( - 'Unregistered Component based Subscription ', - subscriptionInstance - ); + LoggingHandler.logs.unregisteredSubscriptionInfo( + subscriptionInstance, + 'Component' + ); return; } @@ -181,23 +173,14 @@ export class SubController { unsub(subContainer as ComponentSubscriptionContainer); this.componentSubs.delete(subContainer); - // Logging - Agile.logger.if - .tag(['runtime', 'subscription']) - .info( - 'Unregistered Component based Subscription ', - subscriptionInstance - ); + LoggingHandler.logs.unregisteredSubscriptionInfo( + subscriptionInstance, + 'Component' + ); } ); return; } - - // Logging - Agile.logger.if - .tag(['runtime', 'subscription']) - .warn(`Couldn't find anything to unregister in `, subscriptionInstance); - return; } //========================================================================================================= @@ -272,13 +255,10 @@ export class SubController { else componentInstance.componentSubscriptionContainer = componentSubscriptionContainer; - // Logging - Agile.logger.if - .tag(['runtime', 'subscription']) - .info( - 'Registered Component based Subscription ', - componentSubscriptionContainer - ); + LoggingHandler.logs.registeredSubscriptionInfo( + componentSubscriptionContainer, + 'Component' + ); return componentSubscriptionContainer; } @@ -306,13 +286,10 @@ export class SubController { this.callbackSubs.add(callbackSubscriptionContainer); callbackSubscriptionContainer.ready = true; - // Logging - Agile.logger.if - .tag(['runtime', 'subscription']) - .info( - 'Registered Callback based Subscription ', - callbackSubscriptionContainer - ); + LoggingHandler.logs.registeredSubscriptionInfo( + callbackSubscriptionContainer, + 'Callback' + ); return callbackSubscriptionContainer; } diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index d708183a..d9c6c0ed 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -16,6 +16,7 @@ import { ComputedTracker, StateIngestConfigInterface, removeProperties, + LoggingHandler, } from '../internal'; export class State { @@ -158,12 +159,11 @@ export class State { // Check value has correct Type (js) if (!this.hasCorrectType(_value)) { - const message = `Incorrect type (${typeof _value}) was provided.`; - if (!config.force) { - Agile.logger.error(message); - return this; - } - Agile.logger.warn(message); + LoggingHandler.logs.incorrectTypeProvided( + typeof _value, + config.force ? 'warn' : 'error' + ); + if (!config.force) return this; } // Ingest new value into Runtime @@ -199,9 +199,7 @@ export class State { // Check if type is a supported Type if (!supportedTypes.includes(type.name)) { - Agile.logger.warn( - `'${type}' is not supported! Supported types: String, Boolean, Array, Object, Number` - ); + LoggingHandler.logs.notSupportedTypeError(type); return this; } @@ -254,14 +252,15 @@ export class State { }); if (!isValidObject(this.nextStateValue, true)) { - Agile.logger.error( - "You can't use the patch method on a non object based States!" - ); + LoggingHandler.logs.noPatchMethodOnNonObjectStateError(); return this; } if (!isValidObject(targetWithChanges, true)) { - Agile.logger.error('TargetWithChanges has to be an Object!'); + LoggingHandler.logs.xHasToBeOfTheTypeYError( + 'TargetWithChanges', + 'object' + ); return this; } @@ -313,17 +312,16 @@ export class State { // Check if Callback is valid Function if (!isFunction(_callback)) { - Agile.logger.error( - 'A Watcher Callback Function has to be typeof Function!' + LoggingHandler.logs.xHasToBeOfTheTypeYError( + 'Watcher Callback', + 'function' ); return this; } // Check if watcherKey is already occupied if (this.watchers[key]) { - Agile.logger.error( - `Watcher Callback Function with the key/name '${key}' already exists!` - ); + LoggingHandler.logs.xAlreadyExistsAtKeyYError('Watcher Callback', key); return this; } @@ -410,13 +408,6 @@ export class State { defaultStorageKey: null, }); - if (this.persistent) { - Agile.logger.warn( - `By persisting the State '${this._key}' twice you overwrite the old Persistent Instance!`, - this.persistent - ); - } - // Create persistent -> Persist Value this.persistent = new StatePersistent(this, { instantiate: _config.loadValue, @@ -438,12 +429,7 @@ export class State { * @param callback - Callback Function */ public onLoad(callback: (success: boolean) => void): this { - if (!this.persistent) { - Agile.logger.error( - `Please make sure you persist the State '${this._key}' before using the 'onLoad' function!` - ); - return this; - } + if (!this.persistent) return this; this.persistent.onLoad = callback; @@ -466,13 +452,17 @@ export class State { callback: (value: ValueType) => ValueType, ms?: number ): this { - if (this.currentInterval) { - Agile.logger.warn( - `You can only have one interval active!`, - this.currentInterval + if (!isFunction(callback)) { + LoggingHandler.logs.xHasToBeOfTheTypeYError( + 'A Interval Callback', + 'function' ); return this; } + if (this.currentInterval) { + LoggingHandler.logs.onlyOneIntervalAtOnceError(); + return this; + } this.currentInterval = setInterval(() => { this.set(callback(this._value)); @@ -527,7 +517,10 @@ export class State { */ public computeExists(method: ComputeExistsMethod): this { if (!isFunction(method)) { - Agile.logger.error(`A 'computeExistsMethod' has to be a function!`); + LoggingHandler.logs.xHasToBeOfTheTypeYError( + 'A ComputeExists Method', + 'function' + ); return this; } this.computeExistsMethod = method; @@ -571,7 +564,7 @@ export class State { if (typeof this._value === 'boolean') { this.set(!this._value as any); } else { - Agile.logger.error('You can only invert boolean based States!'); + LoggingHandler.logs.onlyInvertBooleanBasedStatesError(); } return this; } @@ -586,7 +579,10 @@ export class State { */ public computeValue(method: ComputeValueMethod): this { if (!isFunction(method)) { - Agile.logger.error(`A 'computeValueMethod' has to be a function!`); + LoggingHandler.logs.xHasToBeOfTheTypeYError( + 'A ComputeValue Method', + 'function' + ); return this; } this.computeValueMethod = method; @@ -616,7 +612,10 @@ export class State { weight: 10, }); if (!isFunction(callback)) { - Agile.logger.error('A sideEffect function has to be a function!'); + LoggingHandler.logs.xHasToBeOfTheTypeYError( + 'A Side Effect Function', + 'function' + ); return this; } this.sideEffects[key] = { diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index 5e51ea79..23d2f961 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -90,18 +90,18 @@ export class StatePersistent extends Persistent { /** * @internal * Loads State Value from the Storage - * @param storageKey - Prefix Key of Persisted Instances (default PersistentKey) + * @param storageItemKey - Prefix Key of Persisted Instances (default PersistentKey) * @return Success? */ public async loadPersistedValue( - storageKey?: PersistentKey + storageItemKey?: PersistentKey ): Promise { if (!this.ready) return false; - const _storageKey = storageKey || this._key; + const _storageItemKey = storageItemKey ?? this._key; // Load Value from default Storage const loadedValue = await this.agileInstance().storages.get( - _storageKey, + _storageItemKey, this.config.defaultStorageKey as any ); if (!loadedValue) return false; @@ -110,7 +110,7 @@ export class StatePersistent extends Persistent { this.state().set(loadedValue, { storage: false }); // Persist State, so that the Storage Value updates dynamically if the State updates - await this.persistValue(_storageKey); + await this.persistValue(_storageItemKey); return true; } @@ -120,25 +120,25 @@ export class StatePersistent extends Persistent { //========================================================================================================= /** * @internal - * Sets everything up so that the State gets saved in the Storage on every Value change - * @param storageKey - Prefix Key of Persisted Instances (default PersistentKey) + * Sets everything up so that the State is saved in the Storage on every Value change + * @param storageItemKey - Prefix Key of Persisted Instances (default PersistentKey) * @return Success? */ - public async persistValue(storageKey?: PersistentKey): Promise { + public async persistValue(storageItemKey?: PersistentKey): Promise { if (!this.ready) return false; - const _storageKey = storageKey || this._key; + const _storageItemKey = storageItemKey ?? this._key; // Add SideEffect to State, that updates the saved State Value depending on the current State Value this.state().addSideEffect( StatePersistent.storeValueSideEffectKey, (instance, config) => { - this.rebuildStorageSideEffect(this.state(), _storageKey, config); + this.rebuildStorageSideEffect(this.state(), _storageItemKey, config); }, { weight: 0 } ); // Initial rebuild Storage for saving State Value in the Storage - this.rebuildStorageSideEffect(this.state(), _storageKey); + this.rebuildStorageSideEffect(this.state(), _storageItemKey); this.isPersisted = true; return true; @@ -150,20 +150,20 @@ export class StatePersistent extends Persistent { /** * @internal * Removes State Value form the Storage - * @param storageKey - Prefix Key of Persisted Instances (default PersistentKey) + * @param storageItemKey - Prefix Key of Persisted Instances (default PersistentKey) * @return Success? */ public async removePersistedValue( - storageKey?: PersistentKey + storageItemKey?: PersistentKey ): Promise { if (!this.ready) return false; - const _storageKey = storageKey || this._key; + const _storageItemKey = storageItemKey || this._key; // Remove SideEffect this.state().removeSideEffect(StatePersistent.storeValueSideEffectKey); // Remove Value from Storage - this.agileInstance().storages.remove(_storageKey, this.storageKeys); + this.agileInstance().storages.remove(_storageItemKey, this.storageKeys); this.isPersisted = false; return true; diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index 826269b1..0bac8b3a 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -6,6 +6,7 @@ import { StorageKey, StorageItemKey, notEqual, + LoggingHandler, } from '../internal'; export class Storages { @@ -44,9 +45,7 @@ export class Storages { public instantiateLocalStorage(): boolean { // Check if Local Storage is Available if (!Storages.localStorageAvailable()) { - Agile.logger.warn( - 'Local Storage is here not available, to use Storage functionalities like persist please provide a custom Storage!' - ); + LoggingHandler.logs.localStorageNotAvailableWarning(); return false; } @@ -80,18 +79,13 @@ export class Storages { // Check if Storage already exists if (Object.prototype.hasOwnProperty.call(this.storages, storage.key)) { - Agile.logger.error( - `Storage with the key/name '${storage.key}' already exists` - ); + LoggingHandler.logs.xAlreadyExistsAtKeyYError('Storage', storage.key); return false; } // Set first added Storage as default Storage - if (!hasRegisteredAnyStorage && config.default === false) { - Agile.logger.warn( - 'Be aware that Agile has to assign the first added Storage as default Storage!' - ); - } + if (!hasRegisteredAnyStorage && config.default === false) + LoggingHandler.logs.firstAssignedStorageHasToBeDefaultStorageWarning(); if (!hasRegisteredAnyStorage) config.default = true; // Register Storage @@ -133,17 +127,13 @@ export class Storages { // Check if Storage exists if (!storage) { - Agile.logger.error( - `Storage with the key/name '${storageKey}' doesn't exist!` - ); + LoggingHandler.logs.xDoesNotExistsAtKeyYError('Storage', storageKey); return undefined; } // Check if Storage is ready if (!storage.ready) { - Agile.logger.error( - `Storage with the key/name '${storageKey}' isn't ready yet!` - ); + LoggingHandler.logs.storageAtKeyNotReadyError(storageKey); return undefined; } @@ -156,29 +146,29 @@ export class Storages { /** * @internal * Gets value at provided Key - * @param key - Key of Storage property + * @param storageItemKey - Key of Storage property * @param storageKey - Key/Name of Storage from which the Item is fetched (if not provided default Storage will be used) */ public get( - key: StorageItemKey, + storageItemKey: StorageItemKey, storageKey?: StorageKey ): Promise { if (!this.hasStorage()) { - Agile.logger.error( - 'No Storage found! Please provide at least one Storage.' - ); + LoggingHandler.logs.noStorageFoundError(); return Promise.resolve(undefined); } // Call get Method in specific Storage if (storageKey) { const storage = this.getStorage(storageKey); - if (storage) return storage.get(key); + if (storage) return storage.get(storageItemKey); } // Call get Method in default Storage const defaultStorage = this.getStorage(this.config.defaultStorageKey); - return defaultStorage?.get(key) || Promise.resolve(undefined); + return ( + defaultStorage?.get(storageItemKey) || Promise.resolve(undefined) + ); } //========================================================================================================= @@ -187,32 +177,30 @@ export class Storages { /** * @internal * Saves/Updates value at provided Key - * @param key - Key of Storage property + * @param storageItemKey - Key of Storage property * @param value - new Value that gets set at provided Key * @param storageKeys - Key/Name of Storages where the Value gets set (if not provided default Storage will be used) */ public set( - key: StorageItemKey, + storageItemKey: StorageItemKey, value: any, storageKeys?: StorageKey[] ): void { if (!this.hasStorage()) { - Agile.logger.error( - 'No Storage found! Please provide at least one Storage.' - ); + LoggingHandler.logs.noStorageFoundError(); return; } // Call set Method in specific Storages if (storageKeys) { for (const storageKey of storageKeys) - this.getStorage(storageKey)?.set(key, value); + this.getStorage(storageKey)?.set(storageItemKey, value); return; } // Call set Method in default Storage const defaultStorage = this.getStorage(this.config.defaultStorageKey); - defaultStorage?.set(key, value); + defaultStorage?.set(storageItemKey, value); } //========================================================================================================= @@ -221,27 +209,28 @@ export class Storages { /** * @internal * Removes value at provided Key - * @param key - Key of Storage property + * @param storageItemKey - Key of Storage property * @param storageKeys - Key/Name of Storages where the Value gets removed (if not provided default Storage will be used) */ - public remove(key: StorageItemKey, storageKeys?: StorageKey[]): void { + public remove( + storageItemKey: StorageItemKey, + storageKeys?: StorageKey[] + ): void { if (!this.hasStorage()) { - Agile.logger.error( - 'No Storage found! Please provide at least one Storage.' - ); + LoggingHandler.logs.noStorageFoundError(); return; } // Call remove Method in specific Storages if (storageKeys) { for (const storageKey of storageKeys) - this.getStorage(storageKey)?.remove(key); + this.getStorage(storageKey)?.remove(storageItemKey); return; } // Call remove Method in default Storage const defaultStorage = this.getStorage(this.config.defaultStorageKey); - defaultStorage?.remove(key); + defaultStorage?.remove(storageItemKey); } //========================================================================================================= diff --git a/packages/core/src/storages/persistent.ts b/packages/core/src/storages/persistent.ts index 00269b89..dce6c867 100644 --- a/packages/core/src/storages/persistent.ts +++ b/packages/core/src/storages/persistent.ts @@ -1,4 +1,10 @@ -import { Agile, copy, defineConfig, StorageKey } from '../internal'; +import { + Agile, + copy, + defineConfig, + LoggingHandler, + StorageKey, +} from '../internal'; export class Persistent { public agileInstance: () => Agile; @@ -11,7 +17,7 @@ export class Persistent { public isPersisted = false; // If Value is stored in Agile Storage public onLoad: ((success: boolean) => void) | undefined; // Gets called if PersistValue got loaded for the first Time - // StorageKeys of Storages in that the Persisted Value gets saved + // Storages in which the Persisted Value is saved public storageKeys: StorageKey[] = []; /** @@ -103,26 +109,20 @@ export class Persistent { // Validate Key if (this._key === Persistent.placeHolderKey) { - Agile.logger.error( - 'No valid persist Key found! Please provide a Key or assign one to the parent instance.' - ); + LoggingHandler.logs.noPersistKeyFoundError(); isValid = false; } // Validate StorageKeys if (!this.config.defaultStorageKey || this.storageKeys.length <= 0) { - Agile.logger.error( - 'No persist Storage Key found! Please provide at least one Storage Key.' - ); + LoggingHandler.logs.noPersistStorageKeyFoundError(); isValid = false; } // Check if Storages exist this.storageKeys.map((key) => { if (!this.agileInstance().storages.storages[key]) { - Agile.logger.error( - `Storage '${key}' doesn't exist yet. Please provide only existing StorageKeys!` - ); + LoggingHandler.logs.xDoesNotExistsAtKeyYError('Storage', key); isValid = false; } }); @@ -147,16 +147,12 @@ export class Persistent { const storages = this.agileInstance().storages; const _storageKeys = copy(storageKeys); - // Print warning if default StorageKey passed, but it isn't in storageKeys + // Add passed default Storage Key to 'storageKeys' if (defaultStorageKey && !_storageKeys.includes(defaultStorageKey)) { - Agile.logger.warn( - `Default Storage Key '${defaultStorageKey}' isn't contained in storageKeys!`, - _storageKeys - ); _storageKeys.push(defaultStorageKey); } - // Add default Storage of AgileTs to storageKeys if no storageKey provided + // Add default Storage of AgileTs to storageKeys and assign it as default Storage Key of Persistent if no storageKeys provided if (_storageKeys.length <= 0) { this.config.defaultStorageKey = storages.config.defaultStorageKey as any; _storageKeys.push(storages.config.defaultStorageKey as any); @@ -189,9 +185,7 @@ export class Persistent { * @return Success? */ public async loadPersistedValue(): Promise { - Agile.logger.error( - `'loadPersistedValue' function isn't Set in Persistent! Be aware that Persistent is no stand alone class!` - ); + LoggingHandler.logs.classMethodXNotSet('loadPersistedValue', 'Persistent'); return false; } @@ -204,9 +198,7 @@ export class Persistent { * @return Success? */ public async persistValue(): Promise { - Agile.logger.error( - `'persistValue' function isn't Set in Persistent! Be aware that Persistent is no stand alone class!` - ); + LoggingHandler.logs.classMethodXNotSet('persistValue', 'Persistent'); return false; } @@ -219,8 +211,9 @@ export class Persistent { * @return Success? */ public async removePersistedValue(): Promise { - Agile.logger.error( - `'removePersistedValue' function isn't Set in Persistent! Be aware that Persistent is no stand alone class!` + LoggingHandler.logs.classMethodXNotSet( + 'removePersistedValue', + 'Persistent' ); return false; } diff --git a/packages/core/src/storages/storage.ts b/packages/core/src/storages/storage.ts index 2fce3664..c2232b1a 100644 --- a/packages/core/src/storages/storage.ts +++ b/packages/core/src/storages/storage.ts @@ -4,6 +4,7 @@ import { isAsyncFunction, isFunction, Agile, + LoggingHandler, } from '../internal'; export class Storage { @@ -49,15 +50,15 @@ export class Storage { */ public validate(): boolean { if (!isFunction(this.methods?.get)) { - Agile.logger.error("Your GET StorageMethod isn't valid!"); + LoggingHandler.logs.noValidStorageMethodError('get'); return false; } if (!isFunction(this.methods?.set)) { - Agile.logger.error("Your SET StorageMethod isn't valid!"); + LoggingHandler.logs.noValidStorageMethodError('set'); return false; } if (!isFunction(this.methods?.remove)) { - Agile.logger.error("Your REMOVE StorageMethod isn't valid!"); + LoggingHandler.logs.noValidStorageMethodError('remove'); return false; } return true; @@ -75,9 +76,7 @@ export class Storage { public normalGet(key: StorageItemKey): GetTpe | undefined { if (!this.ready || !this.methods.get) return; if (isAsyncFunction(this.methods.get)) - Agile.logger.warn( - "Be aware that 'normalGet' returns a Promise with a stringified Value if using it in an async Storage!" - ); + LoggingHandler.logs.normalGetInAsyncStorageWarning(); // Get Value const res = this.methods.get(this.getStorageKey(key)); diff --git a/packages/proxytree/src/branch.ts b/packages/proxytree/src/branch.ts index f1b999d6..f9c05f3e 100644 --- a/packages/proxytree/src/branch.ts +++ b/packages/proxytree/src/branch.ts @@ -1,7 +1,7 @@ import { ProxyTree } from './index'; import { isObject } from './utils'; -export class Branch { +export class Branch { public proxy: T; // Target object wrapped in proxy public target: T; // Target object @@ -103,7 +103,7 @@ export class Branch { * @param branch - Branch to which the Route leads. * If Branch is null it means that the Tree ends here since the Route represents a primitive value (like a number). */ -export interface BranchRoute { +export interface BranchRoute { key: BranchKey; timesAccessed: number; branch: Branch | null; diff --git a/packages/proxytree/src/proxytree.ts b/packages/proxytree/src/proxytree.ts index 8721b39c..70f8c97c 100644 --- a/packages/proxytree/src/proxytree.ts +++ b/packages/proxytree/src/proxytree.ts @@ -1,7 +1,7 @@ import { Branch, BranchKey, DefaultProxyTreeObject } from './branch'; import { isObject } from './utils'; -export class ProxyTree { +export class ProxyTree { public rootBranch: Branch; // Root Branch of the proxy tree public proxy: T; // Target object wrapped in proxy @@ -21,7 +21,7 @@ export class ProxyTree { * Creates a new Branch of the ProxyTree which represents the passed target object. * @param target - Target Object */ - public createBranch( + public createBranch( target: X ): Branch | null { if (!isObject(target)) { From 3ad5dcad1c1818ae8b01efa99642bccb863df6bf Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 24 May 2021 15:08:16 +0200 Subject: [PATCH 003/117] added initialRebuild config to Group --- packages/core/src/collection/group.ts | 27 ++++-- packages/core/src/collection/index.ts | 49 +++++----- packages/core/src/collection/selector.ts | 5 +- packages/core/src/internal.ts | 2 +- packages/core/src/loggingHandler.ts | 116 +++++++++++++++-------- packages/core/src/state/index.ts | 9 ++ 6 files changed, 133 insertions(+), 75 deletions(-) diff --git a/packages/core/src/collection/group.ts b/packages/core/src/collection/group.ts index 691ad9e7..d4b1d1a4 100644 --- a/packages/core/src/collection/group.ts +++ b/packages/core/src/collection/group.ts @@ -16,6 +16,7 @@ import { StateRuntimeJobConfigInterface, StateIngestConfigInterface, removeProperties, + LoggingHandler, } from '../internal'; export class Group extends State< @@ -40,14 +41,21 @@ export class Group extends State< initialItems?: Array, config: GroupConfigInterface = {} ) { - super(collection.agileInstance(), initialItems || [], config); + super( + collection.agileInstance(), + initialItems || [], + removeProperties(config, ['initialRebuild']) + ); this.collection = () => collection; + config = defineConfig(config, { + initialRebuild: true, + }); // Add rebuild to sideEffects to rebuild Group on Value Change this.addSideEffect(Group.rebuildGroupSideEffectKey, () => this.rebuild()); // Initial Rebuild - this.rebuild(); + if (config.initialRebuild) this.rebuild(); } /** @@ -129,9 +137,6 @@ export class Group extends State< _itemKeys.forEach((itemKey) => { // Check if itemKey exists in Group if (!newGroupValue.includes(itemKey)) { - Agile.logger.error( - `Couldn't find ItemKey '${itemKey}' in Group '${this._key}'!` - ); notExistingItemKeys.push(itemKey); notExistingItemKeysInCollection.push(itemKey); return; @@ -278,6 +283,7 @@ export class Group extends State< defaultStorageKey: null, }); + // Create storageItemKey based on Collection Name if (_config.followCollectionPersistKeyPattern) { key = CollectionPersistent.getGroupStorageKey( key || this._key, @@ -308,7 +314,7 @@ export class Group extends State< // Create groupItems by finding Item at ItemKey in Collection this._value.forEach((itemKey) => { const item = this.collection().getItem(itemKey); - if (item) groupItems.push(item); + if (item != null) groupItems.push(item); else notFoundItemKeys.push(itemKey); }); @@ -319,10 +325,9 @@ export class Group extends State< // Logging if (notFoundItemKeys.length > 0) { - Agile.logger.warn( - `Couldn't find some Items in Collection '${this.collection()._key}' (${ - this._key - })`, + LoggingHandler.logs.couldNotFindItemsInCollectionWarning( + this.collection()._key, + this._key, notFoundItemKeys ); } @@ -357,10 +362,12 @@ export interface GroupRemoveConfigInterface { /** * @param key - Key/Name of Group * @param isPlaceholder - If Group is initially a Placeholder + * @param initialRebuild - If the Group is rebuilt shortly after the instantiation */ export interface GroupConfigInterface { key?: GroupKey; isPlaceholder?: boolean; + initialRebuild?: boolean; } /** diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 96bb36e2..6efac921 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -19,6 +19,7 @@ import { SelectorConfigInterface, removeProperties, LoggingHandler, + isFunction, } from '../internal'; export class Collection { @@ -132,6 +133,11 @@ export class Collection { return this.createGroup(key, initialItems); } + // Set 'initialRebuild' to false + // since the Group can't properly rebuilt its value + // because no Items are added to the Collection yet + config.initialRebuild = false; + return new Group(this, initialItems, config); } @@ -619,9 +625,7 @@ export class Collection { */ public removeSelector(selectorKey: SelectorKey): this { if (!this.selectors[selectorKey]) { - Agile.logger.warn( - `Selector with the key/name '${selectorKey}' doesn't exist!` - ); + LoggingHandler.logs.xDoesNotExistsAtKeyYError('Selector', selectorKey); return this; } this.selectors[selectorKey]?.unselect(); // Unselects current selected Item @@ -800,11 +804,6 @@ export class Collection { defaultStorageKey: null, }); - if (this.persistent) - Agile.logger.warn( - `By persisting the Collection '${this._key}' twice you overwrite the old Persistent Instance!` - ); - // Create persistent -> Persist Value this.persistent = new CollectionPersistent(this, { instantiate: _config.loadValue, @@ -826,16 +825,22 @@ export class Collection { * @param callback - Callback Function */ public onLoad(callback: (success: boolean) => void): this { - if (this.persistent) { - this.persistent.onLoad = callback; + if (!this.persistent) return this; - // If Collection is already 'isPersisted' the loading was successful -> callback can be called - if (this.isPersisted) callback(true); - } else { - Agile.logger.error( - `Please make sure you persist the Collection '${this._key}' before using the 'onLoad' function!` + // Check if Callback is valid Function + if (!isFunction(callback)) { + LoggingHandler.logs.xHasToBeOfTheTypeYError( + 'onLoad Callback', + 'function' ); + return this; } + + this.persistent.onLoad = callback; + + // If Collection is already 'isPersisted' the loading was successful -> callback can be called + if (this.isPersisted) callback(true); + return this; } @@ -967,8 +972,9 @@ export class Collection { // Check if Item with newItemKey already exists if (this.hasItem(newItemKey)) { - Agile.logger.warn( - `Couldn't update ItemKey from '${oldItemKey}' to '${newItemKey}' because an Item with the key/name '${newItemKey}' already exists!` + LoggingHandler.logs.couldNotUpdateItemKeyBecauseItemKeyAlreadyExistsError( + oldItemKey, + newItemKey ); return false; } @@ -1151,15 +1157,14 @@ export class Collection { }); if (!isValidObject(_data)) { - Agile.logger.error( - `Item Data of Collection '${this._key}' has to be an valid Object!` - ); + LoggingHandler.logs.itemDataHasToBeValidObjectError(this._key); return false; } if (!Object.prototype.hasOwnProperty.call(_data, primaryKey)) { - Agile.logger.warn( - `Collection '${this._key}' Item Data should contain a primaryKey property called '${this.config.primaryKey}'!` + LoggingHandler.logs.itemDataHasToContainPrimaryKeyWarning( + this._key, + this.config.primaryKey ); _data[this.config.primaryKey] = generateId(); } diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index 0a7acce2..67bdb88b 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -90,10 +90,7 @@ export class Selector extends State< storage: true, }); - if (this.hasSelected(itemKey) && !config.force) { - Agile.logger.warn(`Selector has already selected '${itemKey}'!`); - return this; - } + if (this.hasSelected(itemKey) && !config.force) return this; // Unselect old Item this.unselect({ background: true }); diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index 1e53fb0d..bad2ee4f 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -6,7 +6,6 @@ // Logger export * from '@agile-ts/logger'; -export * from './loggingHandler'; // Utils export * from './utils'; @@ -14,6 +13,7 @@ export * from '@agile-ts/utils'; // Agile export * from './agile'; +export * from './loggingHandler'; // Runtime export * from './runtime'; diff --git a/packages/core/src/loggingHandler.ts b/packages/core/src/loggingHandler.ts index 6a3f2c28..b56c1e53 100644 --- a/packages/core/src/loggingHandler.ts +++ b/packages/core/src/loggingHandler.ts @@ -1,57 +1,63 @@ import { Agile } from './internal'; -const l = Agile.logger; - export class LoggingHandler { public static logs = { // Agile createdAgileInstanceSuccess: (agileInstance: any, loggerInstance: any) => - l.success('Created new AgileInstance ', agileInstance, loggerInstance), + Agile.logger.success( + 'Created new AgileInstance ', + agileInstance, + loggerInstance + ), multipleGlobalBoundAgileInstancesWarning: () => - l.warn( + Agile.logger.warn( 'Be careful with binding multiple Agile Instances globally in one Application!' ), // Storages localStorageNotAvailableWarning: () => - l.warn( + Agile.logger.warn( `The 'Local Storage' is in your current environment not available. To use the .persist() functionality please provide a custom Storage!` ), firstAssignedStorageHasToBeDefaultStorageWarning: () => - l.warn( + Agile.logger.warn( 'The first allocated Storage for AgileTs must be set as the default Storage!' ), storageAtKeyNotReadyError: (key: any) => - l.error(`Storage with the key/name '${key}' isn't ready yet!`), + Agile.logger.error(`Storage with the key/name '${key}' isn't ready yet!`), noStorageFoundError: () => - l.error('No Storage found! Please provide at least one Storage.'), + Agile.logger.error( + 'No Storage found! Please provide at least one Storage.' + ), // Persistent noPersistKeyFoundError: () => - l.error( + Agile.logger.error( 'No valid persist Key found! Please provide a Key or assign one to the parent instance.' ), noPersistStorageKeyFoundError: () => - l.error( + Agile.logger.error( 'No persist Storage Key found! Please provide at least one Storage Key.' ), // Storage noValidStorageMethodError: (methodName: string) => - l.error(`Invalid ${methodName}() method as StorageMethod provided!`), + Agile.logger.error( + `Invalid ${methodName}() method as StorageMethod provided!` + ), normalGetInAsyncStorageWarning: () => - l.warn( + Agile.logger.warn( 'Using normalGet() in a async based Storage might lead to a unexpected return value. Instead of an resolved value it returns an Promise!' ), // Runtime createdRuntimeJobInfo: (job: any) => - l.if.tag(['runtime']).info(`Created Job '${job._key}'`, job), + Agile.logger.if.tag(['runtime']).info(`Created Job '${job._key}'`, job), completedRuntimeJobInfo: (job: any) => - l.if.tag(['runtime']).info(`Completed Job '${job._key}'`, job), + Agile.logger.if.tag(['runtime']).info(`Completed Job '${job._key}'`, job), notReadySubscriptionContainerWarning: (subscriptionContainer: any) => - l.warn( + Agile.logger.warn( "SubscriptionContainer/Component isn't ready to rerender!", subscriptionContainer ), @@ -59,25 +65,25 @@ export class LoggingHandler { subscriptionContainer: any, triesOfUpdating ) => - l.warn( + Agile.logger.warn( `Job with not ready SubscriptionContainer/Component was removed from the runtime after ${triesOfUpdating} tries to avoid an overflow.`, subscriptionContainer ), updatedSubscriptionsInfo: (subscriptionsToUpdate: any) => - l.if + Agile.logger.if .tag(['runtime']) .info('Updated/Rerendered Subscriptions', subscriptionsToUpdate), // SubController unregisteredSubscriptionInfo: (subscriptionInstance: any, based: string) => - l.if + Agile.logger.if .tag(['runtime', 'subscription']) .info( `Unregistered '${based}' based Subscription.`, subscriptionInstance ), registeredSubscriptionInfo: (subscriptionInstance: any, based: string) => - l.if + Agile.logger.if .tag(['runtime', 'subscription']) .info( `Registered '${based}' based Subscription.`, @@ -86,50 +92,55 @@ export class LoggingHandler { // Integrations failedToIntegrateFrameworkError: (integration: any) => - l.error( + Agile.logger.error( `Failed to integrate Framework '${integration._key}'!`, integration ), integratedFrameworkSuccess: (integration: any) => - l.success(`Integrated '${integration._key}' into AgileTs`, integration), + Agile.logger.success( + `Integrated '${integration._key}' into AgileTs`, + integration + ), notReadyIntegrationWarning: (integration: any) => - l.warn(`Integration '${integration.key}' isn't ready yet!`), + Agile.logger.warn(`Integration '${integration.key}' isn't ready yet!`), // State incorrectTypeProvided: (type: string, messageType: 'error' | 'warn') => - l[messageType](`Incorrect type (${type}) was provided!`), + Agile.logger[messageType](`Incorrect type (${type}) was provided!`), notSupportedTypeError: (type: string) => - l.error( + Agile.logger.error( `'${type}' is not supported! Supported types: String, Boolean, Array, Object, Number` ), noPatchMethodOnNonObjectStateError: () => - l.error('The patch() method works only on States of the type object!'), + Agile.logger.error( + 'The patch() method works only on States of the type object!' + ), onlyOneIntervalAtOnceError: () => - l.error(`Only one Interval can be active at once!`), + Agile.logger.error(`Only one Interval can be active at once!`), onlyInvertBooleanBasedStatesError: () => - l.error('Only boolean based States can be inverted!'), + Agile.logger.error('Only boolean based States can be inverted!'), // Collection useCreateGroupAfterInstantiationWarning: () => - l.warn( + Agile.logger.warn( "We recommend using 'createGroup()' instead of 'Group()' outside the Collection configuration object" ), useCreateSelectorAfterInstantiationWarning: () => - l.warn( + Agile.logger.warn( "We recommend using 'createSelector()' instead of 'Selector()' outside the Collection configuration object" ), itemAtKeyDoesNotExistInCollectionError: ( itemKey: any, collectionKey: any ) => - l.error( + Agile.logger.error( `Item with key/name '${itemKey}' doesn't exist in Collection '${collectionKey}'!` ), validObjectRequiredToUpdateCollectionItemError: ( itemKey: any, collectionKey: any ) => - l.error( + Agile.logger.error( `You have to pass an valid Changes Object to update '${itemKey}' in '${collectionKey}'!` ), overwriteWholeItemWarning: (changes: any) => @@ -138,17 +149,46 @@ export class LoggingHandler { changes ), useGroupMethodBeforeInstantiationWarning: () => - l.warn( + Agile.logger.warn( "We recommend using 'Group()' instead of 'createGroup()' in the Collection configuration object!" ), useSelectorMethodBeforeInstantiationWarning: () => - l.warn( + Agile.logger.warn( "We recommend using 'Selector()' instead of 'createSelector()' in the Collection configuration object!" ), + couldNotUpdateItemKeyBecauseItemKeyAlreadyExistsError: ( + oldItemKey: any, + newItemKey: any + ) => + Agile.logger.error( + `Couldn't update ItemKey from '${oldItemKey}' to '${newItemKey}' because an Item with the key/name '${newItemKey}' already exists!` + ), + itemDataHasToBeValidObjectError: (collectionKey: any) => + Agile.logger.error( + `Item Data of Collection '${collectionKey}' has to be an valid Object!` + ), + itemDataHasToContainPrimaryKeyWarning: ( + collectionKey: any, + primaryKeyProperty: any + ) => + Agile.logger.warn( + `Collection '${collectionKey}' Item Data should contain a primaryKey property called '${primaryKeyProperty}'!` + ), + + // Group + couldNotFindItemsInCollectionWarning: ( + collectionKey: any, + groupKey: any, + notFoundItemKeys: any + ) => + Agile.logger.warn( + `Couldn't find Items in Collection '${collectionKey}' during the rebuild of the Group '${groupKey}'`, + notFoundItemKeys + ), // Utils classMethodXNotSet: (methodName: string, className: string) => - l.error( + Agile.logger.error( `${methodName}() isn't set in ${className} but need to be set! ${className} is no stand alone class.` ), canNotUseMethodXOnClassX: ( @@ -156,15 +196,15 @@ export class LoggingHandler { className: string, reason: string ) => - l.error( + Agile.logger.error( `We can't use the '${methodName}()' in the ${className}! ${reason}` ), xAlreadyExistsAtKeyYError: (x: any, y: any) => - l.error(`${x} with the key/name '${y}' already exists!`), + Agile.logger.error(`${x} with the key/name '${y}' already exists!`), xDoesNotExistsAtKeyYError: (x: any, y: any) => - l.error(`${x} with the key/name '${y}' doesn't exists!`), + Agile.logger.error(`${x} with the key/name '${y}' doesn't exists!`), xHasToBeOfTheTypeYError: (x: any, y: any) => - l.error(`${x} has to be of the type ${y}!`), + Agile.logger.error(`${x} has to be of the type ${y}!`), }; constructor() { diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index d9c6c0ed..f2a3f589 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -431,6 +431,15 @@ export class State { public onLoad(callback: (success: boolean) => void): this { if (!this.persistent) return this; + // Check if Callback is valid Function + if (!isFunction(callback)) { + LoggingHandler.logs.xHasToBeOfTheTypeYError( + 'onLoad Callback', + 'function' + ); + return this; + } + this.persistent.onLoad = callback; // If State is already 'isPersisted' the loading was successful -> callback can be called From 4e80ba8a9823f2b44804cda1cff0ec0d8f29580b Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 24 May 2021 17:52:24 +0200 Subject: [PATCH 004/117] added initialRebuild config to Group --- .../functional-component-ts/src/App.tsx | 8 ++-- .../functional-component-ts/src/core/index.ts | 14 ++++--- packages/core/src/collection/group.ts | 16 +++----- packages/core/src/collection/index.ts | 25 +++++------- packages/core/src/collection/item.ts | 3 +- packages/core/src/collection/selector.ts | 38 ++++++++++--------- 6 files changed, 52 insertions(+), 52 deletions(-) diff --git a/examples/react/develop/functional-component-ts/src/App.tsx b/examples/react/develop/functional-component-ts/src/App.tsx index 66e894a6..ad7478e5 100644 --- a/examples/react/develop/functional-component-ts/src/App.tsx +++ b/examples/react/develop/functional-component-ts/src/App.tsx @@ -133,11 +133,13 @@ const App = (props: any) => {

My Collection

{myGroup.map((item) => ( -

{item.name}

+

{item.name}

))}
+ + ); +}; + +const CounterB = () => { + const count = useAgile(COUNTER_B); + return ( +
+ B: {count} +
+ ); +}; + +const CounterC = () => { + const count = useAgile(COUNTER_C); + return ( +
+ C: {count} +
+ ); +}; + +const App = () => ( +
+ + + + + + +
+); + +export default App; diff --git a/examples/react/develop/simple-counter/src/index.js b/examples/react/develop/simple-counter/src/index.js new file mode 100644 index 00000000..dd1c38f9 --- /dev/null +++ b/examples/react/develop/simple-counter/src/index.js @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/examples/react/develop/simple-counter/yarn.lock b/examples/react/develop/simple-counter/yarn.lock new file mode 100644 index 00000000..e0c5ba2c --- /dev/null +++ b/examples/react/develop/simple-counter/yarn.lock @@ -0,0 +1,12067 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@agile-ts/core@^0.0.17": + version "0.0.17" + resolved "https://registry.yarnpkg.com/@agile-ts/core/-/core-0.0.17.tgz#410ab31ff6279567ff0266a5a55818744598f2c2" + integrity sha512-EVWqf6PkBwDS/gdTTVwo9juyGPrnnKlq8dna3diXGZdDpwEzMc09nGCmLThYM5sEkDQGzir6enn3Oo2l+7Zp2Q== + dependencies: + "@agile-ts/logger" "^0.0.4" + "@agile-ts/utils" "^0.0.4" + +"@agile-ts/logger@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@agile-ts/logger/-/logger-0.0.4.tgz#7f4d82ef8f03b13089af0878c360575c43f0962d" + integrity sha512-qm0obAKqJMaPKM+c76gktRXyw3OL1v39AnhMZ0FBGwJqHWU+fLRkCzlQwjaROCr3F1XP01Lc/Ls3efF0WzyEPw== + dependencies: + "@agile-ts/utils" "^0.0.4" + +"@agile-ts/proxytree@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@agile-ts/proxytree/-/proxytree-0.0.3.tgz#e3dacab123a311f2f0d4a0369793fe90fdab7569" + integrity sha512-auO6trCo7ivLJYuLjxrnK4xuUTangVPTq8UuOMTlGbJFjmb8PLEkaXuRoVGSzv9jsT2FeS7KsP7Fs+yvv0WPdg== + +"@agile-ts/react@^0.0.18": + version "0.0.18" + resolved "https://registry.yarnpkg.com/@agile-ts/react/-/react-0.0.18.tgz#db1a617ad535f7a70254d62980d97350d4a85718" + integrity sha512-K2FO3Odqaw/XkU3DO/mWSLkxLn45W7pXk/UlZl5E/CQPFFWlWsjuxtH/C/kfK+E6rnaNoToTjGscmcoeN/bLjQ== + dependencies: + "@agile-ts/proxytree" "^0.0.3" + +"@agile-ts/utils@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.4.tgz#66e9536e561796489a37155da6b74ce2dc482697" + integrity sha512-GiZyTYmCm4j2N57oDjeMuPpfQdgn9clb0Cxpfuwi2Bq5T/KPXlaROLsVGwHLjwwT+NX7xxr5qNJH8pZTnHnYRQ== + +"@babel/code-frame@7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.5.5": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" + integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== + dependencies: + "@babel/highlight" "^7.12.13" + +"@babel/compat-data@^7.12.1", "@babel/compat-data@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.12.13.tgz#27e19e0ed3726ccf54067ced4109501765e7e2e8" + integrity sha512-U/hshG5R+SIoW7HVWIdmy1cB7s3ki+r3FpyEZiCgpi4tFgPnX/vynY80ZGSASOIrUM6O7VxOgCZgdt7h97bUGg== + +"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.13.15", "@babel/compat-data@^7.14.0": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.0.tgz#a901128bce2ad02565df95e6ecbf195cf9465919" + integrity sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q== + +"@babel/core@7.12.3": + version "7.12.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.3.tgz#1b436884e1e3bff6fb1328dc02b208759de92ad8" + integrity sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.12.1" + "@babel/helper-module-transforms" "^7.12.1" + "@babel/helpers" "^7.12.1" + "@babel/parser" "^7.12.3" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.12.1" + "@babel/types" "^7.12.1" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.5", "@babel/core@^7.8.4": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.17.tgz#993c5e893333107a2815d8e0d73a2c3755e280b2" + integrity sha512-V3CuX1aBywbJvV2yzJScRxeiiw0v2KZZYYE3giywxzFJL13RiyPjaaDwhDnxmgFTTS7FgvM2ijr4QmKNIu0AtQ== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.12.17" + "@babel/helper-module-transforms" "^7.12.17" + "@babel/helpers" "^7.12.17" + "@babel/parser" "^7.12.17" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.12.17" + "@babel/types" "^7.12.17" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/core@^7.9.0": + version "7.14.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.3.tgz#5395e30405f0776067fbd9cf0884f15bfb770a38" + integrity sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.14.3" + "@babel/helper-compilation-targets" "^7.13.16" + "@babel/helper-module-transforms" "^7.14.2" + "@babel/helpers" "^7.14.0" + "@babel/parser" "^7.14.3" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.14.2" + "@babel/types" "^7.14.2" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + semver "^6.3.0" + source-map "^0.5.0" + +"@babel/generator@^7.12.1", "@babel/generator@^7.12.17": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.17.tgz#9ef1dd792d778b32284411df63f4f668a9957287" + integrity sha512-DSA7ruZrY4WI8VxuS1jWSRezFnghEoYEFrZcw9BizQRmOZiUsiHl59+qEARGPqPikwA/GPTyRCi7isuCK/oyqg== + dependencies: + "@babel/types" "^7.12.17" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/generator@^7.14.2", "@babel/generator@^7.14.3": + version "7.14.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.3.tgz#0c2652d91f7bddab7cccc6ba8157e4f40dcedb91" + integrity sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA== + dependencies: + "@babel/types" "^7.14.2" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz#0f58e86dfc4bb3b1fcd7db806570e177d439b6ab" + integrity sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz#6bc20361c88b0a74d05137a65cac8d3cbf6f61fc" + integrity sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.12.13" + "@babel/types" "^7.12.13" + +"@babel/helper-compilation-targets@^7.12.1", "@babel/helper-compilation-targets@^7.12.17": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.17.tgz#91d83fae61ef390d39c3f0507cb83979bab837c7" + integrity sha512-5EkibqLVYOuZ89BSg2lv+GG8feywLuvMXNYgf0Im4MssE0mFWPztSpJbildNnUgw0bLI2EsIN4MpSHC2iUJkQA== + dependencies: + "@babel/compat-data" "^7.12.13" + "@babel/helper-validator-option" "^7.12.17" + browserslist "^4.14.5" + semver "^5.5.0" + +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.13.16": + version "7.13.16" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz#6e91dccf15e3f43e5556dffe32d860109887563c" + integrity sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA== + dependencies: + "@babel/compat-data" "^7.13.15" + "@babel/helper-validator-option" "^7.12.17" + browserslist "^4.14.5" + semver "^6.3.0" + +"@babel/helper-create-class-features-plugin@^7.12.1", "@babel/helper-create-class-features-plugin@^7.12.13", "@babel/helper-create-class-features-plugin@^7.12.17": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.17.tgz#704b69c8a78d03fb1c5fcc2e7b593f8a65628944" + integrity sha512-I/nurmTxIxHV0M+rIpfQBF1oN342+yvl2kwZUrQuOClMamHF1w5tknfZubgNOLRoA73SzBFAdFcpb4M9HwOeWQ== + dependencies: + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-member-expression-to-functions" "^7.12.17" + "@babel/helper-optimise-call-expression" "^7.12.13" + "@babel/helper-replace-supers" "^7.12.13" + "@babel/helper-split-export-declaration" "^7.12.13" + +"@babel/helper-create-class-features-plugin@^7.13.0", "@babel/helper-create-class-features-plugin@^7.14.0", "@babel/helper-create-class-features-plugin@^7.14.3": + version "7.14.3" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.3.tgz#832111bcf4f57ca57a4c5b1a000fc125abc6554a" + integrity sha512-BnEfi5+6J2Lte9LeiL6TxLWdIlEv9Woacc1qXzXBgbikcOzMRM2Oya5XGg/f/ngotv1ej2A/b+3iJH8wbS1+lQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + "@babel/helper-function-name" "^7.14.2" + "@babel/helper-member-expression-to-functions" "^7.13.12" + "@babel/helper-optimise-call-expression" "^7.12.13" + "@babel/helper-replace-supers" "^7.14.3" + "@babel/helper-split-export-declaration" "^7.12.13" + +"@babel/helper-create-regexp-features-plugin@^7.12.13": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz#a2ac87e9e319269ac655b8d4415e94d38d663cb7" + integrity sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + regexpu-core "^4.7.1" + +"@babel/helper-define-polyfill-provider@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.1.tgz#e6f5f4a6edc3722152c21359190de67fc6cf664d" + integrity sha512-x3AUTVZNPunaw1opRTa5OwVA5N0YxGlIad9xQ5QflK1uIS7PnAGGU5O2Dj/G183fR//N8AzTq+Q8+oiu9m0VFg== + dependencies: + "@babel/helper-compilation-targets" "^7.13.0" + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/traverse" "^7.13.0" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + +"@babel/helper-explode-assignable-expression@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.13.tgz#0e46990da9e271502f77507efa4c9918d3d8634a" + integrity sha512-5loeRNvMo9mx1dA/d6yNi+YiKziJZFylZnCo1nmFF4qPU4yJ14abhWESuSMQSlQxWdxdOFzxXjk/PpfudTtYyw== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-function-name@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz#93ad656db3c3c2232559fd7b2c3dbdcbe0eb377a" + integrity sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA== + dependencies: + "@babel/helper-get-function-arity" "^7.12.13" + "@babel/template" "^7.12.13" + "@babel/types" "^7.12.13" + +"@babel/helper-function-name@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz#397688b590760b6ef7725b5f0860c82427ebaac2" + integrity sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ== + dependencies: + "@babel/helper-get-function-arity" "^7.12.13" + "@babel/template" "^7.12.13" + "@babel/types" "^7.14.2" + +"@babel/helper-get-function-arity@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz#bc63451d403a3b3082b97e1d8b3fe5bd4091e583" + integrity sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-hoist-variables@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.12.13.tgz#13aba58b7480b502362316ea02f52cca0e9796cd" + integrity sha512-KSC5XSj5HreRhYQtZ3cnSnQwDzgnbdUDEFsxkN0m6Q3WrCRt72xrnZ8+h+pX7YxM7hr87zIO3a/v5p/H3TrnVw== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-hoist-variables@^7.13.0": + version "7.13.16" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.16.tgz#1b1651249e94b51f8f0d33439843e33e39775b30" + integrity sha512-1eMtTrXtrwscjcAeO4BVK+vvkxaLJSPFz1w1KLawz6HLNi9bPFGBNwwDyVfiu1Tv/vRRFYfoGaKhmAQPGPn5Wg== + dependencies: + "@babel/traverse" "^7.13.15" + "@babel/types" "^7.13.16" + +"@babel/helper-member-expression-to-functions@^7.12.13", "@babel/helper-member-expression-to-functions@^7.12.17": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.17.tgz#f82838eb06e1235307b6d71457b6670ff71ee5ac" + integrity sha512-Bzv4p3ODgS/qpBE0DiJ9qf5WxSmrQ8gVTe8ClMfwwsY2x/rhykxxy3bXzG7AGTnPB2ij37zGJ/Q/6FruxHxsxg== + dependencies: + "@babel/types" "^7.12.17" + +"@babel/helper-member-expression-to-functions@^7.13.12": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz#dfe368f26d426a07299d8d6513821768216e6d72" + integrity sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw== + dependencies: + "@babel/types" "^7.13.12" + +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.1", "@babel/helper-module-imports@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz#ec67e4404f41750463e455cc3203f6a32e93fcb0" + integrity sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-module-imports@^7.13.12": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz#c6a369a6f3621cb25da014078684da9196b61977" + integrity sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA== + dependencies: + "@babel/types" "^7.13.12" + +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.12.13", "@babel/helper-module-transforms@^7.12.17": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.12.17.tgz#7c75b987d6dfd5b48e575648f81eaac891539509" + integrity sha512-sFL+p6zOCQMm9vilo06M4VHuTxUAwa6IxgL56Tq1DVtA0ziAGTH1ThmJq7xwPqdQlgAbKX3fb0oZNbtRIyA5KQ== + dependencies: + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-replace-supers" "^7.12.13" + "@babel/helper-simple-access" "^7.12.13" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/helper-validator-identifier" "^7.12.11" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.12.17" + "@babel/types" "^7.12.17" + lodash "^4.17.19" + +"@babel/helper-module-transforms@^7.13.0", "@babel/helper-module-transforms@^7.14.0", "@babel/helper-module-transforms@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz#ac1cc30ee47b945e3e0c4db12fa0c5389509dfe5" + integrity sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA== + dependencies: + "@babel/helper-module-imports" "^7.13.12" + "@babel/helper-replace-supers" "^7.13.12" + "@babel/helper-simple-access" "^7.13.12" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/helper-validator-identifier" "^7.14.0" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.14.2" + "@babel/types" "^7.14.2" + +"@babel/helper-optimise-call-expression@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz#5c02d171b4c8615b1e7163f888c1c81c30a2aaea" + integrity sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz#174254d0f2424d8aefb4dd48057511247b0a9eeb" + integrity sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA== + +"@babel/helper-plugin-utils@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af" + integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ== + +"@babel/helper-remap-async-to-generator@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.13.tgz#170365f4140e2d20e5c88f8ba23c24468c296878" + integrity sha512-Qa6PU9vNcj1NZacZZI1Mvwt+gXDH6CTfgAkSjeRMLE8HxtDK76+YDId6NQR+z7Rgd5arhD2cIbS74r0SxD6PDA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + "@babel/helper-wrap-function" "^7.12.13" + "@babel/types" "^7.12.13" + +"@babel/helper-remap-async-to-generator@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz#376a760d9f7b4b2077a9dd05aa9c3927cadb2209" + integrity sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + "@babel/helper-wrap-function" "^7.13.0" + "@babel/types" "^7.13.0" + +"@babel/helper-replace-supers@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.12.13.tgz#00ec4fb6862546bd3d0aff9aac56074277173121" + integrity sha512-pctAOIAMVStI2TMLhozPKbf5yTEXc0OJa0eENheb4w09SrgOWEs+P4nTOZYJQCqs8JlErGLDPDJTiGIp3ygbLg== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.12.13" + "@babel/helper-optimise-call-expression" "^7.12.13" + "@babel/traverse" "^7.12.13" + "@babel/types" "^7.12.13" + +"@babel/helper-replace-supers@^7.13.12", "@babel/helper-replace-supers@^7.14.3": + version "7.14.3" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.14.3.tgz#ca17b318b859d107f0e9b722d58cf12d94436600" + integrity sha512-Rlh8qEWZSTfdz+tgNV/N4gz1a0TMNwCUcENhMjHTHKp3LseYH5Jha0NSlyTQWMnjbYcwFt+bqAMqSLHVXkQ6UA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.13.12" + "@babel/helper-optimise-call-expression" "^7.12.13" + "@babel/traverse" "^7.14.2" + "@babel/types" "^7.14.2" + +"@babel/helper-simple-access@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.12.13.tgz#8478bcc5cacf6aa1672b251c1d2dde5ccd61a6c4" + integrity sha512-0ski5dyYIHEfwpWGx5GPWhH35j342JaflmCeQmsPWcrOQDtCN6C1zKAVRFVbK53lPW2c9TsuLLSUDf0tIGJ5hA== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-simple-access@^7.13.12": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz#dd6c538afb61819d205a012c31792a39c7a5eaf6" + integrity sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA== + dependencies: + "@babel/types" "^7.13.12" + +"@babel/helper-skip-transparent-expression-wrappers@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz#462dc63a7e435ade8468385c63d2b84cce4b3cbf" + integrity sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA== + dependencies: + "@babel/types" "^7.12.1" + +"@babel/helper-split-export-declaration@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz#e9430be00baf3e88b0e13e6f9d4eaf2136372b05" + integrity sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-validator-identifier@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" + integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== + +"@babel/helper-validator-identifier@^7.14.0": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288" + integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A== + +"@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.12.17": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831" + integrity sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw== + +"@babel/helper-wrap-function@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.12.13.tgz#e3ea8cb3ee0a16911f9c1b50d9e99fe8fe30f9ff" + integrity sha512-t0aZFEmBJ1LojdtJnhOaQEVejnzYhyjWHSsNSNo8vOYRbAJNh6r6GQF7pd36SqG7OKGbn+AewVQ/0IfYfIuGdw== + dependencies: + "@babel/helper-function-name" "^7.12.13" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.12.13" + "@babel/types" "^7.12.13" + +"@babel/helper-wrap-function@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz#bdb5c66fda8526ec235ab894ad53a1235c79fcc4" + integrity sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA== + dependencies: + "@babel/helper-function-name" "^7.12.13" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.0" + +"@babel/helpers@^7.12.1", "@babel/helpers@^7.12.17": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.12.17.tgz#71e03d2981a6b5ee16899964f4101dc8471d60bc" + integrity sha512-tEpjqSBGt/SFEsFikKds1sLNChKKGGR17flIgQKXH4fG6m9gTgl3gnOC1giHNyaBCSKuTfxaSzHi7UnvqiVKxg== + dependencies: + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.12.17" + "@babel/types" "^7.12.17" + +"@babel/helpers@^7.14.0": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.0.tgz#ea9b6be9478a13d6f961dbb5f36bf75e2f3b8f62" + integrity sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg== + dependencies: + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.14.0" + "@babel/types" "^7.14.0" + +"@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.12.13.tgz#8ab538393e00370b26271b01fa08f7f27f2e795c" + integrity sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.12.17", "@babel/parser@^7.12.3", "@babel/parser@^7.7.0": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.17.tgz#bc85d2d47db38094e5bb268fc761716e7d693848" + integrity sha512-r1yKkiUTYMQ8LiEI0UcQx5ETw5dpTLn9wijn9hk6KkTtOK95FndDN10M+8/s6k/Ymlbivw0Av9q4SlgF80PtHg== + +"@babel/parser@^7.14.2", "@babel/parser@^7.14.3": + version "7.14.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.3.tgz#9b530eecb071fd0c93519df25c5ff9f14759f298" + integrity sha512-7MpZDIfI7sUC5zWo2+foJ50CSI5lcqDehZ0lVgIhSi4bFEk94fLAKlF3Q0nzSQQ+ca0lm+O6G9ztKVBeu8PMRQ== + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.13.12": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz#a3484d84d0b549f3fc916b99ee4783f26fabad2a" + integrity sha512-d0u3zWKcoZf379fOeJdr1a5WPDny4aOFZ6hlfKivgK0LY7ZxNfoaHL2fWwdGtHyVvra38FC+HVYkO+byfSA8AQ== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" + "@babel/plugin-proposal-optional-chaining" "^7.13.12" + +"@babel/plugin-proposal-async-generator-functions@^7.12.1", "@babel/plugin-proposal-async-generator-functions@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.13.tgz#d1c6d841802ffb88c64a2413e311f7345b9e66b5" + integrity sha512-1KH46Hx4WqP77f978+5Ye/VUbuwQld2hph70yaw2hXS2v7ER2f3nlpNMu909HO2rbvP0NKLlMVDPh9KXklVMhA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-remap-async-to-generator" "^7.12.13" + "@babel/plugin-syntax-async-generators" "^7.8.0" + +"@babel/plugin-proposal-async-generator-functions@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.2.tgz#3a2085abbf5d5f962d480dbc81347385ed62eb1e" + integrity sha512-b1AM4F6fwck4N8ItZ/AtC4FP/cqZqmKRQ4FaTDutwSYyjuhtvsGEMLK4N/ztV/ImP40BjIDyMgBQAeAMsQYVFQ== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-remap-async-to-generator" "^7.13.0" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-proposal-class-properties@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz#a082ff541f2a29a4821065b8add9346c0c16e5de" + integrity sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.12.1" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-proposal-class-properties@^7.12.1", "@babel/plugin-proposal-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.13.tgz#3d2ce350367058033c93c098e348161d6dc0d8c8" + integrity sha512-8SCJ0Ddrpwv4T7Gwb33EmW1V9PY5lggTO+A8WjyIwxrSHDUyBw4MtF96ifn1n8H806YlxbVCoKXbbmzD6RD+cA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-proposal-class-properties@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.13.0.tgz#146376000b94efd001e57a40a88a525afaab9f37" + integrity sha512-KnTDjFNC1g+45ka0myZNvSBFLhNCLN+GeGYLDEA8Oq7MZ6yMgfLoIRh86GRT0FjtJhZw8JyUskP9uvj5pHM9Zg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.13.0" + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-proposal-class-static-block@^7.13.11": + version "7.14.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.14.3.tgz#5a527e2cae4a4753119c3a3e7f64ecae8ccf1360" + integrity sha512-HEjzp5q+lWSjAgJtSluFDrGGosmwTgKwCXdDQZvhKsRlwv3YdkUEqxNrrjesJd+B9E9zvr1PVPVBvhYZ9msjvQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.14.3" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-class-static-block" "^7.12.13" + +"@babel/plugin-proposal-decorators@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.12.1.tgz#59271439fed4145456c41067450543aee332d15f" + integrity sha512-knNIuusychgYN8fGJHONL0RbFxLGawhXOJNLBk75TniTsZZeA+wdkDuv6wp4lGwzQEKjZi6/WYtnb3udNPmQmQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.12.1" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-decorators" "^7.12.1" + +"@babel/plugin-proposal-dynamic-import@^7.12.1", "@babel/plugin-proposal-dynamic-import@^7.12.17": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.17.tgz#e0ebd8db65acc37eac518fa17bead2174e224512" + integrity sha512-ZNGoFZqrnuy9H2izB2jLlnNDAfVPlGl5NhFEiFe4D84ix9GQGygF+CWMGHKuE+bpyS/AOuDQCnkiRNqW2IzS1Q== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + +"@babel/plugin-proposal-dynamic-import@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.2.tgz#01ebabd7c381cff231fa43e302939a9de5be9d9f" + integrity sha512-oxVQZIWFh91vuNEMKltqNsKLFWkOIyJc95k2Gv9lWVyDfPUQGSSlbDEgWuJUU1afGE9WwlzpucMZ3yDRHIItkA== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-proposal-export-namespace-from@^7.12.1", "@babel/plugin-proposal-export-namespace-from@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.13.tgz#393be47a4acd03fa2af6e3cde9b06e33de1b446d" + integrity sha512-INAgtFo4OnLN3Y/j0VwAgw3HDXcDtX+C/erMvWzuV9v71r7urb6iyMXu7eM9IgLr1ElLlOkaHjJ0SbCmdOQ3Iw== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-proposal-export-namespace-from@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.2.tgz#62542f94aa9ce8f6dba79eec698af22112253791" + integrity sha512-sRxW3z3Zp3pFfLAgVEvzTFutTXax837oOatUIvSG9o5gRj9mKwm3br1Se5f4QalTQs9x4AzlA/HrCWbQIHASUQ== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-proposal-json-strings@^7.12.1", "@babel/plugin-proposal-json-strings@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.13.tgz#ced7888a2db92a3d520a2e35eb421fdb7fcc9b5d" + integrity sha512-v9eEi4GiORDg8x+Dmi5r8ibOe0VXoKDeNPYcTTxdGN4eOWikrJfDJCJrr1l5gKGvsNyGJbrfMftC2dTL6oz7pg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-json-strings" "^7.8.0" + +"@babel/plugin-proposal-json-strings@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.2.tgz#830b4e2426a782e8b2878fbfe2cba85b70cbf98c" + integrity sha512-w2DtsfXBBJddJacXMBhElGEYqCZQqN99Se1qeYn8DVLB33owlrlLftIbMzn5nz1OITfDVknXF433tBrLEAOEjA== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-proposal-logical-assignment-operators@^7.12.1", "@babel/plugin-proposal-logical-assignment-operators@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.13.tgz#575b5d9a08d8299eeb4db6430da6e16e5cf14350" + integrity sha512-fqmiD3Lz7jVdK6kabeSr1PZlWSUVqSitmHEe3Z00dtGTKieWnX9beafvavc32kjORa5Bai4QNHgFDwWJP+WtSQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-proposal-logical-assignment-operators@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.2.tgz#222348c080a1678e0e74ea63fe76f275882d1fd7" + integrity sha512-1JAZtUrqYyGsS7IDmFeaem+/LJqujfLZ2weLR9ugB0ufUPjzf8cguyVT1g5im7f7RXxuLq1xUxEzvm68uYRtGg== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-proposal-nullish-coalescing-operator@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz#3ed4fff31c015e7f3f1467f190dbe545cd7b046c" + integrity sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.12.1", "@babel/plugin-proposal-nullish-coalescing-operator@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.13.tgz#24867307285cee4e1031170efd8a7ac807deefde" + integrity sha512-Qoxpy+OxhDBI5kRqliJFAl4uWXk3Bn24WeFstPH0iLymFehSAUR8MHpqU7njyXv/qbo7oN6yTy5bfCmXdKpo1Q== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.2.tgz#425b11dc62fc26939a2ab42cbba680bdf5734546" + integrity sha512-ebR0zU9OvI2N4qiAC38KIAK75KItpIPTpAtd2r4OZmMFeKbKJpUFLYP2EuDut82+BmYi8sz42B+TfTptJ9iG5Q== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-proposal-numeric-separator@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.1.tgz#0e2c6774c4ce48be412119b4d693ac777f7685a6" + integrity sha512-MR7Ok+Af3OhNTCxYVjJZHS0t97ydnJZt/DbR4WISO39iDnhiD8XHrY12xuSJ90FFEGjir0Fzyyn7g/zY6hxbxA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-proposal-numeric-separator@^7.12.1", "@babel/plugin-proposal-numeric-separator@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.13.tgz#bd9da3188e787b5120b4f9d465a8261ce67ed1db" + integrity sha512-O1jFia9R8BUCl3ZGB7eitaAPu62TXJRHn7rh+ojNERCFyqRwJMTmhz+tJ+k0CwI6CLjX/ee4qW74FSqlq9I35w== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-proposal-numeric-separator@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.2.tgz#82b4cc06571143faf50626104b335dd71baa4f9e" + integrity sha512-DcTQY9syxu9BpU3Uo94fjCB3LN9/hgPS8oUL7KrSW3bA2ePrKZZPJcc5y0hoJAM9dft3pGfErtEUvxXQcfLxUg== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-proposal-object-rest-spread@^7.12.1", "@babel/plugin-proposal-object-rest-spread@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.13.tgz#f93f3116381ff94bc676fdcb29d71045cd1ec011" + integrity sha512-WvA1okB/0OS/N3Ldb3sziSrXg6sRphsBgqiccfcQq7woEn5wQLNX82Oc4PlaFcdwcWHuQXAtb8ftbS8Fbsg/sg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.12.13" + +"@babel/plugin-proposal-object-rest-spread@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.2.tgz#e17d418f81cc103fedd4ce037e181c8056225abc" + integrity sha512-hBIQFxwZi8GIp934+nj5uV31mqclC1aYDhctDu5khTi9PCCUOczyy0b34W0oE9U/eJXiqQaKyVsmjeagOaSlbw== + dependencies: + "@babel/compat-data" "^7.14.0" + "@babel/helper-compilation-targets" "^7.13.16" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.14.2" + +"@babel/plugin-proposal-optional-catch-binding@^7.12.1", "@babel/plugin-proposal-optional-catch-binding@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.13.tgz#4640520afe57728af14b4d1574ba844f263bcae5" + integrity sha512-9+MIm6msl9sHWg58NvqpNpLtuFbmpFYk37x8kgnGzAHvX35E1FyAwSUt5hIkSoWJFSAH+iwU8bJ4fcD1zKXOzg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + +"@babel/plugin-proposal-optional-catch-binding@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.2.tgz#150d4e58e525b16a9a1431bd5326c4eed870d717" + integrity sha512-XtkJsmJtBaUbOxZsNk0Fvrv8eiqgneug0A6aqLFZ4TSkar2L5dSXWcnUKHgmjJt49pyB/6ZHvkr3dPgl9MOWRQ== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-proposal-optional-chaining@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.1.tgz#cce122203fc8a32794296fc377c6dedaf4363797" + integrity sha512-c2uRpY6WzaVDzynVY9liyykS+kVU+WRZPMPYpkelXH8KBt1oXoI89kPbZKKG/jDT5UK92FTW2fZkZaJhdiBabw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + +"@babel/plugin-proposal-optional-chaining@^7.12.1", "@babel/plugin-proposal-optional-chaining@^7.12.17": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.17.tgz#e382becadc2cb16b7913b6c672d92e4b33385b5c" + integrity sha512-TvxwI80pWftrGPKHNfkvX/HnoeSTR7gC4ezWnAL39PuktYUe6r8kEpOLTYnkBTsaoeazXm2jHJ22EQ81sdgfcA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + +"@babel/plugin-proposal-optional-chaining@^7.13.12", "@babel/plugin-proposal-optional-chaining@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.2.tgz#df8171a8b9c43ebf4c1dabe6311b432d83e1b34e" + integrity sha512-qQByMRPwMZJainfig10BoaDldx/+VDtNcrA7qdNaEOAj6VXud+gfrkA8j4CRAU5HjnWREXqIpSpH30qZX1xivA== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-proposal-private-methods@^7.12.1", "@babel/plugin-proposal-private-methods@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.13.tgz#ea78a12554d784ecf7fc55950b752d469d9c4a71" + integrity sha512-sV0V57uUwpauixvR7s2o75LmwJI6JECwm5oPUY5beZB1nBl2i37hc7CJGqB5G+58fur5Y6ugvl3LRONk5x34rg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-proposal-private-methods@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz#04bd4c6d40f6e6bbfa2f57e2d8094bad900ef787" + integrity sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.13.0" + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-proposal-private-property-in-object@^7.14.0": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.14.0.tgz#b1a1f2030586b9d3489cc26179d2eb5883277636" + integrity sha512-59ANdmEwwRUkLjB7CRtwJxxwtjESw+X2IePItA+RGQh+oy5RmpCh/EvVVvh5XQc3yxsm5gtv0+i9oBZhaDNVTg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + "@babel/helper-create-class-features-plugin" "^7.14.0" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-private-property-in-object" "^7.14.0" + +"@babel/plugin-proposal-unicode-property-regex@^7.12.1", "@babel/plugin-proposal-unicode-property-regex@^7.12.13", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz#bebde51339be829c17aaaaced18641deb62b39ba" + integrity sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-async-generators@^7.8.0", "@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.1", "@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.12.13.tgz#8e3d674b0613e67975ceac2776c97b60cafc5c9c" + integrity sha512-ZmKQ0ZXR0nYpHZIIuj9zE7oIqCx2hw9TKi+lIo73NNrMPAZGHfS92/VRV0ZmPj6H2ffBgyFHXvJ5NYsNeEaP2A== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-decorators@^7.12.1": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.12.13.tgz#fac829bf3c7ef4a1bc916257b403e58c6bdaf648" + integrity sha512-Rw6aIXGuqDLr6/LoBBYE57nKOzQpz/aDkKlMqEwH+Vp0MXbG6H/TfRjaY343LKxzAKAMXIHsQ8JzaZKuDZ9MwA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-dynamic-import@^7.8.0", "@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-flow@^7.12.1": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.12.13.tgz#5df9962503c0a9c918381c929d51d4d6949e7e86" + integrity sha512-J/RYxnlSLXZLVR7wTRsozxKT8qbsx1mNKJzXEEjQ0Kjx1ZACcyHgbanNWNCFtc36IzuWhYWPpvJFFoexoOWFmA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.0", "@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.13.tgz#044fb81ebad6698fe62c478875575bcbb9b70f15" + integrity sha512-d4HM23Q1K7oq/SLNmG6mRt85l2csmQ0cHRaxRXjKW0YFdEXqlZ5kzFQKH5Uc3rDJECgu+yCRgPkG04Mm98R/1g== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0", "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.0", "@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.0", "@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.0": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.0.tgz#762a4babec61176fec6c88480dec40372b140c0b" + integrity sha512-bda3xF8wGl5/5btF794utNOL0Jw+9jE5C1sLZcoK7c4uonE/y3iQiyG+KbkF3WBV/paX58VCpjhxLPkdj5Fe4w== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-syntax-top-level-await@^7.12.1", "@babel/plugin-syntax-top-level-await@^7.12.13", "@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz#c5f0fa6e249f5b739727f923540cf7a806130178" + integrity sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-typescript@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.13.tgz#9dff111ca64154cef0f4dc52cf843d9f12ce4474" + integrity sha512-cHP3u1JiUiG2LFDKbXnwVad81GvfyIOmCD6HIEId6ojrY0Drfy2q1jw7BwN7dE84+kTnBjLkXoL3IEy/3JPu2w== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-arrow-functions@^7.12.1", "@babel/plugin-transform-arrow-functions@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.13.tgz#eda5670b282952100c229f8a3bd49e0f6a72e9fe" + integrity sha512-tBtuN6qtCTd+iHzVZVOMNp+L04iIJBpqkdY42tWbmjIT5wvR2kx7gxMBsyhQtFzHwBbyGi9h8J8r9HgnOpQHxg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-arrow-functions@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz#10a59bebad52d637a027afa692e8d5ceff5e3dae" + integrity sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-async-to-generator@^7.12.1", "@babel/plugin-transform-async-to-generator@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.13.tgz#fed8c69eebf187a535bfa4ee97a614009b24f7ae" + integrity sha512-psM9QHcHaDr+HZpRuJcE1PXESuGWSCcbiGFFhhwfzdbTxaGDVzuVtdNYliAwcRo3GFg0Bc8MmI+AvIGYIJG04A== + dependencies: + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-remap-async-to-generator" "^7.12.13" + +"@babel/plugin-transform-async-to-generator@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz#8e112bf6771b82bf1e974e5e26806c5c99aa516f" + integrity sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg== + dependencies: + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-remap-async-to-generator" "^7.13.0" + +"@babel/plugin-transform-block-scoped-functions@^7.12.1", "@babel/plugin-transform-block-scoped-functions@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz#a9bf1836f2a39b4eb6cf09967739de29ea4bf4c4" + integrity sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-block-scoping@^7.12.1", "@babel/plugin-transform-block-scoping@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.13.tgz#f36e55076d06f41dfd78557ea039c1b581642e61" + integrity sha512-Pxwe0iqWJX4fOOM2kEZeUuAxHMWb9nK+9oh5d11bsLoB0xMg+mkDpt0eYuDZB7ETrY9bbcVlKUGTOGWy7BHsMQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-block-scoping@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.2.tgz#761cb12ab5a88d640ad4af4aa81f820e6b5fdf5c" + integrity sha512-neZZcP19NugZZqNwMTH+KoBjx5WyvESPSIOQb4JHpfd+zPfqcH65RMu5xJju5+6q/Y2VzYrleQTr+b6METyyxg== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-classes@^7.12.1", "@babel/plugin-transform-classes@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.13.tgz#9728edc1838b5d62fc93ad830bd523b1fcb0e1f6" + integrity sha512-cqZlMlhCC1rVnxE5ZGMtIb896ijL90xppMiuWXcwcOAuFczynpd3KYemb91XFFPi3wJSe/OcrX9lXoowatkkxA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-optimise-call-expression" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-replace-supers" "^7.12.13" + "@babel/helper-split-export-declaration" "^7.12.13" + globals "^11.1.0" + +"@babel/plugin-transform-classes@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.2.tgz#3f1196c5709f064c252ad056207d87b7aeb2d03d" + integrity sha512-7oafAVcucHquA/VZCsXv/gmuiHeYd64UJyyTYU+MPfNu0KeNlxw06IeENBO8bJjXVbolu+j1MM5aKQtH1OMCNg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + "@babel/helper-function-name" "^7.14.2" + "@babel/helper-optimise-call-expression" "^7.12.13" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-replace-supers" "^7.13.12" + "@babel/helper-split-export-declaration" "^7.12.13" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.12.1", "@babel/plugin-transform-computed-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.13.tgz#6a210647a3d67f21f699cfd2a01333803b27339d" + integrity sha512-dDfuROUPGK1mTtLKyDPUavmj2b6kFu82SmgpztBFEO974KMjJT+Ytj3/oWsTUMBmgPcp9J5Pc1SlcAYRpJ2hRA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-computed-properties@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz#845c6e8b9bb55376b1fa0b92ef0bdc8ea06644ed" + integrity sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-destructuring@^7.12.1", "@babel/plugin-transform-destructuring@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.13.tgz#fc56c5176940c5b41735c677124d1d20cecc9aeb" + integrity sha512-Dn83KykIFzjhA3FDPA1z4N+yfF3btDGhjnJwxIj0T43tP0flCujnU8fKgEkf0C1biIpSv9NZegPBQ1J6jYkwvQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-destructuring@^7.13.17": + version "7.13.17" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.17.tgz#678d96576638c19d5b36b332504d3fd6e06dea27" + integrity sha512-UAUqiLv+uRLO+xuBKKMEpC+t7YRNVRqBsWWq1yKXbBZBje/t3IXCiSinZhjn/DC3qzBfICeYd2EFGEbHsh5RLA== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-dotall-regex@^7.12.1", "@babel/plugin-transform-dotall-regex@^7.12.13", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz#3f1601cc29905bfcb67f53910f197aeafebb25ad" + integrity sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-duplicate-keys@^7.12.1", "@babel/plugin-transform-duplicate-keys@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz#6f06b87a8b803fd928e54b81c258f0a0033904de" + integrity sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-exponentiation-operator@^7.12.1", "@babel/plugin-transform-exponentiation-operator@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz#4d52390b9a273e651e4aba6aee49ef40e80cd0a1" + integrity sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-flow-strip-types@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.12.1.tgz#8430decfa7eb2aea5414ed4a3fa6e1652b7d77c4" + integrity sha512-8hAtkmsQb36yMmEtk2JZ9JnVyDSnDOdlB+0nEGzIDLuK4yR3JcEjfuFPYkdEPSh8Id+rAMeBEn+X0iVEyho6Hg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-flow" "^7.12.1" + +"@babel/plugin-transform-for-of@^7.12.1", "@babel/plugin-transform-for-of@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.13.tgz#561ff6d74d9e1c8879cb12dbaf4a14cd29d15cf6" + integrity sha512-xCbdgSzXYmHGyVX3+BsQjcd4hv4vA/FDy7Kc8eOpzKmBBPEOTurt0w5fCRQaGl+GSBORKgJdstQ1rHl4jbNseQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-for-of@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz#c799f881a8091ac26b54867a845c3e97d2696062" + integrity sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-function-name@^7.12.1", "@babel/plugin-transform-function-name@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz#bb024452f9aaed861d374c8e7a24252ce3a50051" + integrity sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ== + dependencies: + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-literals@^7.12.1", "@babel/plugin-transform-literals@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz#2ca45bafe4a820197cf315794a4d26560fe4bdb9" + integrity sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-member-expression-literals@^7.12.1", "@babel/plugin-transform-member-expression-literals@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz#5ffa66cd59b9e191314c9f1f803b938e8c081e40" + integrity sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-modules-amd@^7.12.1", "@babel/plugin-transform-modules-amd@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.13.tgz#43db16249b274ee2e551e2422090aa1c47692d56" + integrity sha512-JHLOU0o81m5UqG0Ulz/fPC68/v+UTuGTWaZBUwpEk1fYQ1D9LfKV6MPn4ttJKqRo5Lm460fkzjLTL4EHvCprvA== + dependencies: + "@babel/helper-module-transforms" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-amd@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.2.tgz#6622806fe1a7c07a1388444222ef9535f2ca17b0" + integrity sha512-hPC6XBswt8P3G2D1tSV2HzdKvkqOpmbyoy+g73JG0qlF/qx2y3KaMmXb1fLrpmWGLZYA0ojCvaHdzFWjlmV+Pw== + dependencies: + "@babel/helper-module-transforms" "^7.14.2" + "@babel/helper-plugin-utils" "^7.13.0" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-commonjs@^7.12.1", "@babel/plugin-transform-modules-commonjs@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.13.tgz#5043b870a784a8421fa1fd9136a24f294da13e50" + integrity sha512-OGQoeVXVi1259HjuoDnsQMlMkT9UkZT9TpXAsqWplS/M0N1g3TJAn/ByOCeQu7mfjc5WpSsRU+jV1Hd89ts0kQ== + dependencies: + "@babel/helper-module-transforms" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-simple-access" "^7.12.13" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-commonjs@^7.14.0": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.0.tgz#52bc199cb581e0992edba0f0f80356467587f161" + integrity sha512-EX4QePlsTaRZQmw9BsoPeyh5OCtRGIhwfLquhxGp5e32w+dyL8htOcDwamlitmNFK6xBZYlygjdye9dbd9rUlQ== + dependencies: + "@babel/helper-module-transforms" "^7.14.0" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-simple-access" "^7.13.12" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-systemjs@^7.12.1", "@babel/plugin-transform-modules-systemjs@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.13.tgz#351937f392c7f07493fc79b2118201d50404a3c5" + integrity sha512-aHfVjhZ8QekaNF/5aNdStCGzwTbU7SI5hUybBKlMzqIMC7w7Ho8hx5a4R/DkTHfRfLwHGGxSpFt9BfxKCoXKoA== + dependencies: + "@babel/helper-hoist-variables" "^7.12.13" + "@babel/helper-module-transforms" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-validator-identifier" "^7.12.11" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-systemjs@^7.13.8": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz#6d066ee2bff3c7b3d60bf28dec169ad993831ae3" + integrity sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A== + dependencies: + "@babel/helper-hoist-variables" "^7.13.0" + "@babel/helper-module-transforms" "^7.13.0" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-validator-identifier" "^7.12.11" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-umd@^7.12.1", "@babel/plugin-transform-modules-umd@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.13.tgz#26c66f161d3456674e344b4b1255de4d530cfb37" + integrity sha512-BgZndyABRML4z6ibpi7Z98m4EVLFI9tVsZDADC14AElFaNHHBcJIovflJ6wtCqFxwy2YJ1tJhGRsr0yLPKoN+w== + dependencies: + "@babel/helper-module-transforms" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-modules-umd@^7.14.0": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.0.tgz#2f8179d1bbc9263665ce4a65f305526b2ea8ac34" + integrity sha512-nPZdnWtXXeY7I87UZr9VlsWme3Y0cfFFE41Wbxz4bbaexAjNMInXPFUpRRUJ8NoMm0Cw+zxbqjdPmLhcjfazMw== + dependencies: + "@babel/helper-module-transforms" "^7.14.0" + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.12.1", "@babel/plugin-transform-named-capturing-groups-regex@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz#2213725a5f5bbbe364b50c3ba5998c9599c5c9d9" + integrity sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.12.13" + +"@babel/plugin-transform-new-target@^7.12.1", "@babel/plugin-transform-new-target@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz#e22d8c3af24b150dd528cbd6e685e799bf1c351c" + integrity sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-object-super@^7.12.1", "@babel/plugin-transform-object-super@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz#b4416a2d63b8f7be314f3d349bd55a9c1b5171f7" + integrity sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-replace-supers" "^7.12.13" + +"@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.13.tgz#461e76dfb63c2dfd327b8a008a9e802818ce9853" + integrity sha512-e7QqwZalNiBRHCpJg/P8s/VJeSRYgmtWySs1JwvfwPqhBbiWfOcHDKdeAi6oAyIimoKWBlwc8oTgbZHdhCoVZA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-parameters@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.14.2.tgz#e4290f72e0e9e831000d066427c4667098decc31" + integrity sha512-NxoVmA3APNCC1JdMXkdYXuQS+EMdqy0vIwyDHeKHiJKRxmp1qGSdb0JLEIoPRhkx6H/8Qi3RJ3uqOCYw8giy9A== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-property-literals@^7.12.1", "@babel/plugin-transform-property-literals@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz#4e6a9e37864d8f1b3bc0e2dce7bf8857db8b1a81" + integrity sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-react-constant-elements@^7.9.0": + version "7.13.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.13.13.tgz#0208b1d942bf939cd4f7aa5b255d42602aa4a920" + integrity sha512-SNJU53VM/SjQL0bZhyU+f4kJQz7bQQajnrZRSaU21hruG/NWY41AEM9AWXeXX90pYr/C2yAmTgI6yW3LlLrAUQ== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-react-display-name@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.1.tgz#1cbcd0c3b1d6648c55374a22fc9b6b7e5341c00d" + integrity sha512-cAzB+UzBIrekfYxyLlFqf/OagTvHLcVBb5vpouzkYkBclRPraiygVnafvAoipErZLI8ANv8Ecn6E/m5qPXD26w== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-react-display-name@^7.12.1", "@babel/plugin-transform-react-display-name@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.13.tgz#c28effd771b276f4647411c9733dbb2d2da954bd" + integrity sha512-MprESJzI9O5VnJZrL7gg1MpdqmiFcUv41Jc7SahxYsNP2kDkFqClxxTZq+1Qv4AFCamm+GXMRDQINNn+qrxmiA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-react-jsx-development@^7.12.1", "@babel/plugin-transform-react-jsx-development@^7.12.17": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.17.tgz#f510c0fa7cd7234153539f9a362ced41a5ca1447" + integrity sha512-BPjYV86SVuOaudFhsJR1zjgxxOhJDt6JHNoD48DxWEIxUCAMjV1ys6DYw4SDYZh0b1QsS2vfIA9t/ZsQGsDOUQ== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.12.17" + +"@babel/plugin-transform-react-jsx-self@^7.12.1": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.12.13.tgz#422d99d122d592acab9c35ea22a6cfd9bf189f60" + integrity sha512-FXYw98TTJ125GVCCkFLZXlZ1qGcsYqNQhVBQcZjyrwf8FEUtVfKIoidnO8S0q+KBQpDYNTmiGo1gn67Vti04lQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-react-jsx-source@^7.12.1": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.12.13.tgz#051d76126bee5c9a6aa3ba37be2f6c1698856bcb" + integrity sha512-O5JJi6fyfih0WfDgIJXksSPhGP/G0fQpfxYy87sDc+1sFmsCS6wr3aAn+whbzkhbjtq4VMqLRaSzR6IsshIC0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-react-jsx@^7.12.1", "@babel/plugin-transform-react-jsx@^7.12.17": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.17.tgz#dd2c1299f5e26de584939892de3cfc1807a38f24" + integrity sha512-mwaVNcXV+l6qJOuRhpdTEj8sT/Z0owAVWf9QujTZ0d2ye9X/K+MTOTSizcgKOj18PGnTc/7g1I4+cIUjsKhBcw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-jsx" "^7.12.13" + "@babel/types" "^7.12.17" + +"@babel/plugin-transform-react-jsx@^7.13.12": + version "7.14.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.14.3.tgz#0e26597805cf0862da735f264550933c38babb66" + integrity sha512-uuxuoUNVhdgYzERiHHFkE4dWoJx+UFVyuAl0aqN8P2/AKFHwqgUC5w2+4/PjpKXJsFgBlYAFXlUmDQ3k3DUkXw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.12.13" + "@babel/helper-module-imports" "^7.13.12" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/plugin-syntax-jsx" "^7.12.13" + "@babel/types" "^7.14.2" + +"@babel/plugin-transform-react-pure-annotations@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz#05d46f0ab4d1339ac59adf20a1462c91b37a1a42" + integrity sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-regenerator@^7.12.1", "@babel/plugin-transform-regenerator@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.13.tgz#b628bcc9c85260ac1aeb05b45bde25210194a2f5" + integrity sha512-lxb2ZAvSLyJ2PEe47hoGWPmW22v7CtSl9jW8mingV4H2sEX/JOcrAj2nPuGWi56ERUm2bUpjKzONAuT6HCn2EA== + dependencies: + regenerator-transform "^0.14.2" + +"@babel/plugin-transform-regenerator@^7.13.15": + version "7.13.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz#e5eb28945bf8b6563e7f818945f966a8d2997f39" + integrity sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ== + dependencies: + regenerator-transform "^0.14.2" + +"@babel/plugin-transform-reserved-words@^7.12.1", "@babel/plugin-transform-reserved-words@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz#7d9988d4f06e0fe697ea1d9803188aa18b472695" + integrity sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-runtime@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.1.tgz#04b792057eb460389ff6a4198e377614ea1e7ba5" + integrity sha512-Ac/H6G9FEIkS2tXsZjL4RAdS3L3WHxci0usAnz7laPWUmFiGtj7tIASChqKZMHTSQTQY6xDbOq+V1/vIq3QrWg== + dependencies: + "@babel/helper-module-imports" "^7.12.1" + "@babel/helper-plugin-utils" "^7.10.4" + resolve "^1.8.1" + semver "^5.5.1" + +"@babel/plugin-transform-shorthand-properties@^7.12.1", "@babel/plugin-transform-shorthand-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz#db755732b70c539d504c6390d9ce90fe64aff7ad" + integrity sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-spread@^7.12.1", "@babel/plugin-transform-spread@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.13.tgz#ca0d5645abbd560719c354451b849f14df4a7949" + integrity sha512-dUCrqPIowjqk5pXsx1zPftSq4sT0aCeZVAxhdgs3AMgyaDmoUT0G+5h3Dzja27t76aUEIJWlFgPJqJ/d4dbTtg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" + +"@babel/plugin-transform-spread@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz#84887710e273c1815ace7ae459f6f42a5d31d5fd" + integrity sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" + +"@babel/plugin-transform-sticky-regex@^7.12.1", "@babel/plugin-transform-sticky-regex@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz#760ffd936face73f860ae646fb86ee82f3d06d1f" + integrity sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-template-literals@^7.12.1", "@babel/plugin-transform-template-literals@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.13.tgz#655037b07ebbddaf3b7752f55d15c2fd6f5aa865" + integrity sha512-arIKlWYUgmNsF28EyfmiQHJLJFlAJNYkuQO10jL46ggjBpeb2re1P9K9YGxNJB45BqTbaslVysXDYm/g3sN/Qg== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-template-literals@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz#a36049127977ad94438dee7443598d1cefdf409d" + integrity sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + +"@babel/plugin-transform-typeof-symbol@^7.12.1", "@babel/plugin-transform-typeof-symbol@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz#785dd67a1f2ea579d9c2be722de8c84cb85f5a7f" + integrity sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-typescript@^7.12.1": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.17.tgz#4aa6a5041888dd2e5d316ec39212b0cf855211bb" + integrity sha512-1bIYwnhRoetxkFonuZRtDZPFEjl1l5r+3ITkxLC3mlMaFja+GQFo94b/WHEPjqWLU9Bc+W4oFZbvCGe9eYMu1g== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.12.17" + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-typescript" "^7.12.13" + +"@babel/plugin-transform-unicode-escapes@^7.12.1", "@babel/plugin-transform-unicode-escapes@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz#840ced3b816d3b5127dd1d12dcedc5dead1a5e74" + integrity sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-transform-unicode-regex@^7.12.1", "@babel/plugin-transform-unicode-regex@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz#b52521685804e155b1202e83fc188d34bb70f5ac" + integrity sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/preset-env@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.12.1.tgz#9c7e5ca82a19efc865384bb4989148d2ee5d7ac2" + integrity sha512-H8kxXmtPaAGT7TyBvSSkoSTUK6RHh61So05SyEbpmr0MCZrsNYn7mGMzzeYoOUCdHzww61k8XBft2TaES+xPLg== + dependencies: + "@babel/compat-data" "^7.12.1" + "@babel/helper-compilation-targets" "^7.12.1" + "@babel/helper-module-imports" "^7.12.1" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-validator-option" "^7.12.1" + "@babel/plugin-proposal-async-generator-functions" "^7.12.1" + "@babel/plugin-proposal-class-properties" "^7.12.1" + "@babel/plugin-proposal-dynamic-import" "^7.12.1" + "@babel/plugin-proposal-export-namespace-from" "^7.12.1" + "@babel/plugin-proposal-json-strings" "^7.12.1" + "@babel/plugin-proposal-logical-assignment-operators" "^7.12.1" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1" + "@babel/plugin-proposal-numeric-separator" "^7.12.1" + "@babel/plugin-proposal-object-rest-spread" "^7.12.1" + "@babel/plugin-proposal-optional-catch-binding" "^7.12.1" + "@babel/plugin-proposal-optional-chaining" "^7.12.1" + "@babel/plugin-proposal-private-methods" "^7.12.1" + "@babel/plugin-proposal-unicode-property-regex" "^7.12.1" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-class-properties" "^7.12.1" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.12.1" + "@babel/plugin-transform-arrow-functions" "^7.12.1" + "@babel/plugin-transform-async-to-generator" "^7.12.1" + "@babel/plugin-transform-block-scoped-functions" "^7.12.1" + "@babel/plugin-transform-block-scoping" "^7.12.1" + "@babel/plugin-transform-classes" "^7.12.1" + "@babel/plugin-transform-computed-properties" "^7.12.1" + "@babel/plugin-transform-destructuring" "^7.12.1" + "@babel/plugin-transform-dotall-regex" "^7.12.1" + "@babel/plugin-transform-duplicate-keys" "^7.12.1" + "@babel/plugin-transform-exponentiation-operator" "^7.12.1" + "@babel/plugin-transform-for-of" "^7.12.1" + "@babel/plugin-transform-function-name" "^7.12.1" + "@babel/plugin-transform-literals" "^7.12.1" + "@babel/plugin-transform-member-expression-literals" "^7.12.1" + "@babel/plugin-transform-modules-amd" "^7.12.1" + "@babel/plugin-transform-modules-commonjs" "^7.12.1" + "@babel/plugin-transform-modules-systemjs" "^7.12.1" + "@babel/plugin-transform-modules-umd" "^7.12.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.12.1" + "@babel/plugin-transform-new-target" "^7.12.1" + "@babel/plugin-transform-object-super" "^7.12.1" + "@babel/plugin-transform-parameters" "^7.12.1" + "@babel/plugin-transform-property-literals" "^7.12.1" + "@babel/plugin-transform-regenerator" "^7.12.1" + "@babel/plugin-transform-reserved-words" "^7.12.1" + "@babel/plugin-transform-shorthand-properties" "^7.12.1" + "@babel/plugin-transform-spread" "^7.12.1" + "@babel/plugin-transform-sticky-regex" "^7.12.1" + "@babel/plugin-transform-template-literals" "^7.12.1" + "@babel/plugin-transform-typeof-symbol" "^7.12.1" + "@babel/plugin-transform-unicode-escapes" "^7.12.1" + "@babel/plugin-transform-unicode-regex" "^7.12.1" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.12.1" + core-js-compat "^3.6.2" + semver "^5.5.0" + +"@babel/preset-env@^7.8.4": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.12.17.tgz#94a3793ff089c32ee74d76a3c03a7597693ebaaa" + integrity sha512-9PMijx8zFbCwTHrd2P4PJR5nWGH3zWebx2OcpTjqQrHhCiL2ssSR2Sc9ko2BsI2VmVBfoaQmPrlMTCui4LmXQg== + dependencies: + "@babel/compat-data" "^7.12.13" + "@babel/helper-compilation-targets" "^7.12.17" + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-validator-option" "^7.12.17" + "@babel/plugin-proposal-async-generator-functions" "^7.12.13" + "@babel/plugin-proposal-class-properties" "^7.12.13" + "@babel/plugin-proposal-dynamic-import" "^7.12.17" + "@babel/plugin-proposal-export-namespace-from" "^7.12.13" + "@babel/plugin-proposal-json-strings" "^7.12.13" + "@babel/plugin-proposal-logical-assignment-operators" "^7.12.13" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.13" + "@babel/plugin-proposal-numeric-separator" "^7.12.13" + "@babel/plugin-proposal-object-rest-spread" "^7.12.13" + "@babel/plugin-proposal-optional-catch-binding" "^7.12.13" + "@babel/plugin-proposal-optional-chaining" "^7.12.17" + "@babel/plugin-proposal-private-methods" "^7.12.13" + "@babel/plugin-proposal-unicode-property-regex" "^7.12.13" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.12.13" + "@babel/plugin-transform-arrow-functions" "^7.12.13" + "@babel/plugin-transform-async-to-generator" "^7.12.13" + "@babel/plugin-transform-block-scoped-functions" "^7.12.13" + "@babel/plugin-transform-block-scoping" "^7.12.13" + "@babel/plugin-transform-classes" "^7.12.13" + "@babel/plugin-transform-computed-properties" "^7.12.13" + "@babel/plugin-transform-destructuring" "^7.12.13" + "@babel/plugin-transform-dotall-regex" "^7.12.13" + "@babel/plugin-transform-duplicate-keys" "^7.12.13" + "@babel/plugin-transform-exponentiation-operator" "^7.12.13" + "@babel/plugin-transform-for-of" "^7.12.13" + "@babel/plugin-transform-function-name" "^7.12.13" + "@babel/plugin-transform-literals" "^7.12.13" + "@babel/plugin-transform-member-expression-literals" "^7.12.13" + "@babel/plugin-transform-modules-amd" "^7.12.13" + "@babel/plugin-transform-modules-commonjs" "^7.12.13" + "@babel/plugin-transform-modules-systemjs" "^7.12.13" + "@babel/plugin-transform-modules-umd" "^7.12.13" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.12.13" + "@babel/plugin-transform-new-target" "^7.12.13" + "@babel/plugin-transform-object-super" "^7.12.13" + "@babel/plugin-transform-parameters" "^7.12.13" + "@babel/plugin-transform-property-literals" "^7.12.13" + "@babel/plugin-transform-regenerator" "^7.12.13" + "@babel/plugin-transform-reserved-words" "^7.12.13" + "@babel/plugin-transform-shorthand-properties" "^7.12.13" + "@babel/plugin-transform-spread" "^7.12.13" + "@babel/plugin-transform-sticky-regex" "^7.12.13" + "@babel/plugin-transform-template-literals" "^7.12.13" + "@babel/plugin-transform-typeof-symbol" "^7.12.13" + "@babel/plugin-transform-unicode-escapes" "^7.12.13" + "@babel/plugin-transform-unicode-regex" "^7.12.13" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.12.17" + core-js-compat "^3.8.0" + semver "^5.5.0" + +"@babel/preset-env@^7.9.5": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.14.2.tgz#e80612965da73579c84ad2f963c2359c71524ed5" + integrity sha512-7dD7lVT8GMrE73v4lvDEb85cgcQhdES91BSD7jS/xjC6QY8PnRhux35ac+GCpbiRhp8crexBvZZqnaL6VrY8TQ== + dependencies: + "@babel/compat-data" "^7.14.0" + "@babel/helper-compilation-targets" "^7.13.16" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-validator-option" "^7.12.17" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.13.12" + "@babel/plugin-proposal-async-generator-functions" "^7.14.2" + "@babel/plugin-proposal-class-properties" "^7.13.0" + "@babel/plugin-proposal-class-static-block" "^7.13.11" + "@babel/plugin-proposal-dynamic-import" "^7.14.2" + "@babel/plugin-proposal-export-namespace-from" "^7.14.2" + "@babel/plugin-proposal-json-strings" "^7.14.2" + "@babel/plugin-proposal-logical-assignment-operators" "^7.14.2" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.14.2" + "@babel/plugin-proposal-numeric-separator" "^7.14.2" + "@babel/plugin-proposal-object-rest-spread" "^7.14.2" + "@babel/plugin-proposal-optional-catch-binding" "^7.14.2" + "@babel/plugin-proposal-optional-chaining" "^7.14.2" + "@babel/plugin-proposal-private-methods" "^7.13.0" + "@babel/plugin-proposal-private-property-in-object" "^7.14.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.12.13" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.12.13" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.0" + "@babel/plugin-syntax-top-level-await" "^7.12.13" + "@babel/plugin-transform-arrow-functions" "^7.13.0" + "@babel/plugin-transform-async-to-generator" "^7.13.0" + "@babel/plugin-transform-block-scoped-functions" "^7.12.13" + "@babel/plugin-transform-block-scoping" "^7.14.2" + "@babel/plugin-transform-classes" "^7.14.2" + "@babel/plugin-transform-computed-properties" "^7.13.0" + "@babel/plugin-transform-destructuring" "^7.13.17" + "@babel/plugin-transform-dotall-regex" "^7.12.13" + "@babel/plugin-transform-duplicate-keys" "^7.12.13" + "@babel/plugin-transform-exponentiation-operator" "^7.12.13" + "@babel/plugin-transform-for-of" "^7.13.0" + "@babel/plugin-transform-function-name" "^7.12.13" + "@babel/plugin-transform-literals" "^7.12.13" + "@babel/plugin-transform-member-expression-literals" "^7.12.13" + "@babel/plugin-transform-modules-amd" "^7.14.2" + "@babel/plugin-transform-modules-commonjs" "^7.14.0" + "@babel/plugin-transform-modules-systemjs" "^7.13.8" + "@babel/plugin-transform-modules-umd" "^7.14.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.12.13" + "@babel/plugin-transform-new-target" "^7.12.13" + "@babel/plugin-transform-object-super" "^7.12.13" + "@babel/plugin-transform-parameters" "^7.14.2" + "@babel/plugin-transform-property-literals" "^7.12.13" + "@babel/plugin-transform-regenerator" "^7.13.15" + "@babel/plugin-transform-reserved-words" "^7.12.13" + "@babel/plugin-transform-shorthand-properties" "^7.12.13" + "@babel/plugin-transform-spread" "^7.13.0" + "@babel/plugin-transform-sticky-regex" "^7.12.13" + "@babel/plugin-transform-template-literals" "^7.13.0" + "@babel/plugin-transform-typeof-symbol" "^7.12.13" + "@babel/plugin-transform-unicode-escapes" "^7.12.13" + "@babel/plugin-transform-unicode-regex" "^7.12.13" + "@babel/preset-modules" "^0.1.4" + "@babel/types" "^7.14.2" + babel-plugin-polyfill-corejs2 "^0.2.0" + babel-plugin-polyfill-corejs3 "^0.2.0" + babel-plugin-polyfill-regenerator "^0.2.0" + core-js-compat "^3.9.0" + semver "^6.3.0" + +"@babel/preset-modules@^0.1.3", "@babel/preset-modules@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e" + integrity sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/preset-react@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.12.1.tgz#7f022b13f55b6dd82f00f16d1c599ae62985358c" + integrity sha512-euCExymHCi0qB9u5fKw7rvlw7AZSjw/NaB9h7EkdTt5+yHRrXdiRTh7fkG3uBPpJg82CqLfp1LHLqWGSCrab+g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-react-display-name" "^7.12.1" + "@babel/plugin-transform-react-jsx" "^7.12.1" + "@babel/plugin-transform-react-jsx-development" "^7.12.1" + "@babel/plugin-transform-react-jsx-self" "^7.12.1" + "@babel/plugin-transform-react-jsx-source" "^7.12.1" + "@babel/plugin-transform-react-pure-annotations" "^7.12.1" + +"@babel/preset-react@^7.9.4": + version "7.13.13" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.13.13.tgz#fa6895a96c50763fe693f9148568458d5a839761" + integrity sha512-gx+tDLIE06sRjKJkVtpZ/t3mzCDOnPG+ggHZG9lffUbX8+wC739x20YQc9V35Do6ZAxaUc/HhVHIiOzz5MvDmA== + dependencies: + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-validator-option" "^7.12.17" + "@babel/plugin-transform-react-display-name" "^7.12.13" + "@babel/plugin-transform-react-jsx" "^7.13.12" + "@babel/plugin-transform-react-jsx-development" "^7.12.17" + "@babel/plugin-transform-react-pure-annotations" "^7.12.1" + +"@babel/preset-typescript@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.12.1.tgz#86480b483bb97f75036e8864fe404cc782cc311b" + integrity sha512-hNK/DhmoJPsksdHuI/RVrcEws7GN5eamhi28JkO52MqIxU8Z0QpmiSOQxZHWOHV7I3P4UjHV97ay4TcamMA6Kw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-typescript" "^7.12.1" + +"@babel/runtime-corejs3@^7.10.2": + version "7.12.18" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.12.18.tgz#e5663237e5658e4c09586995d2dd6d2c8cfd6fc0" + integrity sha512-ngR7yhNTjDxxe1VYmhqQqqXZWujGb6g0IoA4qeG6MxNGRnIw2Zo8ImY8HfaQ7l3T6GklWhdNfyhWk0C0iocdVA== + dependencies: + core-js-pure "^3.0.0" + regenerator-runtime "^0.13.4" + +"@babel/runtime@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740" + integrity sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4": + version "7.12.18" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.18.tgz#af137bd7e7d9705a412b3caaf991fe6aaa97831b" + integrity sha512-BogPQ7ciE6SYAUPtlm9tWbgI9+2AgqSam6QivMgXgAT+fKbgppaj4ZX15MHeLC1PVF5sNk70huBu20XxWOs8Cg== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.10.4", "@babel/template@^7.12.13", "@babel/template@^7.3.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" + integrity sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/parser" "^7.12.13" + "@babel/types" "^7.12.13" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.12.1", "@babel/traverse@^7.12.13", "@babel/traverse@^7.12.17", "@babel/traverse@^7.7.0": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.17.tgz#40ec8c7ffb502c4e54c7f95492dc11b88d718619" + integrity sha512-LGkTqDqdiwC6Q7fWSwQoas/oyiEYw6Hqjve5KOSykXkmFJFqzvGMb9niaUEag3Rlve492Mkye3gLw9FTv94fdQ== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.12.17" + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/parser" "^7.12.17" + "@babel/types" "^7.12.17" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + +"@babel/traverse@^7.13.0", "@babel/traverse@^7.13.15", "@babel/traverse@^7.14.0", "@babel/traverse@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.2.tgz#9201a8d912723a831c2679c7ebbf2fe1416d765b" + integrity sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.14.2" + "@babel/helper-function-name" "^7.14.2" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/parser" "^7.14.2" + "@babel/types" "^7.14.2" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.12.17", "@babel/types@^7.12.6", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": + version "7.12.17" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.17.tgz#9d711eb807e0934c90b8b1ca0eb1f7230d150963" + integrity sha512-tNMDjcv/4DIcHxErTgwB9q2ZcYyN0sUfgGKUK/mm1FJK7Wz+KstoEekxrl/tBiNDgLK1HGi+sppj1An/1DR4fQ== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + +"@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.13.16", "@babel/types@^7.14.0", "@babel/types@^7.14.2": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.2.tgz#4208ae003107ef8a057ea8333e56eb64d2f6a2c3" + integrity sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw== + dependencies: + "@babel/helper-validator-identifier" "^7.14.0" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cnakazawa/watch@^1.0.3": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" + integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== + dependencies: + exec-sh "^0.3.2" + minimist "^1.2.0" + +"@csstools/convert-colors@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" + integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== + +"@csstools/normalize.css@^10.1.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18" + integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== + +"@eslint/eslintrc@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318" + integrity sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + lodash "^4.17.20" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@hapi/address@2.x.x": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" + integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ== + +"@hapi/bourne@1.x.x": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-1.3.2.tgz#0a7095adea067243ce3283e1b56b8a8f453b242a" + integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA== + +"@hapi/hoek@8.x.x", "@hapi/hoek@^8.3.0": + version "8.5.1" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06" + integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== + +"@hapi/joi@^15.1.0": + version "15.1.1" + resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7" + integrity sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ== + dependencies: + "@hapi/address" "2.x.x" + "@hapi/bourne" "1.x.x" + "@hapi/hoek" "8.x.x" + "@hapi/topo" "3.x.x" + +"@hapi/topo@3.x.x": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-3.1.6.tgz#68d935fa3eae7fdd5ab0d7f953f3205d8b2bfc29" + integrity sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ== + dependencies: + "@hapi/hoek" "^8.3.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" + integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^26.6.2" + jest-util "^26.6.2" + slash "^3.0.0" + +"@jest/core@^26.6.0", "@jest/core@^26.6.3": + version "26.6.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" + integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== + dependencies: + "@jest/console" "^26.6.2" + "@jest/reporters" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-changed-files "^26.6.2" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-resolve-dependencies "^26.6.3" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + jest-watcher "^26.6.2" + micromatch "^4.0.2" + p-each-series "^2.1.0" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^26.6.0", "@jest/environment@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" + integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== + dependencies: + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + +"@jest/fake-timers@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" + integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== + dependencies: + "@jest/types" "^26.6.2" + "@sinonjs/fake-timers" "^6.0.1" + "@types/node" "*" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-util "^26.6.2" + +"@jest/globals@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" + integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/types" "^26.6.2" + expect "^26.6.2" + +"@jest/reporters@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" + integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + graceful-fs "^4.2.4" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^4.0.3" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + jest-haste-map "^26.6.2" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^4.0.1" + terminal-link "^2.0.0" + v8-to-istanbul "^7.0.0" + optionalDependencies: + node-notifier "^8.0.0" + +"@jest/source-map@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" + integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.4" + source-map "^0.6.0" + +"@jest/test-result@^26.6.0", "@jest/test-result@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" + integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== + dependencies: + "@jest/console" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^26.6.3": + version "26.6.3" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" + integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== + dependencies: + "@jest/test-result" "^26.6.2" + graceful-fs "^4.2.4" + jest-haste-map "^26.6.2" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + +"@jest/transform@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" + integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^26.6.2" + babel-plugin-istanbul "^6.0.0" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.4" + jest-haste-map "^26.6.2" + jest-regex-util "^26.0.0" + jest-util "^26.6.2" + micromatch "^4.0.2" + pirates "^4.0.1" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/types@^26.6.0", "@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + +"@nodelib/fs.scandir@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" + integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== + dependencies: + "@nodelib/fs.stat" "2.0.4" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" + integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" + integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== + dependencies: + "@nodelib/fs.scandir" "2.1.4" + fastq "^1.6.0" + +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + +"@pmmmwh/react-refresh-webpack-plugin@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.2.tgz#1f9741e0bde9790a0e13272082ed7272a083620d" + integrity sha512-Loc4UDGutcZ+Bd56hBInkm6JyjyCwWy4t2wcDXzN8EDPANgVRj0VP8Nxn0Zq2pc+WKauZwEivQgbDGg4xZO20A== + dependencies: + ansi-html "^0.0.7" + error-stack-parser "^2.0.6" + html-entities "^1.2.1" + native-url "^0.2.6" + schema-utils "^2.6.5" + source-map "^0.7.3" + +"@rollup/plugin-node-resolve@^7.1.1": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz#80de384edfbd7bfc9101164910f86078151a3eca" + integrity sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q== + dependencies: + "@rollup/pluginutils" "^3.0.8" + "@types/resolve" "0.0.8" + builtin-modules "^3.1.0" + is-module "^1.0.0" + resolve "^1.14.2" + +"@rollup/plugin-replace@^2.3.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-2.4.1.tgz#c411b5ab72809fb1bfc8b487d8d02eef661460d3" + integrity sha512-XwC1oK5rrtRJ0tn1ioLHS6OV5JTluJF7QE1J/q1hN3bquwjnVxjtMyY9iCnoyH9DQbf92CxajB3o98wZbP3oAQ== + dependencies: + "@rollup/pluginutils" "^3.1.0" + magic-string "^0.25.7" + +"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + +"@sinonjs/commons@^1.7.0": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.2.tgz#858f5c4b48d80778fde4b9d541f27edc0d56488b" + integrity sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" + integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@surma/rollup-plugin-off-main-thread@^1.1.1": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-1.4.2.tgz#e6786b6af5799f82f7ab3a82e53f6182d2b91a58" + integrity sha512-yBMPqmd1yEJo/280PAMkychuaALyQ9Lkb5q1ck3mjJrFuEobIfhnQ4J3mbvBoISmR3SWMWV+cGB/I0lCQee79A== + dependencies: + ejs "^2.6.1" + magic-string "^0.25.0" + +"@svgr/babel-plugin-add-jsx-attribute@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906" + integrity sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg== + +"@svgr/babel-plugin-remove-jsx-attribute@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz#6b2c770c95c874654fd5e1d5ef475b78a0a962ef" + integrity sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg== + +"@svgr/babel-plugin-remove-jsx-empty-expression@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz#25621a8915ed7ad70da6cea3d0a6dbc2ea933efd" + integrity sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA== + +"@svgr/babel-plugin-replace-jsx-attribute-value@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz#0b221fc57f9fcd10e91fe219e2cd0dd03145a897" + integrity sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ== + +"@svgr/babel-plugin-svg-dynamic-title@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz#139b546dd0c3186b6e5db4fefc26cb0baea729d7" + integrity sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg== + +"@svgr/babel-plugin-svg-em-dimensions@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz#6543f69526632a133ce5cabab965deeaea2234a0" + integrity sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw== + +"@svgr/babel-plugin-transform-react-native-svg@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz#00bf9a7a73f1cad3948cdab1f8dfb774750f8c80" + integrity sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q== + +"@svgr/babel-plugin-transform-svg-component@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz#583a5e2a193e214da2f3afeb0b9e8d3250126b4a" + integrity sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ== + +"@svgr/babel-preset@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-5.5.0.tgz#8af54f3e0a8add7b1e2b0fcd5a882c55393df327" + integrity sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "^5.4.0" + "@svgr/babel-plugin-remove-jsx-attribute" "^5.4.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "^5.0.1" + "@svgr/babel-plugin-replace-jsx-attribute-value" "^5.0.1" + "@svgr/babel-plugin-svg-dynamic-title" "^5.4.0" + "@svgr/babel-plugin-svg-em-dimensions" "^5.4.0" + "@svgr/babel-plugin-transform-react-native-svg" "^5.4.0" + "@svgr/babel-plugin-transform-svg-component" "^5.5.0" + +"@svgr/core@^5.4.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-5.5.0.tgz#82e826b8715d71083120fe8f2492ec7d7874a579" + integrity sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ== + dependencies: + "@svgr/plugin-jsx" "^5.5.0" + camelcase "^6.2.0" + cosmiconfig "^7.0.0" + +"@svgr/hast-util-to-babel-ast@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz#5ee52a9c2533f73e63f8f22b779f93cd432a5461" + integrity sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ== + dependencies: + "@babel/types" "^7.12.6" + +"@svgr/plugin-jsx@^5.4.0", "@svgr/plugin-jsx@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz#1aa8cd798a1db7173ac043466d7b52236b369000" + integrity sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA== + dependencies: + "@babel/core" "^7.12.3" + "@svgr/babel-preset" "^5.5.0" + "@svgr/hast-util-to-babel-ast" "^5.5.0" + svg-parser "^2.0.2" + +"@svgr/plugin-svgo@^5.4.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz#02da55d85320549324e201c7b2e53bf431fcc246" + integrity sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ== + dependencies: + cosmiconfig "^7.0.0" + deepmerge "^4.2.2" + svgo "^1.2.2" + +"@svgr/webpack@5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-5.4.0.tgz#b68bc86e29cf007292b96ced65f80971175632e0" + integrity sha512-LjepnS/BSAvelnOnnzr6Gg0GcpLmnZ9ThGFK5WJtm1xOqdBE/1IACZU7MMdVzjyUkfFqGz87eRE4hFaSLiUwYg== + dependencies: + "@babel/core" "^7.9.0" + "@babel/plugin-transform-react-constant-elements" "^7.9.0" + "@babel/preset-env" "^7.9.5" + "@babel/preset-react" "^7.9.4" + "@svgr/core" "^5.4.0" + "@svgr/plugin-jsx" "^5.4.0" + "@svgr/plugin-svgo" "^5.4.0" + loader-utils "^2.0.0" + +"@types/anymatch@*": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" + integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== + +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": + version "7.1.12" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.12.tgz#4d8e9e51eb265552a7e4f1ff2219ab6133bdfb2d" + integrity sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.2.tgz#f3d71178e187858f7c45e30380f8f1b7415a12d8" + integrity sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.0.tgz#0c888dd70b3ee9eebb6e4f200e809da0076262be" + integrity sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.11.0.tgz#b9a1efa635201ba9bc850323a8793ee2d36c04a0" + integrity sha512-kSjgDMZONiIfSH1Nxcr5JIRMwUetDki63FSQfpTCz8ogF3Ulqm8+mr5f78dUYs6vMiB6gBusQqfQmBvHZj/lwg== + dependencies: + "@babel/types" "^7.3.0" + +"@types/eslint@^7.2.6": + version "7.2.6" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.6.tgz#5e9aff555a975596c03a98b59ecd103decc70c3c" + integrity sha512-I+1sYH+NPQ3/tVqCeUSBwTE/0heyvtXqpIopUUArlBm0Kpocb8FbMa3AZ/ASKIFpN3rnEx932TTXDbt9OXsNDw== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*": + version "0.0.46" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" + integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== + +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== + +"@types/glob@^7.1.1": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" + integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/graceful-fs@^4.1.2": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + dependencies: + "@types/node" "*" + +"@types/html-minifier-terser@^5.0.0": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50" + integrity sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA== + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" + integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" + integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": + version "7.0.7" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" + integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + +"@types/minimatch@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + +"@types/node@*": + version "14.14.31" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055" + integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g== + +"@types/normalize-package-data@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" + integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/prettier@^2.0.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.1.tgz#374e31645d58cb18a07b3ecd8e9dede4deb2cccd" + integrity sha512-DxZZbyMAM9GWEzXL+BMZROWz9oo6A9EilwwOMET2UVu2uZTqMWS5S69KVtuVKaRjCUpcrOXRalet86/OpG4kqw== + +"@types/q@^1.5.1": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" + integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== + +"@types/resolve@0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" + integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== + dependencies: + "@types/node" "*" + +"@types/source-list-map@*": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" + integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== + +"@types/stack-utils@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" + integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== + +"@types/tapable@*", "@types/tapable@^1.0.5": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" + integrity sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA== + +"@types/uglify-js@*": + version "3.12.0" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.12.0.tgz#2bb061c269441620d46b946350c8f16d52ef37c5" + integrity sha512-sYAF+CF9XZ5cvEBkI7RtrG9g2GtMBkviTnBxYYyq+8BWvO4QtXfwwR6a2LFwCi4evMKZfpv6U43ViYvv17Wz3Q== + dependencies: + source-map "^0.6.1" + +"@types/webpack-sources@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.0.tgz#8882b0bd62d1e0ce62f183d0d01b72e6e82e8c10" + integrity sha512-LXn/oYIpBeucgP1EIJbKQ2/4ZmpvRl+dlrFdX7+94SKRUV3Evy3FsfMZY318vGhkWUS5MPhtOM3w1/hCOAOXcg== + dependencies: + "@types/node" "*" + "@types/source-list-map" "*" + source-map "^0.7.3" + +"@types/webpack@^4.41.8": + version "4.41.26" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.26.tgz#27a30d7d531e16489f9c7607c747be6bc1a459ef" + integrity sha512-7ZyTfxjCRwexh+EJFwRUM+CDB2XvgHl4vfuqf1ZKrgGvcS5BrNvPQqJh3tsZ0P6h6Aa1qClVHaJZszLPzpqHeA== + dependencies: + "@types/anymatch" "*" + "@types/node" "*" + "@types/tapable" "*" + "@types/uglify-js" "*" + "@types/webpack-sources" "*" + source-map "^0.6.0" + +"@types/yargs-parser@*": + version "20.2.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9" + integrity sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA== + +"@types/yargs@^15.0.0": + version "15.0.13" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.13.tgz#34f7fec8b389d7f3c1fd08026a5763e072d3c6dc" + integrity sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^4.5.0": + version "4.15.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.15.2.tgz#981b26b4076c62a5a55873fbef3fe98f83360c61" + integrity sha512-uiQQeu9tWl3f1+oK0yoAv9lt/KXO24iafxgQTkIYO/kitruILGx3uH+QtIAHqxFV+yIsdnJH+alel9KuE3J15Q== + dependencies: + "@typescript-eslint/experimental-utils" "4.15.2" + "@typescript-eslint/scope-manager" "4.15.2" + debug "^4.1.1" + functional-red-black-tree "^1.0.1" + lodash "^4.17.15" + regexpp "^3.0.0" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/experimental-utils@4.15.2", "@typescript-eslint/experimental-utils@^4.0.1": + version "4.15.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.15.2.tgz#5efd12355bd5b535e1831282e6cf465b9a71cf36" + integrity sha512-Fxoshw8+R5X3/Vmqwsjc8nRO/7iTysRtDqx6rlfLZ7HbT8TZhPeQqbPjTyk2RheH3L8afumecTQnUc9EeXxohQ== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/scope-manager" "4.15.2" + "@typescript-eslint/types" "4.15.2" + "@typescript-eslint/typescript-estree" "4.15.2" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/experimental-utils@^3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686" + integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/typescript-estree" "3.10.1" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/parser@^4.5.0": + version "4.15.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.15.2.tgz#c804474321ef76a3955aec03664808f0d6e7872e" + integrity sha512-SHeF8xbsC6z2FKXsaTb1tBCf0QZsjJ94H6Bo51Y1aVEZ4XAefaw5ZAilMoDPlGghe+qtq7XdTiDlGfVTOmvA+Q== + dependencies: + "@typescript-eslint/scope-manager" "4.15.2" + "@typescript-eslint/types" "4.15.2" + "@typescript-eslint/typescript-estree" "4.15.2" + debug "^4.1.1" + +"@typescript-eslint/scope-manager@4.15.2": + version "4.15.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.15.2.tgz#5725bda656995960ae1d004bfd1cd70320f37f4f" + integrity sha512-Zm0tf/MSKuX6aeJmuXexgdVyxT9/oJJhaCkijv0DvJVT3ui4zY6XYd6iwIo/8GEZGy43cd7w1rFMiCLHbRzAPQ== + dependencies: + "@typescript-eslint/types" "4.15.2" + "@typescript-eslint/visitor-keys" "4.15.2" + +"@typescript-eslint/types@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727" + integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ== + +"@typescript-eslint/types@4.15.2": + version "4.15.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.15.2.tgz#04acf3a2dc8001a88985291744241e732ef22c60" + integrity sha512-r7lW7HFkAarfUylJ2tKndyO9njwSyoy6cpfDKWPX6/ctZA+QyaYscAHXVAfJqtnY6aaTwDYrOhp+ginlbc7HfQ== + +"@typescript-eslint/typescript-estree@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853" + integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w== + dependencies: + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/visitor-keys" "3.10.1" + debug "^4.1.1" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/typescript-estree@4.15.2": + version "4.15.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.2.tgz#c2f7a1e94f3428d229d5ecff3ead6581ee9b62fa" + integrity sha512-cGR8C2g5SPtHTQvAymEODeqx90pJHadWsgTtx6GbnTWKqsg7yp6Eaya9nFzUd4KrKhxdYTTFBiYeTPQaz/l8bw== + dependencies: + "@typescript-eslint/types" "4.15.2" + "@typescript-eslint/visitor-keys" "4.15.2" + debug "^4.1.1" + globby "^11.0.1" + is-glob "^4.0.1" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/visitor-keys@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931" + integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ== + dependencies: + eslint-visitor-keys "^1.1.0" + +"@typescript-eslint/visitor-keys@4.15.2": + version "4.15.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.2.tgz#3d1c7979ce75bf6acf9691109bd0d6b5706192b9" + integrity sha512-TME1VgSb7wTwgENN5KVj4Nqg25hP8DisXxNBojM4Nn31rYaNDIocNm5cmjOFfh42n7NVERxWrDFoETO/76ePyg== + dependencies: + "@typescript-eslint/types" "4.15.2" + eslint-visitor-keys "^2.0.0" + +"@webassemblyjs/ast@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" + integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== + dependencies: + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + +"@webassemblyjs/floating-point-hex-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" + integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== + +"@webassemblyjs/helper-api-error@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" + integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== + +"@webassemblyjs/helper-buffer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" + integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== + +"@webassemblyjs/helper-code-frame@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" + integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== + dependencies: + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/helper-fsm@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" + integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== + +"@webassemblyjs/helper-module-context@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" + integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== + dependencies: + "@webassemblyjs/ast" "1.9.0" + +"@webassemblyjs/helper-wasm-bytecode@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" + integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== + +"@webassemblyjs/helper-wasm-section@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" + integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + +"@webassemblyjs/ieee754@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" + integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" + integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" + integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== + +"@webassemblyjs/wasm-edit@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" + integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/helper-wasm-section" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-opt" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/wasm-gen@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" + integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wasm-opt@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" + integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + +"@webassemblyjs/wasm-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" + integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wast-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" + integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/floating-point-hex-parser" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-code-frame" "1.9.0" + "@webassemblyjs/helper-fsm" "1.9.0" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/wast-printer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" + integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +abab@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" + integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== + +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + +acorn-jsx@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" + integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== + +acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn@^6.4.1: + version "6.4.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" + integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== + +acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +address@1.1.2, address@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" + integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA== + +adjust-sourcemap-loader@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz#5ae12fb5b7b1c585e80bbb5a63ec163a1a45e61e" + integrity sha512-YBrGyT2/uVQ/c6Rr+t6ZJXniY03YtHGMJQYal368burRGYKqhx9qGTWqcBU5s1CwYY9E/ri63RYyG1IacMZtqw== + dependencies: + loader-utils "^2.0.0" + regex-parser "^2.2.11" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv-errors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" + integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== + +ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^7.0.2: + version "7.1.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.1.1.tgz#1e6b37a454021fa9941713f38b952fc1c8d32a84" + integrity sha512-ga/aqDYnUy/o7vbsRTFhhTsNeXiYb5JWDIcRIeZfwRNCefwjNTVYCGdGSUrEmiu3yDK3vFvNbgJxvrQW4JXrYQ== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +alphanum-sort@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= + +ansi-colors@^3.0.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" + integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== + +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" + integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== + dependencies: + type-fest "^0.11.0" + +ansi-html@0.0.7, ansi-html@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" + integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +anymatch@^3.0.3, anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +aproba@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +aria-query@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" + integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== + dependencies: + "@babel/runtime" "^7.10.2" + "@babel/runtime-corejs3" "^7.10.2" + +arity-n@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arity-n/-/arity-n-1.0.4.tgz#d9e76b11733e08569c0847ae7b39b2860b30b745" + integrity sha1-2edrEXM+CFacCEeuezmyhgswt0U= + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +array-flatten@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== + +array-includes@^3.1.1, array-includes@^3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a" + integrity sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + get-intrinsic "^1.1.1" + is-string "^1.0.5" + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= + dependencies: + array-uniq "^1.0.1" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +array.prototype.flat@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123" + integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + +array.prototype.flatmap@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9" + integrity sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + function-bind "^1.1.1" + +arrify@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" + integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== + +asap@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + +asn1.js@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +assert@^1.1.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== + dependencies: + object-assign "^4.1.1" + util "0.10.3" + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +ast-types-flow@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" + integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async-each@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +async@0.9.x: + version "0.9.2" + resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" + integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= + +async@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" + integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + dependencies: + lodash "^4.17.14" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +autoprefixer@^9.6.1: + version "9.8.6" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" + integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== + dependencies: + browserslist "^4.12.0" + caniuse-lite "^1.0.30001109" + colorette "^1.2.1" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.32" + postcss-value-parser "^4.1.0" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== + +axe-core@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.2.tgz#7cf783331320098bfbef620df3b3c770147bc224" + integrity sha512-V+Nq70NxKhYt89ArVcaNL9FDryB3vQOd+BFXZIfO3RP6rwtj+2yqqqdHEkacutglPaZLkJeuXKCjCJDMGPtPqg== + +axobject-query@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" + integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== + +babel-eslint@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" + integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + eslint-visitor-keys "^1.0.0" + resolve "^1.12.0" + +babel-extract-comments@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/babel-extract-comments/-/babel-extract-comments-1.0.0.tgz#0a2aedf81417ed391b85e18b4614e693a0351a21" + integrity sha512-qWWzi4TlddohA91bFwgt6zO/J0X+io7Qp184Fw0m2JYRSTZnJbFR8+07KmzudHCZgOiKRCrjhylwv9Xd8gfhVQ== + dependencies: + babylon "^6.18.0" + +babel-jest@^26.6.0, babel-jest@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" + integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== + dependencies: + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/babel__core" "^7.1.7" + babel-plugin-istanbul "^6.0.0" + babel-preset-jest "^26.6.2" + chalk "^4.0.0" + graceful-fs "^4.2.4" + slash "^3.0.0" + +babel-loader@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3" + integrity sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw== + dependencies: + find-cache-dir "^2.1.0" + loader-utils "^1.4.0" + mkdirp "^0.5.3" + pify "^4.0.1" + schema-utils "^2.6.5" + +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + +babel-plugin-istanbul@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" + integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^4.0.0" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" + integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" + "@types/babel__traverse" "^7.0.6" + +babel-plugin-macros@2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" + integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== + dependencies: + "@babel/runtime" "^7.7.2" + cosmiconfig "^6.0.0" + resolve "^1.12.0" + +babel-plugin-named-asset-import@^0.3.7: + version "0.3.7" + resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz#156cd55d3f1228a5765774340937afc8398067dd" + integrity sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw== + +babel-plugin-polyfill-corejs2@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.1.tgz#ae2cf6d6f1aa7c0edcf04a25180e8856a6d1184f" + integrity sha512-hXGSPbr6IbjeMyGew+3uGIAkRjBFSOJ9FLDZNOfHuyJZCcoia4nd/72J0bSgvfytcVfUcP/dxEVcUhVJuQRtSw== + dependencies: + "@babel/compat-data" "^7.13.11" + "@babel/helper-define-polyfill-provider" "^0.2.1" + semver "^6.1.1" + +babel-plugin-polyfill-corejs3@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.1.tgz#786f40218040030f0edecfd48e6e59f1ee9bef53" + integrity sha512-WZCqF3DLUhdTD/P381MDJfuP18hdCZ+iqJ+wHtzhWENpsiof284JJ1tMQg1CE+hfCWyG48F7e5gDMk2c3Laz7w== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.2.1" + core-js-compat "^3.9.1" + +babel-plugin-polyfill-regenerator@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.1.tgz#ca9595d7d5f3afefec2d83126148b90db751a091" + integrity sha512-T3bYyL3Sll2EtC94v3f+fA8M28q7YPTOZdB++SRHjvYZTvtd+WorMUq3tDTD4Q7Kjk1LG0gGromslKjcO5p2TA== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.2.1" + +babel-plugin-syntax-object-rest-spread@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" + integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U= + +babel-plugin-transform-object-rest-spread@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" + integrity sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY= + dependencies: + babel-plugin-syntax-object-rest-spread "^6.8.0" + babel-runtime "^6.26.0" + +babel-plugin-transform-react-remove-prop-types@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a" + integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA== + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" + integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== + dependencies: + babel-plugin-jest-hoist "^26.6.2" + babel-preset-current-node-syntax "^1.0.0" + +babel-preset-react-app@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/babel-preset-react-app/-/babel-preset-react-app-10.0.0.tgz#689b60edc705f8a70ce87f47ab0e560a317d7045" + integrity sha512-itL2z8v16khpuKutx5IH8UdCdSTuzrOhRFTEdIhveZ2i1iBKDrVE0ATa4sFVy+02GLucZNVBWtoarXBy0Msdpg== + dependencies: + "@babel/core" "7.12.3" + "@babel/plugin-proposal-class-properties" "7.12.1" + "@babel/plugin-proposal-decorators" "7.12.1" + "@babel/plugin-proposal-nullish-coalescing-operator" "7.12.1" + "@babel/plugin-proposal-numeric-separator" "7.12.1" + "@babel/plugin-proposal-optional-chaining" "7.12.1" + "@babel/plugin-transform-flow-strip-types" "7.12.1" + "@babel/plugin-transform-react-display-name" "7.12.1" + "@babel/plugin-transform-runtime" "7.12.1" + "@babel/preset-env" "7.12.1" + "@babel/preset-react" "7.12.1" + "@babel/preset-typescript" "7.12.1" + "@babel/runtime" "7.12.1" + babel-plugin-macros "2.8.0" + babel-plugin-transform-react-remove-prop-types "0.4.24" + +babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base64-js@^1.0.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +bfj@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/bfj/-/bfj-7.0.2.tgz#1988ce76f3add9ac2913fd8ba47aad9e651bfbb2" + integrity sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw== + dependencies: + bluebird "^3.5.5" + check-types "^11.1.1" + hoopy "^0.1.4" + tryer "^1.0.1" + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^1.0.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bluebird@^3.5.5: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: + version "4.11.9" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" + integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== + +bn.js@^5.0.0, bn.js@^5.1.1: + version "5.1.3" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b" + integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ== + +body-parser@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + +bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^6.0.1" + multicast-dns-service-types "^1.1.0" + +boolbase@^1.0.0, boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1, braces@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +braces@^3.0.1, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +brorand@^1.0.1, brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== + dependencies: + bn.js "^5.0.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" + integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== + dependencies: + bn.js "^5.1.1" + browserify-rsa "^4.0.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.3" + inherits "^2.0.4" + parse-asn1 "^5.1.5" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== + dependencies: + pako "~1.0.5" + +browserslist@4.14.2: + version "4.14.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.2.tgz#1b3cec458a1ba87588cc5e9be62f19b6d48813ce" + integrity sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw== + dependencies: + caniuse-lite "^1.0.30001125" + electron-to-chromium "^1.3.564" + escalade "^3.0.2" + node-releases "^1.1.61" + +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.3, browserslist@^4.6.2, browserslist@^4.6.4: + version "4.16.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717" + integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw== + dependencies: + caniuse-lite "^1.0.30001181" + colorette "^1.2.1" + electron-to-chromium "^1.3.649" + escalade "^3.1.1" + node-releases "^1.1.70" + +browserslist@^4.16.6: + version "4.16.6" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" + integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== + dependencies: + caniuse-lite "^1.0.30001219" + colorette "^1.2.2" + electron-to-chromium "^1.3.723" + escalade "^3.1.1" + node-releases "^1.1.71" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +btoa@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73" + integrity sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g== + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +buffer-indexof@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" + integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= + +buffer@^4.3.0: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +builtin-modules@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" + integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== + +builtin-status-codes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +cacache@^12.0.2: + version "12.0.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" + integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== + dependencies: + bluebird "^3.5.5" + chownr "^1.1.1" + figgy-pudding "^3.5.1" + glob "^7.1.4" + graceful-fs "^4.1.15" + infer-owner "^1.0.3" + lru-cache "^5.1.1" + mississippi "^3.0.0" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + promise-inflight "^1.0.1" + rimraf "^2.6.3" + ssri "^6.0.1" + unique-filename "^1.1.1" + y18n "^4.0.0" + +cacache@^15.0.5: + version "15.0.5" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.5.tgz#69162833da29170d6732334643c60e005f5f17d0" + integrity sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A== + dependencies: + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.0" + tar "^6.0.2" + unique-filename "^1.1.1" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + dependencies: + callsites "^2.0.0" + +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + dependencies: + caller-callsite "^2.0.0" + +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camel-case@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + +camelcase@5.3.1, camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.0.0, camelcase@^6.1.0, camelcase@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" + integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001181: + version "1.0.30001191" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001191.tgz#bacb432b6701f690c8c5f7c680166b9a9f0843d9" + integrity sha512-xJJqzyd+7GCJXkcoBiQ1GuxEiOBCLQ0aVW9HMekifZsAVGdj5eJ4mFB9fEhSHipq9IOk/QXFJUiIr9lZT+EsGw== + +caniuse-lite@^1.0.30001219: + version "1.0.30001228" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz#bfdc5942cd3326fa51ee0b42fbef4da9d492a7fa" + integrity sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A== + +capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== + dependencies: + rsvp "^4.8.4" + +case-sensitive-paths-webpack-plugin@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz#23ac613cc9a856e4f88ff8bb73bbb5e989825cf7" + integrity sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" + integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +check-types@^11.1.1: + version "11.1.2" + resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f" + integrity sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ== + +chokidar@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.1" + braces "^2.3.2" + glob-parent "^3.1.0" + inherits "^2.0.3" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^3.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.2.1" + upath "^1.1.1" + optionalDependencies: + fsevents "^1.2.7" + +chokidar@^3.4.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" + integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.5.0" + optionalDependencies: + fsevents "~2.3.1" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +chrome-trace-event@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" + integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== + dependencies: + tslib "^1.9.0" + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +cjs-module-lexer@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" + integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +clean-css@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" + integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA== + dependencies: + source-map "~0.6.0" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + +coa@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" + integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== + dependencies: + "@types/q" "^1.5.1" + chalk "^2.4.1" + q "^1.1.2" + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0, color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" + integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e" + integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.4" + +colorette@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" + integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== + +colorette@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" + integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +common-tags@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" + integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +compose-function@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/compose-function/-/compose-function-3.0.3.tgz#9ed675f13cc54501d30950a486ff6a7ba3ab185f" + integrity sha1-ntZ18TzFRQHTCVCkhv9qe6OrGF8= + dependencies: + arity-n "^1.0.4" + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@^1.5.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +confusing-browser-globals@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz#30d1e7f3d1b882b25ec4933d1d1adac353d20a59" + integrity sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA== + +connect-history-api-fallback@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" + integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== + +console-browserify@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== + +constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= + +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= + +content-disposition@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== + dependencies: + safe-buffer "5.1.2" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +convert-source-map@1.7.0, convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +convert-source-map@^0.3.3: + version "0.3.5" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-0.3.5.tgz#f1d802950af7dd2631a1febe0596550c86ab3190" + integrity sha1-8dgClQr33SYxof6+BZZVDIarMZA= + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + +copy-concurrently@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" + integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== + dependencies: + aproba "^1.1.1" + fs-write-stream-atomic "^1.0.8" + iferr "^0.1.5" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.0" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-js-compat@^3.6.2, core-js-compat@^3.8.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.9.0.tgz#29da39385f16b71e1915565aa0385c4e0963ad56" + integrity sha512-YK6fwFjCOKWwGnjFUR3c544YsnA/7DoLL0ysncuOJ4pwbriAtOpvM2bygdlcXbvQCQZ7bBU9CL4t7tGl7ETRpQ== + dependencies: + browserslist "^4.16.3" + semver "7.0.0" + +core-js-compat@^3.9.0, core-js-compat@^3.9.1: + version "3.12.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.12.1.tgz#2c302c4708505fa7072b0adb5156d26f7801a18b" + integrity sha512-i6h5qODpw6EsHAoIdQhKoZdWn+dGBF3dSS8m5tif36RlWvW3A6+yu2S16QHUo3CrkzrnEskMAt9f8FxmY9fhWQ== + dependencies: + browserslist "^4.16.6" + semver "7.0.0" + +core-js-pure@^3.0.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.9.0.tgz#326cc74e1fef8b7443a6a793ddb0adfcd81f9efb" + integrity sha512-3pEcmMZC9Cq0D4ZBh3pe2HLtqxpGNJBLXF/kZ2YzK17RbKp94w0HFbdbSx8H8kAlZG5k76hvLrkPm57Uyef+kg== + +core-js@^2.4.0: + version "2.6.12" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" + integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== + +core-js@^3.6.5: + version "3.9.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.9.0.tgz#790b1bb11553a2272b36e2625c7179db345492f8" + integrity sha512-PyFBJaLq93FlyYdsndE5VaueA9K5cNB7CGzeCj191YYLhkQM0gdZR2SKihM70oF0wdqKSKClv/tEBOpoRmdOVQ== + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cosmiconfig@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.13.1" + parse-json "^4.0.0" + +cosmiconfig@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.7.2" + +cosmiconfig@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" + integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +create-ecdh@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== + dependencies: + bn.js "^4.1.0" + elliptic "^6.5.3" + +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +crypto-browserify@^3.11.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + +crypto-random-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" + integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= + +css-blank-pseudo@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" + integrity sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w== + dependencies: + postcss "^7.0.5" + +css-color-names@0.0.4, css-color-names@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= + +css-declaration-sorter@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" + integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== + dependencies: + postcss "^7.0.1" + timsort "^0.3.0" + +css-has-pseudo@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee" + integrity sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ== + dependencies: + postcss "^7.0.6" + postcss-selector-parser "^5.0.0-rc.4" + +css-loader@4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.3.0.tgz#c888af64b2a5b2e85462c72c0f4a85c7e2e0821e" + integrity sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg== + dependencies: + camelcase "^6.0.0" + cssesc "^3.0.0" + icss-utils "^4.1.1" + loader-utils "^2.0.0" + postcss "^7.0.32" + postcss-modules-extract-imports "^2.0.0" + postcss-modules-local-by-default "^3.0.3" + postcss-modules-scope "^2.2.0" + postcss-modules-values "^3.0.0" + postcss-value-parser "^4.1.0" + schema-utils "^2.7.1" + semver "^7.3.2" + +css-prefers-color-scheme@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4" + integrity sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg== + dependencies: + postcss "^7.0.5" + +css-select-base-adapter@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" + integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== + +css-select@^2.0.0, css-select@^2.0.2: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" + integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== + dependencies: + boolbase "^1.0.0" + css-what "^3.2.1" + domutils "^1.7.0" + nth-check "^1.0.2" + +css-tree@1.0.0-alpha.37: + version "1.0.0-alpha.37" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== + dependencies: + mdn-data "2.0.4" + source-map "^0.6.1" + +css-tree@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.2.tgz#9ae393b5dafd7dae8a622475caec78d3d8fbd7b5" + integrity sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-what@^3.2.1: + version "3.4.2" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" + integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== + +css@^2.0.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" + integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== + dependencies: + inherits "^2.0.3" + source-map "^0.6.1" + source-map-resolve "^0.5.2" + urix "^0.1.0" + +cssdb@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" + integrity sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ== + +cssesc@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" + integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-default@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" + integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== + dependencies: + css-declaration-sorter "^4.0.1" + cssnano-util-raw-cache "^4.0.1" + postcss "^7.0.0" + postcss-calc "^7.0.1" + postcss-colormin "^4.0.3" + postcss-convert-values "^4.0.1" + postcss-discard-comments "^4.0.2" + postcss-discard-duplicates "^4.0.2" + postcss-discard-empty "^4.0.1" + postcss-discard-overridden "^4.0.1" + postcss-merge-longhand "^4.0.11" + postcss-merge-rules "^4.0.3" + postcss-minify-font-values "^4.0.2" + postcss-minify-gradients "^4.0.2" + postcss-minify-params "^4.0.2" + postcss-minify-selectors "^4.0.2" + postcss-normalize-charset "^4.0.1" + postcss-normalize-display-values "^4.0.2" + postcss-normalize-positions "^4.0.2" + postcss-normalize-repeat-style "^4.0.2" + postcss-normalize-string "^4.0.2" + postcss-normalize-timing-functions "^4.0.2" + postcss-normalize-unicode "^4.0.1" + postcss-normalize-url "^4.0.1" + postcss-normalize-whitespace "^4.0.2" + postcss-ordered-values "^4.1.2" + postcss-reduce-initial "^4.0.3" + postcss-reduce-transforms "^4.0.2" + postcss-svgo "^4.0.2" + postcss-unique-selectors "^4.0.1" + +cssnano-util-get-arguments@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" + integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= + +cssnano-util-get-match@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" + integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= + +cssnano-util-raw-cache@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" + integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== + dependencies: + postcss "^7.0.0" + +cssnano-util-same-parent@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" + integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== + +cssnano@^4.1.10: + version "4.1.10" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" + integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== + dependencies: + cosmiconfig "^5.0.0" + cssnano-preset-default "^4.0.7" + is-resolvable "^1.0.0" + postcss "^7.0.0" + +csso@^4.0.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== + dependencies: + css-tree "^1.1.2" + +cssom@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + +cyclist@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" + integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= + +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + +damerau-levenshtein@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791" + integrity sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug== + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.1.1, debug@^3.2.5: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decimal.js@^10.2.0: + version "10.2.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" + integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw== + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= + +deep-equal@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" + +deep-is@^0.1.3, deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +default-gateway@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" + integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA== + dependencies: + execa "^1.0.0" + ip-regex "^2.1.0" + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +del@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" + integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== + dependencies: + "@types/glob" "^7.1.1" + globby "^6.1.0" + is-path-cwd "^2.0.0" + is-path-in-cwd "^2.0.0" + p-map "^2.0.0" + pify "^4.0.1" + rimraf "^2.6.3" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +des.js@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +detect-node@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" + integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== + +detect-port-alt@1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" + integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== + dependencies: + address "^1.0.1" + debug "^2.6.0" + +diff-sequences@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" + integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== + +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= + +dns-packet@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" + integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + +dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= + dependencies: + buffer-indexof "^1.0.0" + +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-converter@^0.2: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" + integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== + dependencies: + utila "~0.4" + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + +domain-browser@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== + +domelementtype@1, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e" + integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w== + +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== + dependencies: + webidl-conversions "^5.0.0" + +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== + dependencies: + domelementtype "1" + +domutils@^1.5.1, domutils@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +dot-prop@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +dotenv-expand@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + +duplexer@^0.1.1, duplexer@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + +duplexify@^3.4.2, duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +ejs@^2.6.1: + version "2.7.4" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" + integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== + +ejs@^3.1.5: + version "3.1.6" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a" + integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw== + dependencies: + jake "^10.6.1" + +electron-to-chromium@^1.3.564, electron-to-chromium@^1.3.649: + version "1.3.671" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.671.tgz#8feaed6eae42d279fa4611f58c42a5a1eb81b2a0" + integrity sha512-RTD97QkdrJKaKwRv9h/wGAaoR2lGxNXEcBXS31vjitgTPwTWAbLdS7cEsBK68eEQy7p6YyT8D5BxBEYHu2SuwQ== + +electron-to-chromium@^1.3.723: + version "1.3.737" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.737.tgz#196f2e9656f4f3c31930750e1899c091b72d36b5" + integrity sha512-P/B84AgUSQXaum7a8m11HUsYL8tj9h/Pt5f7Hg7Ty6bm5DxlFq+e5+ouHUoNQMsKDJ7u4yGfI8mOErCmSH9wyg== + +elliptic@^6.5.3: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +emittery@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" + integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.0.0: + version "9.2.1" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.1.tgz#c9b25604256bb3428964bead3ab63069d736f7ee" + integrity sha512-117l1H6U4X3Krn+MrzYrL57d5H7siRHWraBs7s+LjRuFK7Fe7hJqnJ0skWlinqsycVLU5YAo6L8CsEYQ0V5prg== + +emojis-list@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" + integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enhanced-resolve@^4.3.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" + integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + +enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +entities@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +errno@^0.1.3, errno@~0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" + +error-ex@^1.2.0, error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +error-stack-parser@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8" + integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ== + dependencies: + stackframe "^1.1.1" + +es-abstract@^1.17.2: + version "1.17.7" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" + integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: + version "1.18.0-next.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.2.tgz#088101a55f0541f595e7e057199e27ddc8f3a5c2" + integrity sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-negative-zero "^2.0.1" + is-regex "^1.1.1" + object-inspect "^1.9.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.3" + string.prototype.trimstart "^1.0.3" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es5-ext@^0.10.35, es5-ext@^0.10.50: + version "0.10.53" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" + integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.3" + next-tick "~1.0.0" + +es6-iterator@2.0.3, es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + +escalade@^3.0.2, escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@^1.0.3, escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escodegen@^1.14.1: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +eslint-config-react-app@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz#ccff9fc8e36b322902844cbd79197982be355a0e" + integrity sha512-bpoAAC+YRfzq0dsTk+6v9aHm/uqnDwayNAXleMypGl6CpxI9oXXscVHo4fk3eJPIn+rsbtNetB4r/ZIidFIE8A== + dependencies: + confusing-browser-globals "^1.0.10" + +eslint-import-resolver-node@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" + integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== + dependencies: + debug "^2.6.9" + resolve "^1.13.1" + +eslint-module-utils@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" + integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== + dependencies: + debug "^2.6.9" + pkg-dir "^2.0.0" + +eslint-plugin-flowtype@^5.2.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-5.2.2.tgz#c6e5dd2fad4e757a1c63e652da6cff597659554f" + integrity sha512-C4PlPYpszr9h1cBfUbTNRI1IdxUCF0qrXAHkXS2+bESp7WUUCnvb3UBBnYlaQLvJYJ2lRz+2SPQQ/WyV7p/Tow== + dependencies: + lodash "^4.17.15" + string-natural-compare "^3.0.1" + +eslint-plugin-import@^2.22.1: + version "2.22.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702" + integrity sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw== + dependencies: + array-includes "^3.1.1" + array.prototype.flat "^1.2.3" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.4" + eslint-module-utils "^2.6.0" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.1" + read-pkg-up "^2.0.0" + resolve "^1.17.0" + tsconfig-paths "^3.9.0" + +eslint-plugin-jest@^24.1.0: + version "24.1.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.1.5.tgz#1e866a9f0deac587d0a3d5d7cefe99815a580de2" + integrity sha512-FIP3lwC8EzEG+rOs1y96cOJmMVpdFNreoDJv29B5vIupVssRi8zrSY3QadogT0K3h1Y8TMxJ6ZSAzYUmFCp2hg== + dependencies: + "@typescript-eslint/experimental-utils" "^4.0.1" + +eslint-plugin-jsx-a11y@^6.3.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz#a2d84caa49756942f42f1ffab9002436391718fd" + integrity sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg== + dependencies: + "@babel/runtime" "^7.11.2" + aria-query "^4.2.2" + array-includes "^3.1.1" + ast-types-flow "^0.0.7" + axe-core "^4.0.2" + axobject-query "^2.2.0" + damerau-levenshtein "^1.0.6" + emoji-regex "^9.0.0" + has "^1.0.3" + jsx-ast-utils "^3.1.0" + language-tags "^1.0.5" + +eslint-plugin-react-hooks@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556" + integrity sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ== + +eslint-plugin-react@^7.21.5: + version "7.22.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.22.0.tgz#3d1c542d1d3169c45421c1215d9470e341707269" + integrity sha512-p30tuX3VS+NWv9nQot9xIGAHBXR0+xJVaZriEsHoJrASGCJZDJ8JLNM0YqKqI0AKm6Uxaa1VUHoNEibxRCMQHA== + dependencies: + array-includes "^3.1.1" + array.prototype.flatmap "^1.2.3" + doctrine "^2.1.0" + has "^1.0.3" + jsx-ast-utils "^2.4.1 || ^3.0.0" + object.entries "^1.1.2" + object.fromentries "^2.0.2" + object.values "^1.1.1" + prop-types "^15.7.2" + resolve "^1.18.1" + string.prototype.matchall "^4.0.2" + +eslint-plugin-testing-library@^3.9.2: + version "3.10.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.10.1.tgz#4dd02306d601c3238fdabf1d1dbc5f2a8e85d531" + integrity sha512-nQIFe2muIFv2oR2zIuXE4vTbcFNx8hZKRzgHZqJg8rfopIWwoTwtlbCCNELT/jXzVe1uZF68ALGYoDXjLczKiQ== + dependencies: + "@typescript-eslint/experimental-utils" "^3.10.1" + +eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-scope@^5.0.0, eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^2.0.0, eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" + integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + +eslint-webpack-plugin@^2.1.0: + version "2.5.4" + resolved "https://registry.yarnpkg.com/eslint-webpack-plugin/-/eslint-webpack-plugin-2.5.4.tgz#473b84932f1a8e2c2b8e66a402d0497bf440b986" + integrity sha512-7rYh0m76KyKSDE+B+2PUQrlNS4HJ51t3WKpkJg6vo2jFMbEPTG99cBV0Dm7LXSHucN4WGCG65wQcRiTFrj7iWw== + dependencies: + "@types/eslint" "^7.2.6" + arrify "^2.0.1" + jest-worker "^26.6.2" + micromatch "^4.0.2" + normalize-path "^3.0.0" + schema-utils "^3.0.0" + +eslint@^7.11.0: + version "7.20.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.20.0.tgz#db07c4ca4eda2e2316e7aa57ac7fc91ec550bdc7" + integrity sha512-qGi0CTcOGP2OtCQBgWZlQjcTuP0XkIpYFj25XtRTQSHC+umNnp7UMshr2G8SLsRFYDdAPFeHOsiteadmMH02Yw== + dependencies: + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.3.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.4.0" + esutils "^2.0.2" + file-entry-cache "^6.0.0" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash "^4.17.20" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^6.0.4" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.1.0, esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1, estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + +estree-walker@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" + integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== + +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" + integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== + +eventsource@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" + integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== + dependencies: + original "^1.0.0" + +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +exec-sh@^0.3.2: + version "0.3.4" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" + integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expect@^26.6.0, expect@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" + integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== + dependencies: + "@jest/types" "^26.6.2" + ansi-styles "^4.0.0" + jest-get-type "^26.3.0" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-regex-util "^26.0.0" + +express@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +ext@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" + integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== + dependencies: + type "^2.0.0" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.1.1: + version "3.2.5" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" + integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + +fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastq@^1.6.0: + version "1.10.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.10.1.tgz#8b8f2ac8bf3632d67afcd65dac248d5fdc45385e" + integrity sha512-AWuv6Ery3pM+dY7LYS8YIaCiQvUaos9OB1RyNgaOWnaX+Tik7Onvcsf8x8c+YtDeT0maYLniBip2hox5KtEXXA== + dependencies: + reusify "^1.0.4" + +faye-websocket@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" + integrity sha1-TkkvjQTftviQA1B/btvy1QHnxvQ= + dependencies: + websocket-driver ">=0.5.1" + +faye-websocket@~0.11.1: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + +figgy-pudding@^3.5.1: + version "3.5.2" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" + integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== + +file-entry-cache@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +file-loader@6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.1.1.tgz#a6f29dfb3f5933a1c350b2dbaa20ac5be0539baa" + integrity sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +filelist@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b" + integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ== + dependencies: + minimatch "^3.0.4" + +filesize@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.1.0.tgz#e81bdaa780e2451d714d71c0d7a4f3238d37ad00" + integrity sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg== + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +find-cache-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== + dependencies: + commondir "^1.0.1" + make-dir "^2.0.0" + pkg-dir "^3.0.0" + +find-cache-dir@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" + integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@4.1.0, find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" + integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== + +flatten@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" + integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== + +flush-write-stream@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== + dependencies: + inherits "^2.0.3" + readable-stream "^2.3.6" + +follow-redirects@^1.0.0: + version "1.13.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.2.tgz#dd73c8effc12728ba5cf4259d760ea5fb83e3147" + integrity sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA== + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +fork-ts-checker-webpack-plugin@4.1.6: + version "4.1.6" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz#5055c703febcf37fa06405d400c122b905167fc5" + integrity sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw== + dependencies: + "@babel/code-frame" "^7.5.5" + chalk "^2.4.1" + micromatch "^3.1.10" + minimatch "^3.0.4" + semver "^5.6.0" + tapable "^1.0.0" + worker-rpc "^0.1.0" + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +from2@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" + integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.0" + +fs-extra@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^9.0.1: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs-write-stream-atomic@^1.0.8: + version "1.0.10" + resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" + integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= + dependencies: + graceful-fs "^4.1.2" + iferr "^0.1.5" + imurmurhash "^0.1.4" + readable-stream "1 || 2" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^1.2.7: + version "1.2.13" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" + integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== + dependencies: + bindings "^1.5.0" + nan "^2.12.1" + +fsevents@^2.1.2, fsevents@^2.1.3, fsevents@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.1, get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + +glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-modules@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +globby@11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" + integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + +globby@^11.0.1: + version "11.0.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.2.tgz#1af538b766a3b540ebfb58a32b2e2d5897321d83" + integrity sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= + +gzip-size@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" + integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== + dependencies: + duplexer "^0.1.1" + pify "^4.0.1" + +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + +harmony-reflect@^1.4.6: + version "1.6.1" + resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.1.tgz#c108d4f2bb451efef7a37861fdbdae72c9bdefa9" + integrity sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.0, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== + dependencies: + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +hex-color-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" + integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +hoopy@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" + integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== + +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +hsl-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" + integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= + +hsla-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" + integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= + +html-comment-regex@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" + integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== + +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== + dependencies: + whatwg-encoding "^1.0.5" + +html-entities@^1.2.1, html-entities@^1.3.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc" + integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +html-minifier-terser@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054" + integrity sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg== + dependencies: + camel-case "^4.1.1" + clean-css "^4.2.3" + commander "^4.1.1" + he "^1.2.0" + param-case "^3.0.3" + relateurl "^0.2.7" + terser "^4.6.3" + +html-webpack-plugin@4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz#625097650886b97ea5dae331c320e3238f6c121c" + integrity sha512-MouoXEYSjTzCrjIxWwg8gxL5fE2X2WZJLmBYXlaJhQUH5K/b5OrqmV7T4dB7iu0xkmJ6JlUuV6fFVtnqbPopZw== + dependencies: + "@types/html-minifier-terser" "^5.0.0" + "@types/tapable" "^1.0.5" + "@types/webpack" "^4.41.8" + html-minifier-terser "^5.0.1" + loader-utils "^1.2.3" + lodash "^4.17.15" + pretty-error "^2.1.1" + tapable "^1.1.3" + util.promisify "1.0.0" + +htmlparser2@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== + dependencies: + domelementtype "^1.3.1" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.1.1" + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= + +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-parser-js@>=0.5.1: + version "0.5.3" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" + integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== + +http-proxy-middleware@0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" + integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== + dependencies: + http-proxy "^1.17.0" + is-glob "^4.0.0" + lodash "^4.17.11" + micromatch "^3.1.10" + +http-proxy@^1.17.0: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= + +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +icss-utils@^4.0.0, icss-utils@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" + integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== + dependencies: + postcss "^7.0.14" + +identity-obj-proxy@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" + integrity sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ= + dependencies: + harmony-reflect "^1.4.6" + +ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +iferr@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" + integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + +immer@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656" + integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA== + +import-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" + integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= + dependencies: + import-from "^2.1.0" + +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + +import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-from@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" + integrity sha1-M1238qev/VOqpHHUuAId7ja387E= + dependencies: + resolve-from "^3.0.0" + +import-local@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" + integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== + dependencies: + pkg-dir "^3.0.0" + resolve-cwd "^2.0.0" + +import-local@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" + integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= + +infer-owner@^1.0.3, infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@^1.3.5: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +internal-ip@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" + integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg== + dependencies: + default-gateway "^4.2.0" + ipaddr.js "^1.9.0" + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +ip-regex@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" + integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= + +ip@^1.1.0, ip@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + +ipaddr.js@1.9.1, ipaddr.js@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= + +is-absolute-url@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-arguments@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9" + integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg== + dependencies: + call-bind "^1.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + dependencies: + binary-extensions "^1.0.0" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-callable@^1.1.4, is-callable@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" + integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-color-stop@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" + integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= + dependencies: + css-color-names "^0.0.4" + hex-color-regex "^1.1.0" + hsl-regex "^1.0.0" + hsla-regex "^1.0.0" + rgb-regex "^1.0.1" + rgba-regex "^1.0.0" + +is-core-module@^2.0.0, is-core-module@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= + +is-docker@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" + integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= + +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-cwd@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-in-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" + integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== + dependencies: + is-path-inside "^2.1.0" + +is-path-inside@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" + integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== + dependencies: + path-is-inside "^1.0.2" + +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-potential-custom-element-name@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397" + integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c= + +is-regex@^1.0.4, is-regex@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251" + integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg== + dependencies: + call-bind "^1.0.2" + has-symbols "^1.0.1" + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= + +is-resolvable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" + integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== + +is-root@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" + integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + +is-svg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" + integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== + dependencies: + html-comment-regex "^1.1.0" + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +is-typedarray@^1.0.0, is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + +is-wsl@^2.1.1, is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +istanbul-lib-coverage@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" + integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== + +istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" + integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b" + integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jake@^10.6.1: + version "10.8.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b" + integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A== + dependencies: + async "0.9.x" + chalk "^2.4.2" + filelist "^1.0.1" + minimatch "^3.0.4" + +jest-changed-files@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" + integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== + dependencies: + "@jest/types" "^26.6.2" + execa "^4.0.0" + throat "^5.0.0" + +jest-circus@26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-26.6.0.tgz#7d9647b2e7f921181869faae1f90a2629fd70705" + integrity sha512-L2/Y9szN6FJPWFK8kzWXwfp+FOR7xq0cUL4lIsdbIdwz3Vh6P1nrpcqOleSzr28zOtSHQNV9Z7Tl+KkuK7t5Ng== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^26.6.0" + "@jest/test-result" "^26.6.0" + "@jest/types" "^26.6.0" + "@types/babel__traverse" "^7.0.4" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^26.6.0" + is-generator-fn "^2.0.0" + jest-each "^26.6.0" + jest-matcher-utils "^26.6.0" + jest-message-util "^26.6.0" + jest-runner "^26.6.0" + jest-runtime "^26.6.0" + jest-snapshot "^26.6.0" + jest-util "^26.6.0" + pretty-format "^26.6.0" + stack-utils "^2.0.2" + throat "^5.0.0" + +jest-cli@^26.6.0: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" + integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== + dependencies: + "@jest/core" "^26.6.3" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + import-local "^3.0.2" + is-ci "^2.0.0" + jest-config "^26.6.3" + jest-util "^26.6.2" + jest-validate "^26.6.2" + prompts "^2.0.1" + yargs "^15.4.1" + +jest-config@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" + integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^26.6.3" + "@jest/types" "^26.6.2" + babel-jest "^26.6.3" + chalk "^4.0.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.4" + jest-environment-jsdom "^26.6.2" + jest-environment-node "^26.6.2" + jest-get-type "^26.3.0" + jest-jasmine2 "^26.6.3" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + micromatch "^4.0.2" + pretty-format "^26.6.2" + +jest-diff@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" + integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== + dependencies: + chalk "^4.0.0" + diff-sequences "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + +jest-docblock@^26.0.0: + version "26.0.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" + integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== + dependencies: + detect-newline "^3.0.0" + +jest-each@^26.6.0, jest-each@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" + integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== + dependencies: + "@jest/types" "^26.6.2" + chalk "^4.0.0" + jest-get-type "^26.3.0" + jest-util "^26.6.2" + pretty-format "^26.6.2" + +jest-environment-jsdom@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" + integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" + jsdom "^16.4.0" + +jest-environment-node@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" + integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" + +jest-get-type@^26.3.0: + version "26.3.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" + integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== + +jest-haste-map@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" + integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== + dependencies: + "@jest/types" "^26.6.2" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.4" + jest-regex-util "^26.0.0" + jest-serializer "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" + micromatch "^4.0.2" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.1.2" + +jest-jasmine2@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" + integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^26.6.2" + is-generator-fn "^2.0.0" + jest-each "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + pretty-format "^26.6.2" + throat "^5.0.0" + +jest-leak-detector@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" + integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== + dependencies: + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + +jest-matcher-utils@^26.6.0, jest-matcher-utils@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" + integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw== + dependencies: + chalk "^4.0.0" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + +jest-message-util@^26.6.0, jest-message-util@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" + integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/types" "^26.6.2" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + micromatch "^4.0.2" + pretty-format "^26.6.2" + slash "^3.0.0" + stack-utils "^2.0.2" + +jest-mock@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" + integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^26.0.0: + version "26.0.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" + integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== + +jest-resolve-dependencies@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" + integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== + dependencies: + "@jest/types" "^26.6.2" + jest-regex-util "^26.0.0" + jest-snapshot "^26.6.2" + +jest-resolve@26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.0.tgz#070fe7159af87b03e50f52ea5e17ee95bbee40e1" + integrity sha512-tRAz2bwraHufNp+CCmAD8ciyCpXCs1NQxB5EJAmtCFy6BN81loFEGWKzYu26Y62lAJJe4X4jg36Kf+NsQyiStQ== + dependencies: + "@jest/types" "^26.6.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + jest-pnp-resolver "^1.2.2" + jest-util "^26.6.0" + read-pkg-up "^7.0.1" + resolve "^1.17.0" + slash "^3.0.0" + +jest-resolve@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" + integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== + dependencies: + "@jest/types" "^26.6.2" + chalk "^4.0.0" + graceful-fs "^4.2.4" + jest-pnp-resolver "^1.2.2" + jest-util "^26.6.2" + read-pkg-up "^7.0.1" + resolve "^1.18.1" + slash "^3.0.0" + +jest-runner@^26.6.0, jest-runner@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" + integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== + dependencies: + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.7.1" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-config "^26.6.3" + jest-docblock "^26.0.0" + jest-haste-map "^26.6.2" + jest-leak-detector "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + jest-runtime "^26.6.3" + jest-util "^26.6.2" + jest-worker "^26.6.2" + source-map-support "^0.5.6" + throat "^5.0.0" + +jest-runtime@^26.6.0, jest-runtime@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" + integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== + dependencies: + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/globals" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + cjs-module-lexer "^0.6.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.4" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + slash "^3.0.0" + strip-bom "^4.0.0" + yargs "^15.4.1" + +jest-serializer@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" + integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.4" + +jest-snapshot@^26.6.0, jest-snapshot@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" + integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^26.6.2" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.0.0" + chalk "^4.0.0" + expect "^26.6.2" + graceful-fs "^4.2.4" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + jest-haste-map "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + natural-compare "^1.4.0" + pretty-format "^26.6.2" + semver "^7.3.2" + +jest-util@^26.6.0, jest-util@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" + integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" + +jest-validate@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" + integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== + dependencies: + "@jest/types" "^26.6.2" + camelcase "^6.0.0" + chalk "^4.0.0" + jest-get-type "^26.3.0" + leven "^3.1.0" + pretty-format "^26.6.2" + +jest-watch-typeahead@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/jest-watch-typeahead/-/jest-watch-typeahead-0.6.1.tgz#45221b86bb6710b7e97baaa1640ae24a07785e63" + integrity sha512-ITVnHhj3Jd/QkqQcTqZfRgjfyRhDFM/auzgVo2RKvSwi18YMvh0WvXDJFoFED6c7jd/5jxtu4kSOb9PTu2cPVg== + dependencies: + ansi-escapes "^4.3.1" + chalk "^4.0.0" + jest-regex-util "^26.0.0" + jest-watcher "^26.3.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + +jest-watcher@^26.3.0, jest-watcher@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" + integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== + dependencies: + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + jest-util "^26.6.2" + string-length "^4.0.1" + +jest-worker@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5" + integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== + dependencies: + merge-stream "^2.0.0" + supports-color "^6.1.0" + +jest-worker@^26.5.0, jest-worker@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + +jest@26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.0.tgz#546b25a1d8c888569dbbe93cae131748086a4a25" + integrity sha512-jxTmrvuecVISvKFFhOkjsWRZV7sFqdSUAd1ajOKY+/QE/aLBVstsJ/dX8GczLzwiT6ZEwwmZqtCUHLHHQVzcfA== + dependencies: + "@jest/core" "^26.6.0" + import-local "^3.0.2" + jest-cli "^26.6.0" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsdom@^16.4.0: + version "16.4.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb" + integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w== + dependencies: + abab "^2.0.3" + acorn "^7.1.1" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.2.0" + data-urls "^2.0.0" + decimal.js "^10.2.0" + domexception "^2.0.1" + escodegen "^1.14.1" + html-encoding-sniffer "^2.0.1" + is-potential-custom-element-name "^1.0.0" + nwsapi "^2.2.0" + parse5 "5.1.1" + request "^2.88.2" + request-promise-native "^1.0.8" + saxes "^5.0.0" + symbol-tree "^3.2.4" + tough-cookie "^3.0.1" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.1.0" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + ws "^7.2.3" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + +json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json3@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" + integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +json5@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82" + integrity sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q== + dependencies: + array-includes "^3.1.2" + object.assign "^4.1.2" + +killable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" + integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +language-subtag-registry@~0.3.2: + version "0.3.21" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" + integrity sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg== + +language-tags@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" + integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo= + dependencies: + language-subtag-registry "~0.3.2" + +last-call-webpack-plugin@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555" + integrity sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w== + dependencies: + lodash "^4.17.5" + webpack-sources "^1.1.0" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +loader-runner@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" + integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== + +loader-utils@1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" + integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== + dependencies: + big.js "^5.2.2" + emojis-list "^2.0.0" + json5 "^1.0.1" + +loader-utils@2.0.0, loader-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" + integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash._reinterpolate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash.template@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" + integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.templatesettings@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" + integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== + dependencies: + lodash._reinterpolate "^3.0.0" + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + +"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loglevel@^1.6.8: + version "1.7.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" + integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== + +loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +magic-string@^0.25.0, magic-string@^0.25.7: + version "0.25.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" + integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== + dependencies: + sourcemap-codec "^1.4.4" + +make-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-dir@^3.0.0, make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= + dependencies: + tmpl "1.0.x" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + +mdn-data@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" + integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +memory-fs@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +microevent.ts@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0" + integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g== + +micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + +mime-db@1.46.0, "mime-db@>= 1.43.0 < 2": + version "1.46.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" + integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== + +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: + version "2.1.29" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" + integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ== + dependencies: + mime-db "1.46.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mime@^2.4.4: + version "2.5.2" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mini-css-extract-plugin@0.11.3: + version "0.11.3" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz#15b0910a7f32e62ffde4a7430cfefbd700724ea6" + integrity sha512-n9BA8LonkOkW1/zn+IbLPQmovsL0wMb9yx75fMJQZf2X1Zoec9yTZtyMePcyu19wPkmFbzZZA6fLTotpFhQsOA== + dependencies: + loader-utils "^1.1.0" + normalize-url "1.9.1" + schema-utils "^1.0.0" + webpack-sources "^1.1.0" + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + +minimatch@3.0.4, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" + integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== + dependencies: + yallist "^4.0.0" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mississippi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" + integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== + dependencies: + concat-stream "^1.5.0" + duplexify "^3.4.2" + end-of-stream "^1.1.0" + flush-write-stream "^1.0.0" + from2 "^2.1.0" + parallel-transform "^1.1.0" + pump "^3.0.0" + pumpify "^1.3.3" + stream-each "^1.1.0" + through2 "^2.0.0" + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +move-concurrently@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" + integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= + dependencies: + aproba "^1.1.1" + copy-concurrently "^1.0.0" + fs-write-stream-atomic "^1.0.8" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.3" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= + +multicast-dns@^6.0.1: + version "6.2.3" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" + integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== + dependencies: + dns-packet "^1.3.1" + thunky "^1.0.2" + +nan@^2.12.1: + version "2.14.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" + integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== + +nanoid@^3.1.20: + version "3.1.20" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" + integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +native-url@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.2.6.tgz#ca1258f5ace169c716ff44eccbddb674e10399ae" + integrity sha512-k4bDC87WtgrdD362gZz6zoiXQrl40kYlBmpfmSjwRO1VU0V5ccwJTlxuE72F6m3V0vc1xOf6n3UCP9QyerRqmA== + dependencies: + querystring "^0.2.0" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +neo-async@^2.5.0, neo-async@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +next-tick@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + +node-libs-browser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== + dependencies: + assert "^1.1.1" + browserify-zlib "^0.2.0" + buffer "^4.3.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "^3.11.0" + domain-browser "^1.1.1" + events "^3.0.0" + https-browserify "^1.0.0" + os-browserify "^0.3.0" + path-browserify "0.0.1" + process "^0.11.10" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.3.3" + stream-browserify "^2.0.1" + stream-http "^2.7.2" + string_decoder "^1.0.0" + timers-browserify "^2.0.4" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.11.0" + vm-browserify "^1.0.1" + +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= + +node-notifier@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.1.tgz#f86e89bbc925f2b068784b31f382afdc6ca56be1" + integrity sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA== + dependencies: + growly "^1.3.0" + is-wsl "^2.2.0" + semver "^7.3.2" + shellwords "^0.1.1" + uuid "^8.3.0" + which "^2.0.2" + +node-releases@^1.1.61, node-releases@^1.1.70: + version "1.1.70" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.70.tgz#66e0ed0273aa65666d7fe78febe7634875426a08" + integrity sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw== + +node-releases@^1.1.71: + version "1.1.72" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe" + integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw== + +normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +normalize-url@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + +normalize-url@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" + integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + +npm-run-path@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nth-check@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + +nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-inspect@^1.8.0, object-inspect@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" + integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== + +object-is@^1.0.1: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.assign@^4.1.0, object.assign@^4.1.1, object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.entries@^1.1.0, object.entries@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.3.tgz#c601c7f168b62374541a07ddbd3e2d5e4f7711a6" + integrity sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + has "^1.0.3" + +object.fromentries@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.4.tgz#26e1ba5c4571c5c6f0890cef4473066456a120b8" + integrity sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + has "^1.0.3" + +object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz#1bd63aeacf0d5d2d2f31b5e393b03a7c601a23f7" + integrity sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +object.values@^1.1.0, object.values@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.2.tgz#7a2015e06fcb0f546bd652486ce8583a4731c731" + integrity sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + has "^1.0.3" + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^7.0.2, open@^7.3.1: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + +opn@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" + integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== + dependencies: + is-wsl "^1.1.0" + +optimize-css-assets-webpack-plugin@5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz#85883c6528aaa02e30bbad9908c92926bb52dc90" + integrity sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A== + dependencies: + cssnano "^4.1.10" + last-call-webpack-plugin "^3.0.0" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +original@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" + integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== + dependencies: + url-parse "^1.4.3" + +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= + +p-each-series@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" + integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0, p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-map@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-retry@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" + integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w== + dependencies: + retry "^0.12.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@~1.0.5: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +parallel-transform@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" + integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== + dependencies: + cyclist "^1.0.1" + inherits "^2.0.3" + readable-stream "^2.1.5" + +param-case@^3.0.3: + version "3.0.4" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" + integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-asn1@^5.0.0, parse-asn1@^5.1.5: + version "5.1.6" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" + integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== + dependencies: + asn1.js "^5.2.0" + browserify-aes "^1.0.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + dependencies: + error-ex "^1.2.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse5@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + +parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= + dependencies: + pify "^2.0.0" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pbkdf2@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" + integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + +pirates@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== + dependencies: + node-modules-regexp "^1.0.0" + +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= + dependencies: + find-up "^2.1.0" + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + +pkg-dir@^4.1.0, pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pkg-up@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + +pnp-webpack-plugin@1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" + integrity sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg== + dependencies: + ts-pnp "^1.1.6" + +portfinder@^1.0.26: + version "1.0.28" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" + integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== + dependencies: + async "^2.6.2" + debug "^3.1.1" + mkdirp "^0.5.5" + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +postcss-attribute-case-insensitive@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" + integrity sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^6.0.2" + +postcss-browser-comments@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-browser-comments/-/postcss-browser-comments-3.0.0.tgz#1248d2d935fb72053c8e1f61a84a57292d9f65e9" + integrity sha512-qfVjLfq7HFd2e0HW4s1dvU8X080OZdG46fFbIBFjW7US7YPDcWfRvdElvwMJr2LI6hMmD+7LnH2HcmXTs+uOig== + dependencies: + postcss "^7" + +postcss-calc@^7.0.1: + version "7.0.5" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.5.tgz#f8a6e99f12e619c2ebc23cf6c486fdc15860933e" + integrity sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg== + dependencies: + postcss "^7.0.27" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.2" + +postcss-color-functional-notation@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz#5efd37a88fbabeb00a2966d1e53d98ced93f74e0" + integrity sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-color-gray@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz#532a31eb909f8da898ceffe296fdc1f864be8547" + integrity sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.5" + postcss-values-parser "^2.0.0" + +postcss-color-hex-alpha@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz#a8d9ca4c39d497c9661e374b9c51899ef0f87388" + integrity sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw== + dependencies: + postcss "^7.0.14" + postcss-values-parser "^2.0.1" + +postcss-color-mod-function@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz#816ba145ac11cc3cb6baa905a75a49f903e4d31d" + integrity sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-color-rebeccapurple@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz#c7a89be872bb74e45b1e3022bfe5748823e6de77" + integrity sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-colormin@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" + integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== + dependencies: + browserslist "^4.0.0" + color "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-convert-values@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" + integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-custom-media@^7.0.8: + version "7.0.8" + resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz#fffd13ffeffad73621be5f387076a28b00294e0c" + integrity sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg== + dependencies: + postcss "^7.0.14" + +postcss-custom-properties@^8.0.11: + version "8.0.11" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz#2d61772d6e92f22f5e0d52602df8fae46fa30d97" + integrity sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA== + dependencies: + postcss "^7.0.17" + postcss-values-parser "^2.0.1" + +postcss-custom-selectors@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz#64858c6eb2ecff2fb41d0b28c9dd7b3db4de7fba" + integrity sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-dir-pseudo-class@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz#6e3a4177d0edb3abcc85fdb6fbb1c26dabaeaba2" + integrity sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-discard-comments@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" + integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== + dependencies: + postcss "^7.0.0" + +postcss-discard-duplicates@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" + integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== + dependencies: + postcss "^7.0.0" + +postcss-discard-empty@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" + integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== + dependencies: + postcss "^7.0.0" + +postcss-discard-overridden@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" + integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== + dependencies: + postcss "^7.0.0" + +postcss-double-position-gradients@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz#fc927d52fddc896cb3a2812ebc5df147e110522e" + integrity sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA== + dependencies: + postcss "^7.0.5" + postcss-values-parser "^2.0.0" + +postcss-env-function@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-2.0.2.tgz#0f3e3d3c57f094a92c2baf4b6241f0b0da5365d7" + integrity sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-flexbugs-fixes@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz#9218a65249f30897deab1033aced8578562a6690" + integrity sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ== + dependencies: + postcss "^7.0.26" + +postcss-focus-visible@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz#477d107113ade6024b14128317ade2bd1e17046e" + integrity sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g== + dependencies: + postcss "^7.0.2" + +postcss-focus-within@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz#763b8788596cee9b874c999201cdde80659ef680" + integrity sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w== + dependencies: + postcss "^7.0.2" + +postcss-font-variant@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz#42d4c0ab30894f60f98b17561eb5c0321f502641" + integrity sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA== + dependencies: + postcss "^7.0.2" + +postcss-gap-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz#431c192ab3ed96a3c3d09f2ff615960f902c1715" + integrity sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg== + dependencies: + postcss "^7.0.2" + +postcss-image-set-function@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz#28920a2f29945bed4c3198d7df6496d410d3f288" + integrity sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-initial@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-3.0.2.tgz#f018563694b3c16ae8eaabe3c585ac6319637b2d" + integrity sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA== + dependencies: + lodash.template "^4.5.0" + postcss "^7.0.2" + +postcss-lab-function@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz#bb51a6856cd12289ab4ae20db1e3821ef13d7d2e" + integrity sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-load-config@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a" + integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw== + dependencies: + cosmiconfig "^5.0.0" + import-cwd "^2.0.0" + +postcss-loader@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" + integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA== + dependencies: + loader-utils "^1.1.0" + postcss "^7.0.0" + postcss-load-config "^2.0.0" + schema-utils "^1.0.0" + +postcss-logical@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-3.0.0.tgz#2495d0f8b82e9f262725f75f9401b34e7b45d5b5" + integrity sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA== + dependencies: + postcss "^7.0.2" + +postcss-media-minmax@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz#b75bb6cbc217c8ac49433e12f22048814a4f5ed5" + integrity sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw== + dependencies: + postcss "^7.0.2" + +postcss-merge-longhand@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" + integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== + dependencies: + css-color-names "0.0.4" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + stylehacks "^4.0.0" + +postcss-merge-rules@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" + integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + cssnano-util-same-parent "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + vendors "^1.0.0" + +postcss-minify-font-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" + integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-gradients@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" + integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + is-color-stop "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-params@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" + integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== + dependencies: + alphanum-sort "^1.0.0" + browserslist "^4.0.0" + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + uniqs "^2.0.0" + +postcss-minify-selectors@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" + integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== + dependencies: + alphanum-sort "^1.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +postcss-modules-extract-imports@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" + integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== + dependencies: + postcss "^7.0.5" + +postcss-modules-local-by-default@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" + integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== + dependencies: + icss-utils "^4.1.1" + postcss "^7.0.32" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" + integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== + dependencies: + postcss "^7.0.6" + postcss-selector-parser "^6.0.0" + +postcss-modules-values@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" + integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== + dependencies: + icss-utils "^4.0.0" + postcss "^7.0.6" + +postcss-nesting@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052" + integrity sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg== + dependencies: + postcss "^7.0.2" + +postcss-normalize-charset@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" + integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== + dependencies: + postcss "^7.0.0" + +postcss-normalize-display-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" + integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-positions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" + integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== + dependencies: + cssnano-util-get-arguments "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-repeat-style@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" + integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-string@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" + integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== + dependencies: + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-timing-functions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" + integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-unicode@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" + integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-url@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" + integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-whitespace@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" + integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize/-/postcss-normalize-8.0.1.tgz#90e80a7763d7fdf2da6f2f0f82be832ce4f66776" + integrity sha512-rt9JMS/m9FHIRroDDBGSMsyW1c0fkvOJPy62ggxSHUldJO7B195TqFMqIf+lY5ezpDcYOV4j86aUp3/XbxzCCQ== + dependencies: + "@csstools/normalize.css" "^10.1.0" + browserslist "^4.6.2" + postcss "^7.0.17" + postcss-browser-comments "^3.0.0" + sanitize.css "^10.0.0" + +postcss-ordered-values@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" + integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== + dependencies: + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-overflow-shorthand@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz#31ecf350e9c6f6ddc250a78f0c3e111f32dd4c30" + integrity sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g== + dependencies: + postcss "^7.0.2" + +postcss-page-break@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-2.0.0.tgz#add52d0e0a528cabe6afee8b46e2abb277df46bf" + integrity sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ== + dependencies: + postcss "^7.0.2" + +postcss-place@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-4.0.1.tgz#e9f39d33d2dc584e46ee1db45adb77ca9d1dcc62" + integrity sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-preset-env@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz#c34ddacf8f902383b35ad1e030f178f4cdf118a5" + integrity sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg== + dependencies: + autoprefixer "^9.6.1" + browserslist "^4.6.4" + caniuse-lite "^1.0.30000981" + css-blank-pseudo "^0.1.4" + css-has-pseudo "^0.10.0" + css-prefers-color-scheme "^3.1.1" + cssdb "^4.4.0" + postcss "^7.0.17" + postcss-attribute-case-insensitive "^4.0.1" + postcss-color-functional-notation "^2.0.1" + postcss-color-gray "^5.0.0" + postcss-color-hex-alpha "^5.0.3" + postcss-color-mod-function "^3.0.3" + postcss-color-rebeccapurple "^4.0.1" + postcss-custom-media "^7.0.8" + postcss-custom-properties "^8.0.11" + postcss-custom-selectors "^5.1.2" + postcss-dir-pseudo-class "^5.0.0" + postcss-double-position-gradients "^1.0.0" + postcss-env-function "^2.0.2" + postcss-focus-visible "^4.0.0" + postcss-focus-within "^3.0.0" + postcss-font-variant "^4.0.0" + postcss-gap-properties "^2.0.0" + postcss-image-set-function "^3.0.1" + postcss-initial "^3.0.0" + postcss-lab-function "^2.0.1" + postcss-logical "^3.0.0" + postcss-media-minmax "^4.0.0" + postcss-nesting "^7.0.0" + postcss-overflow-shorthand "^2.0.0" + postcss-page-break "^2.0.0" + postcss-place "^4.0.1" + postcss-pseudo-class-any-link "^6.0.0" + postcss-replace-overflow-wrap "^3.0.0" + postcss-selector-matches "^4.0.0" + postcss-selector-not "^4.0.0" + +postcss-pseudo-class-any-link@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz#2ed3eed393b3702879dec4a87032b210daeb04d1" + integrity sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-reduce-initial@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" + integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + +postcss-reduce-transforms@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" + integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== + dependencies: + cssnano-util-get-match "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-replace-overflow-wrap@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz#61b360ffdaedca84c7c918d2b0f0d0ea559ab01c" + integrity sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw== + dependencies: + postcss "^7.0.2" + +postcss-safe-parser@5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-5.0.2.tgz#459dd27df6bc2ba64608824ba39e45dacf5e852d" + integrity sha512-jDUfCPJbKOABhwpUKcqCVbbXiloe/QXMcbJ6Iipf3sDIihEzTqRCeMBfRaOHxhBuTYqtASrI1KJWxzztZU4qUQ== + dependencies: + postcss "^8.1.0" + +postcss-selector-matches@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz#71c8248f917ba2cc93037c9637ee09c64436fcff" + integrity sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" + +postcss-selector-not@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-4.0.1.tgz#263016eef1cf219e0ade9a913780fc1f48204cbf" + integrity sha512-YolvBgInEK5/79C+bdFMyzqTg6pkYqDbzZIST/PDMqa/o3qtXenD05apBG2jLgT0/BQ77d4U2UK12jWpilqMAQ== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" + +postcss-selector-parser@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" + integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== + dependencies: + dot-prop "^5.2.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" + integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== + dependencies: + cssesc "^2.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3" + integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw== + dependencies: + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + util-deprecate "^1.0.2" + +postcss-svgo@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" + integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== + dependencies: + is-svg "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + svgo "^1.0.0" + +postcss-unique-selectors@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" + integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== + dependencies: + alphanum-sort "^1.0.0" + postcss "^7.0.0" + uniqs "^2.0.0" + +postcss-value-parser@^3.0.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== + +postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" + integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== + +postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" + integrity sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg== + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss@7.0.21: + version "7.0.21" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.21.tgz#06bb07824c19c2021c5d056d5b10c35b989f7e17" + integrity sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + +postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.35" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" + integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + +postcss@^8.1.0: + version "8.2.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.6.tgz#5d69a974543b45f87e464bc4c3e392a97d6be9fe" + integrity sha512-xpB8qYxgPuly166AGlpRjUdEYtmOWx2iCwGmrv4vqZL9YPVviDVPZPRXxnXr6xPZOdxQ9lp3ZBFCRgWJ7LE3Sg== + dependencies: + colorette "^1.2.1" + nanoid "^3.1.20" + source-map "^0.6.1" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prepend-http@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= + +pretty-bytes@^5.3.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + +pretty-error@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.2.tgz#be89f82d81b1c86ec8fdfbc385045882727f93b6" + integrity sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw== + dependencies: + lodash "^4.17.20" + renderkid "^2.0.4" + +pretty-format@^26.6.0, pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== + dependencies: + "@jest/types" "^26.6.2" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^17.0.1" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= + +promise@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" + integrity sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q== + dependencies: + asap "~2.0.6" + +prompts@2.4.0, prompts@^2.0.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7" + integrity sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +proxy-addr@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" + integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.9.1" + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.3: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== + dependencies: + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + +punycode@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +q@^1.1.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + +querystring-es3@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +querystring@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" + integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== + +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + +queue-microtask@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.2.tgz#abf64491e6ecf0f38a6502403d4cda04f372dfd3" + integrity sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg== + +raf@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + +react-app-polyfill@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz#a0bea50f078b8a082970a9d853dc34b6dcc6a3cf" + integrity sha512-0sF4ny9v/B7s6aoehwze9vJNWcmCemAUYBVasscVr92+UYiEqDXOxfKjXN685mDaMRNF3WdhHQs76oTODMocFA== + dependencies: + core-js "^3.6.5" + object-assign "^4.1.1" + promise "^8.1.0" + raf "^3.4.1" + regenerator-runtime "^0.13.7" + whatwg-fetch "^3.4.1" + +react-dev-utils@^11.0.0: + version "11.0.4" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-11.0.4.tgz#a7ccb60257a1ca2e0efe7a83e38e6700d17aa37a" + integrity sha512-dx0LvIGHcOPtKbeiSUM4jqpBl3TcY7CDjZdfOIcKeznE7BWr9dg0iPG90G5yfVQ+p/rGNMXdbfStvzQZEVEi4A== + dependencies: + "@babel/code-frame" "7.10.4" + address "1.1.2" + browserslist "4.14.2" + chalk "2.4.2" + cross-spawn "7.0.3" + detect-port-alt "1.1.6" + escape-string-regexp "2.0.0" + filesize "6.1.0" + find-up "4.1.0" + fork-ts-checker-webpack-plugin "4.1.6" + global-modules "2.0.0" + globby "11.0.1" + gzip-size "5.1.1" + immer "8.0.1" + is-root "2.1.0" + loader-utils "2.0.0" + open "^7.0.2" + pkg-up "3.1.0" + prompts "2.4.0" + react-error-overlay "^6.0.9" + recursive-readdir "2.2.2" + shell-quote "1.7.2" + strip-ansi "6.0.0" + text-table "0.2.0" + +react-dom@17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler "^0.20.2" + +react-error-overlay@^6.0.9: + version "6.0.9" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" + integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== + +react-is@^16.8.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" + integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== + +react-refresh@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" + integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== + +react-scripts@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-4.0.0.tgz#36f3d84ffff708ac0618fd61e71eaaea11c26417" + integrity sha512-icJ/ctwV5XwITUOupBP9TUVGdWOqqZ0H08tbJ1kVC5VpNWYzEZ3e/x8axhV15ZXRsixLo27snwQE7B6Zd9J2Tg== + dependencies: + "@babel/core" "7.12.3" + "@pmmmwh/react-refresh-webpack-plugin" "0.4.2" + "@svgr/webpack" "5.4.0" + "@typescript-eslint/eslint-plugin" "^4.5.0" + "@typescript-eslint/parser" "^4.5.0" + babel-eslint "^10.1.0" + babel-jest "^26.6.0" + babel-loader "8.1.0" + babel-plugin-named-asset-import "^0.3.7" + babel-preset-react-app "^10.0.0" + bfj "^7.0.2" + camelcase "^6.1.0" + case-sensitive-paths-webpack-plugin "2.3.0" + css-loader "4.3.0" + dotenv "8.2.0" + dotenv-expand "5.1.0" + eslint "^7.11.0" + eslint-config-react-app "^6.0.0" + eslint-plugin-flowtype "^5.2.0" + eslint-plugin-import "^2.22.1" + eslint-plugin-jest "^24.1.0" + eslint-plugin-jsx-a11y "^6.3.1" + eslint-plugin-react "^7.21.5" + eslint-plugin-react-hooks "^4.2.0" + eslint-plugin-testing-library "^3.9.2" + eslint-webpack-plugin "^2.1.0" + file-loader "6.1.1" + fs-extra "^9.0.1" + html-webpack-plugin "4.5.0" + identity-obj-proxy "3.0.0" + jest "26.6.0" + jest-circus "26.6.0" + jest-resolve "26.6.0" + jest-watch-typeahead "0.6.1" + mini-css-extract-plugin "0.11.3" + optimize-css-assets-webpack-plugin "5.0.4" + pnp-webpack-plugin "1.6.4" + postcss-flexbugs-fixes "4.2.1" + postcss-loader "3.0.0" + postcss-normalize "8.0.1" + postcss-preset-env "6.7.0" + postcss-safe-parser "5.0.2" + react-app-polyfill "^2.0.0" + react-dev-utils "^11.0.0" + react-refresh "^0.8.3" + resolve "1.18.1" + resolve-url-loader "^3.1.2" + sass-loader "8.0.2" + semver "7.3.2" + style-loader "1.3.0" + terser-webpack-plugin "4.2.3" + ts-pnp "1.2.0" + url-loader "4.1.1" + webpack "4.44.2" + webpack-dev-server "3.11.0" + webpack-manifest-plugin "2.2.0" + workbox-webpack-plugin "5.1.4" + optionalDependencies: + fsevents "^2.1.3" + +react@17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +readdirp@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" + integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== + dependencies: + picomatch "^2.2.1" + +recursive-readdir@2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" + integrity sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg== + dependencies: + minimatch "3.0.4" + +regenerate-unicode-properties@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" + integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.4.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: + version "0.13.7" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== + +regenerator-transform@^0.14.2: + version "0.14.5" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" + integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== + dependencies: + "@babel/runtime" "^7.8.4" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regex-parser@^2.2.11: + version "2.2.11" + resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.11.tgz#3b37ec9049e19479806e878cabe7c1ca83ccfe58" + integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q== + +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" + integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +regexpp@^3.0.0, regexpp@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + +regexpu-core@^4.7.1: + version "4.7.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" + integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.2.0" + regjsgen "^0.5.1" + regjsparser "^0.6.4" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.2.0" + +regjsgen@^0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" + integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== + +regjsparser@^0.6.4: + version "0.6.7" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.7.tgz#c00164e1e6713c2e3ee641f1701c4b7aa0a7f86c" + integrity sha512-ib77G0uxsA2ovgiYbCVGx4Pv3PSttAx2vIwidqQzbL2U5S4Q+j00HdSAneSBuyVcMvEnTXMjiGgB+DlXozVhpQ== + dependencies: + jsesc "~0.5.0" + +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +renderkid@^2.0.4: + version "2.0.5" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.5.tgz#483b1ac59c6601ab30a7a596a5965cabccfdd0a5" + integrity sha512-ccqoLg+HLOHq1vdfYNm4TBeaCDIi1FLt3wGojTDSvdewUv65oTmI3cnT2E4hRjl1gzKZIPK+KZrXzlUYKnR+vQ== + dependencies: + css-select "^2.0.2" + dom-converter "^0.2" + htmlparser2 "^3.10.1" + lodash "^4.17.20" + strip-ansi "^3.0.0" + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +request-promise-core@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" + integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== + dependencies: + lodash "^4.17.19" + +request-promise-native@^1.0.8: + version "1.0.9" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" + integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== + dependencies: + request-promise-core "1.1.4" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.88.2: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= + dependencies: + resolve-from "^3.0.0" + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-url-loader@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.2.tgz#235e2c28e22e3e432ba7a5d4e305c59a58edfc08" + integrity sha512-QEb4A76c8Mi7I3xNKXlRKQSlLBwjUV/ULFMP+G7n3/7tJZ8MG5wsZ3ucxP1Jz8Vevn6fnJsxDx9cIls+utGzPQ== + dependencies: + adjust-sourcemap-loader "3.0.0" + camelcase "5.3.1" + compose-function "3.0.3" + convert-source-map "1.7.0" + es6-iterator "2.0.3" + loader-utils "1.2.3" + postcss "7.0.21" + rework "1.0.1" + rework-visit "1.0.0" + source-map "0.6.1" + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" + integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== + dependencies: + is-core-module "^2.0.0" + path-parse "^1.0.6" + +resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.3.2, resolve@^1.8.1: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rework-visit@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rework-visit/-/rework-visit-1.0.0.tgz#9945b2803f219e2f7aca00adb8bc9f640f842c9a" + integrity sha1-mUWygD8hni96ygCtuLyfZA+ELJo= + +rework@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rework/-/rework-1.0.1.tgz#30806a841342b54510aa4110850cd48534144aa7" + integrity sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc= + dependencies: + convert-source-map "^0.3.3" + css "^2.0.0" + +rgb-regex@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" + integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= + +rgba-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" + integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= + +rimraf@^2.5.4, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rimraf@~2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +rollup-plugin-babel@^4.3.3: + version "4.4.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-babel/-/rollup-plugin-babel-4.4.0.tgz#d15bd259466a9d1accbdb2fe2fff17c52d030acb" + integrity sha512-Lek/TYp1+7g7I+uMfJnnSJ7YWoD58ajo6Oarhlex7lvUce+RCKRuGRSgztDO3/MF/PuGKmUL5iTHKf208UNszw== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + rollup-pluginutils "^2.8.1" + +rollup-plugin-terser@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz#8c650062c22a8426c64268548957463bf981b413" + integrity sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w== + dependencies: + "@babel/code-frame" "^7.5.5" + jest-worker "^24.9.0" + rollup-pluginutils "^2.8.2" + serialize-javascript "^4.0.0" + terser "^4.6.2" + +rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" + integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== + dependencies: + estree-walker "^0.6.1" + +rollup@^1.31.1: + version "1.32.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.32.1.tgz#4480e52d9d9e2ae4b46ba0d9ddeaf3163940f9c4" + integrity sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A== + dependencies: + "@types/estree" "*" + "@types/node" "*" + acorn "^7.1.0" + +rsvp@^4.8.4: + version "4.8.5" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +run-queue@^1.0.0, run-queue@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" + integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= + dependencies: + aproba "^1.1.1" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sane@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + +sanitize.css@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/sanitize.css/-/sanitize.css-10.0.0.tgz#b5cb2547e96d8629a60947544665243b1dc3657a" + integrity sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg== + +sass-loader@8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d" + integrity sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ== + dependencies: + clone-deep "^4.0.1" + loader-utils "^1.2.3" + neo-async "^2.6.1" + schema-utils "^2.6.1" + semver "^6.3.0" + +sax@~1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +saxes@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +schema-utils@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" + integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== + dependencies: + ajv "^6.1.0" + ajv-errors "^1.0.0" + ajv-keywords "^3.1.0" + +schema-utils@^2.6.1, schema-utils@^2.6.5, schema-utils@^2.7.0, schema-utils@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + +schema-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" + integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== + dependencies: + "@types/json-schema" "^7.0.6" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= + +selfsigned@^1.10.7: + version "1.10.11" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.11.tgz#24929cd906fe0f44b6d01fb23999a739537acbe9" + integrity sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA== + dependencies: + node-forge "^0.10.0" + +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +semver@7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.2.1, semver@^7.3.2: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serialize-javascript@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" + integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== + dependencies: + randombytes "^2.1.0" + +serialize-javascript@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setimmediate@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" + integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== + +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +sockjs-client@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" + integrity sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g== + dependencies: + debug "^3.2.5" + eventsource "^1.0.7" + faye-websocket "~0.11.1" + inherits "^2.0.3" + json3 "^3.3.2" + url-parse "^1.4.3" + +sockjs@0.3.20: + version "0.3.20" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.20.tgz#b26a283ec562ef8b2687b44033a4eeceac75d855" + integrity sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA== + dependencies: + faye-websocket "^0.10.0" + uuid "^3.4.0" + websocket-driver "0.6.5" + +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= + dependencies: + is-plain-obj "^1.0.0" + +source-list-map@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + +source-map-explorer@^2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/source-map-explorer/-/source-map-explorer-2.5.2.tgz#857cab5dd9d1d7175e9c5c2739dc9ccfb99f2dc5" + integrity sha512-gBwOyCcHPHcdLbgw6Y6kgoH1uLKL6hN3zz0xJcNI2lpnElZliIlmSYAjUVwAWnc7+HscoTyh1ScR7ITtFuEnxg== + dependencies: + btoa "^1.2.1" + chalk "^4.1.0" + convert-source-map "^1.7.0" + ejs "^3.1.5" + escape-html "^1.0.3" + glob "^7.1.6" + gzip-size "^6.0.0" + lodash "^4.17.20" + open "^7.3.1" + source-map "^0.7.3" + temp "^0.9.4" + yargs "^16.2.0" + +source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.19: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== + +source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.7.3, source-map@~0.7.2: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +sourcemap-codec@^1.4.4: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.7" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" + integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +ssri@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" + integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== + dependencies: + figgy-pudding "^3.5.1" + +ssri@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +stack-utils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" + integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw== + dependencies: + escape-string-regexp "^2.0.0" + +stackframe@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303" + integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA== + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= + +stream-browserify@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" + integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== + dependencies: + inherits "~2.0.1" + readable-stream "^2.0.2" + +stream-each@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" + integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== + dependencies: + end-of-stream "^1.1.0" + stream-shift "^1.0.0" + +stream-http@^2.7.2: + version "2.8.3" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" + integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^2.3.6" + to-arraybuffer "^1.0.0" + xtend "^4.0.0" + +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= + +string-length@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.1.tgz#4a973bf31ef77c4edbceadd6af2611996985f8a1" + integrity sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-natural-compare@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" + integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string.prototype.matchall@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.4.tgz#608f255e93e072107f5de066f81a2dfb78cf6b29" + integrity sha512-pknFIWVachNcyqRfaQSeu/FUfpvJTe4uskUSZ9Wc1RijsPuzbZ8TyYT8WCNnntCjUEqQ3vUHMAfVj2+wLAisPQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + has-symbols "^1.0.1" + internal-slot "^1.0.3" + regexp.prototype.flags "^1.3.1" + side-channel "^1.0.4" + +string.prototype.trimend@^1.0.1, string.prototype.trimend@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz#a22bd53cca5c7cf44d7c9d5c732118873d6cd18b" + integrity sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.1, string.prototype.trimstart@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz#9b4cb590e123bb36564401d59824298de50fd5aa" + integrity sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + +string_decoder@^1.0.0, string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +stringify-object@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" + +strip-ansi@6.0.0, strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-comments@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-1.0.2.tgz#82b9c45e7f05873bee53f37168af930aa368679d" + integrity sha512-kL97alc47hoyIQSV165tTt9rG5dn4w1dNnBhOQ3bOU1Nc1hel09jnXANaHJ7vzHLd4Ju8kseDGzlev96pghLFw== + dependencies: + babel-extract-comments "^1.0.0" + babel-plugin-transform-object-rest-spread "^6.26.0" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +style-loader@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.3.0.tgz#828b4a3b3b7e7aa5847ce7bae9e874512114249e" + integrity sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q== + dependencies: + loader-utils "^2.0.0" + schema-utils "^2.7.0" + +stylehacks@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" + integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz#f663df252af5f37c5d49bbd7eeefa9e0b9e59e47" + integrity sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +svg-parser@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" + integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + +svgo@^1.0.0, svgo@^1.2.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" + integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== + dependencies: + chalk "^2.4.1" + coa "^2.0.2" + css-select "^2.0.0" + css-select-base-adapter "^0.1.1" + css-tree "1.0.0-alpha.37" + csso "^4.0.2" + js-yaml "^3.13.1" + mkdirp "~0.5.1" + object.values "^1.1.0" + sax "~1.2.4" + stable "^0.1.8" + unquote "~1.1.1" + util.promisify "~1.0.0" + +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +table@^6.0.4: + version "6.0.7" + resolved "https://registry.yarnpkg.com/table/-/table-6.0.7.tgz#e45897ffbcc1bcf9e8a87bf420f2c9e5a7a52a34" + integrity sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g== + dependencies: + ajv "^7.0.2" + lodash "^4.17.20" + slice-ansi "^4.0.0" + string-width "^4.2.0" + +tapable@^1.0.0, tapable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +tar@^6.0.2: + version "6.1.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" + integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +temp-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" + integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= + +temp@^0.9.4: + version "0.9.4" + resolved "https://registry.yarnpkg.com/temp/-/temp-0.9.4.tgz#cd20a8580cb63635d0e4e9d4bd989d44286e7620" + integrity sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA== + dependencies: + mkdirp "^0.5.1" + rimraf "~2.6.2" + +tempy@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/tempy/-/tempy-0.3.0.tgz#6f6c5b295695a16130996ad5ab01a8bd726e8bf8" + integrity sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ== + dependencies: + temp-dir "^1.0.0" + type-fest "^0.3.1" + unique-string "^1.0.0" + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +terser-webpack-plugin@4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz#28daef4a83bd17c1db0297070adc07fc8cfc6a9a" + integrity sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ== + dependencies: + cacache "^15.0.5" + find-cache-dir "^3.3.1" + jest-worker "^26.5.0" + p-limit "^3.0.2" + schema-utils "^3.0.0" + serialize-javascript "^5.0.1" + source-map "^0.6.1" + terser "^5.3.4" + webpack-sources "^1.4.3" + +terser-webpack-plugin@^1.4.3: + version "1.4.5" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b" + integrity sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw== + dependencies: + cacache "^12.0.2" + find-cache-dir "^2.1.0" + is-wsl "^1.1.0" + schema-utils "^1.0.0" + serialize-javascript "^4.0.0" + source-map "^0.6.1" + terser "^4.1.2" + webpack-sources "^1.4.0" + worker-farm "^1.7.0" + +terser@^4.1.2, terser@^4.6.2, terser@^4.6.3: + version "4.8.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" + integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + +terser@^5.3.4: + version "5.6.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.0.tgz#138cdf21c5e3100b1b3ddfddf720962f88badcd2" + integrity sha512-vyqLMoqadC1uR0vywqOZzriDYzgEkNJFK4q9GeyOBHIbiECHiWLKcWfbQWAUaPfxkjDhapSlZB9f7fkMrvkVjA== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.19" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@0.2.0, text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +throat@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" + integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== + +through2@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + +timers-browserify@^2.0.4: + version "2.0.12" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" + integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== + dependencies: + setimmediate "^1.0.4" + +timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= + +to-arraybuffer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +tough-cookie@^2.3.3, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tough-cookie@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" + integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== + dependencies: + ip-regex "^2.1.0" + psl "^1.1.28" + punycode "^2.1.1" + +tr46@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479" + integrity sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg== + dependencies: + punycode "^2.1.1" + +tryer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" + integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== + +ts-pnp@1.2.0, ts-pnp@^1.1.6: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" + integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== + +tsconfig-paths@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" + integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + +tslib@^1.8.1, tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== + +tsutils@^3.17.1: + version "3.20.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.20.0.tgz#ea03ea45462e146b53d70ce0893de453ff24f698" + integrity sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg== + dependencies: + tslib "^1.8.1" + +tty-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" + integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== + +type-fest@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" + integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.3.0.tgz#ada7c045f07ead08abf9e2edd29be1a0c0661132" + integrity sha512-rgPIqOdfK/4J9FhiVrZ3cveAjRRo5rsQBAIhnylX874y1DX/kEKSVdLsnuHB6l1KTjHyU01VjiMBHgU2adejyg== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-value-ecmascript@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" + integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" + integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= + +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + +unique-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" + integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= + dependencies: + crypto-random-string "^1.0.0" + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +unquote@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" + integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +upath@^1.1.1, upath@^1.1.2, upath@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +url-loader@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2" + integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA== + dependencies: + loader-utils "^2.0.0" + mime-types "^2.1.27" + schema-utils "^3.0.0" + +url-parse@^1.4.3: + version "1.5.1" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b" + integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util.promisify@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + +util.promisify@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" + +util@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= + dependencies: + inherits "2.0.1" + +util@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" + integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== + dependencies: + inherits "2.0.3" + +utila@~0.4: + version "0.4.0" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +uuid@^3.3.2, uuid@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-compile-cache@^2.0.3: + version "2.2.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" + integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== + +v8-to-istanbul@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.0.tgz#5b95cef45c0f83217ec79f8fc7ee1c8b486aee07" + integrity sha512-uXUVqNUCLa0AH1vuVxzi+MI4RfxEOKt9pBgKwHbgH7st8Kv2P1m+jvWNnektzBh5QShF3ODgKmUFCf38LnVz1g== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + +vendors@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" + integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== + +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== + dependencies: + xml-name-validator "^3.0.0" + +walker@^1.0.7, walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= + dependencies: + makeerror "1.0.x" + +watchpack-chokidar2@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957" + integrity sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww== + dependencies: + chokidar "^2.1.8" + +watchpack@^1.7.4: + version "1.7.5" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" + integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== + dependencies: + graceful-fs "^4.1.2" + neo-async "^2.5.0" + optionalDependencies: + chokidar "^3.4.1" + watchpack-chokidar2 "^2.0.1" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + +webpack-dev-middleware@^3.7.2: + version "3.7.3" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5" + integrity sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ== + dependencies: + memory-fs "^0.4.1" + mime "^2.4.4" + mkdirp "^0.5.1" + range-parser "^1.2.1" + webpack-log "^2.0.0" + +webpack-dev-server@3.11.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#8f154a3bce1bcfd1cc618ef4e703278855e7ff8c" + integrity sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg== + dependencies: + ansi-html "0.0.7" + bonjour "^3.5.0" + chokidar "^2.1.8" + compression "^1.7.4" + connect-history-api-fallback "^1.6.0" + debug "^4.1.1" + del "^4.1.1" + express "^4.17.1" + html-entities "^1.3.1" + http-proxy-middleware "0.19.1" + import-local "^2.0.0" + internal-ip "^4.3.0" + ip "^1.1.5" + is-absolute-url "^3.0.3" + killable "^1.0.1" + loglevel "^1.6.8" + opn "^5.5.0" + p-retry "^3.0.1" + portfinder "^1.0.26" + schema-utils "^1.0.0" + selfsigned "^1.10.7" + semver "^6.3.0" + serve-index "^1.9.1" + sockjs "0.3.20" + sockjs-client "1.4.0" + spdy "^4.0.2" + strip-ansi "^3.0.1" + supports-color "^6.1.0" + url "^0.11.0" + webpack-dev-middleware "^3.7.2" + webpack-log "^2.0.0" + ws "^6.2.1" + yargs "^13.3.2" + +webpack-log@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" + integrity sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg== + dependencies: + ansi-colors "^3.0.0" + uuid "^3.3.2" + +webpack-manifest-plugin@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-2.2.0.tgz#19ca69b435b0baec7e29fbe90fb4015de2de4f16" + integrity sha512-9S6YyKKKh/Oz/eryM1RyLVDVmy3NSPV0JXMRhZ18fJsq+AwGxUY34X54VNwkzYcEmEkDwNxuEOboCZEebJXBAQ== + dependencies: + fs-extra "^7.0.0" + lodash ">=3.5 <5" + object.entries "^1.1.0" + tapable "^1.0.0" + +webpack-sources@^1.1.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== + dependencies: + source-list-map "^2.0.0" + source-map "~0.6.1" + +webpack@4.44.2: + version "4.44.2" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.2.tgz#6bfe2b0af055c8b2d1e90ed2cd9363f841266b72" + integrity sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/wasm-edit" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + acorn "^6.4.1" + ajv "^6.10.2" + ajv-keywords "^3.4.1" + chrome-trace-event "^1.0.2" + enhanced-resolve "^4.3.0" + eslint-scope "^4.0.3" + json-parse-better-errors "^1.0.2" + loader-runner "^2.4.0" + loader-utils "^1.2.3" + memory-fs "^0.4.1" + micromatch "^3.1.10" + mkdirp "^0.5.3" + neo-async "^2.6.1" + node-libs-browser "^2.2.1" + schema-utils "^1.0.0" + tapable "^1.1.3" + terser-webpack-plugin "^1.4.3" + watchpack "^1.7.4" + webpack-sources "^1.4.1" + +websocket-driver@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" + integrity sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY= + dependencies: + websocket-extensions ">=0.1.1" + +websocket-driver@>=0.5.1: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-fetch@^3.4.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.1.tgz#93bc4005af6c2cc30ba3e42ec3125947c8f54ed3" + integrity sha512-IEmN/ZfmMw6G1hgZpVd0LuZXOQDisrMOZrzYd5x3RAK4bMPlJohKUZWZ9t/QsTvH0dV9TbPDcc2OSuIDcihnHA== + +whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^8.0.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.4.0.tgz#50fb9615b05469591d2b2bd6dfaed2942ed72837" + integrity sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^2.0.2" + webidl-conversions "^6.1.0" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@^1.2.9, which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.3, word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +workbox-background-sync@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-5.1.4.tgz#5ae0bbd455f4e9c319e8d827c055bb86c894fd12" + integrity sha512-AH6x5pYq4vwQvfRDWH+vfOePfPIYQ00nCEB7dJRU1e0n9+9HMRyvI63FlDvtFT2AvXVRsXvUt7DNMEToyJLpSA== + dependencies: + workbox-core "^5.1.4" + +workbox-broadcast-update@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-5.1.4.tgz#0eeb89170ddca7f6914fa3523fb14462891f2cfc" + integrity sha512-HTyTWkqXvHRuqY73XrwvXPud/FN6x3ROzkfFPsRjtw/kGZuZkPzfeH531qdUGfhtwjmtO/ZzXcWErqVzJNdXaA== + dependencies: + workbox-core "^5.1.4" + +workbox-build@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-5.1.4.tgz#23d17ed5c32060c363030c8823b39d0eabf4c8c7" + integrity sha512-xUcZn6SYU8usjOlfLb9Y2/f86Gdo+fy1fXgH8tJHjxgpo53VVsqRX0lUDw8/JuyzNmXuo8vXX14pXX2oIm9Bow== + dependencies: + "@babel/core" "^7.8.4" + "@babel/preset-env" "^7.8.4" + "@babel/runtime" "^7.8.4" + "@hapi/joi" "^15.1.0" + "@rollup/plugin-node-resolve" "^7.1.1" + "@rollup/plugin-replace" "^2.3.1" + "@surma/rollup-plugin-off-main-thread" "^1.1.1" + common-tags "^1.8.0" + fast-json-stable-stringify "^2.1.0" + fs-extra "^8.1.0" + glob "^7.1.6" + lodash.template "^4.5.0" + pretty-bytes "^5.3.0" + rollup "^1.31.1" + rollup-plugin-babel "^4.3.3" + rollup-plugin-terser "^5.3.1" + source-map "^0.7.3" + source-map-url "^0.4.0" + stringify-object "^3.3.0" + strip-comments "^1.0.2" + tempy "^0.3.0" + upath "^1.2.0" + workbox-background-sync "^5.1.4" + workbox-broadcast-update "^5.1.4" + workbox-cacheable-response "^5.1.4" + workbox-core "^5.1.4" + workbox-expiration "^5.1.4" + workbox-google-analytics "^5.1.4" + workbox-navigation-preload "^5.1.4" + workbox-precaching "^5.1.4" + workbox-range-requests "^5.1.4" + workbox-routing "^5.1.4" + workbox-strategies "^5.1.4" + workbox-streams "^5.1.4" + workbox-sw "^5.1.4" + workbox-window "^5.1.4" + +workbox-cacheable-response@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-5.1.4.tgz#9ff26e1366214bdd05cf5a43da9305b274078a54" + integrity sha512-0bfvMZs0Of1S5cdswfQK0BXt6ulU5kVD4lwer2CeI+03czHprXR3V4Y8lPTooamn7eHP8Iywi5QjyAMjw0qauA== + dependencies: + workbox-core "^5.1.4" + +workbox-core@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-5.1.4.tgz#8bbfb2362ecdff30e25d123c82c79ac65d9264f4" + integrity sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg== + +workbox-expiration@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-5.1.4.tgz#92b5df461e8126114943a3b15c55e4ecb920b163" + integrity sha512-oDO/5iC65h2Eq7jctAv858W2+CeRW5e0jZBMNRXpzp0ZPvuT6GblUiHnAsC5W5lANs1QS9atVOm4ifrBiYY7AQ== + dependencies: + workbox-core "^5.1.4" + +workbox-google-analytics@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-5.1.4.tgz#b3376806b1ac7d7df8418304d379707195fa8517" + integrity sha512-0IFhKoEVrreHpKgcOoddV+oIaVXBFKXUzJVBI+nb0bxmcwYuZMdteBTp8AEDJacENtc9xbR0wa9RDCnYsCDLjA== + dependencies: + workbox-background-sync "^5.1.4" + workbox-core "^5.1.4" + workbox-routing "^5.1.4" + workbox-strategies "^5.1.4" + +workbox-navigation-preload@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-5.1.4.tgz#30d1b720d26a05efc5fa11503e5cc1ed5a78902a" + integrity sha512-Wf03osvK0wTflAfKXba//QmWC5BIaIZARU03JIhAEO2wSB2BDROWI8Q/zmianf54kdV7e1eLaIEZhth4K4MyfQ== + dependencies: + workbox-core "^5.1.4" + +workbox-precaching@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-5.1.4.tgz#874f7ebdd750dd3e04249efae9a1b3f48285fe6b" + integrity sha512-gCIFrBXmVQLFwvAzuGLCmkUYGVhBb7D1k/IL7pUJUO5xacjLcFUaLnnsoVepBGAiKw34HU1y/YuqvTKim9qAZA== + dependencies: + workbox-core "^5.1.4" + +workbox-range-requests@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-5.1.4.tgz#7066a12c121df65bf76fdf2b0868016aa2bab859" + integrity sha512-1HSujLjgTeoxHrMR2muDW2dKdxqCGMc1KbeyGcmjZZAizJTFwu7CWLDmLv6O1ceWYrhfuLFJO+umYMddk2XMhw== + dependencies: + workbox-core "^5.1.4" + +workbox-routing@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-5.1.4.tgz#3e8cd86bd3b6573488d1a2ce7385e547b547e970" + integrity sha512-8ljknRfqE1vEQtnMtzfksL+UXO822jJlHTIR7+BtJuxQ17+WPZfsHqvk1ynR/v0EHik4x2+826Hkwpgh4GKDCw== + dependencies: + workbox-core "^5.1.4" + +workbox-strategies@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-5.1.4.tgz#96b1418ccdfde5354612914964074d466c52d08c" + integrity sha512-VVS57LpaJTdjW3RgZvPwX0NlhNmscR7OQ9bP+N/34cYMDzXLyA6kqWffP6QKXSkca1OFo/v6v7hW7zrrguo6EA== + dependencies: + workbox-core "^5.1.4" + workbox-routing "^5.1.4" + +workbox-streams@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-5.1.4.tgz#05754e5e3667bdc078df2c9315b3f41210d8cac0" + integrity sha512-xU8yuF1hI/XcVhJUAfbQLa1guQUhdLMPQJkdT0kn6HP5CwiPOGiXnSFq80rAG4b1kJUChQQIGPrq439FQUNVrw== + dependencies: + workbox-core "^5.1.4" + workbox-routing "^5.1.4" + +workbox-sw@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-5.1.4.tgz#2bb34c9f7381f90d84cef644816d45150011d3db" + integrity sha512-9xKnKw95aXwSNc8kk8gki4HU0g0W6KXu+xks7wFuC7h0sembFnTrKtckqZxbSod41TDaGh+gWUA5IRXrL0ECRA== + +workbox-webpack-plugin@5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-5.1.4.tgz#7bfe8c16e40fe9ed8937080ac7ae9c8bde01e79c" + integrity sha512-PZafF4HpugZndqISi3rZ4ZK4A4DxO8rAqt2FwRptgsDx7NF8TVKP86/huHquUsRjMGQllsNdn4FNl8CD/UvKmQ== + dependencies: + "@babel/runtime" "^7.5.5" + fast-json-stable-stringify "^2.0.0" + source-map-url "^0.4.0" + upath "^1.1.2" + webpack-sources "^1.3.0" + workbox-build "^5.1.4" + +workbox-window@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-5.1.4.tgz#2740f7dea7f93b99326179a62f1cc0ca2c93c863" + integrity sha512-vXQtgTeMCUq/4pBWMfQX8Ee7N2wVC4Q7XYFqLnfbXJ2hqew/cU1uMTD2KqGEgEpE4/30luxIxgE+LkIa8glBYw== + dependencies: + workbox-core "^5.1.4" + +worker-farm@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" + integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== + dependencies: + errno "~0.1.7" + +worker-rpc@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5" + integrity sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg== + dependencies: + microevent.ts "~0.1.1" + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +ws@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + +ws@^7.2.3: + version "7.4.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.3.tgz#1f9643de34a543b8edb124bdcbc457ae55a6e5cd" + integrity sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA== + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +xtend@^4.0.0, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" + integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0, yaml@^1.7.2: + version "1.10.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" + integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== + +yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-parser@^20.2.2: + version "20.2.7" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a" + integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw== + +yargs@^13.3.2: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" + +yargs@^15.4.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index 2571529d..a0a2b356 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -17,9 +17,9 @@ const logCodeTypes = { const logCodeManager = { // Agile - '10:00:00': 'Created new AgileInstance. [logCode: 10:00:00]', + '10:00:00': 'Created new AgileInstance.', '10:02:00': - 'Be careful when binding multiple Agile Instances globally in one application! [logCode: 10:02:00]', + 'Be careful when binding multiple Agile Instances globally in one application!', // Storage '11:02:00': From 2b925385ed02f45d0436c6f5c2d621ad9b1ac7f9 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 26 May 2021 08:31:55 +0200 Subject: [PATCH 011/117] fixed collection tests --- packages/core/src/collection/group.ts | 4 +- packages/core/src/collection/index.ts | 28 ++++---- packages/core/src/collection/selector.ts | 64 +++++++++++++------ packages/core/src/logCodeManager.ts | 6 +- .../tests/unit/collection/collection.test.ts | 29 ++++++--- .../tests/unit/collection/selector.test.ts | 12 ++-- 6 files changed, 95 insertions(+), 48 deletions(-) diff --git a/packages/core/src/collection/group.ts b/packages/core/src/collection/group.ts index d1615547..2993f252 100644 --- a/packages/core/src/collection/group.ts +++ b/packages/core/src/collection/group.ts @@ -1,5 +1,4 @@ import { - Agile, State, Collection, DefaultItem, @@ -305,7 +304,8 @@ export class Group extends State< const groupItems: Array> = []; // Don't rebuild Group if Collection is not properly instantiated - // because only after a successful instantiation the Collection contains Items which are essential for a proper rebuild + // (because only after a successful instantiation the Collection + // contains Items which are essential for a proper rebuild) if (!this.collection().isInstantiated) return this; // Create groupItems by finding Item at ItemKey in Collection diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index a2ff099b..fd35f5ee 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -68,13 +68,15 @@ export class Collection { this.isInstantiated = true; - // Reselect Selectors - // Necessary because a selection of an Item didn't work before - // without a 'instantiated' Collection - for (const key in this.selectors) { - const selector = this.selectors[key]; - selector.select(selector.itemKey, { overwrite: true }); - } + // Reselect Selector Items + // Necessary because the selection of an Item + // hasn't worked with a not 'instantiated' Collection + for (const key in this.selectors) this.selectors[key].reselect(); + + // Rebuild of Groups + // Not necessary because if Items are added to the Collection, + // the Groups are rebuilt if they contain these added Items. + // for(const key in this.groups) this.groups[key].rebuild(); } /** @@ -588,10 +590,14 @@ export class Collection { // Create dummy Selector to hold reference if (!selector) { - selector = new Selector(this, 'unknown', { - key: selectorKey, - isPlaceholder: true, - }); + selector = new Selector( + this, + Selector.unknownItemPlaceholderKey, + { + key: selectorKey, + isPlaceholder: true, + } + ); this.selectors[selectorKey] = selector; } diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index d7ff75c0..6397f1e9 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -1,5 +1,4 @@ import { - Agile, Collection, DefaultItem, defineConfig, @@ -12,7 +11,7 @@ import { export class Selector extends State< DataType | undefined > { - static dummyItemKey = 'unknown'; + static unknownItemPlaceholderKey = '__UNKNOWN__ITEM__KEY__'; static rebuildSelectorSideEffectKey = 'rebuildSelector'; static rebuildItemSideEffectKey = 'rebuildItem'; public collection: () => Collection; @@ -39,7 +38,11 @@ export class Selector extends State< this.isPlaceholder = true; // Because hasn't selected any Item yet // Initial Select - this.select(itemKey, { overwrite: true }); + // Only if passed itemKey isn't the 'unknownItemPlaceholderKey' + // which means the Selector represents no Item initially. + // Probably because it should be a placeholder. + if (itemKey !== Selector.unknownItemPlaceholderKey) + this.select(itemKey, { overwrite: true }); } /** @@ -71,11 +74,6 @@ export class Selector extends State< itemKey: ItemKey, config: StateRuntimeJobConfigInterface = {} ): this { - // Don't select Item if Collection is not properly instantiated - // because only after successful instantiation the Collection contains Items which are essential for a proper selection - if (!this.collection().isInstantiated) return this; - - const newItem = this.collection().getItemWithReference(itemKey); config = defineConfig(config, { background: false, sideEffects: { @@ -87,14 +85,22 @@ export class Selector extends State< storage: true, }); - if (this.hasSelected(itemKey) && !config.force) return this; + // Don't select Item if Collection is not properly instantiated + // (because only after a successful instantiation the Collection + // contains the Items which are essential for a proper selection) + if ( + (!this.collection().isInstantiated || this.hasSelected(itemKey)) && + !config.force + ) + return this; // Unselect old Item - // But only if the new itemKey differs from the current itemKey - // because otherwise we would deselect the new Item - // that was never really selected properly - if (itemKey !== this._itemKey) this.unselect({ background: true }); + this.unselect({ background: true }); + + // Get new Item + const newItem = this.collection().getItemWithReference(itemKey); + // Select new Item this._itemKey = itemKey; this.item = newItem; newItem.selectedBy.add(this._key as any); @@ -130,12 +136,33 @@ export class Selector extends State< return this; } + //========================================================================================================= + // Reselect + //========================================================================================================= + /** + * @public + * Reselect Item + * Might help if the Selector failed to properly select an Item. + * You can check with 'hasSelected()' if an Item got properly selected. + * @param config - Config + */ + public reselect(config: StateRuntimeJobConfigInterface = {}): this { + if ( + (this._itemKey != null && !this.hasSelected(this._itemKey)) || + config.force + ) + this.select(this.itemKey, config); + return this; + } + //========================================================================================================= // Unselect //========================================================================================================= /** * @public - * Unselects current selected Item + * Unselects current selected Item. + * Often not necessary because by selecting a new Item, + * the old Item is automatically unselected. * @param config - Config */ public unselect(config: StateRuntimeJobConfigInterface = {}): this { @@ -154,7 +181,7 @@ export class Selector extends State< // Reset and rebuild Selector this.item = undefined; - this._itemKey = Selector.dummyItemKey; + this._itemKey = Selector.unknownItemPlaceholderKey; this.rebuildSelector(config); this.isPlaceholder = true; @@ -166,7 +193,7 @@ export class Selector extends State< // Has Selected //========================================================================================================= /** - * Checks if Selector has correctly selected the passed itemKey + * Checks if Selector has correctly selected the Item at the passed itemKey * @param itemKey - ItemKey */ public hasSelected(itemKey: ItemKey): boolean { @@ -182,12 +209,13 @@ export class Selector extends State< //========================================================================================================= /** * @public - * Rebuilds Selector + * Rebuilds Selector, + * which updates the Selector value based on the Item value * @param config - Config */ public rebuildSelector(config: StateRuntimeJobConfigInterface = {}): this { // Set Selector Value to undefined if Item doesn't exist - if (!this.item || this.item.isPlaceholder) { + if (this.item == null || this.item.isPlaceholder) { this.set(undefined, config); return this; } diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index a0a2b356..dbf01296 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -163,8 +163,10 @@ function getLog>( ): string { let result = logCodeManager[logCode] ?? `'${logCode}' is a unknown logCode!`; - for (const i in replacers) - result = result.split('${' + i + '}').join(replacers[i]); + for (const i in replacers) { + // https://stackoverflow.com/questions/41438656/why-do-i-get-cannot-read-property-tostring-of-undefined + result = result.split('${' + i + '}').join(replacers[i] + ''); + } return result; } diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index 7b5dddaf..b5613d22 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -30,7 +30,6 @@ describe('Collection Tests', () => { jest.spyOn(Collection.prototype, 'initSelectors'); jest.spyOn(Collection.prototype, 'initGroups'); jest.spyOn(Collection.prototype, 'collect'); - jest.spyOn(Selector.prototype, 'select'); }); it('should create Collection (default config)', () => { @@ -59,8 +58,6 @@ describe('Collection Tests', () => { expect(Collection.prototype.initGroups).toHaveBeenCalledWith({}); expect(Collection.prototype.initSelectors).toHaveBeenCalledWith({}); expect(Collection.prototype.collect).not.toHaveBeenCalled(); - - expect(Selector.prototype.select).toHaveBeenCalledTimes(0); }); it('should create Collection (specific config)', () => { @@ -107,8 +104,6 @@ describe('Collection Tests', () => { expect(Collection.prototype.collect).toHaveBeenCalledWith([ { id: '1', name: 'jeff' }, ]); - - expect(Selector.prototype.select).toHaveBeenCalledTimes(0); }); it('should create Collection (specific config in function form)', () => { @@ -160,11 +155,27 @@ describe('Collection Tests', () => { expect(Collection.prototype.collect).toHaveBeenCalledWith([ { id: '1', name: 'jeff' }, ]); + }); - expect(Selector.prototype.select).toHaveBeenCalledTimes(1); - expect(Selector.prototype.select).toHaveBeenCalledWith('id1', { - overwrite: true, - }); + it('should call reselect on Selectors but not rebuild on Groups after the Collection instantiation (specific config in function form)', () => { + jest + .spyOn(Selector.prototype, 'reselect') + .mockReturnValueOnce(undefined as any); + jest + .spyOn(Group.prototype, 'rebuild') + .mockReturnValueOnce(undefined as any); + + new Collection(dummyAgile, (collection) => ({ + groups: { + group1: collection.Group(), + }, + selectors: { + selector1: collection.Selector('id1'), + }, + })); + + expect(Selector.prototype.reselect).toHaveBeenCalledTimes(1); + expect(Group.prototype.rebuild).toHaveBeenCalledTimes(2); // +1 in the creation of 'group1' Group and +1 in the creation of default Group }); describe('Collection Function Tests', () => { diff --git a/packages/core/tests/unit/collection/selector.test.ts b/packages/core/tests/unit/collection/selector.test.ts index 13f18143..2045096a 100644 --- a/packages/core/tests/unit/collection/selector.test.ts +++ b/packages/core/tests/unit/collection/selector.test.ts @@ -30,7 +30,7 @@ describe('Selector Tests', () => { expect(selector.collection()).toBe(dummyCollection); expect(selector.item).toBeUndefined(); - expect(selector._itemKey).toBe(Selector.dummyItemKey); + expect(selector._itemKey).toBe(Selector.unknownItemPlaceholderKey); expect(selector.select).toHaveBeenCalledWith('dummyItemKey', { overwrite: true, }); @@ -66,7 +66,7 @@ describe('Selector Tests', () => { expect(selector.collection()).toBe(dummyCollection); expect(selector.item).toBeUndefined(); - expect(selector._itemKey).toBe(Selector.dummyItemKey); + expect(selector._itemKey).toBe(Selector.unknownItemPlaceholderKey); expect(selector.select).toHaveBeenCalledWith('dummyItemKey', { overwrite: true, }); @@ -102,7 +102,7 @@ describe('Selector Tests', () => { expect(selector.collection()).toBe(dummyCollection); expect(selector.item).toBeUndefined(); - expect(selector._itemKey).toBe(Selector.dummyItemKey); + expect(selector._itemKey).toBe(Selector.unknownItemPlaceholderKey); expect(selector.select).not.toHaveBeenCalled(); expect(selector._key).toBeUndefined(); @@ -487,7 +487,7 @@ describe('Selector Tests', () => { ); expect(selector.item).toBeUndefined(); - expect(selector._itemKey).toBe(Selector.dummyItemKey); + expect(selector._itemKey).toBe(Selector.unknownItemPlaceholderKey); expect(selector.isPlaceholder).toBeTruthy(); expect(selector.rebuildSelector).toHaveBeenCalledWith({}); @@ -507,7 +507,7 @@ describe('Selector Tests', () => { ); expect(selector.item).toBeUndefined(); - expect(selector._itemKey).toBe(Selector.dummyItemKey); + expect(selector._itemKey).toBe(Selector.unknownItemPlaceholderKey); expect(selector.isPlaceholder).toBeTruthy(); expect(selector.rebuildSelector).toHaveBeenCalledWith({ background: true, @@ -529,7 +529,7 @@ describe('Selector Tests', () => { ); expect(selector.item).toBeUndefined(); - expect(selector._itemKey).toBe(Selector.dummyItemKey); + expect(selector._itemKey).toBe(Selector.unknownItemPlaceholderKey); expect(selector.isPlaceholder).toBeTruthy(); expect(selector.rebuildSelector).toHaveBeenCalledWith({}); From 05a78f5b81805c70aaf94e18a0b03b38e7bb7bdf Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 26 May 2021 15:42:38 +0200 Subject: [PATCH 012/117] fixed typos --- packages/core/src/collection/group.ts | 1 - packages/core/src/collection/index.ts | 42 ++++++------- packages/core/src/collection/item.ts | 2 +- packages/core/src/logCodeManager.ts | 60 +++++++++++++------ packages/core/tests/helper/logMock.ts | 4 +- .../tests/unit/collection/collection.test.ts | 20 +++---- .../core/tests/unit/collection/group.test.ts | 21 +++++-- .../core/tests/unit/computed/computed.test.ts | 3 +- .../core/tests/unit/runtime/runtime.test.ts | 12 ++-- packages/core/tests/unit/state/state.test.ts | 10 +++- .../tests/unit/storages/persistent.test.ts | 2 +- 11 files changed, 109 insertions(+), 68 deletions(-) diff --git a/packages/core/src/collection/group.ts b/packages/core/src/collection/group.ts index 2993f252..98fa4ccc 100644 --- a/packages/core/src/collection/group.ts +++ b/packages/core/src/collection/group.ts @@ -359,7 +359,6 @@ export interface GroupRemoveConfigInterface { /** * @param key - Key/Name of Group * @param isPlaceholder - If Group is initially a Placeholder - * @param initialRebuild - If the Group is rebuilt shortly after the instantiation */ export interface GroupConfigInterface { key?: GroupKey; diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index fd35f5ee..6a900999 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -75,8 +75,8 @@ export class Collection { // Rebuild of Groups // Not necessary because if Items are added to the Collection, - // the Groups are rebuilt if they contain these added Items. - // for(const key in this.groups) this.groups[key].rebuild(); + // the Groups which contain these added Items get rebuilt. + // for (const key in this.groups) this.groups[key].rebuild(); } /** @@ -253,7 +253,9 @@ export class Collection { if (!_groupKeys.includes(defaultGroupKey)) _groupKeys.push(defaultGroupKey); // Create not existing Groups - _groupKeys.forEach((key) => !this.groups[key] && this.createGroup(key)); + _groupKeys.forEach( + (key) => this.groups[key] == null && this.createGroup(key) + ); _data.forEach((data, index) => { const itemKey = data[primaryKey]; @@ -374,7 +376,7 @@ export class Collection { if (!this.isInstantiated) LogCodeManager.log('1B:02:03'); // Check if Group already exists - if (group) { + if (group != null) { if (!group.isPlaceholder) { LogCodeManager.log('1B:03:02', [groupKey]); return group; @@ -427,7 +429,7 @@ export class Collection { const group = groupKey ? this.groups[groupKey] : undefined; // Check if Group exists - if (!group || (!config.notExisting && group.isPlaceholder)) + if (group == null || (!config.notExisting && group.isPlaceholder)) return undefined; ComputedTracker.tracked(group.observer); @@ -457,7 +459,7 @@ export class Collection { let group = this.getGroup(groupKey, { notExisting: true }); // Create dummy Group to hold reference - if (!group) { + if (group == null) { group = new Group(this, [], { key: groupKey, isPlaceholder: true, @@ -478,7 +480,7 @@ export class Collection { * @param groupKey - Name/Key of Group */ public removeGroup(groupKey: GroupKey): this { - if (!this.groups[groupKey]) return this; + if (this.groups[groupKey] == null) return this; delete this.groups[groupKey]; return this; } @@ -501,7 +503,7 @@ export class Collection { if (!this.isInstantiated) LogCodeManager.log('1B:02:04'); // Check if Selector already exists - if (selector) { + if (selector != null) { if (!selector.isPlaceholder) { LogCodeManager.log('1B:03:03', [selectorKey]); return selector; @@ -568,7 +570,7 @@ export class Collection { const selector = selectorKey ? this.selectors[selectorKey] : undefined; // Check if Selector exists - if (!selector || (!config.notExisting && selector.isPlaceholder)) + if (selector == null || (!config.notExisting && selector.isPlaceholder)) return undefined; ComputedTracker.tracked(selector.observer); @@ -589,7 +591,7 @@ export class Collection { let selector = this.getSelector(selectorKey, { notExisting: true }); // Create dummy Selector to hold reference - if (!selector) { + if (selector == null) { selector = new Selector( this, Selector.unknownItemPlaceholderKey, @@ -614,8 +616,8 @@ export class Collection { * @param selectorKey - Name/Key of Selector */ public removeSelector(selectorKey: SelectorKey): this { - if (!this.selectors[selectorKey]) return this; - this.selectors[selectorKey]?.unselect(); // Unselects current selected Item + if (this.selectors[selectorKey] == null) return this; + this.selectors[selectorKey].unselect(); // Unselects current selected Item delete this.selectors[selectorKey]; return this; } @@ -657,7 +659,7 @@ export class Collection { const item = itemKey != null ? this.data[itemKey] : undefined; // Check if Item exists - if (!item || (!config.notExisting && !item.exists)) return undefined; + if (item == null || (!config.notExisting && !item.exists)) return undefined; ComputedTracker.tracked(item.observer); return item; @@ -672,7 +674,7 @@ export class Collection { let item = this.getItem(itemKey, { notExisting: true }); // Create dummy Item to hold reference - if (!item) { + if (item == null) { item = new Item( this, { @@ -704,7 +706,7 @@ export class Collection { config: HasConfigInterface = {} ): DataType | undefined { const item = this.getItem(itemKey, config); - if (!item) return undefined; + if (item == null) return undefined; return item.value; } @@ -951,7 +953,7 @@ export class Collection { background: false, }); - if (!item || oldItemKey === newItemKey) return false; + if (item == null || oldItemKey === newItemKey) return false; // Check if Item with newItemKey already exists if (this.hasItem(newItemKey)) { @@ -976,7 +978,7 @@ export class Collection { // Update ItemKey in Groups for (const groupKey in this.groups) { const group = this.getGroup(groupKey, { notExisting: true }); - if (!group || !group.has(oldItemKey)) continue; + if (!group?.has(oldItemKey)) continue; group.replace(oldItemKey, newItemKey, { background: config.background }); } @@ -1063,7 +1065,7 @@ export class Collection { // Remove ItemKey from Groups _groupKeys.forEach((groupKey) => { const group = this.getGroup(groupKey, { notExisting: true }); - if (!group || !group.has(itemKey)) return; + if (!group?.has(itemKey)) return; group.remove(itemKey); removedFromGroupsCount++; }); @@ -1092,7 +1094,7 @@ export class Collection { _itemKeys.forEach((itemKey) => { const item = this.getItem(itemKey, { notExisting: true }); - if (!item) return; + if (item == null) return; // Remove Item from Groups for (const groupKey in this.groups) { @@ -1149,7 +1151,7 @@ export class Collection { const itemKey = _data[primaryKey]; let item = this.getItem(itemKey, { notExisting: true }); const wasPlaceholder = item?.isPlaceholder || false; - const createItem = !item; + const createItem = item == null; // Create or update Item if (!createItem && config.patch) diff --git a/packages/core/src/collection/item.ts b/packages/core/src/collection/item.ts index 2d9ffd21..35843c30 100644 --- a/packages/core/src/collection/item.ts +++ b/packages/core/src/collection/item.ts @@ -12,7 +12,7 @@ export class Item extends State< DataType > { static updateGroupSideEffectKey = 'rebuildGroup'; - public selectedBy: Set = new Set(); // Keys of Selectors whcih selected this Item + public selectedBy: Set = new Set(); // Keys of Selectors that have selected this Item public collection: () => Collection; /** diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index dbf01296..1f098fbd 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -1,21 +1,21 @@ import { Agile } from './agile'; // 00:00:00 -// first digits is based on the AgileClass +// |00|:00:00 first digits are based on the Agile Class // 00 = General // 10 = Agile // 11 = Storage // .. -// second digits is based on the log type +// 00:|00|:00 second digits are based on the Log Type const logCodeTypes = { '00': 'success', '01': 'info', '02': 'warn', '03': 'error', }; -// third digits is based on the log message +// 00:00:|00| third digits are based on the Log Message (ascending counted) -const logCodeManager = { +const logCodeMessages = { // Agile '10:00:00': 'Created new AgileInstance.', '10:02:00': @@ -27,7 +27,6 @@ const logCodeManager = { "To use the '.persist()' functionality, please provide a custom Storage!", '11:02:01': 'The first allocated Storage for AgileTs must be set as the default Storage!', - '11:03:00': "Storage with the key/name '${0}' already exists!", '11:03:01': "Couldn't find Storage '${0}'. " + @@ -78,7 +77,7 @@ const logCodeManager = { '16:02:00': "SubscriptionContainer/Component '${0}' isn't ready to rerender!", '16:02:01': 'Job with not ready SubscriptionContainer/Component was removed from the runtime ' + - 'after ${0} tries to avoid overflow.', + 'after ${0} tries to avoid a Job overflow.', // Observer '17:03:00': @@ -115,7 +114,7 @@ const logCodeManager = { "instead of 'Selector()' outside the Collection configuration object.", '1B:02:02': 'By overwriting the whole Item ' + - 'you have to pass the correct itemKey into the changes object!', + "you have to pass the correct itemKey into the 'changes object!'", '1B:02:03': "We recommend using 'Group()' instead of 'createGroup()' " + 'inside the Collection configuration object.', @@ -152,16 +151,21 @@ const logCodeManager = { '00:03:01': "'${0}' has to be of the type ${1}!", }; -export type LogCodesArrayType = { - [K in keyof T]: T[K] extends string ? K : never; -}[keyof T] & - string; - -function getLog>( +//========================================================================================================= +// Get Log +//========================================================================================================= +/** + * @internal + * Returns the log message according to the passed logCode + * @param logCode - Log Code of Message + * @param replacers - Instances that replace these '${x}' placeholders based on the index + * For example: replacers[0] replaces '${0}', replacers[1] replaces '${1}', ... + */ +function getLog>( logCode: T, replacers: any[] = [] ): string { - let result = logCodeManager[logCode] ?? `'${logCode}' is a unknown logCode!`; + let result = logCodeMessages[logCode] ?? `'${logCode}' is a unknown logCode!`; for (const i in replacers) { // https://stackoverflow.com/questions/41438656/why-do-i-get-cannot-read-property-tostring-of-undefined @@ -171,19 +175,39 @@ function getLog>( return result; } -function log>( +//========================================================================================================= +// Log +//========================================================================================================= +/** + * @internal + * Logs message at the provided logCode with the Agile.logger + * @param logCode - Log Code of Message + * @param replacers - Instances that replace these '${x}' placeholders based on the index + * For example: replacers[0] replaces '${0}', replacers[1] replaces '${1}', .. + * @param data - Data attached to the end of the log message + */ +function log>( logCode: T, replacers: any[] = [], ...data: any[] ): void { const codes = logCode.split(':'); - if (codes.length !== 3) return; - Agile.logger[logCodeTypes[codes[1]]](getLog(logCode, replacers), ...data); + if (codes.length === 3) + Agile.logger[logCodeTypes[codes[1]]](getLog(logCode, replacers), ...data); } +/** + * @internal + * Manages logCode based logging of AgileTs + */ export const LogCodeManager = { getLog, log, logCodeLogTypes: logCodeTypes, - logCodes: logCodeManager, + logCodeMessages: logCodeMessages, }; + +export type LogCodesArrayType = { + [K in keyof T]: T[K] extends string ? K : never; +}[keyof T] & + string; diff --git a/packages/core/tests/helper/logMock.ts b/packages/core/tests/helper/logMock.ts index 3594e031..49c4c4dc 100644 --- a/packages/core/tests/helper/logMock.ts +++ b/packages/core/tests/helper/logMock.ts @@ -50,7 +50,7 @@ function getLogArguments(type: LoggerTypes, ...data: any[]): any[] { } function hasLoggedCode< - T extends LogCodesArrayType + T extends LogCodesArrayType >(logCode: T, replacers: any[] = [], ...data: any[]): void { const codes = logCode.split(':'); if (codes.length === 3) @@ -62,7 +62,7 @@ function hasLoggedCode< } function hasNotLoggedCode< - T extends LogCodesArrayType + T extends LogCodesArrayType >(logCode: T, replacers: any[] = [], ...data: any[]) { const codes = logCode.split(':'); if (codes.length === 3) diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index b5613d22..e9233dbc 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -267,9 +267,6 @@ describe('Collection Tests', () => { LogMock.hasLoggedCode('1B:02:00'); expect(response).toBeInstanceOf(Group); - expect(response._key).toBe('group1Key'); - expect(response._value).toStrictEqual([1, 2]); - expect(response.collection()).toBe(collection); }); it('should create Group with no key which belongs to Collection after instantiation and print warning', () => { @@ -278,12 +275,10 @@ describe('Collection Tests', () => { collection.isInstantiated = true; const response = collection.Group([1, 2]); + expect(collection.createGroup).toHaveBeenCalledWith('randomId', [1, 2]); LogMock.hasLoggedCode('1B:02:00'); expect(response).toBeInstanceOf(Group); - expect(response._key).toBe('randomId'); - expect(response._value).toStrictEqual([1, 2]); - expect(response.collection()).toBe(collection); }); }); @@ -320,9 +315,6 @@ describe('Collection Tests', () => { LogMock.hasLoggedCode('1B:02:01'); expect(response).toBeInstanceOf(Selector); - expect(response._key).toBe('selectorKey1'); - expect(response._itemKey).toStrictEqual(1); - expect(response.collection()).toBe(collection); }); it('should create Selector with no key which belongs to Collection after instantiation and print warning', () => { @@ -331,12 +323,10 @@ describe('Collection Tests', () => { collection.isInstantiated = true; const response = collection.Selector(1); + expect(collection.createSelector).toHaveBeenCalledWith('randomId', 1); LogMock.hasLoggedCode('1B:02:01'); expect(response).toBeInstanceOf(Selector); - expect(response._key).toBe('randomId'); - expect(response._itemKey).toStrictEqual(1); - expect(response.collection()).toBe(collection); }); }); @@ -1098,7 +1088,7 @@ describe('Collection Tests', () => { it('should remove existing Group', () => { collection.removeGroup('dummyGroup'); - expect(collection.groups['dummyGroup']).toBeUndefined(); + expect(collection.groups).not.toHaveProperty('dummyGroup'); }); it("shouldn't remove not existing Group and print warning", () => { @@ -1665,6 +1655,7 @@ describe('Collection Tests', () => { expect(collection.persistent.onLoad).toBe(dummyCallbackFunction); expect(dummyCallbackFunction).not.toHaveBeenCalled(); + LogMock.hasNotLogged('warn'); }); it('should set onLoad function if Collection is persisted and should call it initially (collection.isPersisted = true)', () => { @@ -1675,6 +1666,7 @@ describe('Collection Tests', () => { expect(collection.persistent.onLoad).toBe(dummyCallbackFunction); expect(dummyCallbackFunction).toHaveBeenCalledWith(true); + LogMock.hasNotLogged('warn'); }); it("shouldn't set onLoad function if Collection isn't persisted", () => { @@ -1682,6 +1674,7 @@ describe('Collection Tests', () => { expect(collection?.persistent?.onLoad).toBeUndefined(); expect(dummyCallbackFunction).not.toHaveBeenCalled(); + LogMock.hasNotLogged('warn'); }); it("shouldn't set invalid onLoad callback function", () => { @@ -1690,6 +1683,7 @@ describe('Collection Tests', () => { collection.onLoad(10 as any); + expect(collection?.persistent?.onLoad).toBeUndefined(); LogMock.hasLoggedCode('00:03:01', ['OnLoad Callback', 'function']); }); }); diff --git a/packages/core/tests/unit/collection/group.test.ts b/packages/core/tests/unit/collection/group.test.ts index 6641b485..01ef3502 100644 --- a/packages/core/tests/unit/collection/group.test.ts +++ b/packages/core/tests/unit/collection/group.test.ts @@ -507,17 +507,28 @@ describe('Group Tests', () => { it('should build Group output and items and set notFoundItemKeys to not found Item Keys', () => { group.rebuild(); - LogMock.hasLoggedCode( - '1C:02:00', - [dummyCollection._key, group._key], - ['dummyItem3Key'] - ); expect(group.notFoundItemKeys).toStrictEqual(['dummyItem3Key']); expect(group.items).toStrictEqual([dummyItem1, dummyItem2]); expect(group._output).toStrictEqual([ dummyItem1._value, dummyItem2._value, ]); + LogMock.hasLoggedCode( + '1C:02:00', + [dummyCollection._key, group._key], + ['dummyItem3Key'] + ); + }); + + it("shouldn't build Group output and items if Collection is not properly instantiated", () => { + dummyCollection.isInstantiated = false; + + group.rebuild(); + + expect(group.notFoundItemKeys).toStrictEqual([]); + expect(group.items).toStrictEqual([]); + expect(group._output).toStrictEqual([]); + LogMock.hasNotLogged('warn'); }); }); }); diff --git a/packages/core/tests/unit/computed/computed.test.ts b/packages/core/tests/unit/computed/computed.test.ts index 7bb94727..9f645220 100644 --- a/packages/core/tests/unit/computed/computed.test.ts +++ b/packages/core/tests/unit/computed/computed.test.ts @@ -6,7 +6,6 @@ import { State, ComputedTracker, } from '../../../src'; -import mockConsole from 'jest-mock-console'; import { LogMock } from '../../helper/logMock'; describe('Computed Tests', () => { @@ -14,7 +13,7 @@ describe('Computed Tests', () => { beforeEach(() => { jest.clearAllMocks(); - mockConsole(['error', 'warn']); + LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts index 7d21fa70..95781f72 100644 --- a/packages/core/tests/unit/runtime/runtime.test.ts +++ b/packages/core/tests/unit/runtime/runtime.test.ts @@ -421,12 +421,12 @@ describe('Runtime Tests', () => { LogMock.hasLoggedCode( '16:02:00', - ['nrCallbackSubContainerKey'], + [nrCallbackSubContainer.key], nrCallbackSubContainer ); LogMock.hasLoggedCode( '16:02:00', - ['nrComponentSubContainerKey'], + [nrComponentSubContainer.key], nrComponentSubContainer ); @@ -474,7 +474,11 @@ describe('Runtime Tests', () => { expect(dummyObserver1.subscribedTo.size).toBe(1); expect(rCallbackSubJob.triesToUpdate).toBe(2); - LogMock.hasLoggedCode('16:02:01', [2], rCallbackSubContainer); + LogMock.hasLoggedCode( + '16:02:01', + [rCallbackSubJob.config.numberOfTriesToUpdate], + rCallbackSubContainer + ); expect(response).toBeFalsy(); } @@ -510,7 +514,7 @@ describe('Runtime Tests', () => { LogMock.hasLoggedCode( '16:02:00', - ['rCallbackSubContainerKey'], + [rCallbackSubContainer.key], rCallbackSubContainer ); diff --git a/packages/core/tests/unit/state/state.test.ts b/packages/core/tests/unit/state/state.test.ts index 84ba2e07..66793053 100644 --- a/packages/core/tests/unit/state/state.test.ts +++ b/packages/core/tests/unit/state/state.test.ts @@ -666,6 +666,7 @@ describe('State Tests', () => { expect(numberState.persistent.onLoad).toBe(dummyCallbackFunction); expect(dummyCallbackFunction).not.toHaveBeenCalled(); + LogMock.hasNotLogged('warn'); }); it('should set onLoad function if State is persisted and should call it initially (state.isPersisted = true)', () => { @@ -676,13 +677,15 @@ describe('State Tests', () => { expect(numberState.persistent.onLoad).toBe(dummyCallbackFunction); expect(dummyCallbackFunction).toHaveBeenCalledWith(true); + LogMock.hasNotLogged('warn'); }); it("shouldn't set onLoad function if State isn't persisted", () => { numberState.onLoad(dummyCallbackFunction); - expect(numberState?.persistent?.onLoad).toBe(undefined); + expect(numberState?.persistent?.onLoad).toBeUndefined(); expect(dummyCallbackFunction).not.toHaveBeenCalled(); + LogMock.hasNotLogged('warn'); }); it("shouldn't set invalid onLoad callback function", () => { @@ -691,6 +694,7 @@ describe('State Tests', () => { numberState.onLoad(10 as any); + expect(numberState?.persistent?.onLoad).toBeUndefined(); LogMock.hasLoggedCode('00:03:01', ['OnLoad Callback', 'function']); }); }); @@ -853,6 +857,7 @@ describe('State Tests', () => { numberState.computeExists(computeMethod); expect(numberState.computeExistsMethod).toBe(computeMethod); + LogMock.hasNotLogged('warn'); }); it("shouldn't assign passed invalid function to computeExistsMethod", () => { @@ -938,6 +943,7 @@ describe('State Tests', () => { expect(numberState.set).toHaveBeenCalledWith(10); expect(numberState.computeValueMethod).toBe(computeMethod); + LogMock.hasNotLogged('warn'); }); it("shouldn't assign passed invalid function to computeValueMethod", () => { @@ -962,6 +968,7 @@ describe('State Tests', () => { callback: sideEffectFunction, weight: 10, }); + LogMock.hasNotLogged('warn'); }); it('should add passed callback function to sideEffects at passed key (specific config)', () => { @@ -974,6 +981,7 @@ describe('State Tests', () => { callback: sideEffectFunction, weight: 999, }); + LogMock.hasNotLogged('warn'); }); it("shouldn't add passed invalid function to sideEffects at passed key (default config)", () => { diff --git a/packages/core/tests/unit/storages/persistent.test.ts b/packages/core/tests/unit/storages/persistent.test.ts index c5bcc889..5377a12e 100644 --- a/packages/core/tests/unit/storages/persistent.test.ts +++ b/packages/core/tests/unit/storages/persistent.test.ts @@ -247,7 +247,7 @@ describe('Persistent Tests', () => { }); it( - 'should assign passed StorageKeys, set passed as defaultStorageKey as default StorageKey' + + 'should assign passed StorageKeys, set passed defaultStorageKey as default StorageKey' + 'and push defaultStorageKey into storageKeys', () => { persistent.assignStorageKeys(['test1', 'test2', 'test3'], 'test4'); From a5efe3a3e2925da692e6aa2321ccd0e5f3f6e744 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 26 May 2021 17:56:39 +0200 Subject: [PATCH 013/117] fixed selector and item tests --- packages/core/src/collection/selector.ts | 15 ++- .../core/tests/unit/collection/item.test.ts | 8 +- .../tests/unit/collection/selector.test.ts | 127 +++++++++++++----- 3 files changed, 107 insertions(+), 43 deletions(-) diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index 6397f1e9..ef1f5e5a 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -30,19 +30,20 @@ export class Selector extends State< itemKey: ItemKey, config: SelectorConfigInterface = {} ) { + config = defineConfig(config, { + isPlaceholder: false, + }); super(collection.agileInstance(), undefined, config); this.collection = () => collection; this.item = undefined; - this._itemKey = itemKey; + this._itemKey = !config.isPlaceholder + ? itemKey + : Selector.unknownItemPlaceholderKey; this._key = config?.key; this.isPlaceholder = true; // Because hasn't selected any Item yet // Initial Select - // Only if passed itemKey isn't the 'unknownItemPlaceholderKey' - // which means the Selector represents no Item initially. - // Probably because it should be a placeholder. - if (itemKey !== Selector.unknownItemPlaceholderKey) - this.select(itemKey, { overwrite: true }); + if (!config.isPlaceholder) this.select(itemKey, { overwrite: true }); } /** @@ -81,7 +82,7 @@ export class Selector extends State< exclude: [], }, force: false, - overwrite: this.item?.isPlaceholder || false, + overwrite: this.item?.isPlaceholder ?? false, storage: true, }); diff --git a/packages/core/tests/unit/collection/item.test.ts b/packages/core/tests/unit/collection/item.test.ts index b801fd82..32f61557 100644 --- a/packages/core/tests/unit/collection/item.test.ts +++ b/packages/core/tests/unit/collection/item.test.ts @@ -1,5 +1,5 @@ import { Item, Collection, Agile, StateObserver, State } from '../../../src'; -import mockConsole from 'jest-mock-console'; +import { LogMock } from '../../helper/logMock'; describe('Item Tests', () => { let dummyAgile: Agile; @@ -7,7 +7,7 @@ describe('Item Tests', () => { beforeEach(() => { jest.clearAllMocks(); - mockConsole(['error', 'warn']); + LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); dummyCollection = new Collection(dummyAgile); @@ -48,7 +48,7 @@ describe('Item Tests', () => { expect(item.isPersisted).toBeFalsy(); expect(item.persistent).toBeUndefined(); expect(item.watchers).toStrictEqual({}); - expect(item.isSelected).toBeFalsy(); + expect(item.selectedBy.size).toBe(0); }); it('should create Item (specific config)', () => { @@ -86,7 +86,7 @@ describe('Item Tests', () => { expect(item.isPersisted).toBeFalsy(); expect(item.persistent).toBeUndefined(); expect(item.watchers).toStrictEqual({}); - expect(item.isSelected).toBeFalsy(); + expect(item.selectedBy.size).toBe(0); }); describe('Item Function Tests', () => { diff --git a/packages/core/tests/unit/collection/selector.test.ts b/packages/core/tests/unit/collection/selector.test.ts index 2045096a..2da7af02 100644 --- a/packages/core/tests/unit/collection/selector.test.ts +++ b/packages/core/tests/unit/collection/selector.test.ts @@ -1,5 +1,5 @@ import { Selector, Agile, Collection, StateObserver, Item } from '../../../src'; -import mockConsole from 'jest-mock-console'; +import { LogMock } from '../../helper/logMock'; describe('Selector Tests', () => { interface ItemInterface { @@ -12,7 +12,7 @@ describe('Selector Tests', () => { beforeEach(() => { jest.clearAllMocks(); - mockConsole(['error', 'warn']); + LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); dummyCollection = new Collection(dummyAgile); @@ -30,7 +30,7 @@ describe('Selector Tests', () => { expect(selector.collection()).toBe(dummyCollection); expect(selector.item).toBeUndefined(); - expect(selector._itemKey).toBe(Selector.unknownItemPlaceholderKey); + expect(selector._itemKey).toBe('dummyItemKey'); expect(selector.select).toHaveBeenCalledWith('dummyItemKey', { overwrite: true, }); @@ -66,7 +66,7 @@ describe('Selector Tests', () => { expect(selector.collection()).toBe(dummyCollection); expect(selector.item).toBeUndefined(); - expect(selector._itemKey).toBe(Selector.unknownItemPlaceholderKey); + expect(selector._itemKey).toBe('dummyItemKey'); expect(selector.select).toHaveBeenCalledWith('dummyItemKey', { overwrite: true, }); @@ -212,7 +212,8 @@ describe('Selector Tests', () => { expect.any(Function), { weight: 100 } ); - expect(dummyItem2.isSelected).toBeTruthy(); + expect(dummyItem2.selectedBy.size).toBe(1); + expect(dummyItem2.selectedBy.has(selector._key as any)); }); it('should unselect old selected Item and select new Item (specific config)', () => { @@ -258,21 +259,16 @@ describe('Selector Tests', () => { expect.any(Function), { weight: 100 } ); - expect(dummyItem2.isSelected).toBeTruthy(); + expect(dummyItem2.selectedBy.size).toBe(1); + expect(dummyItem2.selectedBy.has(selector._key as any)); }); - it('should print warning if trying to select selected Item again (default config)', () => { + it("shouldn't select selected Item again (default config)", () => { dummyCollection.getItemWithReference = jest.fn(() => dummyItem1); selector.select('dummyItem1'); - expect(console.warn).toHaveBeenCalledWith( - "Agile Warn: Selector has already selected 'dummyItem1'!" - ); - - expect(dummyCollection.getItemWithReference).toHaveBeenCalledWith( - 'dummyItem1' - ); + expect(dummyCollection.getItemWithReference).not.toHaveBeenCalled(); expect(selector._itemKey).toBe('dummyItem1'); expect(selector.item).toBe(dummyItem1); expect(selector.unselect).not.toHaveBeenCalled(); @@ -280,10 +276,11 @@ describe('Selector Tests', () => { expect(selector.addSideEffect).not.toHaveBeenCalled(); expect(dummyItem1.addSideEffect).not.toHaveBeenCalled(); - expect(dummyItem1.isSelected).toBeTruthy(); + expect(dummyItem1.selectedBy.size).toBe(1); + expect(dummyItem1.selectedBy.has(selector._key as any)); }); - it('should be able to select selected Item again (config.force = true)', () => { + it('should select selected Item again (config.force = true)', () => { dummyCollection.getItemWithReference = jest.fn(() => dummyItem1); selector.select('dummyItem1', { force: true }); @@ -322,7 +319,69 @@ describe('Selector Tests', () => { expect.any(Function), { weight: 100 } ); - expect(dummyItem1.isSelected).toBeTruthy(); + expect(dummyItem1.selectedBy.size).toBe(1); + expect(dummyItem1.selectedBy.has(selector._key as any)); + }); + + it("shouldn't select Item if Collection isn't instantiated (default config)", () => { + dummyCollection.getItemWithReference = jest.fn(() => dummyItem2); + dummyCollection.isInstantiated = false; + + selector.select('dummyItem2'); + + expect(dummyCollection.getItemWithReference).not.toHaveBeenCalled(); + expect(selector._itemKey).toBe('dummyItem1'); + expect(selector.item).toBe(dummyItem1); + expect(selector.unselect).not.toHaveBeenCalled(); + expect(selector.rebuildSelector).not.toHaveBeenCalled(); + expect(selector.addSideEffect).not.toHaveBeenCalled(); + + expect(dummyItem2.addSideEffect).not.toHaveBeenCalled(); + expect(dummyItem2.selectedBy.size).toBe(0); + }); + + it("should unselect old selected Item and select new Item although Collection isn't instantiated (config.force = true)", () => { + dummyCollection.getItemWithReference = jest.fn(() => dummyItem2); + dummyCollection.isInstantiated = false; + + selector.select('dummyItem2', { + force: true, + }); + + expect(dummyCollection.getItemWithReference).toHaveBeenCalledWith( + 'dummyItem2' + ); + + expect(selector._itemKey).toBe('dummyItem2'); + expect(selector.item).toBe(dummyItem2); + expect(selector.unselect).toHaveBeenCalledWith({ background: true }); + expect(selector.rebuildSelector).toHaveBeenCalledWith({ + background: false, + sideEffects: { + enabled: true, + exclude: [], + }, + force: true, + overwrite: false, + storage: true, + }); + expect( + selector.addSideEffect + ).toHaveBeenCalledWith( + Selector.rebuildItemSideEffectKey, + expect.any(Function), + { weight: 90 } + ); + + expect( + dummyItem2.addSideEffect + ).toHaveBeenCalledWith( + Selector.rebuildSelectorSideEffectKey, + expect.any(Function), + { weight: 100 } + ); + expect(dummyItem2.selectedBy.size).toBe(1); + expect(dummyItem2.selectedBy.has(selector._key as any)); }); it('should remove old selected Item, select new Item and overwrite Selector if old Item is placeholder (default config)', async () => { @@ -362,7 +421,8 @@ describe('Selector Tests', () => { expect.any(Function), { weight: 100 } ); - expect(dummyItem2.isSelected).toBeTruthy(); + expect(dummyItem2.selectedBy.size).toBe(1); + expect(dummyItem2.selectedBy.has(selector._key as any)); }); it("should remove old selected Item, select new Item and shouldn't overwrite Selector if old Item is placeholder (config.overwrite = false)", async () => { @@ -402,7 +462,8 @@ describe('Selector Tests', () => { expect.any(Function), { weight: 100 } ); - expect(dummyItem2.isSelected).toBeTruthy(); + expect(dummyItem2.selectedBy.size).toBe(1); + expect(dummyItem2.selectedBy.has(selector._key as any)); }); describe('test added sideEffect called Selector.rebuildSelectorSideEffectKey', () => { @@ -472,6 +533,10 @@ describe('Selector Tests', () => { }); }); + describe('reselect function tests', () => { + // TODO + }); + describe('unselect function tests', () => { beforeEach(() => { selector.rebuildSelector = jest.fn(); @@ -481,7 +546,7 @@ describe('Selector Tests', () => { it("should unselect current selected Item and shouldn't remove it from Collection (default config)", () => { selector.unselect(); - expect(dummyItem1.isSelected).toBeFalsy(); + expect(dummyItem1.selectedBy.size).toBe(0); expect(dummyItem1.removeSideEffect).toHaveBeenCalledWith( Selector.rebuildSelectorSideEffectKey ); @@ -501,7 +566,7 @@ describe('Selector Tests', () => { force: true, }); - expect(dummyItem1.isSelected).toBeFalsy(); + expect(dummyItem1.selectedBy.size).toBe(0); expect(dummyItem1.removeSideEffect).toHaveBeenCalledWith( Selector.rebuildSelectorSideEffectKey ); @@ -523,7 +588,7 @@ describe('Selector Tests', () => { selector.unselect(); - expect(dummyItem1.isSelected).toBeFalsy(); + expect(dummyItem1.selectedBy.size).toBe(0); expect(dummyItem1.removeSideEffect).toHaveBeenCalledWith( Selector.rebuildSelectorSideEffectKey ); @@ -542,28 +607,26 @@ describe('Selector Tests', () => { selector._itemKey = 'dummyItemKey'; }); - it('should return true if Selector has selected ItemKey and Item isSelected', () => { - if (selector.item) selector.item.isSelected = true; + it('should return true if Selector has properly selected ItemKey and Item isSelected', () => { + if (selector.item) selector.item.selectedBy.add(selector._key as any); expect(selector.hasSelected('dummyItemKey')).toBeTruthy(); }); - it("should return false if Selector hasn't selected ItemKey and Item isSelected", () => { - if (selector.item) selector.item.isSelected = true; - + it("should return false if Selector hasn't properly selected ItemKey (itemKey == undefined)", () => { expect(selector.hasSelected('notSelectedItemKey')).toBeFalsy(); }); - it("should return false if Selector has selected ItemKey and Item isn't isSelected", () => { - if (selector.item) selector.item.isSelected = false; + it("should return false if Selector hasn't properly selected ItemKey (item == undefined)", () => { + selector.item = undefined; expect(selector.hasSelected('dummyItemKey')).toBeFalsy(); }); - it("should return false if Selector hasn't selected ItemKey and Item isn't isSelected", () => { - if (selector.item) selector.item.isSelected = false; + it("should return false if Selector has properly selected ItemKey and Item isn't isSelected", () => { + if (selector.item) selector.item.selectedBy = new Set(); - expect(selector.hasSelected('notSelectedItemKey')).toBeFalsy(); + expect(selector.hasSelected('dummyItemKey')).toBeFalsy(); }); }); From 2e4cdfa7eaa999ea3b393a4f115a6f537d787923 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 26 May 2021 19:18:05 +0200 Subject: [PATCH 014/117] added reselect tests --- packages/core/src/collection/selector.ts | 4 +- .../tests/unit/collection/selector.test.ts | 48 +++++++++++++++++-- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index ef1f5e5a..b389c1de 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -142,7 +142,7 @@ export class Selector extends State< //========================================================================================================= /** * @public - * Reselect Item + * Reselects current Item * Might help if the Selector failed to properly select an Item. * You can check with 'hasSelected()' if an Item got properly selected. * @param config - Config @@ -152,7 +152,7 @@ export class Selector extends State< (this._itemKey != null && !this.hasSelected(this._itemKey)) || config.force ) - this.select(this.itemKey, config); + this.select(this._itemKey, config); return this; } diff --git a/packages/core/tests/unit/collection/selector.test.ts b/packages/core/tests/unit/collection/selector.test.ts index 2da7af02..b26b30cc 100644 --- a/packages/core/tests/unit/collection/selector.test.ts +++ b/packages/core/tests/unit/collection/selector.test.ts @@ -534,7 +534,45 @@ describe('Selector Tests', () => { }); describe('reselect function tests', () => { - // TODO + beforeEach(() => { + selector.select = jest.fn(); + }); + + it("should reselect Item if Item isn't selected correctly (default config)", () => { + jest.spyOn(selector, 'hasSelected').mockReturnValueOnce(false); + + selector.reselect(); + + expect(selector.select).toHaveBeenCalledWith(selector._itemKey, {}); + }); + + it("should reselect Item if Item isn't selected correctly (specific config)", () => { + jest.spyOn(selector, 'hasSelected').mockReturnValueOnce(false); + + selector.reselect({ force: true, background: true }); + + expect(selector.select).toHaveBeenCalledWith(selector._itemKey, { + force: true, + background: true, + }); + }); + + it("shouldn't reselect Item if itemKey is not set", () => { + jest.spyOn(selector, 'hasSelected').mockReturnValueOnce(false); + selector._itemKey = null as any; + + selector.reselect(); + + expect(selector.select).not.toHaveBeenCalled(); + }); + + it("shouldn't reselect Item if Item is already selected correctly", () => { + jest.spyOn(selector, 'hasSelected').mockReturnValueOnce(true); + + selector.reselect(); + + expect(selector.select).not.toHaveBeenCalled(); + }); }); describe('unselect function tests', () => { @@ -607,23 +645,23 @@ describe('Selector Tests', () => { selector._itemKey = 'dummyItemKey'; }); - it('should return true if Selector has properly selected ItemKey and Item isSelected', () => { + it('should return true if Selector has selected itemKey correctly and Item isSelected', () => { if (selector.item) selector.item.selectedBy.add(selector._key as any); expect(selector.hasSelected('dummyItemKey')).toBeTruthy(); }); - it("should return false if Selector hasn't properly selected ItemKey (itemKey == undefined)", () => { + it("should return false if Selector hasn't selected itemKey correctly (itemKey == undefined)", () => { expect(selector.hasSelected('notSelectedItemKey')).toBeFalsy(); }); - it("should return false if Selector hasn't properly selected ItemKey (item == undefined)", () => { + it("should return false if Selector hasn't selected itemKey correctly (item == undefined)", () => { selector.item = undefined; expect(selector.hasSelected('dummyItemKey')).toBeFalsy(); }); - it("should return false if Selector has properly selected ItemKey and Item isn't isSelected", () => { + it("should return false if Selector has selected itemKey correctly and Item isn't isSelected", () => { if (selector.item) selector.item.selectedBy = new Set(); expect(selector.hasSelected('dummyItemKey')).toBeFalsy(); From e63544c085626ddceb60b50ad7da95103a6abc7b Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 26 May 2021 20:27:11 +0200 Subject: [PATCH 015/117] added isPlaceholder check --- packages/core/src/collection/index.ts | 5 ++--- .../src/runtime/subscription/sub.controller.ts | 2 +- .../core/tests/unit/collection/collection.test.ts | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 6a900999..aba6de91 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -1094,7 +1094,7 @@ export class Collection { _itemKeys.forEach((itemKey) => { const item = this.getItem(itemKey, { notExisting: true }); - if (item == null) return; + if (item == null || item.isPlaceholder) return; // Remove Item from Groups for (const groupKey in this.groups) { @@ -1111,8 +1111,7 @@ export class Collection { // Reselect Item in Selectors (to create new dummyItem that holds reference) for (const selectorKey in this.selectors) { const selector = this.getSelector(selectorKey, { notExisting: true }); - if (selector?.hasSelected(itemKey)) - selector?.select(itemKey, { force: true }); + if (selector?.hasSelected(itemKey)) selector?.reselect({ force: true }); } this.size--; diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index e7f4f9fb..fbd9b37c 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -283,7 +283,7 @@ export class SubController { Agile.logger.if .tag(['runtime', 'subscription']) - .info('15:01:03', callbackSubscriptionContainer); + .info(LogCodeManager.getLog('15:01:03'), callbackSubscriptionContainer); return callbackSubscriptionContainer; } diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index e9233dbc..a79ac45d 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -2194,15 +2194,22 @@ describe('Collection Tests', () => { let dummyGroup2: Group; let dummyItem1: Item; let dummyItem2: Item; + let placeholderItem: Item; beforeEach(() => { dummyItem1 = new Item(collection, { id: 'dummyItem1', name: 'Jeff' }); dummyItem1.persistent = new StatePersistent(dummyItem1); dummyItem2 = new Item(collection, { id: 'dummyItem2', name: 'Hans' }); dummyItem2.persistent = new StatePersistent(dummyItem2); + placeholderItem = new Item( + collection, + { id: 'placeholderItem', name: 'placeholder' }, + { isPlaceholder: true } + ); collection.data = { dummyItem1: dummyItem1, dummyItem2: dummyItem2, + placeholderItem: placeholderItem, }; collection.size = 2; @@ -2281,6 +2288,13 @@ describe('Collection Tests', () => { force: true, }); }); + + it("shouldn't remove placeholder Item from Collection", () => { + collection.removeItems(['dummyItem1', 'placeholderItem']); + + expect(collection.data).toHaveProperty('placeholderItem'); + expect(collection.data).not.toHaveProperty('dummyItem1'); + }); }); describe('setData function tests', () => { From da826d9e9fed306da12dff2af9937551d8e75eee Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 27 May 2021 07:03:15 +0200 Subject: [PATCH 016/117] added config to removeItems --- packages/core/src/collection/index.ts | 66 ++++++++++++---- packages/core/src/collection/selector.ts | 16 ++-- .../tests/unit/collection/collection.test.ts | 78 ++++++++++++++++--- 3 files changed, 126 insertions(+), 34 deletions(-) diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index aba6de91..d88e53db 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -979,26 +979,29 @@ export class Collection { for (const groupKey in this.groups) { const group = this.getGroup(groupKey, { notExisting: true }); if (!group?.has(oldItemKey)) continue; - group.replace(oldItemKey, newItemKey, { background: config.background }); + group?.replace(oldItemKey, newItemKey, { background: config.background }); } // Update ItemKey in Selectors for (const selectorKey in this.selectors) { const selector = this.getSelector(selectorKey, { notExisting: true }); - if (!selector) continue; - - // Reselect Item in existing Selector which has selected the newItemKey - if (selector.hasSelected(newItemKey)) { - selector.select(newItemKey, { + if (selector == null) continue; + + // Reselect Item in Selector that has selected the newItemKey + // Necessary because the reference placeholder Item got removed + // and replaced with the new Item (Item of which the primaryKey was renamed) + // -> needs to find new Item with the same itemKey + if (selector.hasSelected(newItemKey, false)) { + selector.reselect({ force: true, // Because ItemKeys are the same background: config.background, }); } - // Select newItemKey in existing Selector which has selected the oldItemKey - if (selector.hasSelected(oldItemKey)) + // Select newItemKey in Selector that has selected the oldItemKey + if (selector.hasSelected(oldItemKey, false)) selector.select(newItemKey, { - background: config?.background, + background: config.background, }); } @@ -1034,12 +1037,12 @@ export class Collection { itemKeys: ItemKey | Array ): { fromGroups: (groups: Array | ItemKey) => Collection; - everywhere: () => Collection; + everywhere: (config?: RemoveItemsConfigInterface) => Collection; } { return { fromGroups: (groups: Array | ItemKey) => this.removeFromGroups(itemKeys, groups), - everywhere: () => this.removeItems(itemKeys), + everywhere: (config) => this.removeItems(itemKeys, config || {}), }; } @@ -1088,13 +1091,22 @@ export class Collection { * @public * Removes Item completely from Collection * @param itemKeys - ItemKey/s of Item/s + * @param config - Config */ - public removeItems(itemKeys: ItemKey | Array): this { + public removeItems( + itemKeys: ItemKey | Array, + config: RemoveItemsConfigInterface = {} + ): this { + config = defineConfig(config, { + notExisting: false, + removeSelector: false, + }); const _itemKeys = normalizeArray(itemKeys); _itemKeys.forEach((itemKey) => { - const item = this.getItem(itemKey, { notExisting: true }); - if (item == null || item.isPlaceholder) return; + const item = this.getItem(itemKey, { notExisting: config.notExisting }); + if (item == null) return; + const wasPlaceholder = item.isPlaceholder; // Remove Item from Groups for (const groupKey in this.groups) { @@ -1108,13 +1120,21 @@ export class Collection { // Remove Item from Collection delete this.data[itemKey]; - // Reselect Item in Selectors (to create new dummyItem that holds reference) + // Reselect or remove Selectors representing the removed Item for (const selectorKey in this.selectors) { const selector = this.getSelector(selectorKey, { notExisting: true }); - if (selector?.hasSelected(itemKey)) selector?.reselect({ force: true }); + if (selector?.hasSelected(itemKey, false)) { + if (config.removeSelector) { + // Remove Selector + this.removeSelector(selector?._key ?? 'unknown'); + } else { + // Reselect Item in Selector (to create new dummyItem to hold a reference to this removed Item) + selector?.reselect({ force: true }); + } + } } - this.size--; + if (!wasPlaceholder) this.size--; }); return this; @@ -1301,6 +1321,18 @@ export interface CollectionPersistentConfigInterface { defaultStorageKey?: StorageKey; } +/* + * @param notExisting - If not existing Items like placeholder Items can be removed. + * Keep in mind that sometimes it won't remove the Item entirely + * because another Instance (like a Selector) needs to keep reference to it. + * https://github.com/agile-ts/agile/pull/152 + * @param - If Selectors that have selected an Item to be removed, should be removed too + */ +export interface RemoveItemsConfigInterface { + notExisting?: boolean; + removeSelector?: boolean; +} + /** * @param patch - If Data gets patched into existing Item * @param background - If assigning Data happens in background diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index b389c1de..3f391548 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -196,13 +196,17 @@ export class Selector extends State< /** * Checks if Selector has correctly selected the Item at the passed itemKey * @param itemKey - ItemKey + * @param correctlySelected - If it should consider only correctly selected Items */ - public hasSelected(itemKey: ItemKey): boolean { - return ( - this._itemKey === itemKey && - this.item != null && - this.item.selectedBy.has(this._key as any) - ); + public hasSelected(itemKey: ItemKey, correctlySelected = true): boolean { + if (correctlySelected) { + return ( + this._itemKey === itemKey && + this.item != null && + this.item.selectedBy.has(this._key as any) + ); + } + return this._itemKey === itemKey; } //========================================================================================================= diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index a79ac45d..77b12115 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -1903,6 +1903,9 @@ describe('Collection Tests', () => { dummySelector1.select = jest.fn(); dummySelector2.select = jest.fn(); dummySelector3.select = jest.fn(); + dummySelector1.reselect = jest.fn(); + dummySelector2.reselect = jest.fn(); + dummySelector3.reselect = jest.fn(); }); it('should update ItemKey in Collection, Selectors and Groups (default config)', () => { @@ -2109,11 +2112,26 @@ describe('Collection Tests', () => { expect(collection.removeItems).not.toHaveBeenCalled(); }); - it('should remove Items from everywhere', () => { + it('should remove Items from everywhere (default config)', () => { collection.remove(['test1', 'test2']).everywhere(); expect(collection.removeFromGroups).not.toHaveBeenCalled(); - expect(collection.removeItems).toHaveBeenCalledWith(['test1', 'test2']); + expect(collection.removeItems).toHaveBeenCalledWith( + ['test1', 'test2'], + {} + ); + }); + + it('should remove Items from everywhere (specific config)', () => { + collection + .remove(['test1', 'test2']) + .everywhere({ removeSelector: true, notExisting: true }); + + expect(collection.removeFromGroups).not.toHaveBeenCalled(); + expect(collection.removeItems).toHaveBeenCalledWith( + ['test1', 'test2'], + { removeSelector: true, notExisting: true } + ); }); }); @@ -2241,16 +2259,19 @@ describe('Collection Tests', () => { dummyGroup1.remove = jest.fn(); dummyGroup2.remove = jest.fn(); - dummySelector1.select = jest.fn(); - dummySelector2.select = jest.fn(); + dummySelector1.reselect = jest.fn(); + dummySelector2.reselect = jest.fn(); + + collection.removeSelector = jest.fn(); }); - it('should remove Item from Collection, Groups and Selectors', () => { + it('should remove Item from Collection, Groups and reselect Selectors (default config)', () => { collection.removeItems('dummyItem1'); expect(collection.data).not.toHaveProperty('dummyItem1'); expect(collection.data).toHaveProperty('dummyItem2'); expect(collection.size).toBe(1); + expect(collection.removeSelector).not.toHaveBeenCalled(); expect(dummyItem1.persistent?.removePersistedValue).toHaveBeenCalled(); expect( @@ -2260,18 +2281,19 @@ describe('Collection Tests', () => { expect(dummyGroup1.remove).toHaveBeenCalledWith('dummyItem1'); expect(dummyGroup2.remove).not.toHaveBeenCalled(); - expect(dummySelector1.select).toHaveBeenCalledWith('dummyItem1', { + expect(dummySelector1.reselect).toHaveBeenCalledWith({ force: true, }); - expect(dummySelector2.select).not.toHaveBeenCalled(); + expect(dummySelector2.reselect).not.toHaveBeenCalled(); }); - it('should remove Items from Collection, Groups and Selectors', () => { + it('should remove Items from Collection, Groups and reselect Selectors (default config)', () => { collection.removeItems(['dummyItem1', 'dummyItem2', 'notExistingItem']); expect(collection.data).not.toHaveProperty('dummyItem1'); expect(collection.data).not.toHaveProperty('dummyItem2'); expect(collection.size).toBe(0); + expect(collection.removeSelector).not.toHaveBeenCalled(); expect(dummyItem1.persistent?.removePersistedValue).toHaveBeenCalled(); expect(dummyItem2.persistent?.removePersistedValue).toHaveBeenCalled(); @@ -2281,19 +2303,53 @@ describe('Collection Tests', () => { expect(dummyGroup2.remove).not.toHaveBeenCalledWith('dummyItem1'); expect(dummyGroup2.remove).toHaveBeenCalledWith('dummyItem2'); - expect(dummySelector1.select).toHaveBeenCalledWith('dummyItem1', { + expect(dummySelector1.reselect).toHaveBeenCalledWith({ force: true, }); - expect(dummySelector2.select).toHaveBeenCalledWith('dummyItem2', { + expect(dummySelector2.reselect).toHaveBeenCalledWith({ force: true, }); }); - it("shouldn't remove placeholder Item from Collection", () => { + it('should remove Item from Collection, Groups and remove Selectors (removeSelector = true)', () => { + collection.removeItems('dummyItem1', { removeSelector: true }); + + expect(collection.data).not.toHaveProperty('dummyItem1'); + expect(collection.data).toHaveProperty('dummyItem2'); + expect(collection.size).toBe(1); + expect(collection.removeSelector).toHaveBeenCalledTimes(1); + expect(collection.removeSelector).toHaveBeenCalledWith( + dummySelector1._key + ); + + expect(dummyItem1.persistent?.removePersistedValue).toHaveBeenCalled(); + expect( + dummyItem2.persistent?.removePersistedValue + ).not.toHaveBeenCalled(); + + expect(dummyGroup1.remove).toHaveBeenCalledWith('dummyItem1'); + expect(dummyGroup2.remove).not.toHaveBeenCalled(); + + expect(dummySelector1.reselect).not.toHaveBeenCalled(); + expect(dummySelector2.reselect).not.toHaveBeenCalled(); + }); + + it("shouldn't remove placeholder Items from Collection (default config)", () => { collection.removeItems(['dummyItem1', 'placeholderItem']); expect(collection.data).toHaveProperty('placeholderItem'); expect(collection.data).not.toHaveProperty('dummyItem1'); + expect(collection.size).toBe(1); + }); + + it('should remove placeholder Items from Collection (config.notExisting = true)', () => { + collection.removeItems(['dummyItem1', 'placeholderItem'], { + notExisting: true, + }); + + expect(collection.data).not.toHaveProperty('placeholderItem'); + expect(collection.data).not.toHaveProperty('dummyItem1'); + expect(collection.size).toBe(1); }); }); From de75b1439d4d0bdbb57851091b5b147a1591a092 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 27 May 2021 07:58:40 +0200 Subject: [PATCH 017/117] fixed tests --- packages/core/tests/unit/collection/collection.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index 77b12115..482e74e2 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -1938,7 +1938,7 @@ describe('Collection Tests', () => { background: false, }); expect(dummySelector2.select).not.toHaveBeenCalled(); - expect(dummySelector3.select).toHaveBeenCalledWith('newDummyItem', { + expect(dummySelector3.reselect).toHaveBeenCalledWith({ force: true, background: false, }); @@ -1978,7 +1978,7 @@ describe('Collection Tests', () => { expect(dummySelector1.select).toHaveBeenCalledWith('newDummyItem', { background: true, }); - expect(dummySelector3.select).toHaveBeenCalledWith('newDummyItem', { + expect(dummySelector3.reselect).toHaveBeenCalledWith({ force: true, background: true, }); @@ -2015,7 +2015,7 @@ describe('Collection Tests', () => { expect(dummySelector1.select).toHaveBeenCalledWith('newDummyItem', { background: false, }); - expect(dummySelector3.select).toHaveBeenCalledWith('newDummyItem', { + expect(dummySelector3.reselect).toHaveBeenCalledWith({ force: true, background: false, }); From 3e20f06d8cd85581d18028f064c6519a311e4358 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 27 May 2021 08:13:15 +0200 Subject: [PATCH 018/117] expanded hasSelected tests --- packages/core/src/collection/selector.ts | 4 ++-- .../tests/unit/collection/selector.test.ts | 20 +++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index 3f391548..deaf17b7 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -143,8 +143,8 @@ export class Selector extends State< /** * @public * Reselects current Item - * Might help if the Selector failed to properly select an Item. - * You can check with 'hasSelected()' if an Item got properly selected. + * Might help if the Selector failed to select an Item correctly. + * You can check with 'hasSelected()' if an Item got correctly selected. * @param config - Config */ public reselect(config: StateRuntimeJobConfigInterface = {}): this { diff --git a/packages/core/tests/unit/collection/selector.test.ts b/packages/core/tests/unit/collection/selector.test.ts index b26b30cc..03f088d0 100644 --- a/packages/core/tests/unit/collection/selector.test.ts +++ b/packages/core/tests/unit/collection/selector.test.ts @@ -651,21 +651,37 @@ describe('Selector Tests', () => { expect(selector.hasSelected('dummyItemKey')).toBeTruthy(); }); - it("should return false if Selector hasn't selected itemKey correctly (itemKey == undefined)", () => { + it("should return false if Selector hasn't selected itemKey correctly (itemKey = undefined)", () => { expect(selector.hasSelected('notSelectedItemKey')).toBeFalsy(); }); - it("should return false if Selector hasn't selected itemKey correctly (item == undefined)", () => { + it("should return false if Selector hasn't selected itemKey correctly (itemKey = undefined, correctlySelected = false)", () => { + expect(selector.hasSelected('notSelectedItemKey', false)).toBeFalsy(); + }); + + it("should return false if Selector hasn't selected itemKey correctly (item = undefined)", () => { selector.item = undefined; expect(selector.hasSelected('dummyItemKey')).toBeFalsy(); }); + it("should return true if Selector hasn't selected itemKey correctly (item = undefined, correctlySelected = false)", () => { + selector.item = undefined; + + expect(selector.hasSelected('dummyItemKey', false)).toBeTruthy(); + }); + it("should return false if Selector has selected itemKey correctly and Item isn't isSelected", () => { if (selector.item) selector.item.selectedBy = new Set(); expect(selector.hasSelected('dummyItemKey')).toBeFalsy(); }); + + it("should return true if Selector has selected itemKey correctly and Item isn't isSelected (correctlySelected = false)", () => { + if (selector.item) selector.item.selectedBy = new Set(); + + expect(selector.hasSelected('dummyItemKey', false)).toBeTruthy(); + }); }); describe('rebuildSelector function tests', () => { From 1b0d7af7635753bce404b3af3d551bdfe0e9ea5e Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 28 May 2021 08:11:15 +0200 Subject: [PATCH 019/117] updated computed method descriptions --- packages/core/src/collection/index.ts | 32 +++++----- packages/core/src/computed/index.ts | 89 ++++++++++++++++----------- packages/core/src/state/index.ts | 7 ++- 3 files changed, 77 insertions(+), 51 deletions(-) diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index d88e53db..18611453 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -40,10 +40,13 @@ export class Collection { public isInstantiated = false; /** + * Class that holds a List of Objects with key and causes rerender on subscribed Components + * * @public - * Collection - Class that holds a List of Objects with key and causes rerender on subscribed Components - * @param agileInstance - An instance of Agile - * @param config - Config + * + * @param agileInstance - Instance of Agile the Collection belongs to + * + * @param config - Configuration */ constructor(agileInstance: Agile, config: CollectionConfig = {}) { this.agileInstance = () => agileInstance; @@ -408,14 +411,15 @@ export class Collection { return !!this.getGroup(groupKey, config); } - //========================================================================================================= - // Get Group - //========================================================================================================= /** + * Retrieves a single Group by key/name. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroup) + * * @public - * Get Group by Key/Name - * @param groupKey - Key/Name of Group - * @param config - Config + * @memberOf Collection + * @param groupKey - key/name Group identifier + * @param config - Configuration */ public getGroup( groupKey: GroupKey | undefined, @@ -1321,12 +1325,12 @@ export interface CollectionPersistentConfigInterface { defaultStorageKey?: StorageKey; } -/* - * @param notExisting - If not existing Items like placeholder Items can be removed. +/** + * @property notExisting - If not existing Items like placeholder Items can be removed. * Keep in mind that sometimes it won't remove the Item entirely * because another Instance (like a Selector) needs to keep reference to it. * https://github.com/agile-ts/agile/pull/152 - * @param - If Selectors that have selected an Item to be removed, should be removed too + * @property removeSelector - If Selectors that have selected an Item to be removed, should be removed too */ export interface RemoveItemsConfigInterface { notExisting?: boolean; @@ -1334,8 +1338,8 @@ export interface RemoveItemsConfigInterface { } /** - * @param patch - If Data gets patched into existing Item - * @param background - If assigning Data happens in background + * @property patch - If Data gets patched into existing Item + * @property background - If assigning Data happens in background */ export interface SetDataConfigInterface { patch?: boolean; diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index e4470c22..cb70ad7e 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -17,16 +17,25 @@ export class Computed extends State< > { public agileInstance: () => Agile; - public computeFunction: () => ComputedValueType; - public deps: Array = []; // All Dependencies of Computed (hardCoded and autoDetected) - public hardCodedDeps: Array = []; // HardCoded Dependencies of Computed + public computeFunction: () => ComputedValueType; // Function to compute the computed value + public deps: Array = []; // All dependencies the Computed depends on (including hardCoded and autoDetected dependencies) + public hardCodedDeps: Array = []; // Only hardCoded dependencies the Computed depends /** + * A extension of the State Class that computes its value based on a compute function. + * + * The computed value will be cached to avoid unnecessary recomputes + * and only recomputed when one of its direct dependencies changed. + * + * Direct dependencies can be States and Collections. + * Meaning if a dependent State value changes, the computed value will be recomputed. + * + * [Learn more..](https://agile-ts.org/docs/core/computed/) + * * @public - * Computed - Function that recomputes its value if a dependency changes - * @param agileInstance - An instance of Agile - * @param computeFunction - Function for computing value - * @param config - Config + * @param agileInstance - Instance of Agile the Computed belongs to. + * @param computeFunction - Function to compute the computed value. + * @param config - Configuration object */ constructor( agileInstance: Agile, @@ -43,23 +52,23 @@ export class Computed extends State< this.agileInstance = () => agileInstance; this.computeFunction = computeFunction; - // Format hardCodedDeps + // Extract Observer of passed hardcoded dependency instances this.hardCodedDeps = extractObservers(config.computedDeps).filter( (dep): dep is Observer => dep !== undefined ); this.deps = this.hardCodedDeps; - // Recompute for setting initial value and adding missing dependencies + // Initial recompute to assign initial value and autodetect missing dependencies this.recompute({ autodetect: true }); } - //========================================================================================================= - // Recompute - //========================================================================================================= /** + * Forces a recomputation of the cached value based on the compute function. + * + * [Learn more..](https://agile-ts.org/docs/core/computed/methods/#recompute) + * * @public - * Recomputes Value of Computed - * @param config - Config + * @param config - Configuration object */ public recompute(config: RecomputeConfigInterface = {}): this { config = defineConfig(config, { @@ -72,15 +81,21 @@ export class Computed extends State< return this; } - //========================================================================================================= - // Updates Compute Function - //========================================================================================================= /** + * Assigns new function to the Computed to compute the computed value. + * + * The dependencies of the new compute function are automatically detected + * and accordingly updated in the Computed Class. + * + * A initial computation is automatically performed with the new function + * to update the outdated cached value of the Computed. + * + * [Learn more..](https://agile-ts.org/docs/core/computed/methods/#updatecomputefunction) + * * @public - * Applies new compute Function to Computed - * @param computeFunction - New Function for computing value - * @param deps - Hard coded dependencies of Computed Function - * @param config - Config + * @param computeFunction - New function to compute the computed value. + * @param deps - Hard coded dependencies on which the Computed Class depends. + * @param config - Configuration object */ public updateComputeFunction( computeFunction: () => ComputedValueType, @@ -92,7 +107,7 @@ export class Computed extends State< autodetect: true, }); - // Update deps + // Update dependencies of Computed const newDeps = extractObservers(deps).filter( (dep): dep is Observer => dep !== undefined ); @@ -103,25 +118,24 @@ export class Computed extends State< // Update computeFunction this.computeFunction = computeFunction; - // Recompute for setting initial Computed Function Value and adding missing Dependencies + // Recompute to assign new computed value and autodetect missing dependencies this.recompute(removeProperties(config, ['overwriteDeps'])); return this; } - //========================================================================================================= - // Compute - //========================================================================================================= /** + * Computes the value of the Computed Class and autodetects used dependencies in the compute function. + * * @internal - * Recomputes value and adds missing dependencies to Computed + * @param config - Configuration object */ public compute(config: ComputeConfigInterface = {}): ComputedValueType { config = defineConfig(config, { autodetect: true, }); - // Start auto tracking Observers the computeFunction might depend on + // Start auto tracking of Observers on which the computeFunction might depend if (config.autodetect) ComputedTracker.track(); const computedValue = this.computeFunction(); @@ -129,13 +143,11 @@ export class Computed extends State< // Handle auto tracked Observers if (config.autodetect) { const foundDeps = ComputedTracker.getTrackedObservers(); - - // Handle foundDeps and hardCodedDeps const newDeps: Array = []; this.hardCodedDeps.concat(foundDeps).forEach((observer) => { newDeps.push(observer); - // Make this Observer depend on foundDep Observer + // Make this Observer depend on the foundDep Observer observer.depend(this.observer); }); @@ -145,20 +157,25 @@ export class Computed extends State< return computedValue; } - //========================================================================================================= - // Overwriting some functions which aren't allowed to use in Computed - //========================================================================================================= - + /** + * Not usable in Computed Class. + */ public patch() { LogCodeManager.log('19:03:00'); return this; } + /** + * Not usable in Computed Class. + */ public persist(): this { LogCodeManager.log('19:03:01'); return this; } + /** + * Not usable in Computed Class. + */ public invert(): this { LogCodeManager.log('19:03:02'); return this; @@ -166,7 +183,7 @@ export class Computed extends State< } /** - * @param computedDeps - Hard coded dependencies of Computed Function + * @param computedDeps - Hard coded dependencies of compute function */ export interface ComputedConfigInterface extends StateConfigInterface { computedDeps?: Array; diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index 3c778f96..90155b2c 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -48,9 +48,14 @@ export class State { /** * @public * State - Class that holds one Value and causes rerender on subscribed Components + * * @param agileInstance - An instance of Agile + * * @param initialValue - Initial Value of State - * @param config - Config + * + * @param config - Configuration + * + * @typeparam ValueType - Type of a the value the State represents */ constructor( agileInstance: Agile, From cfeae997561e1683d1e08a7b6a2d4f019509da60 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 29 May 2021 12:38:07 +0200 Subject: [PATCH 020/117] fixed typos --- .../src/collection/collection.persistent.ts | 268 ++++++++++-------- packages/core/src/collection/index.ts | 60 ++-- packages/core/src/collection/item.ts | 70 +++++ .../core/src/computed/computed.tracker.ts | 19 +- packages/core/src/computed/index.ts | 31 +- packages/core/src/state/index.ts | 3 + packages/core/src/state/state.observer.ts | 3 +- packages/core/src/state/state.persistent.ts | 8 +- .../tests/unit/collection/collection.test.ts | 45 +-- 9 files changed, 315 insertions(+), 192 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 04bbe301..d9dd78c5 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -1,5 +1,4 @@ import { - Agile, Collection, CollectionKey, CreatePersistentConfigInterface, @@ -7,6 +6,7 @@ import { defineConfig, Group, GroupKey, + Item, ItemKey, LogCodeManager, Persistent, @@ -24,10 +24,11 @@ export class CollectionPersistent< static storageGroupKeyPattern = '_${collectionKey}_group_${groupKey}'; /** + * Internal Class for managing the permanent persistence of a Collection. + * * @internal - * Collection Persist Manager - Handles permanent storing of Collection Value - * @param collection - Collection that gets stored - * @param config - Config + * @param collection - Collection to be persisted. + * @param config - Configuration object */ constructor( collection: Collection, @@ -48,47 +49,44 @@ export class CollectionPersistent< defaultStorageKey: config.defaultStorageKey, }); - // Load/Store persisted Value/s for the first Time + // Load/Store persisted value/s for the first time if (this.ready && config.instantiate) this.initialLoading(); } - //========================================================================================================= - // Set Key - //========================================================================================================= /** + * Updates key/name identifier of Persistent. + * * @internal - * Updates Key/Name of Persistent - * @param value - New Key/Name of Persistent + * @param value - New key/name identifier. */ public async setKey(value?: StorageKey): Promise { const oldKey = this._key; const wasReady = this.ready; - // Assign Key + // Assign new key to Persistent if (value === this._key) return; this._key = value || Persistent.placeHolderKey; const isValid = this.validatePersistent(); - // Try to Initial Load Value if persistent wasn't ready + // Try to initial load value if persistent wasn't ready before if (!wasReady) { if (isValid) await this.initialLoading(); return; } - // Remove value at old Key + // Remove persisted values at old key await this.removePersistedValue(oldKey); - // Assign Value to new Key + // Persist values at the new key if (isValid) await this.persistValue(value); } - //========================================================================================================= - // Initial Loading - //========================================================================================================= /** * @internal - * Loads/Saves Storage Value for the first Time + * Loads the persisted value into the Collection + * or persists the Collection value in the corresponding Storage. + * This depends on whether the Collection has been persisted before. */ public async initialLoading() { super.initialLoading().then(() => { @@ -96,14 +94,12 @@ export class CollectionPersistent< }); } - //========================================================================================================= - // Load Persisted Value - //========================================================================================================= /** + * Loads the values of the Collection Instances from the corresponding Storage. + * * @internal - * Loads Collection from Storage - * @param storageItemKey - Prefix Key of Persisted Instances (default PersistentKey) - * @return Success? + * @param storageItemKey - Prefix Key of persisted Collection Instances | default = Persistent.key | + * @return Whether the loading was successful. */ public async loadPersistedValue( storageItemKey?: PersistentKey @@ -111,180 +107,205 @@ export class CollectionPersistent< if (!this.ready) return false; const _storageItemKey = storageItemKey || this._key; - // Check if Collection is Persisted + // Check if Collection is already persisted (indicated by the persistence of true at _storageItemKey) const isPersisted = await this.agileInstance().storages.get( _storageItemKey, this.config.defaultStorageKey as any ); + + // Return false if Collection isn't persisted yet if (!isPersisted) return false; - // Loads Values into Collection + // Helper function to load persisted values into the Collection const loadValuesIntoCollection = async () => { - const defaultGroup = this.collection().getGroup( - this.collection().config.defaultGroupKey - ); + const defaultGroup = this.collection().getDefaultGroup(); if (!defaultGroup) return false; + const defaultGroupPersistKey = CollectionPersistent.getGroupStorageKey( + defaultGroup._key, + _storageItemKey + ); - // Persist Default Group and load its Value manually to be 100% sure it got loaded - defaultGroup.persist({ + // Persist default Group and load its value manually to be 100% sure + // that it was loaded completely + defaultGroup.persist(defaultGroupPersistKey, { loadValue: false, - followCollectionPersistKeyPattern: true, }); - if (defaultGroup.persistent?.ready) { - await defaultGroup.persistent?.initialLoading(); - defaultGroup.isPersisted = true; - } + if (defaultGroup.persistent?.ready) + await defaultGroup.persistent.initialLoading(); - // Load Items into Collection + // Persist Items found in the default Group's value for (const itemKey of defaultGroup._value) { - const itemStorageKey = CollectionPersistent.getItemStorageKey( + const item = this.collection().getItem(itemKey); + const itemPersistKey = CollectionPersistent.getItemStorageKey( itemKey, _storageItemKey ); - // Get Storage Value - const storageValue = await this.agileInstance().storages.get( - itemStorageKey, - this.config.defaultStorageKey as any + // Persist already existing Item + if (item != null) { + item.persist(itemPersistKey); + return true; + } + + // Create temporary placeholder Item in which the Item value will be loaded + const dummyItem = new Item( + this.collection(), + { + [this.collection().config.primaryKey]: itemKey, // Setting PrimaryKey of Item to passed itemKey + dummy: 'item', + } as any, + { isPlaceholder: true } ); - if (!storageValue) continue; - // Collect found Storage Value - this.collection().collect(storageValue); + // Persist dummy Item and load its value manually to be 100% sure + // that it was loaded completely and exists + dummyItem?.persist(itemPersistKey, { + loadValue: false, + }); + if (dummyItem?.persistent?.ready) { + const success = await dummyItem.persistent.loadPersistedValue( + itemPersistKey + ); + + // If successfully loaded add Item to Collection + if (success) this.collection().collectItem(dummyItem); + } } + return true; }; const success = await loadValuesIntoCollection(); - // Persist Collection, so that the Storage Value updates dynamically if the Collection updates + // 'Persist' Collection to setup side effects + // that automatically update the corresponding Storage value if the Collection updates if (success) await this.persistValue(_storageItemKey); return success; } - //========================================================================================================= - // Persist Value - //========================================================================================================= /** + * Persists Collection in corresponding Storage if not already done + * and sets up side effects that dynamically update the storage value when the Collection changes. + * * @internal - * Sets everything up so that the Collection gets saved in the Storage - * @param storageItemKey - Prefix Key of Persisted Instances (default PersistentKey) - * @return Success? + * @param storageItemKey - Prefix Key of persisted Collection Instances | default = Persistent.key | + * @return Whether the persisting and the setting up of the side effects was successful. */ public async persistValue(storageItemKey?: PersistentKey): Promise { if (!this.ready) return false; const _storageItemKey = storageItemKey || this._key; - const defaultGroup = this.collection().getGroup( - this.collection().config.defaultGroupKey - ); + const defaultGroup = this.collection().getDefaultGroup(); if (!defaultGroup) return false; + const defaultGroupPersistKey = CollectionPersistent.getGroupStorageKey( + defaultGroup._key, + _storageItemKey + ); - // Set Collection to Persisted (in Storage) + // Set flag in Storage to indicate that the Collection is persisted this.agileInstance().storages.set(_storageItemKey, true, this.storageKeys); // Persist default Group - if (!defaultGroup.isPersisted) - defaultGroup.persist({ followCollectionPersistKeyPattern: true }); + if (!defaultGroup.isPersisted) defaultGroup.persist(defaultGroupPersistKey); - // Add sideEffect to default Group which adds and removes Items from the Storage depending on the Group Value + // Add side effect to default Group + // that adds or removes Items from the Storage depending on the Group value defaultGroup.addSideEffect( CollectionPersistent.defaultGroupSideEffectKey, () => this.rebuildStorageSideEffect(defaultGroup, _storageItemKey), { weight: 0 } ); - // Persist Collection Items + // Persist Items found in the default Group's value for (const itemKey of defaultGroup._value) { const item = this.collection().getItem(itemKey); - const itemStorageKey = CollectionPersistent.getItemStorageKey( + const itemPersistKey = CollectionPersistent.getItemStorageKey( itemKey, _storageItemKey ); - item?.persist(itemStorageKey); + if (!item?.isPersisted) item?.persist(itemPersistKey); } this.isPersisted = true; return true; } - //========================================================================================================= - // Remove Persisted Value - //========================================================================================================= /** + * Removes Collection from the corresponding Storage. + * -> Collection is no longer persisted + * * @internal - * Removes Collection from the Storage - * @param storageItemKey - Prefix Key of Persisted Instances (default PersistentKey) - * @return Success? + * @param storageItemKey - Prefix Key of persisted Collection Instances | default = Persistent.key | + * @return Whether the removing was successful. */ public async removePersistedValue( storageItemKey?: PersistentKey ): Promise { if (!this.ready) return false; const _storageItemKey = storageItemKey || this._key; - const defaultGroup = this.collection().getGroup( - this.collection().config.defaultGroupKey - ); + const defaultGroup = this.collection().getDefaultGroup(); if (!defaultGroup) return false; + const defaultGroupPersistKey = CollectionPersistent.getGroupStorageKey( + defaultGroup._key, + _storageItemKey + ); - // Set Collection to not Persisted + // Remove Collection is persisted indicator flag this.agileInstance().storages.remove(_storageItemKey, this.storageKeys); - // Remove default Group from Storage - defaultGroup.persistent?.removePersistedValue(); - - // Remove Rebuild Storage sideEffect from default Group + // Remove default Group from the Storage + defaultGroup.persistent?.removePersistedValue(defaultGroupPersistKey); defaultGroup.removeSideEffect( CollectionPersistent.defaultGroupSideEffectKey ); - // Remove Collection Items from Storage + // Remove Items found in the default Group's value from the Storage for (const itemKey of defaultGroup._value) { const item = this.collection().getItem(itemKey); - item?.persistent?.removePersistedValue(); + const itemPersistKey = CollectionPersistent.getItemStorageKey( + itemKey, + _storageItemKey + ); + item?.persistent?.removePersistedValue(itemPersistKey); } this.isPersisted = false; return true; } - //========================================================================================================= - // Format Key - //========================================================================================================= /** + * Formats given key so that it can be used as a valid Storage key. + * If no formatable key is given, an attempt is made to use the Collection key as Storage key. + * If this also fails, undefined is returned. + * * @internal - * Formats Storage Key - * @param key - Key that gets formatted + * @param key - Key to be formatted */ public formatKey(key?: StorageKey): StorageKey | undefined { - const collection = this.collection(); - - // Get key from Collection - if (key == null && collection._key) return collection._key; - + if (key == null && this.collection()._key) return this.collection()._key; if (key == null) return; - - // Set Storage Key to Collection Key if Collection has no key - if (collection._key == null) collection._key = key; - + if (this.collection()._key == null) this.collection()._key = key; return key; } - //========================================================================================================= - // Rebuild Storage SideEffect - //========================================================================================================= /** - * @internal * Rebuilds Storage depending on Group + * + * @internal * @param group - Group - * @param key - Prefix Key of Persisted Instances (default PersistentKey) + * @param storageItemKey - Prefix Key of persisted Collection Instances | default = Persistent.key | */ - public rebuildStorageSideEffect(group: Group, key?: PersistentKey) { + public rebuildStorageSideEffect( + group: Group, + storageItemKey?: PersistentKey + ) { const collection = group.collection(); - const _key = key || collection.persistent?._key; + const _storageItemKey = storageItemKey || collection.persistent?._key; - // Return if only a ItemKey got updated + // Return if no Item got added or removed + // because then the Item performs the Storage update itself if (group.previousStateValue.length === group._value.length) return; + // Extract Item keys that got removed or added to the Group const addedKeys = group._value.filter( (key) => !group.previousStateValue.includes(key) ); @@ -292,32 +313,38 @@ export class CollectionPersistent< (key) => !group._value.includes(key) ); - // Persist Added Keys + // Persist newly added Items addedKeys.forEach((itemKey) => { const item = collection.getItem(itemKey); - const _itemKey = CollectionPersistent.getItemStorageKey(itemKey, _key); - if (!item) return; - if (!item.isPersisted) item.persist(_itemKey); - else item.persistent?.persistValue(_itemKey); + const itemPersistKey = CollectionPersistent.getItemStorageKey( + itemKey, + _storageItemKey + ); + if (item != null) { + if (!item.isPersisted) item.persist(itemPersistKey); + else item.persistent?.persistValue(itemPersistKey); + } }); - // Unpersist removed Keys + // Remove removed Items from the Storage removedKeys.forEach((itemKey) => { const item = collection.getItem(itemKey); - const _itemKey = CollectionPersistent.getItemStorageKey(itemKey, _key); - if (!item) return; - if (item.isPersisted) item.persistent?.removePersistedValue(_itemKey); + const itemPersistKey = CollectionPersistent.getItemStorageKey( + itemKey, + _storageItemKey + ); + if (item != null) + if (item.isPersisted) + item.persistent?.removePersistedValue(itemPersistKey); }); } - //========================================================================================================= - // Get Item Storage Key - //========================================================================================================= /** + * Builds valid Item Storage key based on the 'Collection Item Persist Pattern' + * * @internal - * Build Item StorageKey with Collection Persist Pattern - * @param itemKey - Key of Item - * @param collectionKey - Key of Collection + * @param itemKey - Key identifier of Item + * @param collectionKey - Key identifier of Collection */ public static getItemStorageKey( itemKey?: ItemKey, @@ -332,14 +359,12 @@ export class CollectionPersistent< .replace('${itemKey}', itemKey.toString()); } - //========================================================================================================= - // Get Group Storage Key - //========================================================================================================= /** + * Builds valid Item Storage key based on the 'Collection Group Persist Pattern' + * * @internal - * Build Group StorageKey with Collection Persist Pattern - * @param groupKey - Key of Group - * @param collectionKey - Key of Collection + * @param groupKey - Key identifier of Group + * @param collectionKey - Key identifier of Collection */ public static getGroupStorageKey( groupKey?: GroupKey, @@ -349,7 +374,6 @@ export class CollectionPersistent< LogCodeManager.log('1A:02:01'); if (groupKey == null) groupKey = 'unknown'; if (collectionKey == null) collectionKey = 'unknown'; - return this.storageGroupKeyPattern .replace('${collectionKey}', collectionKey.toString()) .replace('${groupKey}', groupKey.toString()); diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 18611453..f3a31c99 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -264,7 +264,7 @@ export class Collection { const itemKey = data[primaryKey]; // Add Item to Collection - const success = this.setData(data, { + const success = this.assignData(data, { patch: config.patch, background: config.background, }); @@ -1153,9 +1153,11 @@ export class Collection { * @param data - Data * @param config - Config */ - public setData(data: DataType, config: SetDataConfigInterface = {}): boolean { + public assignData( + data: DataType, + config: SetDataConfigInterface = {} + ): boolean { const _data = copy(data as any); // Transformed Data to any because of unknown Object (DataType) - const primaryKey = this.config.primaryKey; config = defineConfig(config, { patch: false, background: false, @@ -1166,13 +1168,14 @@ export class Collection { return false; } - if (!Object.prototype.hasOwnProperty.call(_data, primaryKey)) { + // Check if data has valid primaryKey + if (!Object.prototype.hasOwnProperty.call(_data, this.config.primaryKey)) { LogCodeManager.log('1B:02:05', [this._key, this.config.primaryKey]); _data[this.config.primaryKey] = generateId(); } - const itemKey = _data[primaryKey]; - let item = this.getItem(itemKey, { notExisting: true }); + const itemKey = _data[this.config.primaryKey]; + const item = this.getItem(itemKey, { notExisting: true }); const wasPlaceholder = item?.isPlaceholder || false; const createItem = item == null; @@ -1181,21 +1184,44 @@ export class Collection { item?.patch(_data, { background: config.background }); if (!createItem && !config.patch) item?.set(_data, { background: config.background }); - if (createItem) { - // Create and assign Item to Collection - item = new Item(this, _data); - this.data[itemKey] = item; + if (createItem) this.collectItem(new Item(this, _data)); - // Rebuild Groups That include ItemKey after assigning Item to Collection (otherwise it can't find Item) - this.rebuildGroupsThatIncludeItemKey(itemKey, { - background: config.background, - }); + // Increase size of Collection if Item was before a placeholder + if (wasPlaceholder) this.size++; + + return true; + } + + public collectItem( + item: Item, + config: { background?: boolean } = {} + ): this { + const itemKey = item[this.config.primaryKey]; + + // Check if Item has valid primaryKey + if ( + !Object.prototype.hasOwnProperty.call(item._value, this.config.primaryKey) + ) { + LogCodeManager.log('1B:02:05', [this._key, this.config.primaryKey]); + item.patch( + { [this.config.primaryKey]: generateId() }, + { background: true } + ); } - // Increase size of Collection - if (createItem || wasPlaceholder) this.size++; + // Check if Item already exists + if (this.getItem(itemKey) != null) return this; - return true; + this.data[itemKey] = item; + + // Rebuild Groups That include ItemKey after assigning Item to Collection (otherwise it can't find Item) + this.rebuildGroupsThatIncludeItemKey(itemKey, { + background: config.background, + }); + + this.size++; + + return this; } //========================================================================================================= diff --git a/packages/core/src/collection/item.ts b/packages/core/src/collection/item.ts index 35843c30..d64f2ad4 100644 --- a/packages/core/src/collection/item.ts +++ b/packages/core/src/collection/item.ts @@ -6,6 +6,10 @@ import { StateRuntimeJobConfigInterface, defineConfig, SelectorKey, + PersistentKey, + isValidObject, + CollectionPersistent, + StatePersistentConfigInterface, } from '../internal'; export class Item extends State< @@ -85,6 +89,64 @@ export class Item extends State< return this; } + //========================================================================================================= + // Persist + //========================================================================================================= + /** + * @public + * Stores Item Value into Agile Storage permanently + * @param config - Config + */ + public persist(config?: ItemPersistConfigInterface): this; + /** + * @public + * Stores Item Value into Agile Storage permanently + * @param key - Key/Name of created Persistent (Note: Key required if Item has no set Key!) + * @param config - Config + */ + public persist( + key?: PersistentKey, + config?: ItemPersistConfigInterface + ): this; + public persist( + keyOrConfig: PersistentKey | ItemPersistConfigInterface = {}, + config: ItemPersistConfigInterface = {} + ): this { + let _config: ItemPersistConfigInterface; + let key: PersistentKey | undefined; + + if (isValidObject(keyOrConfig)) { + _config = keyOrConfig as ItemPersistConfigInterface; + key = this._key; + } else { + _config = config || {}; + key = keyOrConfig as PersistentKey; + } + + _config = defineConfig(_config, { + loadValue: true, + followCollectionPattern: false, + storageKeys: [], + defaultStorageKey: null, + }); + + // Create storageItemKey based on Collection Name + if (_config.followCollectionPersistKeyPattern) { + key = CollectionPersistent.getItemStorageKey( + key || this._key, + this.collection()._key + ); + } + + super.persist(key, { + loadValue: _config.loadValue, + storageKeys: _config.storageKeys, + defaultStorageKey: _config.defaultStorageKey, + }); + + return this; + } + //========================================================================================================= // Add Rebuild Group That Include ItemKey SideEffect //========================================================================================================= @@ -109,3 +171,11 @@ export class Item extends State< export interface ItemConfigInterface { isPlaceholder?: boolean; } + +/** + * @param useCollectionPattern - If Item storageKey follows the Collection Item StorageKey Pattern + */ +export interface ItemPersistConfigInterface + extends StatePersistentConfigInterface { + followCollectionPersistKeyPattern?: boolean; +} diff --git a/packages/core/src/computed/computed.tracker.ts b/packages/core/src/computed/computed.tracker.ts index 48d8b87e..4274f324 100644 --- a/packages/core/src/computed/computed.tracker.ts +++ b/packages/core/src/computed/computed.tracker.ts @@ -4,40 +4,33 @@ export class ComputedTracker { static isTracking = false; static trackedObservers: Set = new Set(); - //========================================================================================================= - // Track - //========================================================================================================= /** * @internal - * Starts tracking Observers + * Activates Computed Tracker to globally track used Observers. */ static track(): void { this.isTracking = true; } - //========================================================================================================= - // Tracked - //========================================================================================================= /** * @internal - * Adds passed Observer to tracked Observers, if ComputedTracker is currently tracking + * Tracks the passed Observer and caches it + * when the Computed Tracker is actively tracking. * @param observer - Observer */ static tracked(observer: Observer) { if (this.isTracking) this.trackedObservers.add(observer); } - //========================================================================================================= - // Get Tracked Observers - //========================================================================================================= /** * @internal - * Returns tracked Observers and stops tracking anymore Observers + * Returns the last tracked Observers + * and stops the Computed Tracker from tracking any more Observers. */ static getTrackedObservers(): Array { const trackedObservers = Array.from(this.trackedObservers); - // Reset tracking + // Reset Computed Tracker this.isTracking = false; this.trackedObservers = new Set(); diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index cb70ad7e..90c24127 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -22,13 +22,13 @@ export class Computed extends State< public hardCodedDeps: Array = []; // Only hardCoded dependencies the Computed depends /** - * A extension of the State Class that computes its value based on a compute function. + * An extension of the State Class that computes its value based on a compute function. * * The computed value will be cached to avoid unnecessary recomputes - * and only recomputed when one of its direct dependencies changed. + * and is only recomputed when one of its direct dependencies changes. * * Direct dependencies can be States and Collections. - * Meaning if a dependent State value changes, the computed value will be recomputed. + * So if for example a dependent State value changes, the computed value will be recomputed. * * [Learn more..](https://agile-ts.org/docs/core/computed/) * @@ -63,7 +63,7 @@ export class Computed extends State< } /** - * Forces a recomputation of the cached value based on the compute function. + * Forces a recomputation of the cached value with the compute function. * * [Learn more..](https://agile-ts.org/docs/core/computed/methods/#recompute) * @@ -82,18 +82,18 @@ export class Computed extends State< } /** - * Assigns new function to the Computed to compute the computed value. + * Assigns a new function to the Computed Class to compute its value. * * The dependencies of the new compute function are automatically detected - * and accordingly updated in the Computed Class. + * and accordingly updated. * - * A initial computation is automatically performed with the new function - * to update the outdated cached value of the Computed. + * An initial computation is automatically performed with the new function + * to change the obsolete cached value. * * [Learn more..](https://agile-ts.org/docs/core/computed/methods/#updatecomputefunction) * * @public - * @param computeFunction - New function to compute the computed value. + * @param computeFunction - New function to compute the value of the Computed Class. * @param deps - Hard coded dependencies on which the Computed Class depends. * @param config - Configuration object */ @@ -125,7 +125,8 @@ export class Computed extends State< } /** - * Computes the value of the Computed Class and autodetects used dependencies in the compute function. + * Computes the new value of the Computed Class + * and autodetects used dependencies in the compute function. * * @internal * @param config - Configuration object @@ -183,21 +184,25 @@ export class Computed extends State< } /** - * @param computedDeps - Hard coded dependencies of compute function + * @property computedDeps - Hard coded dependencies on which the Computed Class depends. + * | Default = [] | */ export interface ComputedConfigInterface extends StateConfigInterface { computedDeps?: Array; } /** - * @param autodetect - If dependencies get autodetected + * @property autodetect - Whether dependencies used in the compute function should be detected automatically. + * | Default = true | */ export interface ComputeConfigInterface { autodetect?: boolean; } /** - * @param overwriteDeps - If old hardCoded deps get overwritten + * @param overwriteDeps - Whether the old hard coded dependencies + * should be overwritten with the new hard coded dependencies or merged in. + * | Default = true | */ export interface UpdateComputeFunctionConfigInterface extends RecomputeConfigInterface { diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index 90155b2c..d40b9cbe 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -407,6 +407,9 @@ export class State { defaultStorageKey: null, }); + // Check if State was already persisted + if (this.persistent != null && this.isPersisted) return this; + // Create persistent -> Persist Value this.persistent = new StatePersistent(this, { instantiate: _config.loadValue, diff --git a/packages/core/src/state/state.observer.ts b/packages/core/src/state/state.observer.ts index 9c540b6b..32160c75 100644 --- a/packages/core/src/state/state.observer.ts +++ b/packages/core/src/state/state.observer.ts @@ -118,7 +118,6 @@ export class StateObserver extends Observer { */ public perform(job: StateRuntimeJob) { const state = job.observer.state(); - const previousValue = copy(state.getPublicValue()); // Assign new State Values state.previousStateValue = copy(state._value); @@ -142,7 +141,7 @@ export class StateObserver extends Observer { // The Observer value is at some point the public Value because Integrations like React are using it as return value. // For example 'useAgile()' returns the Observer.value and not the State.value. job.observer.value = copy(state.getPublicValue()); - job.observer.previousValue = previousValue; + job.observer.previousValue = copy(state.previousStateValue); } //========================================================================================================= diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index 23d2f961..04f1169a 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -107,7 +107,7 @@ export class StatePersistent extends Persistent { if (!loadedValue) return false; // Assign loaded Value to State - this.state().set(loadedValue, { storage: false }); + this.state().set(loadedValue, { storage: false, overwrite: true }); // Persist State, so that the Storage Value updates dynamically if the State updates await this.persistValue(_storageItemKey); @@ -198,18 +198,18 @@ export class StatePersistent extends Persistent { * @internal * Rebuilds Storage depending on the State Value (Saves current State Value into the Storage) * @param state - State that holds the new Value - * @param storageKey - StorageKey where value should be persisted + * @param storageItemKey - StorageKey where value should be persisted * @param config - Config */ public rebuildStorageSideEffect( state: State, - storageKey: PersistentKey, + storageItemKey: PersistentKey, config: { [key: string]: any } = {} ) { if (config.storage !== undefined && !config.storage) return; this.agileInstance().storages.set( - storageKey, + storageItemKey, this.state().getPersistableValue(), this.storageKeys ); diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index 482e74e2..32219fba 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -459,7 +459,7 @@ describe('Collection Tests', () => { dummyGroup2: dummyGroup2, }; - collection.setData = jest.fn(); + collection.assignData = jest.fn(); collection.createSelector = jest.fn(); collection.createGroup = jest.fn(); @@ -469,11 +469,11 @@ describe('Collection Tests', () => { }); it('should add Data to Collection and to default Group (default config)', () => { - collection.setData = jest.fn(() => true); + collection.assignData = jest.fn(() => true); collection.collect({ id: '1', name: 'frank' }); - expect(collection.setData).toHaveBeenCalledWith( + expect(collection.assignData).toHaveBeenCalledWith( { id: '1', name: 'frank', @@ -496,7 +496,7 @@ describe('Collection Tests', () => { }); it('should add Data to Collection and to default Group (specific config)', () => { - collection.setData = jest.fn(() => true); + collection.assignData = jest.fn(() => true); collection.collect({ id: '1', name: 'frank' }, [], { background: true, @@ -504,7 +504,7 @@ describe('Collection Tests', () => { patch: true, }); - expect(collection.setData).toHaveBeenCalledWith( + expect(collection.assignData).toHaveBeenCalledWith( { id: '1', name: 'frank', @@ -527,7 +527,7 @@ describe('Collection Tests', () => { }); it('should add Data to Collection and to passed Groups + default Group (default config)', () => { - collection.setData = jest.fn(() => true); + collection.assignData = jest.fn(() => true); collection.collect( [ @@ -537,7 +537,7 @@ describe('Collection Tests', () => { ['dummyGroup1', 'dummyGroup2'] ); - expect(collection.setData).toHaveBeenCalledWith( + expect(collection.assignData).toHaveBeenCalledWith( { id: '1', name: 'frank', @@ -547,7 +547,7 @@ describe('Collection Tests', () => { background: false, } ); - expect(collection.setData).toHaveBeenCalledWith( + expect(collection.assignData).toHaveBeenCalledWith( { id: '2', name: 'hans', @@ -588,14 +588,14 @@ describe('Collection Tests', () => { }); it("should call setData and shouldn't add Items to passed Groups if setData failed (default config)", () => { - collection.setData = jest.fn(() => false); + collection.assignData = jest.fn(() => false); collection.collect({ id: '1', name: 'frank' }, [ 'dummyGroup1', 'dummyGroup2', ]); - expect(collection.setData).toHaveBeenCalledWith( + expect(collection.assignData).toHaveBeenCalledWith( { id: '1', name: 'frank', @@ -617,7 +617,7 @@ describe('Collection Tests', () => { it("should add Data to Collection and create Groups that doesn't exist yet (default config)", () => { const notExistingGroup = new Group(collection); notExistingGroup.add = jest.fn(); - collection.setData = jest.fn(() => true); + collection.assignData = jest.fn(() => true); collection.createGroup = jest.fn(function (groupKey) { //@ts-ignore this.groups[groupKey] = notExistingGroup; @@ -626,7 +626,7 @@ describe('Collection Tests', () => { collection.collect({ id: '1', name: 'frank' }, 'notExistingGroup'); - expect(collection.setData).toHaveBeenCalledWith( + expect(collection.assignData).toHaveBeenCalledWith( { id: '1', name: 'frank', @@ -653,7 +653,7 @@ describe('Collection Tests', () => { }); it('should create Selector for each Item (config.select)', () => { - collection.setData = jest.fn(() => true); + collection.assignData = jest.fn(() => true); collection.collect( [ @@ -669,7 +669,7 @@ describe('Collection Tests', () => { }); it("should call 'forEachItem' for each Item (default config)", () => { - collection.setData = jest.fn(() => true); + collection.assignData = jest.fn(() => true); const forEachItemMock = jest.fn(); collection.collect( @@ -2368,7 +2368,10 @@ describe('Collection Tests', () => { }); it('should create new Item out of valid Data, rebuild Groups and increase size (default config)', () => { - const response = collection.setData({ id: 'dummyItem2', name: 'Hans' }); + const response = collection.assignData({ + id: 'dummyItem2', + name: 'Hans', + }); expect(response).toBeTruthy(); expect(collection.data).toHaveProperty('dummyItem1'); @@ -2385,7 +2388,7 @@ describe('Collection Tests', () => { }); it("shouldn't create new Item if passed Data is no valid Object", () => { - const response = collection.setData('noObject' as any); + const response = collection.assignData('noObject' as any); expect(response).toBeFalsy(); expect(collection.size).toBe(1); @@ -2397,7 +2400,7 @@ describe('Collection Tests', () => { it('should create new Item with random primaryKey if passed Data has no primaryKey', () => { jest.spyOn(Utils, 'generateId').mockReturnValueOnce('randomDummyId'); - const response = collection.setData({ name: 'Frank' } as any); + const response = collection.assignData({ name: 'Frank' } as any); expect(response).toBeTruthy(); expect(response).toBeTruthy(); @@ -2418,7 +2421,7 @@ describe('Collection Tests', () => { }); it("should update Item with valid Data, shouldn't rebuild Groups and shouldn't increase size (default config)", () => { - const response = collection.setData({ + const response = collection.assignData({ id: 'dummyItem1', name: 'Dieter', }); @@ -2440,7 +2443,7 @@ describe('Collection Tests', () => { }); it("should update Item with valid Data, shouldn't rebuild Groups and shouldn't increase size (config.background = true)", () => { - const response = collection.setData( + const response = collection.assignData( { id: 'dummyItem1', name: 'Dieter', @@ -2465,7 +2468,7 @@ describe('Collection Tests', () => { }); it("should update Item with valid Data, shouldn't rebuild Groups and shouldn't increase size (config.patch = true, background: true)", () => { - const response = collection.setData( + const response = collection.assignData( { id: 'dummyItem1', name: 'Dieter', @@ -2493,7 +2496,7 @@ describe('Collection Tests', () => { dummyItem1.isPlaceholder = true; collection.size = 0; - const response = collection.setData({ + const response = collection.assignData({ id: 'dummyItem1', name: 'Dieter', }); From 924bfb764e7a40cd9a4cfaad6ec56bcd72a6310c Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 29 May 2021 14:44:59 +0200 Subject: [PATCH 021/117] added storage logs --- .../src/collection/collection.persistent.ts | 3 +- packages/core/src/logCodeManager.ts | 5 ++- packages/core/src/state/state.persistent.ts | 2 +- packages/core/src/storages/storage.ts | 44 ++++++++++++++++--- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index d9dd78c5..49a5207f 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -1,4 +1,5 @@ import { + Agile, Collection, CollectionKey, CreatePersistentConfigInterface, @@ -144,7 +145,7 @@ export class CollectionPersistent< // Persist already existing Item if (item != null) { item.persist(itemPersistKey); - return true; + continue; } // Create temporary placeholder Item in which the Item value will be loaded diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index 1f098fbd..895f2bc6 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -21,7 +21,7 @@ const logCodeMessages = { '10:02:00': 'Be careful when binding multiple Agile Instances globally in one application!', - // Storage + // Storages '11:02:00': "The 'Local Storage' is not available in your current environment." + "To use the '.persist()' functionality, please provide a custom Storage!", @@ -49,6 +49,9 @@ const logCodeMessages = { "The Storage with the key/name '${1}' doesn't exists!`", // Storage + '13:01:00': "GET value at key '${1}' from Storage '${0}'.", + '13:01:01': "SET value at key '${1}' in Storage '${0}'.", + '13:01:02': "REMOVE value at key '${1}' from Storage '${0}'.", '13:02:00': 'Using normalGet() in a async-based Storage might result in an unexpected return value. ' + 'Instead of a resolved value a Promise is returned!', diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index 04f1169a..7d75d9c4 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -104,7 +104,7 @@ export class StatePersistent extends Persistent { _storageItemKey, this.config.defaultStorageKey as any ); - if (!loadedValue) return false; + if (loadedValue == null) return false; // Assign loaded Value to State this.state().set(loadedValue, { storage: false, overwrite: true }); diff --git a/packages/core/src/storages/storage.ts b/packages/core/src/storages/storage.ts index ed8005be..f8751e4a 100644 --- a/packages/core/src/storages/storage.ts +++ b/packages/core/src/storages/storage.ts @@ -74,13 +74,21 @@ export class Storage { * @param key - Key of Storage property */ public normalGet(key: StorageItemKey): GetTpe | undefined { - if (!this.ready || !this.methods.get) return; + if (!this.ready || !this.methods.get) return undefined; if (isAsyncFunction(this.methods.get)) LogCodeManager.log('13:02:00'); // Get Value const res = this.methods.get(this.getStorageKey(key)); - if (isJsonString(res)) return JSON.parse(res); - return res; + const _res = isJsonString(res) ? JSON.parse(res) : res; + + Agile.logger.if + .tag(['storage']) + .info( + LogCodeManager.getLog('13:01:00', [this.key, this.getStorageKey(key)]), + _res + ); + + return _res; } //========================================================================================================= @@ -103,8 +111,19 @@ export class Storage { this.methods ?.get(this.getStorageKey(key)) .then((res: any) => { - if (isJsonString(res)) resolve(JSON.parse(res)); - resolve(res); + const _res = isJsonString(res) ? JSON.parse(res) : res; + + Agile.logger.if + .tag(['storage']) + .info( + LogCodeManager.getLog('13:01:00', [ + this.key, + this.getStorageKey(key), + ]), + _res + ); + + resolve(_res); }) .catch(reject); }); @@ -121,6 +140,14 @@ export class Storage { */ public set(key: StorageItemKey, value: any): void { if (!this.ready || !this.methods.set) return; + + Agile.logger.if + .tag(['storage']) + .info( + LogCodeManager.getLog('13:01:01', [this.key, this.getStorageKey(key)]), + value + ); + this.methods.set(this.getStorageKey(key), JSON.stringify(value)); } @@ -134,6 +161,13 @@ export class Storage { */ public remove(key: StorageItemKey): void { if (!this.ready || !this.methods.remove) return; + + Agile.logger.if + .tag(['storage']) + .info( + LogCodeManager.getLog('13:01:02', [this.key, this.getStorageKey(key)]) + ); + this.methods.remove(this.getStorageKey(key)); } From 0ca8245ae55be69b8b4e8eb9e08e14f53151da83 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 29 May 2021 15:37:59 +0200 Subject: [PATCH 022/117] outsourced storage sideEffects --- .../src/collection/collection.persistent.ts | 55 ++++++++++++------- packages/core/src/state/state.persistent.ts | 32 ++++++++--- 2 files changed, 57 insertions(+), 30 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 49a5207f..94309d0f 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -1,5 +1,4 @@ import { - Agile, Collection, CollectionKey, CreatePersistentConfigInterface, @@ -99,14 +98,14 @@ export class CollectionPersistent< * Loads the values of the Collection Instances from the corresponding Storage. * * @internal - * @param storageItemKey - Prefix Key of persisted Collection Instances | default = Persistent.key | + * @param storageItemKey - Prefix key of persisted Collection Instances | default = Persistent.key | * @return Whether the loading was successful. */ public async loadPersistedValue( storageItemKey?: PersistentKey ): Promise { if (!this.ready) return false; - const _storageItemKey = storageItemKey || this._key; + const _storageItemKey = storageItemKey ?? this._key; // Check if Collection is already persisted (indicated by the persistence of true at _storageItemKey) const isPersisted = await this.agileInstance().storages.get( @@ -177,9 +176,8 @@ export class CollectionPersistent< }; const success = await loadValuesIntoCollection(); - // 'Persist' Collection to setup side effects - // that automatically update the corresponding Storage value if the Collection updates - if (success) await this.persistValue(_storageItemKey); + // Setup Side Effects to keep the Storage value in sync with the State value + if (success) this.setupSideEffects(storageItemKey); return success; } @@ -189,12 +187,12 @@ export class CollectionPersistent< * and sets up side effects that dynamically update the storage value when the Collection changes. * * @internal - * @param storageItemKey - Prefix Key of persisted Collection Instances | default = Persistent.key | + * @param storageItemKey - Prefix key of persisted Collection Instances | default = Persistent.key | * @return Whether the persisting and the setting up of the side effects was successful. */ public async persistValue(storageItemKey?: PersistentKey): Promise { if (!this.ready) return false; - const _storageItemKey = storageItemKey || this._key; + const _storageItemKey = storageItemKey ?? this._key; const defaultGroup = this.collection().getDefaultGroup(); if (!defaultGroup) return false; const defaultGroupPersistKey = CollectionPersistent.getGroupStorageKey( @@ -206,15 +204,7 @@ export class CollectionPersistent< this.agileInstance().storages.set(_storageItemKey, true, this.storageKeys); // Persist default Group - if (!defaultGroup.isPersisted) defaultGroup.persist(defaultGroupPersistKey); - - // Add side effect to default Group - // that adds or removes Items from the Storage depending on the Group value - defaultGroup.addSideEffect( - CollectionPersistent.defaultGroupSideEffectKey, - () => this.rebuildStorageSideEffect(defaultGroup, _storageItemKey), - { weight: 0 } - ); + defaultGroup.persist(defaultGroupPersistKey); // Persist Items found in the default Group's value for (const itemKey of defaultGroup._value) { @@ -223,26 +213,49 @@ export class CollectionPersistent< itemKey, _storageItemKey ); - if (!item?.isPersisted) item?.persist(itemPersistKey); + item?.persist(itemPersistKey); } + // Setup Side Effects to keep the Storage value in sync with the Collection value + this.setupSideEffects(_storageItemKey); + this.isPersisted = true; return true; } + /** + * Sets up side effects to keep the Storage value in sync with the Collection value + * + * @internal + * @param storageItemKey - Prefix key of persisted Collection Instances | default = Persistent.key | + */ + public setupSideEffects(storageItemKey?: PersistentKey): void { + const _storageItemKey = storageItemKey ?? this._key; + const defaultGroup = this.collection().getDefaultGroup(); + if (!defaultGroup) return; + + // Add side effect to default Group + // that adds or removes Items from the Storage based on the Group value + defaultGroup.addSideEffect( + CollectionPersistent.defaultGroupSideEffectKey, + () => this.rebuildStorageSideEffect(defaultGroup, _storageItemKey), + { weight: 0 } + ); + } + /** * Removes Collection from the corresponding Storage. * -> Collection is no longer persisted * * @internal - * @param storageItemKey - Prefix Key of persisted Collection Instances | default = Persistent.key | + * @param storageItemKey - Prefix key of persisted Collection Instances | default = Persistent.key | * @return Whether the removing was successful. */ public async removePersistedValue( storageItemKey?: PersistentKey ): Promise { if (!this.ready) return false; - const _storageItemKey = storageItemKey || this._key; + const _storageItemKey = storageItemKey ?? this._key; const defaultGroup = this.collection().getDefaultGroup(); if (!defaultGroup) return false; const defaultGroupPersistKey = CollectionPersistent.getGroupStorageKey( @@ -293,7 +306,7 @@ export class CollectionPersistent< * * @internal * @param group - Group - * @param storageItemKey - Prefix Key of persisted Collection Instances | default = Persistent.key | + * @param storageItemKey - Prefix key of persisted Collection Instances | default = Persistent.key | */ public rebuildStorageSideEffect( group: Group, diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index 7d75d9c4..8736869a 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -109,8 +109,8 @@ export class StatePersistent extends Persistent { // Assign loaded Value to State this.state().set(loadedValue, { storage: false, overwrite: true }); - // Persist State, so that the Storage Value updates dynamically if the State updates - await this.persistValue(_storageItemKey); + // Setup Side Effects to keep the Storage value in sync with the State value + this.setupSideEffects(storageItemKey); return true; } @@ -128,7 +128,27 @@ export class StatePersistent extends Persistent { if (!this.ready) return false; const _storageItemKey = storageItemKey ?? this._key; - // Add SideEffect to State, that updates the saved State Value depending on the current State Value + // Setup side effects to keep the Storage value in sync with the State value + this.setupSideEffects(storageItemKey); + + // Initial rebuild Storage for persisting State value in the corresponding Storage + this.rebuildStorageSideEffect(this.state(), _storageItemKey); + + this.isPersisted = true; + return true; + } + + /** + * Sets up side effects to keep the Storage value in sync with the State value. + * + * @internal + * @param storageItemKey - Prefix key of persisted Collection Instances | default = Persistent.key | + */ + public setupSideEffects(storageItemKey?: PersistentKey) { + const _storageItemKey = storageItemKey ?? this._key; + + // Add side effect to State + // that updates the Storage value based on the State value this.state().addSideEffect( StatePersistent.storeValueSideEffectKey, (instance, config) => { @@ -136,12 +156,6 @@ export class StatePersistent extends Persistent { }, { weight: 0 } ); - - // Initial rebuild Storage for saving State Value in the Storage - this.rebuildStorageSideEffect(this.state(), _storageItemKey); - - this.isPersisted = true; - return true; } //========================================================================================================= From 4610a0ce39a82414f7051fc5ad3f887e47d4b132 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 29 May 2021 16:39:09 +0200 Subject: [PATCH 023/117] fixed typos --- .../src/collection/collection.persistent.ts | 61 ++++++++++++------- packages/core/src/collection/index.ts | 9 ++- .../collection.persistent.integration.test.ts | 4 +- .../collection/collection.persistent.test.ts | 30 ++++----- 4 files changed, 64 insertions(+), 40 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 94309d0f..13a0fd1c 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -119,16 +119,18 @@ export class CollectionPersistent< // Helper function to load persisted values into the Collection const loadValuesIntoCollection = async () => { const defaultGroup = this.collection().getDefaultGroup(); - if (!defaultGroup) return false; - const defaultGroupPersistKey = CollectionPersistent.getGroupStorageKey( + if (defaultGroup == null) return false; + const defaultGroupStorageKey = CollectionPersistent.getGroupStorageKey( defaultGroup._key, _storageItemKey ); // Persist default Group and load its value manually to be 100% sure // that it was loaded completely - defaultGroup.persist(defaultGroupPersistKey, { + defaultGroup.persist(defaultGroupStorageKey, { loadValue: false, + defaultStorageKey: this.config.defaultStorageKey || undefined, + storageKeys: this.storageKeys, }); if (defaultGroup.persistent?.ready) await defaultGroup.persistent.initialLoading(); @@ -136,14 +138,17 @@ export class CollectionPersistent< // Persist Items found in the default Group's value for (const itemKey of defaultGroup._value) { const item = this.collection().getItem(itemKey); - const itemPersistKey = CollectionPersistent.getItemStorageKey( + const itemStorageKey = CollectionPersistent.getItemStorageKey( itemKey, _storageItemKey ); // Persist already existing Item if (item != null) { - item.persist(itemPersistKey); + item.persist(itemStorageKey, { + defaultStorageKey: this.config.defaultStorageKey || undefined, + storageKeys: this.storageKeys, + }); continue; } @@ -159,12 +164,14 @@ export class CollectionPersistent< // Persist dummy Item and load its value manually to be 100% sure // that it was loaded completely and exists - dummyItem?.persist(itemPersistKey, { + dummyItem?.persist(itemStorageKey, { loadValue: false, + defaultStorageKey: this.config.defaultStorageKey || undefined, + storageKeys: this.storageKeys, }); if (dummyItem?.persistent?.ready) { const success = await dummyItem.persistent.loadPersistedValue( - itemPersistKey + itemStorageKey ); // If successfully loaded add Item to Collection @@ -194,8 +201,8 @@ export class CollectionPersistent< if (!this.ready) return false; const _storageItemKey = storageItemKey ?? this._key; const defaultGroup = this.collection().getDefaultGroup(); - if (!defaultGroup) return false; - const defaultGroupPersistKey = CollectionPersistent.getGroupStorageKey( + if (defaultGroup == null) return false; + const defaultGroupStorageKey = CollectionPersistent.getGroupStorageKey( defaultGroup._key, _storageItemKey ); @@ -204,16 +211,22 @@ export class CollectionPersistent< this.agileInstance().storages.set(_storageItemKey, true, this.storageKeys); // Persist default Group - defaultGroup.persist(defaultGroupPersistKey); + defaultGroup.persist(defaultGroupStorageKey, { + defaultStorageKey: this.config.defaultStorageKey || undefined, + storageKeys: this.storageKeys, + }); // Persist Items found in the default Group's value for (const itemKey of defaultGroup._value) { const item = this.collection().getItem(itemKey); - const itemPersistKey = CollectionPersistent.getItemStorageKey( + const itemStorageKey = CollectionPersistent.getItemStorageKey( itemKey, _storageItemKey ); - item?.persist(itemPersistKey); + item?.persist(itemStorageKey, { + defaultStorageKey: this.config.defaultStorageKey || undefined, + storageKeys: this.storageKeys, + }); } // Setup Side Effects to keep the Storage value in sync with the Collection value @@ -232,7 +245,7 @@ export class CollectionPersistent< public setupSideEffects(storageItemKey?: PersistentKey): void { const _storageItemKey = storageItemKey ?? this._key; const defaultGroup = this.collection().getDefaultGroup(); - if (!defaultGroup) return; + if (defaultGroup == null) return; // Add side effect to default Group // that adds or removes Items from the Storage based on the Group value @@ -258,7 +271,7 @@ export class CollectionPersistent< const _storageItemKey = storageItemKey ?? this._key; const defaultGroup = this.collection().getDefaultGroup(); if (!defaultGroup) return false; - const defaultGroupPersistKey = CollectionPersistent.getGroupStorageKey( + const defaultGroupStorageKey = CollectionPersistent.getGroupStorageKey( defaultGroup._key, _storageItemKey ); @@ -267,7 +280,7 @@ export class CollectionPersistent< this.agileInstance().storages.remove(_storageItemKey, this.storageKeys); // Remove default Group from the Storage - defaultGroup.persistent?.removePersistedValue(defaultGroupPersistKey); + defaultGroup.persistent?.removePersistedValue(defaultGroupStorageKey); defaultGroup.removeSideEffect( CollectionPersistent.defaultGroupSideEffectKey ); @@ -275,11 +288,11 @@ export class CollectionPersistent< // Remove Items found in the default Group's value from the Storage for (const itemKey of defaultGroup._value) { const item = this.collection().getItem(itemKey); - const itemPersistKey = CollectionPersistent.getItemStorageKey( + const itemStorageKey = CollectionPersistent.getItemStorageKey( itemKey, _storageItemKey ); - item?.persistent?.removePersistedValue(itemPersistKey); + item?.persistent?.removePersistedValue(itemStorageKey); } this.isPersisted = false; @@ -330,26 +343,30 @@ export class CollectionPersistent< // Persist newly added Items addedKeys.forEach((itemKey) => { const item = collection.getItem(itemKey); - const itemPersistKey = CollectionPersistent.getItemStorageKey( + const itemStorageKey = CollectionPersistent.getItemStorageKey( itemKey, _storageItemKey ); if (item != null) { - if (!item.isPersisted) item.persist(itemPersistKey); - else item.persistent?.persistValue(itemPersistKey); + if (!item.isPersisted) + item.persist(itemStorageKey, { + defaultStorageKey: this.config.defaultStorageKey || undefined, + storageKeys: this.storageKeys, + }); + else item.persistent?.persistValue(itemStorageKey); } }); // Remove removed Items from the Storage removedKeys.forEach((itemKey) => { const item = collection.getItem(itemKey); - const itemPersistKey = CollectionPersistent.getItemStorageKey( + const itemStorageKey = CollectionPersistent.getItemStorageKey( itemKey, _storageItemKey ); if (item != null) if (item.isPersisted) - item.persistent?.removePersistedValue(itemPersistKey); + item.persistent?.removePersistedValue(itemStorageKey); }); } diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index f3a31c99..38b9b9bb 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -1192,11 +1192,18 @@ export class Collection { return true; } + /** + * Adds passed Item to Collection. + * + * @public + * @param item - Item to be added + * @param config - Configuration object + */ public collectItem( item: Item, config: { background?: boolean } = {} ): this { - const itemKey = item[this.config.primaryKey]; + const itemKey = item._value[this.config.primaryKey]; // Check if Item has valid primaryKey if ( diff --git a/packages/core/tests/integration/collection.persistent.integration.test.ts b/packages/core/tests/integration/collection.persistent.integration.test.ts index 20215777..203fbfd4 100644 --- a/packages/core/tests/integration/collection.persistent.integration.test.ts +++ b/packages/core/tests/integration/collection.persistent.integration.test.ts @@ -1,5 +1,5 @@ import { Agile, Item } from '../../src'; -import mockConsole from 'jest-mock-console'; +import { LogMock } from '../helper/logMock'; describe('Collection Persist Function Tests', () => { const myStorage: any = {}; @@ -34,8 +34,8 @@ describe('Collection Persist Function Tests', () => { } beforeEach(() => { + LogMock.mockLogs(); jest.clearAllMocks(); - mockConsole(['error', 'warn']); }); describe('Collection', () => { diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index c3e4ebb3..bb4b73df 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -289,15 +289,15 @@ describe('CollectionPersistent Tests', () => { .mockReturnValueOnce({ id: '1', name: 'hans' }) .mockReturnValueOnce(undefined) .mockReturnValueOnce({ id: '3', name: 'frank' }); - dummyCollection.getGroup = jest.fn(() => dummyDefaultGroup as any); + dummyCollection.getDefaultGroup = jest.fn( + () => dummyDefaultGroup as any + ); const response = await collectionPersistent.loadPersistedValue(); expect(response).toBeTruthy(); - expect(dummyCollection.getGroup).toHaveBeenCalledWith( - dummyCollection.config.defaultGroupKey - ); + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); expect(dummyAgile.storages.get).toHaveBeenCalledWith( collectionPersistent._key, @@ -352,13 +352,15 @@ describe('CollectionPersistent Tests', () => { dummyAgile.storages.get = jest .fn() .mockReturnValueOnce(Promise.resolve(undefined)); - dummyCollection.getGroup = jest.fn(() => dummyDefaultGroup as any); + dummyCollection.getDefaultGroup = jest.fn( + () => dummyDefaultGroup as any + ); const response = await collectionPersistent.loadPersistedValue(); expect(response).toBeFalsy(); - expect(dummyCollection.getGroup).not.toHaveBeenCalled(); + expect(dummyCollection.getDefaultGroup).not.toHaveBeenCalled(); expect(dummyAgile.storages.get).toHaveBeenCalledWith( collectionPersistent._key, @@ -413,9 +415,7 @@ describe('CollectionPersistent Tests', () => { expect(response).toBeTruthy(); - expect(dummyCollection.getGroup).toHaveBeenCalledWith( - dummyCollection.config.defaultGroupKey - ); + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); expect(dummyAgile.storages.get).toHaveBeenCalledWith( 'dummyKey', @@ -461,13 +461,15 @@ describe('CollectionPersistent Tests', () => { dummyAgile.storages.get = jest.fn(() => Promise.resolve(undefined as any) ); - dummyCollection.getGroup = jest.fn(() => dummyDefaultGroup as any); + dummyCollection.getDefaultGroup = jest.fn( + () => dummyDefaultGroup as any + ); const response = await collectionPersistent.loadPersistedValue(); expect(response).toBeFalsy(); - expect(dummyCollection.getGroup).not.toHaveBeenCalled(); + expect(dummyCollection.getDefaultGroup).not.toHaveBeenCalled(); expect(dummyAgile.storages.get).not.toHaveBeenCalled(); @@ -488,15 +490,13 @@ describe('CollectionPersistent Tests', () => { dummyAgile.storages.get = jest .fn() .mockReturnValueOnce(Promise.resolve(true)); - dummyCollection.getGroup = jest.fn(() => undefined); + dummyCollection.getDefaultGroup = jest.fn(() => undefined); const response = await collectionPersistent.loadPersistedValue(); expect(response).toBeFalsy(); - expect(dummyCollection.getGroup).toHaveBeenCalledWith( - dummyCollection.config.defaultGroupKey - ); + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); expect(dummyAgile.storages.get).toHaveBeenCalledWith( collectionPersistent._key, From f73d6b4a18e5091be5909b3538e85dc429861225 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 29 May 2021 18:40:09 +0200 Subject: [PATCH 024/117] started fixing collection persistent tests --- .../src/collection/collection.persistent.ts | 18 +- packages/core/src/collection/index.ts | 43 +- .../collection/collection.persistent.test.ts | 474 ++++++++++-------- 3 files changed, 312 insertions(+), 223 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 13a0fd1c..21d7c713 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -153,14 +153,7 @@ export class CollectionPersistent< } // Create temporary placeholder Item in which the Item value will be loaded - const dummyItem = new Item( - this.collection(), - { - [this.collection().config.primaryKey]: itemKey, // Setting PrimaryKey of Item to passed itemKey - dummy: 'item', - } as any, - { isPlaceholder: true } - ); + const dummyItem = this.collection().createPlaceholderItem(itemKey); // Persist dummy Item and load its value manually to be 100% sure // that it was loaded completely and exists @@ -170,12 +163,13 @@ export class CollectionPersistent< storageKeys: this.storageKeys, }); if (dummyItem?.persistent?.ready) { - const success = await dummyItem.persistent.loadPersistedValue( + const loadedPersistedValueIntoItem = await dummyItem.persistent.loadPersistedValue( itemStorageKey ); - // If successfully loaded add Item to Collection - if (success) this.collection().collectItem(dummyItem); + // If successfully loaded Item value add Item to Collection + if (loadedPersistedValueIntoItem) + this.collection().collectItem(dummyItem); } } @@ -184,7 +178,7 @@ export class CollectionPersistent< const success = await loadValuesIntoCollection(); // Setup Side Effects to keep the Storage value in sync with the State value - if (success) this.setupSideEffects(storageItemKey); + if (success) this.setupSideEffects(_storageItemKey); return success; } diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 38b9b9bb..55821c8e 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -678,24 +678,41 @@ export class Collection { let item = this.getItem(itemKey, { notExisting: true }); // Create dummy Item to hold reference - if (item == null) { - item = new Item( - this, - { - [this.config.primaryKey]: itemKey, // Setting PrimaryKey of Item to passed itemKey - dummy: 'item', - } as any, - { - isPlaceholder: true, - } - ); - this.data[itemKey] = item; - } + if (item == null) item = this.createPlaceholderItem(itemKey, true); ComputedTracker.tracked(item.observer); return item; } + /** + * Creates a placeholder Item + * that can be used to hold a reference to an Item that doesn't yet exist. + * + * @private + * @param itemKey - Key/Name identifier of the Item to be created + * @param addToCollection - Whether the created Item should be added to the Collection + */ + public createPlaceholderItem( + itemKey: ItemKey, + addToCollection = false + ): Item { + const item = new Item( + this, + { + [this.config.primaryKey]: itemKey, // Setting PrimaryKey of Item to passed itemKey + dummy: 'item', + } as any, + { isPlaceholder: true } + ); + if ( + addToCollection && + !Object.prototype.hasOwnProperty.call(this.data, itemKey) + ) + this.data[itemKey] = item; + + return item; + } + //========================================================================================================= // Get Value by Id //========================================================================================================= diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index bb4b73df..ccdcd974 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -91,7 +91,7 @@ describe('CollectionPersistent Tests', () => { expect(collectionPersistent.onLoad).toBeUndefined(); expect(collectionPersistent.storageKeys).toStrictEqual([]); expect(collectionPersistent.config).toStrictEqual({ - defaultStorageKey: null, // gets set in 'instantiatePersistent' which is mocked + defaultStorageKey: null, // is assigned in 'instantiatePersistent' which is mocked }); }); @@ -152,18 +152,21 @@ describe('CollectionPersistent Tests', () => { name: 'frank', }); dummyItem1.persistent = new StatePersistent(dummyItem1); + dummyItem1.persist = jest.fn(); dummyItem2 = new Item(dummyCollection, { id: '2', name: 'dieter', }); dummyItem2.persistent = new StatePersistent(dummyItem2); + dummyItem2.persist = jest.fn(); dummyItem3 = new Item(dummyCollection, { id: '3', name: 'hans', }); dummyItem3.persistent = new StatePersistent(dummyItem3); + dummyItem3.persist = jest.fn(); dummyItem4WithoutPersistent = new Item(dummyCollection, { id: '4', @@ -263,197 +266,293 @@ describe('CollectionPersistent Tests', () => { describe('loadPersistedValue function tests', () => { let dummyDefaultGroup: Group; + let placeholderItem1: Item; + let placeholderItem2: Item; + let placeholderItem3: Item; beforeEach(() => { collectionPersistent.config.defaultStorageKey = 'test'; - dummyDefaultGroup = new Group(dummyCollection, ['1', '2', '3']); - dummyDefaultGroup.persistent = new StatePersistent(dummyDefaultGroup); - if (dummyDefaultGroup.persistent) - dummyDefaultGroup.persistent.ready = true; - - collectionPersistent.persistValue = jest.fn(); + placeholderItem1 = dummyCollection.createPlaceholderItem('1'); + placeholderItem1.persist = jest.fn(); + placeholderItem2 = dummyCollection.createPlaceholderItem('2'); + placeholderItem2.persist = jest.fn(); + placeholderItem3 = dummyCollection.createPlaceholderItem('3'); + placeholderItem3.persist = jest.fn(); + dummyDefaultGroup = new Group(dummyCollection, ['1', '2', '3'], { + key: 'default', + }); dummyDefaultGroup.persist = jest.fn(); - if (dummyDefaultGroup.persistent) + dummyDefaultGroup.persistent = new StatePersistent(dummyDefaultGroup); + if (dummyDefaultGroup.persistent) { + dummyDefaultGroup.persistent.ready = true; dummyDefaultGroup.persistent.initialLoading = jest.fn(); + } - dummyCollection.collect = jest.fn(); + collectionPersistent.setupSideEffects = jest.fn(); + + dummyCollection.getDefaultGroup = jest.fn( + () => dummyDefaultGroup as any + ); + dummyCollection.collectItem = jest.fn(); }); - it('should load default Group and its Items with the persistentKey and apply it to the Collection if loading was successful', async () => { + it('should load defaultGroup and Items that are already present in Collection and apply the loaded value to the Item (persistentKey)', async () => { collectionPersistent.ready = true; + dummyCollection.data = { + ['3']: dummyItem3, + }; dummyAgile.storages.get = jest .fn() - .mockReturnValueOnce(Promise.resolve(true)) - .mockReturnValueOnce({ id: '1', name: 'hans' }) - .mockReturnValueOnce(undefined) - .mockReturnValueOnce({ id: '3', name: 'frank' }); - dummyCollection.getDefaultGroup = jest.fn( - () => dummyDefaultGroup as any - ); + .mockReturnValueOnce(Promise.resolve(true)); + dummyDefaultGroup._value = ['3']; const response = await collectionPersistent.loadPersistedValue(); expect(response).toBeTruthy(); - - expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( collectionPersistent._key, collectionPersistent.config.defaultStorageKey ); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey( - '1', - collectionPersistent._key - ), - collectionPersistent.config.defaultStorageKey - ); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey( - '2', + + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); + expect(dummyDefaultGroup.persist).toHaveBeenCalledWith( + CollectionPersistent.getGroupStorageKey( + dummyDefaultGroup._key, collectionPersistent._key ), - collectionPersistent.config.defaultStorageKey + { + loadValue: false, + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } ); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( + expect(dummyDefaultGroup.persistent?.initialLoading).toHaveBeenCalled(); + expect(dummyDefaultGroup.isPersisted).toBeTruthy(); + + expect(dummyItem3.persist).toHaveBeenCalledWith( CollectionPersistent.getItemStorageKey( '3', collectionPersistent._key ), - collectionPersistent.config.defaultStorageKey + { + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } ); - expect(dummyDefaultGroup.persist).toHaveBeenCalledWith({ - loadValue: false, - followCollectionPersistKeyPattern: true, - }); - expect(dummyDefaultGroup.persistent?.initialLoading).toHaveBeenCalled(); - expect(dummyDefaultGroup.isPersisted).toBeTruthy(); - - expect(dummyCollection.collect).toHaveBeenCalledWith({ - id: '1', - name: 'hans', - }); - expect(dummyCollection.collect).not.toHaveBeenCalledWith(undefined); - expect(dummyCollection.collect).toHaveBeenCalledWith({ - id: '3', - name: 'frank', - }); - - expect(collectionPersistent.persistValue).toHaveBeenCalledWith( + expect(collectionPersistent.setupSideEffects).toHaveBeenCalledWith( collectionPersistent._key ); }); - it("shouldn't load default Group and its Items with the persistentKey and shouldn't apply it to the Collection if loading wasn't successful", async () => { + it("should load default Group and create persisted Items that aren't present in Collection yet (persistentKey)", async () => { collectionPersistent.ready = true; + dummyCollection.data = {}; dummyAgile.storages.get = jest .fn() - .mockReturnValueOnce(Promise.resolve(undefined)); - dummyCollection.getDefaultGroup = jest.fn( - () => dummyDefaultGroup as any - ); + .mockReturnValueOnce(Promise.resolve(true)); + placeholderItem1.persist = jest.fn(function () { + placeholderItem1.persistent = new StatePersistent(placeholderItem1); + placeholderItem1.persistent.ready = true; + placeholderItem1.persistent.loadPersistedValue = jest + .fn() + .mockReturnValueOnce(true); + return null as any; + }); + placeholderItem2.persist = jest.fn(function () { + placeholderItem2.persistent = new StatePersistent(placeholderItem2); + placeholderItem2.persistent.ready = false; + placeholderItem2.persistent.loadPersistedValue = jest.fn(); + return null as any; + }); + placeholderItem3.persist = jest.fn(function () { + placeholderItem3.persistent = new StatePersistent(placeholderItem3); + placeholderItem3.persistent.ready = true; + placeholderItem3.persistent.loadPersistedValue = jest + .fn() + .mockReturnValueOnce(false); + return null as any; + }); + dummyCollection.createPlaceholderItem = jest + .fn() + .mockReturnValueOnce(placeholderItem1) + .mockReturnValueOnce(placeholderItem2) + .mockReturnValueOnce(placeholderItem3); + dummyDefaultGroup._value = ['1', '2', '3']; const response = await collectionPersistent.loadPersistedValue(); - expect(response).toBeFalsy(); - - expect(dummyCollection.getDefaultGroup).not.toHaveBeenCalled(); - + expect(response).toBeTruthy(); expect(dummyAgile.storages.get).toHaveBeenCalledWith( collectionPersistent._key, collectionPersistent.config.defaultStorageKey ); - expect(dummyAgile.storages.get).not.toHaveBeenCalledWith( + + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); + expect(dummyDefaultGroup.persist).toHaveBeenCalledWith( + CollectionPersistent.getGroupStorageKey( + dummyDefaultGroup._key, + collectionPersistent._key + ), + { + loadValue: false, + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } + ); + expect(dummyDefaultGroup.persistent?.initialLoading).toHaveBeenCalled(); + expect(dummyDefaultGroup.isPersisted).toBeTruthy(); + + expect(dummyCollection.createPlaceholderItem).toHaveBeenCalledWith('1'); + expect(dummyCollection.createPlaceholderItem).toHaveBeenCalledWith('2'); + expect(dummyCollection.createPlaceholderItem).toHaveBeenCalledWith('3'); + expect(placeholderItem1.persist).toHaveBeenCalledWith( CollectionPersistent.getItemStorageKey( '1', collectionPersistent._key ), - collectionPersistent.config.defaultStorageKey + { + loadValue: false, + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } ); - expect(dummyAgile.storages.get).not.toHaveBeenCalledWith( + expect(placeholderItem2.persist).toHaveBeenCalledWith( CollectionPersistent.getItemStorageKey( '2', collectionPersistent._key ), - collectionPersistent.config.defaultStorageKey + { + loadValue: false, + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } ); - expect(dummyAgile.storages.get).not.toHaveBeenCalledWith( + expect(placeholderItem3.persist).toHaveBeenCalledWith( CollectionPersistent.getItemStorageKey( '3', collectionPersistent._key ), - collectionPersistent.config.defaultStorageKey + { + loadValue: false, + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } + ); + expect(dummyCollection.collectItem).toHaveBeenCalledWith( + placeholderItem1 + ); + expect(dummyCollection.collectItem).not.toHaveBeenCalledWith( + placeholderItem2 + ); // Because Item persistent isn't ready + expect(dummyCollection.collectItem).not.toHaveBeenCalledWith( + placeholderItem3 + ); // Because Item persistent 'leadPersistedValue()' returned false + + expect(collectionPersistent.setupSideEffects).toHaveBeenCalledWith( + collectionPersistent._key ); - - expect(dummyDefaultGroup.persist).not.toHaveBeenCalled(); - expect( - dummyDefaultGroup.persistent?.initialLoading - ).not.toHaveBeenCalled(); - expect(dummyDefaultGroup.isPersisted).toBeFalsy(); - - expect(dummyCollection.collect).not.toHaveBeenCalled(); - - expect(collectionPersistent.persistValue).not.toHaveBeenCalled(); }); - it('should load default Group and its Items with a specific Key and should apply it to the Collection if loading was successful', async () => { + it("should load defaultGroup and Items that are or aren't already present in Collection and apply the loaded value to the Item (specific key)", async () => { collectionPersistent.ready = true; + dummyCollection.data = { + ['3']: dummyItem3, + }; dummyAgile.storages.get = jest .fn() - .mockReturnValueOnce(Promise.resolve(true)) - .mockReturnValueOnce({ id: '1', name: 'hans' }) - .mockReturnValueOnce(undefined) - .mockReturnValueOnce({ id: '3', name: 'frank' }); - dummyCollection.getGroup = jest.fn(() => dummyDefaultGroup as any); + .mockReturnValueOnce(Promise.resolve(true)); + placeholderItem1.persist = jest.fn(function () { + placeholderItem1.persistent = new StatePersistent(placeholderItem1); + placeholderItem1.persistent.ready = true; + placeholderItem1.persistent.loadPersistedValue = jest + .fn() + .mockReturnValueOnce(true); + return null as any; + }); + dummyCollection.createPlaceholderItem = jest + .fn() + .mockReturnValueOnce(placeholderItem1); + dummyDefaultGroup._value = ['1', '3']; const response = await collectionPersistent.loadPersistedValue( 'dummyKey' ); expect(response).toBeTruthy(); - - expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( 'dummyKey', collectionPersistent.config.defaultStorageKey ); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( + + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); + expect(dummyDefaultGroup.persist).toHaveBeenCalledWith( + CollectionPersistent.getGroupStorageKey( + dummyDefaultGroup._key, + 'dummyKey' + ), + { + loadValue: false, + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } + ); + expect(dummyDefaultGroup.persistent?.initialLoading).toHaveBeenCalled(); + expect(dummyDefaultGroup.isPersisted).toBeTruthy(); + + expect(dummyItem3.persist).toHaveBeenCalledWith( + CollectionPersistent.getItemStorageKey('3', 'dummyKey'), + { + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } + ); + + expect(dummyCollection.createPlaceholderItem).toHaveBeenCalledWith('1'); + expect(placeholderItem1.persist).toHaveBeenCalledWith( CollectionPersistent.getItemStorageKey('1', 'dummyKey'), - collectionPersistent.config.defaultStorageKey + { + loadValue: false, + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } ); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey('2', 'dummyKey'), - collectionPersistent.config.defaultStorageKey + expect(dummyCollection.collectItem).toHaveBeenCalledWith( + placeholderItem1 ); + expect(dummyCollection.collectItem).not.toHaveBeenCalledWith( + placeholderItem3 + ); // Because Item is already present in Collection + + expect(collectionPersistent.setupSideEffects).toHaveBeenCalledWith( + 'dummyKey' + ); + }); + + it("shouldn't load default Group and its Items if Collection flag isn't persisted", async () => { + collectionPersistent.ready = true; + dummyAgile.storages.get = jest + .fn() + .mockReturnValueOnce(Promise.resolve(undefined)); + + const response = await collectionPersistent.loadPersistedValue(); + + expect(response).toBeFalsy(); expect(dummyAgile.storages.get).toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey('3', 'dummyKey'), + collectionPersistent._key, collectionPersistent.config.defaultStorageKey ); - expect(dummyDefaultGroup.persist).toHaveBeenCalledWith({ - loadValue: false, - followCollectionPersistKeyPattern: true, - }); - expect(dummyDefaultGroup.persistent?.initialLoading).toHaveBeenCalled(); - expect(dummyDefaultGroup.isPersisted).toBeTruthy(); + expect(dummyCollection.getDefaultGroup).not.toHaveBeenCalled(); + expect(dummyDefaultGroup.persist).not.toHaveBeenCalled(); - expect(dummyCollection.collect).toHaveBeenCalledWith({ - id: '1', - name: 'hans', - }); - expect(dummyCollection.collect).not.toHaveBeenCalledWith(undefined); - expect(dummyCollection.collect).toHaveBeenCalledWith({ - id: '3', - name: 'frank', - }); + expect(placeholderItem1.persist).not.toHaveBeenCalled(); + expect(placeholderItem2.persist).not.toHaveBeenCalled(); + expect(placeholderItem3.persist).not.toHaveBeenCalled(); - expect(collectionPersistent.persistValue).toHaveBeenCalledWith( - 'dummyKey' - ); + expect(collectionPersistent.setupSideEffects).not.toHaveBeenCalled(); }); it("shouldn't load default Group and its Items if Persistent isn't ready", async () => { @@ -461,27 +560,20 @@ describe('CollectionPersistent Tests', () => { dummyAgile.storages.get = jest.fn(() => Promise.resolve(undefined as any) ); - dummyCollection.getDefaultGroup = jest.fn( - () => dummyDefaultGroup as any - ); const response = await collectionPersistent.loadPersistedValue(); expect(response).toBeFalsy(); - - expect(dummyCollection.getDefaultGroup).not.toHaveBeenCalled(); - expect(dummyAgile.storages.get).not.toHaveBeenCalled(); + expect(dummyCollection.getDefaultGroup).not.toHaveBeenCalled(); expect(dummyDefaultGroup.persist).not.toHaveBeenCalled(); - expect( - dummyDefaultGroup.persistent?.initialLoading - ).not.toHaveBeenCalled(); - expect(dummyDefaultGroup.isPersisted).toBeFalsy(); - expect(dummyCollection.collect).not.toHaveBeenCalled(); + expect(placeholderItem1.persist).not.toHaveBeenCalled(); + expect(placeholderItem2.persist).not.toHaveBeenCalled(); + expect(placeholderItem3.persist).not.toHaveBeenCalled(); - expect(collectionPersistent.persistValue).not.toHaveBeenCalled(); + expect(collectionPersistent.setupSideEffects).not.toHaveBeenCalled(); }); it("shouldn't load default Group and its Items if Collection has no defaultGroup", async () => { @@ -495,44 +587,19 @@ describe('CollectionPersistent Tests', () => { const response = await collectionPersistent.loadPersistedValue(); expect(response).toBeFalsy(); - - expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( collectionPersistent._key, collectionPersistent.config.defaultStorageKey ); - expect(dummyAgile.storages.get).not.toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey( - '1', - collectionPersistent._key - ), - collectionPersistent.config.defaultStorageKey - ); - expect(dummyAgile.storages.get).not.toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey( - '2', - collectionPersistent._key - ), - collectionPersistent.config.defaultStorageKey - ); - expect(dummyAgile.storages.get).not.toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey( - '3', - collectionPersistent._key - ), - collectionPersistent.config.defaultStorageKey - ); + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); expect(dummyDefaultGroup.persist).not.toHaveBeenCalled(); - expect( - dummyDefaultGroup.persistent?.initialLoading - ).not.toHaveBeenCalled(); - expect(dummyDefaultGroup.isPersisted).toBeFalsy(); - expect(dummyCollection.collect).not.toHaveBeenCalled(); + expect(placeholderItem1.persist).not.toHaveBeenCalled(); + expect(placeholderItem2.persist).not.toHaveBeenCalled(); + expect(placeholderItem3.persist).not.toHaveBeenCalled(); - expect(collectionPersistent.persistValue).not.toHaveBeenCalled(); + expect(collectionPersistent.setupSideEffects).not.toHaveBeenCalled(); }); }); @@ -543,26 +610,30 @@ describe('CollectionPersistent Tests', () => { collectionPersistent.storageKeys = ['test1', 'test2']; collectionPersistent.isPersisted = undefined as any; - dummyDefaultGroup = new Group(dummyCollection, ['1', '2', '3']); dummyCollection.data = { ['1']: dummyItem1, ['3']: dummyItem3, }; + dummyDefaultGroup = new Group(dummyCollection, ['1', '2', '3'], { + key: 'default', + }); dummyDefaultGroup.persist = jest.fn(); - jest.spyOn(dummyDefaultGroup, 'addSideEffect'); dummyItem1.persist = jest.fn(); dummyItem3.persist = jest.fn(); - dummyCollection.collect = jest.fn(); + collectionPersistent.setupSideEffects = jest.fn(); + + dummyCollection.getDefaultGroup = jest.fn( + () => dummyDefaultGroup as any + ); dummyAgile.storages.set = jest.fn(); }); - it('should persist defaultGroup and its Items with persistentKey', async () => { + it('should persist defaultGroup and its Items (persistentKey)', async () => { collectionPersistent.ready = true; - dummyCollection.getGroup = jest.fn(() => dummyDefaultGroup as any); const response = await collectionPersistent.persistValue(); @@ -574,39 +645,45 @@ describe('CollectionPersistent Tests', () => { collectionPersistent.storageKeys ); - expect(dummyCollection.getGroup).toHaveBeenCalledWith( - dummyCollection.config.defaultGroupKey - ); - expect(dummyDefaultGroup.persist).toHaveBeenCalledWith({ - followCollectionPersistKeyPattern: true, - }); - expect( - dummyDefaultGroup.addSideEffect - ).toHaveBeenCalledWith( - CollectionPersistent.defaultGroupSideEffectKey, - expect.any(Function), - { weight: 0 } + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); + expect(dummyDefaultGroup.persist).toHaveBeenCalledWith( + CollectionPersistent.getGroupStorageKey( + dummyDefaultGroup._key, + collectionPersistent._key + ), + { + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } ); expect(dummyItem1.persist).toHaveBeenCalledWith( CollectionPersistent.getItemStorageKey( dummyItem1._key, collectionPersistent._key - ) + ), + { + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } ); expect(dummyItem3.persist).toHaveBeenCalledWith( CollectionPersistent.getItemStorageKey( dummyItem3._key, collectionPersistent._key - ) + ), + { + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } ); + expect(collectionPersistent.setupSideEffects).toHaveBeenCalled(); expect(collectionPersistent.isPersisted).toBeTruthy(); }); - it('should persist defaultGroup and its Items with specific Key', async () => { + it('should persist defaultGroup and its Items (specific key)', async () => { collectionPersistent.ready = true; - dummyCollection.getGroup = jest.fn(() => dummyDefaultGroup as any); const response = await collectionPersistent.persistValue('dummyKey'); @@ -618,72 +695,73 @@ describe('CollectionPersistent Tests', () => { collectionPersistent.storageKeys ); - expect(dummyCollection.getGroup).toHaveBeenCalledWith( - dummyCollection.config.defaultGroupKey - ); - expect(dummyDefaultGroup.persist).toHaveBeenCalledWith({ - followCollectionPersistKeyPattern: true, - }); - expect( - dummyDefaultGroup.addSideEffect - ).toHaveBeenCalledWith( - CollectionPersistent.defaultGroupSideEffectKey, - expect.any(Function), - { weight: 0 } + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); + expect(dummyDefaultGroup.persist).toHaveBeenCalledWith( + CollectionPersistent.getGroupStorageKey( + dummyDefaultGroup._key, + 'dummyKey' + ), + { + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } ); expect(dummyItem1.persist).toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey(dummyItem1._key, 'dummyKey') + CollectionPersistent.getItemStorageKey(dummyItem1._key, 'dummyKey'), + { + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } ); expect(dummyItem3.persist).toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey(dummyItem3._key, 'dummyKey') + CollectionPersistent.getItemStorageKey(dummyItem3._key, 'dummyKey'), + { + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } ); + expect(collectionPersistent.setupSideEffects).toHaveBeenCalled(); expect(collectionPersistent.isPersisted).toBeTruthy(); }); it("shouldn't persist defaultGroup and its Items if Persistent isn't ready", async () => { collectionPersistent.ready = false; - dummyCollection.getGroup = jest.fn(() => dummyDefaultGroup as any); const response = await collectionPersistent.persistValue('dummyKey'); expect(response).toBeFalsy(); - expect(dummyAgile.storages.set).not.toHaveBeenCalled(); - - expect(dummyCollection.getGroup).not.toHaveBeenCalled(); + expect(dummyCollection.getDefaultGroup).not.toHaveBeenCalled(); expect(dummyDefaultGroup.persist).not.toHaveBeenCalled(); - expect(dummyDefaultGroup.addSideEffect).not.toHaveBeenCalled(); expect(dummyItem1.persist).not.toHaveBeenCalled(); expect(dummyItem3.persist).not.toHaveBeenCalled(); + expect(collectionPersistent.setupSideEffects).not.toHaveBeenCalled(); expect(collectionPersistent.isPersisted).toBeUndefined(); }); it("shouldn't persist defaultGroup and its Items if Collection has no defaultGroup", async () => { collectionPersistent.ready = true; - dummyCollection.getGroup = jest.fn(() => undefined as any); + dummyCollection.getDefaultGroup = jest.fn(() => undefined as any); const response = await collectionPersistent.persistValue(); expect(response).toBeFalsy(); - expect(dummyAgile.storages.set).not.toHaveBeenCalled(); - - expect(dummyCollection.getGroup).toHaveBeenCalledWith( - dummyCollection.config.defaultGroupKey - ); + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); expect(dummyDefaultGroup.persist).not.toHaveBeenCalled(); - expect(dummyDefaultGroup.addSideEffect).not.toHaveBeenCalled(); expect(dummyItem1.persist).not.toHaveBeenCalled(); expect(dummyItem3.persist).not.toHaveBeenCalled(); + expect(collectionPersistent.setupSideEffects).not.toHaveBeenCalled(); expect(collectionPersistent.isPersisted).toBeUndefined(); }); + // TODO move since the added sideEffect isn't here anymore!! describe('test added sideEffect called CollectionPersistent.defaultGroupSideEffectKey', () => { beforeEach(() => { collectionPersistent.rebuildStorageSideEffect = jest.fn(); From a4e2d3d2cea9262fe558803c314c9041b537b804 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 29 May 2021 20:04:21 +0200 Subject: [PATCH 025/117] added collection persistent setupSideEffect tests --- packages/core/src/collection/index.ts | 3 + packages/core/src/state/index.ts | 2 +- .../collection/collection.persistent.test.ts | 128 +++++++++++++----- 3 files changed, 96 insertions(+), 37 deletions(-) diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 55821c8e..290cc46d 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -813,6 +813,9 @@ export class Collection { defaultStorageKey: null, }); + // Check if Collection is already persisted + if (this.persistent != null && this.isPersisted) return this; + // Create persistent -> Persist Value this.persistent = new CollectionPersistent(this, { instantiate: _config.loadValue, diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index d40b9cbe..c33afdf3 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -407,7 +407,7 @@ export class State { defaultStorageKey: null, }); - // Check if State was already persisted + // Check if State is already persisted if (this.persistent != null && this.isPersisted) return this; // Create persistent -> Persist Value diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index ccdcd974..46c2bee3 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -760,16 +760,46 @@ describe('CollectionPersistent Tests', () => { expect(collectionPersistent.setupSideEffects).not.toHaveBeenCalled(); expect(collectionPersistent.isPersisted).toBeUndefined(); }); + }); + + describe('setupSideEffect function tests', () => { + let dummyDefaultGroup: Group; + + beforeEach(() => { + dummyDefaultGroup = new Group(dummyCollection, ['1', '2', '3'], { + key: 'default', + }); + jest.spyOn(dummyDefaultGroup, 'addSideEffect'); + + collectionPersistent.rebuildStorageSideEffect = jest.fn(); + + dummyCollection.getDefaultGroup = jest.fn( + () => dummyDefaultGroup as any + ); + }); + + it('should add rebuild Storage side effect to default Group', () => { + collectionPersistent.setupSideEffects(); + + expect( + dummyDefaultGroup.addSideEffect + ).toHaveBeenCalledWith( + CollectionPersistent.defaultGroupSideEffectKey, + expect.any(Function), + { weight: 0 } + ); + }); - // TODO move since the added sideEffect isn't here anymore!! describe('test added sideEffect called CollectionPersistent.defaultGroupSideEffectKey', () => { beforeEach(() => { collectionPersistent.rebuildStorageSideEffect = jest.fn(); }); - it('should call rebuildStorageSideEffect with persistentKey', async () => { + it('should call rebuildStorageSideEffect (persistentKey)', async () => { collectionPersistent.ready = true; - dummyCollection.getGroup = jest.fn(() => dummyDefaultGroup as any); + dummyCollection.getDefaultGroup = jest.fn( + () => dummyDefaultGroup as any + ); await collectionPersistent.persistValue(); @@ -782,9 +812,11 @@ describe('CollectionPersistent Tests', () => { ).toHaveBeenCalledWith(dummyDefaultGroup, collectionPersistent._key); }); - it('should call rebuildStorageSideEffect with specific Key', async () => { + it('should call rebuildStorageSideEffect (specific key)', async () => { collectionPersistent.ready = true; - dummyCollection.getGroup = jest.fn(() => dummyDefaultGroup as any); + dummyCollection.getDefaultGroup = jest.fn( + () => dummyDefaultGroup as any + ); await collectionPersistent.persistValue('dummyKey'); @@ -806,16 +838,21 @@ describe('CollectionPersistent Tests', () => { collectionPersistent.storageKeys = ['test1', 'test2']; collectionPersistent.isPersisted = undefined as any; - dummyDefaultGroup = new Group(dummyCollection, ['1', '2', '3']); - dummyDefaultGroup.persistent = new StatePersistent(dummyDefaultGroup); dummyCollection.data = { ['1']: dummyItem1, ['3']: dummyItem3, }; + dummyDefaultGroup = new Group(dummyCollection, ['1', '2', '3']); + dummyDefaultGroup.persistent = new StatePersistent(dummyDefaultGroup); + dummyDefaultGroup.removeSideEffect = jest.fn(); + if (dummyDefaultGroup.persistent) dummyDefaultGroup.persistent.removePersistedValue = jest.fn(); - dummyDefaultGroup.removeSideEffect = jest.fn(); + + dummyCollection.getDefaultGroup = jest.fn( + () => dummyDefaultGroup as any + ); if (dummyItem1.persistent) dummyItem1.persistent.removePersistedValue = jest.fn(); @@ -825,77 +862,99 @@ describe('CollectionPersistent Tests', () => { dummyAgile.storages.remove = jest.fn(); }); - it('should remove persisted defaultGroup and its Items from Storage with persistentKey', async () => { + it('should remove persisted default Group and its Items from Storage (persistentKey)', async () => { collectionPersistent.ready = true; - dummyCollection.getGroup = jest.fn(() => dummyDefaultGroup as any); const response = await collectionPersistent.removePersistedValue(); expect(response).toBeTruthy(); - expect(dummyAgile.storages.remove).toHaveBeenCalledWith( collectionPersistent._key, collectionPersistent.storageKeys ); + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); - expect(dummyCollection.getGroup).toHaveBeenCalledWith( - dummyCollection.config.defaultGroupKey - ); expect( dummyDefaultGroup.persistent?.removePersistedValue - ).toHaveBeenCalled(); + ).toHaveBeenCalledWith( + CollectionPersistent.getGroupStorageKey( + dummyDefaultGroup._key, + collectionPersistent._key + ) + ); expect(dummyDefaultGroup.removeSideEffect).toHaveBeenCalledWith( CollectionPersistent.defaultGroupSideEffectKey ); - expect(dummyItem1.persistent?.removePersistedValue).toHaveBeenCalled(); - expect(dummyItem3.persistent?.removePersistedValue).toHaveBeenCalled(); + expect( + dummyItem1.persistent?.removePersistedValue + ).toHaveBeenCalledWith( + CollectionPersistent.getItemStorageKey( + dummyItem1._key, + collectionPersistent._key + ) + ); + expect( + dummyItem3.persistent?.removePersistedValue + ).toHaveBeenCalledWith( + CollectionPersistent.getItemStorageKey( + dummyItem3._key, + collectionPersistent._key + ) + ); expect(collectionPersistent.isPersisted).toBeFalsy(); }); - it('should remove persisted defaultGroup and its Items from Storage with specific Key', async () => { + it('should remove persisted default Group and its Items from Storage (specific key)', async () => { collectionPersistent.ready = true; - dummyCollection.getGroup = jest.fn(() => dummyDefaultGroup as any); const response = await collectionPersistent.removePersistedValue( 'dummyKey' ); expect(response).toBeTruthy(); - expect(dummyAgile.storages.remove).toHaveBeenCalledWith( 'dummyKey', collectionPersistent.storageKeys ); + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); - expect(dummyCollection.getGroup).toHaveBeenCalledWith( - dummyCollection.config.defaultGroupKey - ); expect( dummyDefaultGroup.persistent?.removePersistedValue - ).toHaveBeenCalled(); + ).toHaveBeenCalledWith( + CollectionPersistent.getGroupStorageKey( + dummyDefaultGroup._key, + 'dummyKey' + ) + ); expect(dummyDefaultGroup.removeSideEffect).toHaveBeenCalledWith( CollectionPersistent.defaultGroupSideEffectKey ); - expect(dummyItem1.persistent?.removePersistedValue).toHaveBeenCalled(); - expect(dummyItem3.persistent?.removePersistedValue).toHaveBeenCalled(); + expect( + dummyItem1.persistent?.removePersistedValue + ).toHaveBeenCalledWith( + CollectionPersistent.getItemStorageKey(dummyItem1._key, 'dummyKey') + ); + expect( + dummyItem3.persistent?.removePersistedValue + ).toHaveBeenCalledWith( + CollectionPersistent.getItemStorageKey(dummyItem3._key, 'dummyKey') + ); expect(collectionPersistent.isPersisted).toBeFalsy(); }); - it("shouldn't remove persisted defaultGroup and its Items from Storage if Persistent isn't ready", async () => { + it("shouldn't remove persisted default Group and its Items from Storage if Persistent isn't ready", async () => { collectionPersistent.ready = false; - dummyCollection.getGroup = jest.fn(() => dummyDefaultGroup as any); const response = await collectionPersistent.removePersistedValue(); expect(response).toBeFalsy(); - expect(dummyAgile.storages.remove).not.toHaveBeenCalled(); + expect(dummyCollection.getDefaultGroup).not.toHaveBeenCalled(); - expect(dummyCollection.getGroup).not.toHaveBeenCalled(); expect( dummyDefaultGroup.persistent?.removePersistedValue ).not.toHaveBeenCalled(); @@ -911,19 +970,16 @@ describe('CollectionPersistent Tests', () => { expect(collectionPersistent.isPersisted).toBeUndefined(); }); - it("shouldn't remove persisted defaultGroup and its Items from Storage if Collection has no default Group", async () => { + it("shouldn't remove persisted default Group and its Items from Storage if Collection has no default Group", async () => { collectionPersistent.ready = true; - dummyCollection.getGroup = jest.fn(() => undefined as any); + dummyCollection.getDefaultGroup = jest.fn(() => undefined as any); const response = await collectionPersistent.removePersistedValue(); expect(response).toBeFalsy(); - expect(dummyAgile.storages.remove).not.toHaveBeenCalled(); + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); - expect(dummyCollection.getGroup).toHaveBeenCalledWith( - dummyCollection.config.defaultGroupKey - ); expect( dummyDefaultGroup.persistent?.removePersistedValue ).not.toHaveBeenCalled(); From 32d2426593f8d0ba55f57229b3d7c53ee5b29947 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 30 May 2021 07:13:49 +0200 Subject: [PATCH 026/117] fixed typos --- .../src/collection/collection.persistent.ts | 63 +++++++------ .../core/src/computed/computed.tracker.ts | 18 +++- packages/core/src/computed/index.ts | 10 +- .../collection/collection.persistent.test.ts | 91 +++++++++++++------ 4 files changed, 117 insertions(+), 65 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 21d7c713..e2b1f98c 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -6,7 +6,6 @@ import { defineConfig, Group, GroupKey, - Item, ItemKey, LogCodeManager, Persistent, @@ -65,7 +64,7 @@ export class CollectionPersistent< // Assign new key to Persistent if (value === this._key) return; - this._key = value || Persistent.placeHolderKey; + this._key = value ?? Persistent.placeHolderKey; const isValid = this.validatePersistent(); @@ -83,10 +82,11 @@ export class CollectionPersistent< } /** - * @internal * Loads the persisted value into the Collection * or persists the Collection value in the corresponding Storage. - * This depends on whether the Collection has been persisted before. + * This behaviour depends on whether the Collection has been persisted before. + * + * @internal */ public async initialLoading() { super.initialLoading().then(() => { @@ -95,7 +95,8 @@ export class CollectionPersistent< } /** - * Loads the values of the Collection Instances from the corresponding Storage. + * Loads Collection Instances (like Items or Groups) from the corresponding Storage + * and sets up side effects that dynamically update the Storage value when the Collection (Instances) changes. * * @internal * @param storageItemKey - Prefix key of persisted Collection Instances | default = Persistent.key | @@ -143,33 +144,34 @@ export class CollectionPersistent< _storageItemKey ); - // Persist already existing Item + // Persist and therefore load already present Item if (item != null) { item.persist(itemStorageKey, { defaultStorageKey: this.config.defaultStorageKey || undefined, storageKeys: this.storageKeys, }); - continue; } - - // Create temporary placeholder Item in which the Item value will be loaded - const dummyItem = this.collection().createPlaceholderItem(itemKey); - - // Persist dummy Item and load its value manually to be 100% sure - // that it was loaded completely and exists - dummyItem?.persist(itemStorageKey, { - loadValue: false, - defaultStorageKey: this.config.defaultStorageKey || undefined, - storageKeys: this.storageKeys, - }); - if (dummyItem?.persistent?.ready) { - const loadedPersistedValueIntoItem = await dummyItem.persistent.loadPersistedValue( - itemStorageKey - ); - - // If successfully loaded Item value add Item to Collection - if (loadedPersistedValueIntoItem) - this.collection().collectItem(dummyItem); + // Persist and therefore load not present Item + else { + // Create temporary placeholder Item in which the Item value will be loaded + const dummyItem = this.collection().createPlaceholderItem(itemKey); + + // Persist dummy Item and load its value manually to be 100% sure + // that it was loaded completely and exists at all + dummyItem?.persist(itemStorageKey, { + loadValue: false, + defaultStorageKey: this.config.defaultStorageKey || undefined, + storageKeys: this.storageKeys, + }); + if (dummyItem?.persistent?.ready) { + const loadedPersistedValueIntoItem = await dummyItem.persistent.loadPersistedValue( + itemStorageKey + ); + + // If successfully loaded Item value, add Item to Collection + if (loadedPersistedValueIntoItem) + this.collection().collectItem(dummyItem); + } } } @@ -177,15 +179,16 @@ export class CollectionPersistent< }; const success = await loadValuesIntoCollection(); - // Setup Side Effects to keep the Storage value in sync with the State value + // Setup Side Effects to keep the Storage value in sync with the Collection (Instances) value if (success) this.setupSideEffects(_storageItemKey); return success; } + // TODO STOPPED HERE (in looking at code) /** - * Persists Collection in corresponding Storage if not already done - * and sets up side effects that dynamically update the storage value when the Collection changes. + * Persists Collection Instances (like Items or Groups) in the corresponding Storage + * and sets up side effects that dynamically update the Storage value when the Collection (Instances) changes. * * @internal * @param storageItemKey - Prefix key of persisted Collection Instances | default = Persistent.key | @@ -223,7 +226,7 @@ export class CollectionPersistent< }); } - // Setup Side Effects to keep the Storage value in sync with the Collection value + // Setup Side Effects to keep the Storage value in sync with the Collection (Instances) value this.setupSideEffects(_storageItemKey); this.isPersisted = true; diff --git a/packages/core/src/computed/computed.tracker.ts b/packages/core/src/computed/computed.tracker.ts index 4274f324..e3254f90 100644 --- a/packages/core/src/computed/computed.tracker.ts +++ b/packages/core/src/computed/computed.tracker.ts @@ -5,17 +5,28 @@ export class ComputedTracker { static trackedObservers: Set = new Set(); /** + * Helper Class for automatic tracking used Observers (dependencies) in a compute function. + * * @internal + */ + constructor() { + // empty + } + + /** * Activates Computed Tracker to globally track used Observers. + * + * @internal */ static track(): void { this.isTracking = true; } /** - * @internal * Tracks the passed Observer and caches it * when the Computed Tracker is actively tracking. + * + * @internal * @param observer - Observer */ static tracked(observer: Observer) { @@ -23,9 +34,10 @@ export class ComputedTracker { } /** - * @internal - * Returns the last tracked Observers + * Returns the latest tracked Observers * and stops the Computed Tracker from tracking any more Observers. + * + * @internal */ static getTrackedObservers(): Array { const trackedObservers = Array.from(this.trackedObservers); diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index 90c24127..1c0e6bdd 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -28,7 +28,7 @@ export class Computed extends State< * and is only recomputed when one of its direct dependencies changes. * * Direct dependencies can be States and Collections. - * So if for example a dependent State value changes, the computed value will be recomputed. + * So when for example a dependent State value changes, the computed value will be recomputed. * * [Learn more..](https://agile-ts.org/docs/core/computed/) * @@ -82,12 +82,12 @@ export class Computed extends State< } /** - * Assigns a new function to the Computed Class to compute its value. + * Assigns a new function to the Computed Class for computing its value. * * The dependencies of the new compute function are automatically detected * and accordingly updated. * - * An initial computation is automatically performed with the new function + * An initial computation is performed with the new function * to change the obsolete cached value. * * [Learn more..](https://agile-ts.org/docs/core/computed/methods/#updatecomputefunction) @@ -148,7 +148,7 @@ export class Computed extends State< this.hardCodedDeps.concat(foundDeps).forEach((observer) => { newDeps.push(observer); - // Make this Observer depend on the foundDep Observer + // Make this Observer depend on the found dep Observers observer.depend(this.observer); }); @@ -201,7 +201,7 @@ export interface ComputeConfigInterface { /** * @param overwriteDeps - Whether the old hard coded dependencies - * should be overwritten with the new hard coded dependencies or merged in. + * should be entirely overwritten with the new hard coded dependencies or merged in. * | Default = true | */ export interface UpdateComputeFunctionConfigInterface diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index 46c2bee3..0f138a35 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -32,43 +32,42 @@ describe('CollectionPersistent Tests', () => { jest.spyOn(CollectionPersistent.prototype, 'initialLoading'); }); - it("should create CollectionPersistent and shouldn't call initialLoading if Persistent isn't ready (default config)", () => { - // Overwrite instantiatePersistent once to not call it and set ready property + it('should create CollectionPersistent and should call initialLoading if Persistent is ready (default config)', () => { + // Overwrite instantiatePersistent once to not call it jest .spyOn(CollectionPersistent.prototype, 'instantiatePersistent') .mockImplementationOnce(function () { // @ts-ignore - this.ready = false; + this.ready = true; }); const collectionPersistent = new CollectionPersistent(dummyCollection); expect(collectionPersistent).toBeInstanceOf(CollectionPersistent); - expect(collectionPersistent.collection()).toBe(dummyCollection); expect(collectionPersistent.instantiatePersistent).toHaveBeenCalledWith({ key: undefined, storageKeys: [], defaultStorageKey: null, }); - expect(collectionPersistent.initialLoading).not.toHaveBeenCalled(); + expect(collectionPersistent.initialLoading).toHaveBeenCalled(); expect(collectionPersistent._key).toBe(CollectionPersistent.placeHolderKey); - expect(collectionPersistent.ready).toBeFalsy(); + expect(collectionPersistent.ready).toBeTruthy(); expect(collectionPersistent.isPersisted).toBeFalsy(); expect(collectionPersistent.onLoad).toBeUndefined(); expect(collectionPersistent.storageKeys).toStrictEqual([]); expect(collectionPersistent.config).toStrictEqual({ - defaultStorageKey: null, + defaultStorageKey: null, // is assigned in 'instantiatePersistent' which is mocked }); }); - it("should create CollectionPersistent and shouldn't call initialLoading if Persistent isn't ready (specific config)", () => { + it('should create CollectionPersistent and should call initialLoading if Persistent is ready (specific config)', () => { // Overwrite instantiatePersistent once to not call it and set ready property jest .spyOn(CollectionPersistent.prototype, 'instantiatePersistent') .mockImplementationOnce(function () { // @ts-ignore - this.ready = false; + this.ready = true; }); const collectionPersistent = new CollectionPersistent(dummyCollection, { @@ -83,10 +82,10 @@ describe('CollectionPersistent Tests', () => { storageKeys: ['test1', 'test2'], defaultStorageKey: 'test2', }); - expect(collectionPersistent.initialLoading).not.toHaveBeenCalled(); + expect(collectionPersistent.initialLoading).toHaveBeenCalled(); expect(collectionPersistent._key).toBe(CollectionPersistent.placeHolderKey); - expect(collectionPersistent.ready).toBeFalsy(); + expect(collectionPersistent.ready).toBeTruthy(); expect(collectionPersistent.isPersisted).toBeFalsy(); expect(collectionPersistent.onLoad).toBeUndefined(); expect(collectionPersistent.storageKeys).toStrictEqual([]); @@ -95,18 +94,34 @@ describe('CollectionPersistent Tests', () => { }); }); - it('should create CollectionPersistent and should call initialLoading if Persistent is ready (default config)', () => { - // Overwrite instantiatePersistent once to not call it + it("should create CollectionPersistent and shouldn't call initialLoading if Persistent isn't ready", () => { + // Overwrite instantiatePersistent once to not call it and set ready property jest .spyOn(CollectionPersistent.prototype, 'instantiatePersistent') .mockImplementationOnce(function () { // @ts-ignore - this.ready = true; + this.ready = false; }); const collectionPersistent = new CollectionPersistent(dummyCollection); - expect(collectionPersistent.initialLoading).toHaveBeenCalled(); + expect(collectionPersistent).toBeInstanceOf(CollectionPersistent); + expect(collectionPersistent.collection()).toBe(dummyCollection); + expect(collectionPersistent.instantiatePersistent).toHaveBeenCalledWith({ + key: undefined, + storageKeys: [], + defaultStorageKey: null, + }); + expect(collectionPersistent.initialLoading).not.toHaveBeenCalled(); + + expect(collectionPersistent._key).toBe(CollectionPersistent.placeHolderKey); + expect(collectionPersistent.ready).toBeFalsy(); + expect(collectionPersistent.isPersisted).toBeFalsy(); + expect(collectionPersistent.onLoad).toBeUndefined(); + expect(collectionPersistent.storageKeys).toStrictEqual([]); + expect(collectionPersistent.config).toStrictEqual({ + defaultStorageKey: null, + }); }); it("should create CollectionPersistent and shouldn't call initialLoading if Persistent is ready (config.instantiate = false)", () => { @@ -122,7 +137,23 @@ describe('CollectionPersistent Tests', () => { instantiate: false, }); + expect(collectionPersistent).toBeInstanceOf(CollectionPersistent); + expect(collectionPersistent.collection()).toBe(dummyCollection); + expect(collectionPersistent.instantiatePersistent).toHaveBeenCalledWith({ + key: undefined, + storageKeys: [], + defaultStorageKey: null, + }); expect(collectionPersistent.initialLoading).not.toHaveBeenCalled(); + + expect(collectionPersistent._key).toBe(CollectionPersistent.placeHolderKey); + expect(collectionPersistent.ready).toBeTruthy(); + expect(collectionPersistent.isPersisted).toBeFalsy(); + expect(collectionPersistent.onLoad).toBeUndefined(); + expect(collectionPersistent.storageKeys).toStrictEqual([]); + expect(collectionPersistent.config).toStrictEqual({ + defaultStorageKey: null, + }); }); describe('CollectionPersistent Function Tests', () => { @@ -179,17 +210,18 @@ describe('CollectionPersistent Tests', () => { collectionPersistent.removePersistedValue = jest.fn(); collectionPersistent.persistValue = jest.fn(); collectionPersistent.initialLoading = jest.fn(); - jest.spyOn(collectionPersistent, 'validatePersistent'); }); it('should update key with valid key in ready Persistent', async () => { collectionPersistent.ready = true; collectionPersistent._key = 'dummyKey'; + jest + .spyOn(collectionPersistent, 'validatePersistent') + .mockReturnValueOnce(true); await collectionPersistent.setKey('newKey'); expect(collectionPersistent._key).toBe('newKey'); - expect(collectionPersistent.ready).toBeTruthy(); expect(collectionPersistent.validatePersistent).toHaveBeenCalled(); expect(collectionPersistent.initialLoading).not.toHaveBeenCalled(); expect(collectionPersistent.persistValue).toHaveBeenCalledWith( @@ -203,13 +235,13 @@ describe('CollectionPersistent Tests', () => { it('should update key with not valid key in ready Persistent', async () => { collectionPersistent.ready = true; collectionPersistent._key = 'dummyKey'; + jest + .spyOn(collectionPersistent, 'validatePersistent') + .mockReturnValueOnce(false); await collectionPersistent.setKey(); - expect(collectionPersistent._key).toBe( - CollectionPersistent.placeHolderKey - ); - expect(collectionPersistent.ready).toBeFalsy(); + expect(collectionPersistent._key).toBe(Persistent.placeHolderKey); expect(collectionPersistent.validatePersistent).toHaveBeenCalled(); expect(collectionPersistent.initialLoading).not.toHaveBeenCalled(); expect(collectionPersistent.persistValue).not.toHaveBeenCalled(); @@ -220,11 +252,14 @@ describe('CollectionPersistent Tests', () => { it('should update key with valid key in not ready Persistent', async () => { collectionPersistent.ready = false; + collectionPersistent._key = 'dummyKey'; + jest + .spyOn(collectionPersistent, 'validatePersistent') + .mockReturnValueOnce(true); await collectionPersistent.setKey('newKey'); expect(collectionPersistent._key).toBe('newKey'); - expect(collectionPersistent.ready).toBeTruthy(); expect(collectionPersistent.validatePersistent).toHaveBeenCalled(); expect(collectionPersistent.initialLoading).toHaveBeenCalled(); expect(collectionPersistent.persistValue).not.toHaveBeenCalled(); @@ -235,13 +270,14 @@ describe('CollectionPersistent Tests', () => { it('should update key with not valid key in not ready Persistent', async () => { collectionPersistent.ready = false; + collectionPersistent._key = 'dummyKey'; + jest + .spyOn(collectionPersistent, 'validatePersistent') + .mockReturnValueOnce(false); await collectionPersistent.setKey(); - expect(collectionPersistent._key).toBe( - CollectionPersistent.placeHolderKey - ); - expect(collectionPersistent.ready).toBeFalsy(); + expect(collectionPersistent._key).toBe(Persistent.placeHolderKey); expect(collectionPersistent.validatePersistent).toHaveBeenCalled(); expect(collectionPersistent.initialLoading).not.toHaveBeenCalled(); expect(collectionPersistent.persistValue).not.toHaveBeenCalled(); @@ -256,7 +292,7 @@ describe('CollectionPersistent Tests', () => { jest.spyOn(Persistent.prototype, 'initialLoading'); }); - it('should initialLoad and set isPersisted in Collection to true', async () => { + it('should call initialLoad in parent and set Collection.isPersisted to true', async () => { await collectionPersistent.initialLoading(); expect(Persistent.prototype.initialLoading).toHaveBeenCalled(); @@ -264,6 +300,7 @@ describe('CollectionPersistent Tests', () => { }); }); + // TODO STOPPED HERE (in tests) describe('loadPersistedValue function tests', () => { let dummyDefaultGroup: Group; let placeholderItem1: Item; From 3227b6a14a86a24acb73614f5e18e87fe1ff8355 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 30 May 2021 12:16:40 +0200 Subject: [PATCH 027/117] fixed collection persist tests --- .../src/collection/collection.persistent.ts | 67 ++-- packages/core/src/collection/index.ts | 3 + packages/core/src/state/state.persistent.ts | 4 +- .../collection/collection.persistent.test.ts | 305 ++++++++---------- 4 files changed, 179 insertions(+), 200 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index e2b1f98c..33f4ae11 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -99,7 +99,7 @@ export class CollectionPersistent< * and sets up side effects that dynamically update the Storage value when the Collection (Instances) changes. * * @internal - * @param storageItemKey - Prefix key of persisted Collection Instances | default = Persistent.key | + * @param storageItemKey - Prefix key of persisted Collection Instances. | default = Persistent.key | * @return Whether the loading was successful. */ public async loadPersistedValue( @@ -184,14 +184,13 @@ export class CollectionPersistent< return success; } - // TODO STOPPED HERE (in looking at code) /** * Persists Collection Instances (like Items or Groups) in the corresponding Storage * and sets up side effects that dynamically update the Storage value when the Collection (Instances) changes. * * @internal - * @param storageItemKey - Prefix key of persisted Collection Instances | default = Persistent.key | + * @param storageItemKey - Prefix key of persisted Collection Instances. | default = Persistent.key | * @return Whether the persisting and the setting up of the side effects was successful. */ public async persistValue(storageItemKey?: PersistentKey): Promise { @@ -234,10 +233,10 @@ export class CollectionPersistent< } /** - * Sets up side effects to keep the Storage value in sync with the Collection value + * Sets up side effects to keep the Storage value in sync with the Collection (Instances) value. * * @internal - * @param storageItemKey - Prefix key of persisted Collection Instances | default = Persistent.key | + * @param storageItemKey - Prefix key of persisted Collection Instances. | default = Persistent.key | */ public setupSideEffects(storageItemKey?: PersistentKey): void { const _storageItemKey = storageItemKey ?? this._key; @@ -245,10 +244,10 @@ export class CollectionPersistent< if (defaultGroup == null) return; // Add side effect to default Group - // that adds or removes Items from the Storage based on the Group value - defaultGroup.addSideEffect( + // that adds and removes Items from the Storage based on the Group value + defaultGroup.addSideEffect( CollectionPersistent.defaultGroupSideEffectKey, - () => this.rebuildStorageSideEffect(defaultGroup, _storageItemKey), + (instance) => this.rebuildStorageSideEffect(instance, _storageItemKey), { weight: 0 } ); } @@ -258,7 +257,7 @@ export class CollectionPersistent< * -> Collection is no longer persisted * * @internal - * @param storageItemKey - Prefix key of persisted Collection Instances | default = Persistent.key | + * @param storageItemKey - Prefix key of persisted Collection Instances. | default = Persistent.key | * @return Whether the removing was successful. */ public async removePersistedValue( @@ -273,7 +272,7 @@ export class CollectionPersistent< _storageItemKey ); - // Remove Collection is persisted indicator flag + // Remove Collection is persisted indicator flag from Storage this.agileInstance().storages.remove(_storageItemKey, this.storageKeys); // Remove default Group from the Storage @@ -297,14 +296,14 @@ export class CollectionPersistent< } /** - * Formats given key so that it can be used as a valid Storage key. - * If no formatable key is given, an attempt is made to use the Collection key as Storage key. - * If this also fails, undefined is returned. + * Formats given key so that it can be used as a valid Storage key and returns it. + * If no formatable key (undefined/null) is given, + * an attempt is made to use the Collection key. * * @internal * @param key - Key to be formatted */ - public formatKey(key?: StorageKey): StorageKey | undefined { + public formatKey(key: StorageKey | undefined | null): StorageKey | undefined { if (key == null && this.collection()._key) return this.collection()._key; if (key == null) return; if (this.collection()._key == null) this.collection()._key = key; @@ -312,11 +311,11 @@ export class CollectionPersistent< } /** - * Rebuilds Storage depending on Group + * Adds and removes Items from the Storage based on the Group value. * * @internal - * @param group - Group - * @param storageItemKey - Prefix key of persisted Collection Instances | default = Persistent.key | + * @param group - Group whose Items should be dynamically added and removed from the Storage. + * @param storageItemKey - Prefix key of persisted Collection Instances. | default = Persistent.key | */ public rebuildStorageSideEffect( group: Group, @@ -326,10 +325,10 @@ export class CollectionPersistent< const _storageItemKey = storageItemKey || collection.persistent?._key; // Return if no Item got added or removed - // because then the Item performs the Storage update itself + // because then the changed Item performs the Storage update itself if (group.previousStateValue.length === group._value.length) return; - // Extract Item keys that got removed or added to the Group + // Extract Item keys that got added or removed from the Group const addedKeys = group._value.filter( (key) => !group.previousStateValue.includes(key) ); @@ -344,14 +343,11 @@ export class CollectionPersistent< itemKey, _storageItemKey ); - if (item != null) { - if (!item.isPersisted) - item.persist(itemStorageKey, { - defaultStorageKey: this.config.defaultStorageKey || undefined, - storageKeys: this.storageKeys, - }); - else item.persistent?.persistValue(itemStorageKey); - } + if (item != null && !item.isPersisted) + item.persist(itemStorageKey, { + defaultStorageKey: this.config.defaultStorageKey || undefined, + storageKeys: this.storageKeys, + }); }); // Remove removed Items from the Storage @@ -361,22 +357,21 @@ export class CollectionPersistent< itemKey, _storageItemKey ); - if (item != null) - if (item.isPersisted) - item.persistent?.removePersistedValue(itemStorageKey); + if (item != null && item.isPersisted) + item.persistent?.removePersistedValue(itemStorageKey); }); } /** - * Builds valid Item Storage key based on the 'Collection Item Persist Pattern' + * Builds valid Item Storage key based on the 'Collection Item Persist Pattern'. * * @internal * @param itemKey - Key identifier of Item * @param collectionKey - Key identifier of Collection */ public static getItemStorageKey( - itemKey?: ItemKey, - collectionKey?: CollectionKey + itemKey: ItemKey | undefined | null, + collectionKey: CollectionKey | undefined | null ): string { if (itemKey == null || collectionKey == null) LogCodeManager.log('1A:02:00'); @@ -388,15 +383,15 @@ export class CollectionPersistent< } /** - * Builds valid Item Storage key based on the 'Collection Group Persist Pattern' + * Builds valid Item Storage key based on the 'Collection Group Persist Pattern'. * * @internal * @param groupKey - Key identifier of Group * @param collectionKey - Key identifier of Collection */ public static getGroupStorageKey( - groupKey?: GroupKey, - collectionKey?: CollectionKey + groupKey: GroupKey | undefined | null, + collectionKey: CollectionKey | undefined | null ): string { if (groupKey == null || collectionKey == null) LogCodeManager.log('1A:02:01'); diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 290cc46d..e67536c5 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -1225,6 +1225,9 @@ export class Collection { ): this { const itemKey = item._value[this.config.primaryKey]; + // TODO add to Groups at least default Group + // and implement it to collect method + // Check if Item has valid primaryKey if ( !Object.prototype.hasOwnProperty.call(item._value, this.config.primaryKey) diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index 8736869a..da15578c 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -191,7 +191,9 @@ export class StatePersistent extends Persistent { * Formats Storage Key * @param key - Key that gets formatted */ - public formatKey(key?: PersistentKey): PersistentKey | undefined { + public formatKey( + key: PersistentKey | undefined | null + ): PersistentKey | undefined { const state = this.state(); // Get key from State diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index 0f138a35..b59dd9ae 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -300,7 +300,6 @@ describe('CollectionPersistent Tests', () => { }); }); - // TODO STOPPED HERE (in tests) describe('loadPersistedValue function tests', () => { let dummyDefaultGroup: Group; let placeholderItem1: Item; @@ -333,9 +332,11 @@ describe('CollectionPersistent Tests', () => { () => dummyDefaultGroup as any ); dummyCollection.collectItem = jest.fn(); + + dummyAgile.storages.get = jest.fn(); }); - it('should load defaultGroup and Items that are already present in Collection and apply the loaded value to the Item (persistentKey)', async () => { + it('should load default Group and apply persisted value to Items that are already present in Collection (persistentKey)', async () => { collectionPersistent.ready = true; dummyCollection.data = { ['3']: dummyItem3, @@ -384,7 +385,7 @@ describe('CollectionPersistent Tests', () => { ); }); - it("should load default Group and create persisted Items that aren't present in Collection yet (persistentKey)", async () => { + it("should load default Group and create/add persisted Items that aren't present in Collection yet (persistentKey)", async () => { collectionPersistent.ready = true; dummyCollection.data = {}; dummyAgile.storages.get = jest @@ -486,87 +487,99 @@ describe('CollectionPersistent Tests', () => { ); // Because Item persistent isn't ready expect(dummyCollection.collectItem).not.toHaveBeenCalledWith( placeholderItem3 - ); // Because Item persistent 'leadPersistedValue()' returned false + ); // Because Item persistent 'leadPersistedValue()' returned false -> Item properly doesn't exist in Storage expect(collectionPersistent.setupSideEffects).toHaveBeenCalledWith( collectionPersistent._key ); }); - it("should load defaultGroup and Items that are or aren't already present in Collection and apply the loaded value to the Item (specific key)", async () => { - collectionPersistent.ready = true; - dummyCollection.data = { - ['3']: dummyItem3, - }; - dummyAgile.storages.get = jest - .fn() - .mockReturnValueOnce(Promise.resolve(true)); - placeholderItem1.persist = jest.fn(function () { - placeholderItem1.persistent = new StatePersistent(placeholderItem1); - placeholderItem1.persistent.ready = true; - placeholderItem1.persistent.loadPersistedValue = jest + it( + 'should load default Group, ' + + "create/add persisted Items that aren't present in Collection yet " + + 'and apply persisted value to Items that are already present in Collection (specific key)', + async () => { + collectionPersistent.ready = true; + dummyCollection.data = { + ['3']: dummyItem3, + }; + dummyAgile.storages.get = jest .fn() - .mockReturnValueOnce(true); - return null as any; - }); - dummyCollection.createPlaceholderItem = jest - .fn() - .mockReturnValueOnce(placeholderItem1); - dummyDefaultGroup._value = ['1', '3']; - - const response = await collectionPersistent.loadPersistedValue( - 'dummyKey' - ); - - expect(response).toBeTruthy(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( - 'dummyKey', - collectionPersistent.config.defaultStorageKey - ); + .mockReturnValueOnce(Promise.resolve(true)); + placeholderItem1.persist = jest.fn(function () { + placeholderItem1.persistent = new StatePersistent(placeholderItem1); + placeholderItem1.persistent.ready = true; + placeholderItem1.persistent.loadPersistedValue = jest + .fn() + .mockReturnValueOnce(true); + return null as any; + }); + dummyCollection.createPlaceholderItem = jest + .fn() + .mockReturnValueOnce(placeholderItem1); + dummyDefaultGroup._value = ['1', '3']; - expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); - expect(dummyDefaultGroup.persist).toHaveBeenCalledWith( - CollectionPersistent.getGroupStorageKey( - dummyDefaultGroup._key, + const response = await collectionPersistent.loadPersistedValue( 'dummyKey' - ), - { - loadValue: false, - defaultStorageKey: collectionPersistent.config.defaultStorageKey, - storageKeys: collectionPersistent.storageKeys, - } - ); - expect(dummyDefaultGroup.persistent?.initialLoading).toHaveBeenCalled(); - expect(dummyDefaultGroup.isPersisted).toBeTruthy(); + ); - expect(dummyItem3.persist).toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey('3', 'dummyKey'), - { - defaultStorageKey: collectionPersistent.config.defaultStorageKey, - storageKeys: collectionPersistent.storageKeys, - } - ); + expect(response).toBeTruthy(); + expect(dummyAgile.storages.get).toHaveBeenCalledWith( + 'dummyKey', + collectionPersistent.config.defaultStorageKey + ); - expect(dummyCollection.createPlaceholderItem).toHaveBeenCalledWith('1'); - expect(placeholderItem1.persist).toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey('1', 'dummyKey'), - { - loadValue: false, - defaultStorageKey: collectionPersistent.config.defaultStorageKey, - storageKeys: collectionPersistent.storageKeys, - } - ); - expect(dummyCollection.collectItem).toHaveBeenCalledWith( - placeholderItem1 - ); - expect(dummyCollection.collectItem).not.toHaveBeenCalledWith( - placeholderItem3 - ); // Because Item is already present in Collection + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); + expect(dummyDefaultGroup.persist).toHaveBeenCalledWith( + CollectionPersistent.getGroupStorageKey( + dummyDefaultGroup._key, + 'dummyKey' + ), + { + loadValue: false, + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } + ); + expect( + dummyDefaultGroup.persistent?.initialLoading + ).toHaveBeenCalled(); + expect(dummyDefaultGroup.isPersisted).toBeTruthy(); + + expect(dummyItem3.persist).toHaveBeenCalledWith( + CollectionPersistent.getItemStorageKey('3', 'dummyKey'), + { + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } + ); - expect(collectionPersistent.setupSideEffects).toHaveBeenCalledWith( - 'dummyKey' - ); - }); + expect( + dummyCollection.createPlaceholderItem + ).not.toHaveBeenCalledWith('3'); // Because Item 3 is already present in Collection + expect(dummyCollection.createPlaceholderItem).toHaveBeenCalledWith( + '1' + ); + expect(placeholderItem1.persist).toHaveBeenCalledWith( + CollectionPersistent.getItemStorageKey('1', 'dummyKey'), + { + loadValue: false, + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } + ); + expect(dummyCollection.collectItem).toHaveBeenCalledWith( + placeholderItem1 + ); + expect(dummyCollection.collectItem).not.toHaveBeenCalledWith( + placeholderItem3 + ); // Because Item 3 is already present in Collection + + expect(collectionPersistent.setupSideEffects).toHaveBeenCalledWith( + 'dummyKey' + ); + } + ); it("shouldn't load default Group and its Items if Collection flag isn't persisted", async () => { collectionPersistent.ready = true; @@ -588,15 +601,15 @@ describe('CollectionPersistent Tests', () => { expect(placeholderItem1.persist).not.toHaveBeenCalled(); expect(placeholderItem2.persist).not.toHaveBeenCalled(); expect(placeholderItem3.persist).not.toHaveBeenCalled(); + expect(dummyItem1.persist).not.toHaveBeenCalled(); + expect(dummyItem2.persist).not.toHaveBeenCalled(); + expect(dummyItem3.persist).not.toHaveBeenCalled(); expect(collectionPersistent.setupSideEffects).not.toHaveBeenCalled(); }); it("shouldn't load default Group and its Items if Persistent isn't ready", async () => { collectionPersistent.ready = false; - dummyAgile.storages.get = jest.fn(() => - Promise.resolve(undefined as any) - ); const response = await collectionPersistent.loadPersistedValue(); @@ -609,13 +622,15 @@ describe('CollectionPersistent Tests', () => { expect(placeholderItem1.persist).not.toHaveBeenCalled(); expect(placeholderItem2.persist).not.toHaveBeenCalled(); expect(placeholderItem3.persist).not.toHaveBeenCalled(); + expect(dummyItem1.persist).not.toHaveBeenCalled(); + expect(dummyItem2.persist).not.toHaveBeenCalled(); + expect(dummyItem3.persist).not.toHaveBeenCalled(); expect(collectionPersistent.setupSideEffects).not.toHaveBeenCalled(); }); it("shouldn't load default Group and its Items if Collection has no defaultGroup", async () => { collectionPersistent.ready = true; - dummyCollection.groups = {}; dummyAgile.storages.get = jest .fn() .mockReturnValueOnce(Promise.resolve(true)); @@ -635,6 +650,9 @@ describe('CollectionPersistent Tests', () => { expect(placeholderItem1.persist).not.toHaveBeenCalled(); expect(placeholderItem2.persist).not.toHaveBeenCalled(); expect(placeholderItem3.persist).not.toHaveBeenCalled(); + expect(dummyItem1.persist).not.toHaveBeenCalled(); + expect(dummyItem2.persist).not.toHaveBeenCalled(); + expect(dummyItem3.persist).not.toHaveBeenCalled(); expect(collectionPersistent.setupSideEffects).not.toHaveBeenCalled(); }); @@ -669,13 +687,12 @@ describe('CollectionPersistent Tests', () => { dummyAgile.storages.set = jest.fn(); }); - it('should persist defaultGroup and its Items (persistentKey)', async () => { + it('should persist default Group and its Items (persistentKey)', async () => { collectionPersistent.ready = true; const response = await collectionPersistent.persistValue(); expect(response).toBeTruthy(); - expect(dummyAgile.storages.set).toHaveBeenCalledWith( collectionPersistent._key, true, @@ -719,13 +736,12 @@ describe('CollectionPersistent Tests', () => { expect(collectionPersistent.isPersisted).toBeTruthy(); }); - it('should persist defaultGroup and its Items (specific key)', async () => { + it('should persist default Group and its Items (specific key)', async () => { collectionPersistent.ready = true; const response = await collectionPersistent.persistValue('dummyKey'); expect(response).toBeTruthy(); - expect(dummyAgile.storages.set).toHaveBeenCalledWith( 'dummyKey', true, @@ -763,12 +779,13 @@ describe('CollectionPersistent Tests', () => { expect(collectionPersistent.isPersisted).toBeTruthy(); }); - it("shouldn't persist defaultGroup and its Items if Persistent isn't ready", async () => { + it("shouldn't persist default Group and its Items if Persistent isn't ready", async () => { collectionPersistent.ready = false; const response = await collectionPersistent.persistValue('dummyKey'); expect(response).toBeFalsy(); + expect(dummyAgile.storages.set).not.toHaveBeenCalled(); expect(dummyCollection.getDefaultGroup).not.toHaveBeenCalled(); expect(dummyDefaultGroup.persist).not.toHaveBeenCalled(); @@ -780,13 +797,14 @@ describe('CollectionPersistent Tests', () => { expect(collectionPersistent.isPersisted).toBeUndefined(); }); - it("shouldn't persist defaultGroup and its Items if Collection has no defaultGroup", async () => { + it("shouldn't persist default Group and its Items if Collection has no default Group", async () => { collectionPersistent.ready = true; dummyCollection.getDefaultGroup = jest.fn(() => undefined as any); const response = await collectionPersistent.persistValue(); expect(response).toBeFalsy(); + expect(dummyAgile.storages.set).not.toHaveBeenCalled(); expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); expect(dummyDefaultGroup.persist).not.toHaveBeenCalled(); @@ -815,7 +833,7 @@ describe('CollectionPersistent Tests', () => { ); }); - it('should add rebuild Storage side effect to default Group', () => { + it("shouldn't add rebuild Storage side effect to default Group", () => { collectionPersistent.setupSideEffects(); expect( @@ -827,17 +845,25 @@ describe('CollectionPersistent Tests', () => { ); }); + it("shouldn't add rebuild Storage side effect to default Group if Collection has no default Group", () => { + dummyCollection.getDefaultGroup = jest.fn(() => undefined as any); + + collectionPersistent.setupSideEffects(); + + expect(dummyDefaultGroup.addSideEffect).not.toHaveBeenCalled(); + }); + describe('test added sideEffect called CollectionPersistent.defaultGroupSideEffectKey', () => { beforeEach(() => { - collectionPersistent.rebuildStorageSideEffect = jest.fn(); - }); - - it('should call rebuildStorageSideEffect (persistentKey)', async () => { collectionPersistent.ready = true; + + collectionPersistent.rebuildStorageSideEffect = jest.fn(); dummyCollection.getDefaultGroup = jest.fn( () => dummyDefaultGroup as any ); + }); + it('should call rebuildStorageSideEffect (persistentKey)', async () => { await collectionPersistent.persistValue(); dummyDefaultGroup.sideEffects[ @@ -850,11 +876,6 @@ describe('CollectionPersistent Tests', () => { }); it('should call rebuildStorageSideEffect (specific key)', async () => { - collectionPersistent.ready = true; - dummyCollection.getDefaultGroup = jest.fn( - () => dummyDefaultGroup as any - ); - await collectionPersistent.persistValue('dummyKey'); dummyDefaultGroup.sideEffects[ @@ -909,8 +930,8 @@ describe('CollectionPersistent Tests', () => { collectionPersistent._key, collectionPersistent.storageKeys ); - expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); expect( dummyDefaultGroup.persistent?.removePersistedValue ).toHaveBeenCalledWith( @@ -955,8 +976,8 @@ describe('CollectionPersistent Tests', () => { 'dummyKey', collectionPersistent.storageKeys ); - expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); expect( dummyDefaultGroup.persistent?.removePersistedValue ).toHaveBeenCalledWith( @@ -990,8 +1011,8 @@ describe('CollectionPersistent Tests', () => { expect(response).toBeFalsy(); expect(dummyAgile.storages.remove).not.toHaveBeenCalled(); - expect(dummyCollection.getDefaultGroup).not.toHaveBeenCalled(); + expect(dummyCollection.getDefaultGroup).not.toHaveBeenCalled(); expect( dummyDefaultGroup.persistent?.removePersistedValue ).not.toHaveBeenCalled(); @@ -1015,8 +1036,8 @@ describe('CollectionPersistent Tests', () => { expect(response).toBeFalsy(); expect(dummyAgile.storages.remove).not.toHaveBeenCalled(); - expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); expect( dummyDefaultGroup.persistent?.removePersistedValue ).not.toHaveBeenCalled(); @@ -1034,15 +1055,15 @@ describe('CollectionPersistent Tests', () => { }); describe('formatKey function tests', () => { - it('should return key of Collection if no key got passed', () => { + it('should return key of Collection if no valid key got provided', () => { dummyCollection._key = 'coolKey'; - const response = collectionPersistent.formatKey(); + const response = collectionPersistent.formatKey(null); expect(response).toBe('coolKey'); }); - it('should return passed key', () => { + it('should return provided key if key is valid', () => { dummyCollection._key = 'coolKey'; const response = collectionPersistent.formatKey('awesomeKey'); @@ -1050,7 +1071,7 @@ describe('CollectionPersistent Tests', () => { expect(response).toBe('awesomeKey'); }); - it('should return and apply passed key to Collection if Collection had no own key before', () => { + it('should return and apply valid provided key to Collection if Collection has no own key', () => { dummyCollection._key = undefined; const response = collectionPersistent.formatKey('awesomeKey'); @@ -1059,10 +1080,10 @@ describe('CollectionPersistent Tests', () => { expect(dummyCollection._key).toBe('awesomeKey'); }); - it('should return undefined if no key got passed and Collection has no key', () => { + it('should return undefined if no valid key got provided and Collection has no key', () => { dummyCollection._key = undefined; - const response = collectionPersistent.formatKey(); + const response = collectionPersistent.formatKey(null); expect(response).toBeUndefined(); }); @@ -1092,13 +1113,6 @@ describe('CollectionPersistent Tests', () => { dummyItem2.persistent.removePersistedValue = jest.fn(); if (dummyItem3.persistent) dummyItem3.persistent.removePersistedValue = jest.fn(); - - if (dummyItem1.persistent) - dummyItem1.persistent.persistValue = jest.fn(); - if (dummyItem2.persistent) - dummyItem2.persistent.persistValue = jest.fn(); - if (dummyItem3.persistent) - dummyItem3.persistent.persistValue = jest.fn(); }); it('should return if no Item got added or removed', () => { @@ -1121,13 +1135,9 @@ describe('CollectionPersistent Tests', () => { expect( dummyItem3.persistent?.removePersistedValue ).not.toHaveBeenCalled(); - - expect(dummyItem1.persistent?.persistValue).not.toHaveBeenCalled(); - expect(dummyItem2.persistent?.persistValue).not.toHaveBeenCalled(); - expect(dummyItem3.persistent?.persistValue).not.toHaveBeenCalled(); }); - it('should call removePersistedValue on Items that got removed from Group', () => { + it('should call removePersistedValue() on Items that got removed from Group', () => { dummyGroup.previousStateValue = ['1', '2', '3']; dummyGroup._value = ['2']; @@ -1151,45 +1161,11 @@ describe('CollectionPersistent Tests', () => { ).toHaveBeenCalledWith( CollectionPersistent.getItemStorageKey('3', collectionPersistent._key) ); - - expect(dummyItem1.persistent?.persistValue).not.toHaveBeenCalled(); - expect(dummyItem2.persistent?.persistValue).not.toHaveBeenCalled(); - expect(dummyItem3.persistent?.persistValue).not.toHaveBeenCalled(); }); - it('should call persistValue on Items that have a persistent and got added to Group', () => { + it("should call persist on Items that got added to Group and hasn't been persisted yet", () => { dummyGroup.previousStateValue = ['1']; - dummyGroup._value = ['1', '2', '3']; - - collectionPersistent.rebuildStorageSideEffect(dummyGroup); - - expect(dummyItem1.persist).not.toHaveBeenCalled(); - expect(dummyItem2.persist).not.toHaveBeenCalled(); - expect(dummyItem3.persist).not.toHaveBeenCalled(); - expect(dummyItem4WithoutPersistent.persist).not.toHaveBeenCalled(); - - expect( - dummyItem1.persistent?.removePersistedValue - ).not.toHaveBeenCalled(); - expect( - dummyItem2.persistent?.removePersistedValue - ).not.toHaveBeenCalled(); - expect( - dummyItem3.persistent?.removePersistedValue - ).not.toHaveBeenCalled(); - - expect(dummyItem1.persistent?.persistValue).not.toHaveBeenCalled(); - expect(dummyItem2.persistent?.persistValue).toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey('2', collectionPersistent._key) - ); - expect(dummyItem3.persistent?.persistValue).toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey('3', collectionPersistent._key) - ); - }); - - it('should call persist on Items that have no persistent and got added to Group', () => { - dummyGroup.previousStateValue = ['1']; - dummyGroup._value = ['1', '4']; + dummyGroup._value = ['1', '4', '3']; collectionPersistent.rebuildStorageSideEffect(dummyGroup); @@ -1197,7 +1173,14 @@ describe('CollectionPersistent Tests', () => { expect(dummyItem2.persist).not.toHaveBeenCalled(); expect(dummyItem3.persist).not.toHaveBeenCalled(); expect(dummyItem4WithoutPersistent.persist).toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey('4', collectionPersistent._key) + CollectionPersistent.getItemStorageKey( + '4', + collectionPersistent._key + ), + { + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + } ); expect( @@ -1209,15 +1192,11 @@ describe('CollectionPersistent Tests', () => { expect( dummyItem3.persistent?.removePersistedValue ).not.toHaveBeenCalled(); - - expect(dummyItem1.persistent?.persistValue).not.toHaveBeenCalled(); - expect(dummyItem2.persistent?.persistValue).not.toHaveBeenCalled(); - expect(dummyItem3.persistent?.persistValue).not.toHaveBeenCalled(); }); }); describe('getItemStorageKey function tests', () => { - it('should build ItemStorageKey out of itemKey and collectionKey', () => { + it('should build ItemStorageKey based on itemKey and collectionKey', () => { const response = CollectionPersistent.getItemStorageKey( 'itemKey', 'collectionKey' @@ -1227,7 +1206,7 @@ describe('CollectionPersistent Tests', () => { LogMock.hasNotLogged('warn'); }); - it('should build ItemStorageKey out of collectionKey with warning', () => { + it('should build ItemStorageKey based on only collectionKey with warning', () => { const response = CollectionPersistent.getItemStorageKey( undefined, 'collectionKey' @@ -1237,7 +1216,7 @@ describe('CollectionPersistent Tests', () => { LogMock.hasLoggedCode('1A:02:00'); }); - it('should build ItemStorageKey out of itemKey with warning', () => { + it('should build ItemStorageKey based on only itemKey with warning', () => { const response = CollectionPersistent.getItemStorageKey( 'itemKey', undefined @@ -1247,7 +1226,7 @@ describe('CollectionPersistent Tests', () => { LogMock.hasLoggedCode('1A:02:00'); }); - it('should build ItemStorageKey out of nothing with warning', () => { + it('should build ItemStorageKey based on nothing with warning', () => { const response = CollectionPersistent.getItemStorageKey( undefined, undefined @@ -1259,7 +1238,7 @@ describe('CollectionPersistent Tests', () => { }); describe('getGroupStorageKey function tests', () => { - it('should build GroupStorageKey out of groupKey and collectionKey', () => { + it('should build GroupStorageKey based on groupKey and collectionKey', () => { const response = CollectionPersistent.getGroupStorageKey( 'groupKey', 'collectionKey' @@ -1269,7 +1248,7 @@ describe('CollectionPersistent Tests', () => { LogMock.hasNotLogged('warn'); }); - it('should build GroupStorageKey out of collectionKey with warning', () => { + it('should build GroupStorageKey based on only collectionKey with warning', () => { const response = CollectionPersistent.getGroupStorageKey( undefined, 'collectionKey' @@ -1279,7 +1258,7 @@ describe('CollectionPersistent Tests', () => { LogMock.hasLoggedCode('1A:02:01'); }); - it('should build GroupStorageKey out of groupKey with warning', () => { + it('should build GroupStorageKey based on only groupKey with warning', () => { const response = CollectionPersistent.getGroupStorageKey( 'groupKey', undefined @@ -1289,7 +1268,7 @@ describe('CollectionPersistent Tests', () => { LogMock.hasLoggedCode('1A:02:01'); }); - it('should build GroupStorageKey out of nothing with warning', () => { + it('should build GroupStorageKey based on nothing with warning', () => { const response = CollectionPersistent.getGroupStorageKey( undefined, undefined From 7d34ad360e9c72e8e23bbb2249848f109b685416 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 30 May 2021 18:01:57 +0200 Subject: [PATCH 028/117] added assignItem tests --- .../src/collection/collection.persistent.ts | 4 +- packages/core/src/collection/index.ts | 160 +++++---- packages/core/src/logCodeManager.ts | 2 + packages/core/src/utils.ts | 2 +- .../collection/collection.persistent.test.ts | 12 +- .../tests/unit/collection/collection.test.ts | 322 ++++++++++++++++-- packages/logger/src/index.ts | 2 +- packages/multieditor/src/multieditor.ts | 8 +- packages/proxytree/src/branch.ts | 2 +- packages/react/src/hocs/AgileHOC.ts | 6 +- packages/vue/src/bindAgileInstances.ts | 4 +- 11 files changed, 414 insertions(+), 110 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 33f4ae11..0ee6c8b3 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -168,9 +168,9 @@ export class CollectionPersistent< itemStorageKey ); - // If successfully loaded Item value, add Item to Collection + // If successfully loaded Item value, assign Item to Collection if (loadedPersistedValueIntoItem) - this.collection().collectItem(dummyItem); + this.collection().assignItem(dummyItem); } } } diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index e67536c5..6f6e1008 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -237,11 +237,11 @@ export class Collection { * @param config - Config */ public collect( - data: DataType | Array, + data: DataType | Item | Array>, groupKeys?: GroupKey | Array, config: CollectConfigInterface = {} ): this { - const _data = normalizeArray(data); + const _data = normalizeArray>(data); const _groupKeys = normalizeArray(groupKeys); const defaultGroupKey = this.config.defaultGroupKey; const primaryKey = this.config.primaryKey; @@ -262,12 +262,20 @@ export class Collection { _data.forEach((data, index) => { const itemKey = data[primaryKey]; + let success = false; + + // Assign Data or Item to Collection + if (data instanceof Item) { + success = this.assignItem(data, { + background: config.background, + }); + } else { + success = this.assignData(data, { + patch: config.patch, + background: config.background, + }); + } - // Add Item to Collection - const success = this.assignData(data, { - patch: config.patch, - background: config.background, - }); if (!success) return this; // Add ItemKey to provided Groups @@ -417,7 +425,6 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroup) * * @public - * @memberOf Collection * @param groupKey - key/name Group identifier * @param config - Configuration */ @@ -686,11 +693,11 @@ export class Collection { /** * Creates a placeholder Item - * that can be used to hold a reference to an Item that doesn't yet exist. + * that can be used to hold a reference to an Item that doesn't exist yet. * - * @private - * @param itemKey - Key/Name identifier of the Item to be created - * @param addToCollection - Whether the created Item should be added to the Collection + * @internal + * @param itemKey - Key/Name identifier of the Item to be created. + * @param addToCollection - Whether the created Item should be added to the Collection. */ public createPlaceholderItem( itemKey: ItemKey, @@ -704,12 +711,14 @@ export class Collection { } as any, { isPlaceholder: true } ); + if ( addToCollection && !Object.prototype.hasOwnProperty.call(this.data, itemKey) ) this.data[itemKey] = item; + ComputedTracker.tracked(item.observer); return item; } @@ -1164,94 +1173,118 @@ export class Collection { return this; } - //========================================================================================================= - // Set Data - //========================================================================================================= /** + * Assigns provided data object to an already existing Item at itemKey. + * If Item at itemKey doesn't exist yet, + * a new Item with the data object as value is created and added to the Collection. + * * @internal - * Updates existing or creates Item from provided Data - * @param data - Data - * @param config - Config + * @param data - Data object + * @param config - Configuration object */ public assignData( data: DataType, - config: SetDataConfigInterface = {} + config: AssignDataConfigInterface = {} ): boolean { - const _data = copy(data as any); // Transformed Data to any because of unknown Object (DataType) config = defineConfig(config, { patch: false, background: false, }); + const _data = copy(data); // Copy data object to get rid of reference + const primaryKey = this.config.primaryKey; if (!isValidObject(_data)) { LogCodeManager.log('1B:03:05', [this._key]); return false; } - // Check if data has valid primaryKey - if (!Object.prototype.hasOwnProperty.call(_data, this.config.primaryKey)) { - LogCodeManager.log('1B:02:05', [this._key, this.config.primaryKey]); - _data[this.config.primaryKey] = generateId(); + // Check if data object contains valid itemKey + // otherwise add random itemKey to Item + if (!Object.prototype.hasOwnProperty.call(_data, primaryKey)) { + LogCodeManager.log('1B:02:05', [this._key, primaryKey]); + _data[primaryKey] = generateId(); } - const itemKey = _data[this.config.primaryKey]; + const itemKey = _data[primaryKey]; const item = this.getItem(itemKey, { notExisting: true }); const wasPlaceholder = item?.isPlaceholder || false; - const createItem = item == null; - // Create or update Item - if (!createItem && config.patch) - item?.patch(_data, { background: config.background }); - if (!createItem && !config.patch) - item?.set(_data, { background: config.background }); - if (createItem) this.collectItem(new Item(this, _data)); + // Create new Item or update existing Item + if (item != null) { + if (config.patch) { + item.patch(_data, { background: config.background }); + } else { + item.set(_data, { background: config.background }); + } + } else { + this.assignItem(new Item(this, _data), { + background: config.background, + }); + } - // Increase size of Collection if Item was before a placeholder + // Increase size of Collection if Item was previously a placeholder + // (-> didn't officially exit in Collection) if (wasPlaceholder) this.size++; return true; } /** - * Adds passed Item to Collection. + * Adds provided Item to the Collection. * - * @public - * @param item - Item to be added + * @internal + * @param item - Item to be added. * @param config - Configuration object */ - public collectItem( + public assignItem( item: Item, - config: { background?: boolean } = {} - ): this { - const itemKey = item._value[this.config.primaryKey]; - - // TODO add to Groups at least default Group - // and implement it to collect method - - // Check if Item has valid primaryKey - if ( - !Object.prototype.hasOwnProperty.call(item._value, this.config.primaryKey) - ) { - LogCodeManager.log('1B:02:05', [this._key, this.config.primaryKey]); + config: AssignItemConfigInterface = {} + ): boolean { + config = defineConfig(config, { + overwrite: false, + background: false, + }); + const primaryKey = this.config.primaryKey; + let itemKey = item._value[primaryKey]; + let increaseCollectionSize = true; + + // Check if Item has valid itemKey + // otherwise add random itemKey to Item + if (!Object.prototype.hasOwnProperty.call(item._value, primaryKey)) { + LogCodeManager.log('1B:02:05', [this._key, primaryKey]); + itemKey = generateId(); item.patch( - { [this.config.primaryKey]: generateId() }, - { background: true } + { [this.config.primaryKey]: itemKey }, + { background: config.background } ); + item._key = itemKey; + } + + // Check if Item belongs to this Collection + if (item.collection() !== this) { + LogCodeManager.log('1B:03:06', [this._key, item.collection()._key]); + return false; } // Check if Item already exists - if (this.getItem(itemKey) != null) return this; + if (this.getItem(itemKey) != null) { + if (!config.overwrite) return true; + else increaseCollectionSize = false; + } + // Assign/add Item to Collection this.data[itemKey] = item; - // Rebuild Groups That include ItemKey after assigning Item to Collection (otherwise it can't find Item) + // Rebuild Groups that include itemKey + // after adding Item to Collection + // (because otherwise it can't find the Item since it doesn't exist in Collection yet) this.rebuildGroupsThatIncludeItemKey(itemKey, { background: config.background, }); - this.size++; + if (increaseCollectionSize) this.size++; - return this; + return true; } //========================================================================================================= @@ -1331,7 +1364,11 @@ export interface CollectionConfigInterface { export interface CollectConfigInterface { patch?: boolean; method?: 'push' | 'unshift'; - forEachItem?: (data: DataType, key: ItemKey, index: number) => void; + forEachItem?: ( + data: DataType | Item, + key: ItemKey, + index: number + ) => void; background?: boolean; select?: boolean; } @@ -1397,11 +1434,20 @@ export interface RemoveItemsConfigInterface { * @property patch - If Data gets patched into existing Item * @property background - If assigning Data happens in background */ -export interface SetDataConfigInterface { +export interface AssignDataConfigInterface { patch?: boolean; background?: boolean; } +/** + * @property overwrite - If old Item should be overwritten + * @property background - If assigning Data happens in background + */ +export interface AssignItemConfigInterface { + overwrite?: boolean; + background?: boolean; +} + export type CollectionConfig = | CreateCollectionConfigInterface | (( diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index 895f2bc6..8ac05a98 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -137,6 +137,8 @@ const logCodeMessages = { "Couldn't update ItemKey from '${0}' to '${1}' " + "because an Item with the key/name '${1}' already exists in the Collection '${2}'!", '1B:03:05': "Item Data of Collection '${0}' has to be a valid object!", + '1B:03:06': + "Item tried to add to the Collection '${0}' belongs to another Collection '${1}'!", // Group '1C:02:00': diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 56f5000d..d998f602 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -39,7 +39,7 @@ export function getAgileInstance(instance: any): Agile | undefined { // Extract Observers //========================================================================================================= /** - * @private + * @internal * Extract Observers from specific Instances * @param instances - Instances that will be formatted */ diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index b59dd9ae..7c10829d 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -331,7 +331,7 @@ describe('CollectionPersistent Tests', () => { dummyCollection.getDefaultGroup = jest.fn( () => dummyDefaultGroup as any ); - dummyCollection.collectItem = jest.fn(); + dummyCollection.assignItem = jest.fn(); dummyAgile.storages.get = jest.fn(); }); @@ -479,13 +479,13 @@ describe('CollectionPersistent Tests', () => { storageKeys: collectionPersistent.storageKeys, } ); - expect(dummyCollection.collectItem).toHaveBeenCalledWith( + expect(dummyCollection.assignItem).toHaveBeenCalledWith( placeholderItem1 ); - expect(dummyCollection.collectItem).not.toHaveBeenCalledWith( + expect(dummyCollection.assignItem).not.toHaveBeenCalledWith( placeholderItem2 ); // Because Item persistent isn't ready - expect(dummyCollection.collectItem).not.toHaveBeenCalledWith( + expect(dummyCollection.assignItem).not.toHaveBeenCalledWith( placeholderItem3 ); // Because Item persistent 'leadPersistedValue()' returned false -> Item properly doesn't exist in Storage @@ -568,10 +568,10 @@ describe('CollectionPersistent Tests', () => { storageKeys: collectionPersistent.storageKeys, } ); - expect(dummyCollection.collectItem).toHaveBeenCalledWith( + expect(dummyCollection.assignItem).toHaveBeenCalledWith( placeholderItem1 ); - expect(dummyCollection.collectItem).not.toHaveBeenCalledWith( + expect(dummyCollection.assignItem).not.toHaveBeenCalledWith( placeholderItem3 ); // Because Item 3 is already present in Collection diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index 32219fba..aeedde2b 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -1407,12 +1407,18 @@ describe('Collection Tests', () => { describe('getItemWithReference function tests', () => { let dummyItem: Item; + let placeholderItem: Item; beforeEach(() => { dummyItem = new Item(collection, { id: '1', name: 'Jeff' }); + placeholderItem = collection.createPlaceholderItem('1'); + collection.data = { ['1']: dummyItem, }; + collection.createPlaceholderItem = jest + .fn() + .mockReturnValueOnce(placeholderItem); ComputedTracker.tracked = jest.fn(); }); @@ -1421,6 +1427,7 @@ describe('Collection Tests', () => { const response = collection.getItemWithReference('1'); expect(response).toBe(dummyItem); + expect(collection.createPlaceholderItem).not.toHaveBeenCalled(); expect(ComputedTracker.tracked).toHaveBeenCalledWith( dummyItem.observer ); @@ -1429,14 +1436,82 @@ describe('Collection Tests', () => { it("should return and track created reference Item if Item doesn't exist yet", () => { const response = collection.getItemWithReference('notExistingItem'); - expect(response).toBeInstanceOf(Item); - expect(response.isPlaceholder).toBeTruthy(); - expect(response._key).toBe('notExistingItem'); - expect(collection.data['notExistingItem']).toBe(response); + expect(response).toBe(placeholderItem); + expect(collection.createPlaceholderItem).toHaveBeenCalledWith( + 'notExistingItem', + true + ); expect(ComputedTracker.tracked).toHaveBeenCalledWith(response.observer); }); }); + describe('createPlaceholderItem function tests', () => { + let dummyItem: Item; + + beforeEach(() => { + dummyItem = new Item(collection, { id: '1', name: 'Jeff' }); + + collection.data = { + ['1']: dummyItem, + }; + + ComputedTracker.tracked = jest.fn(); + }); + + it("should create placeholder Item and shouldn't add it to Collection (addToCollection = false)", () => { + const item = collection.createPlaceholderItem('2', false); + + expect(item).not.toBe(dummyItem); + expect(item.collection()).toBe(collection); + expect(item._key).toBe('2'); + expect(item._value).toStrictEqual({ id: '2', dummy: 'item' }); + expect(item.isPlaceholder).toBeTruthy(); + + expect(collection.data).not.toHaveProperty('2'); + + expect(ComputedTracker.tracked).toHaveBeenCalledTimes(1); + expect(ComputedTracker.tracked).not.toHaveBeenCalledWith( + dummyItem.observer + ); + }); + + it("should create placeholder Item and shouldn't add it to Collection if Item already exists (addToCollection = true)", () => { + const item = collection.createPlaceholderItem('1', false); + + expect(item).not.toBe(dummyItem); + expect(item.collection()).toBe(collection); + expect(item._key).toBe('1'); + expect(item._value).toStrictEqual({ id: '1', dummy: 'item' }); + expect(item.isPlaceholder).toBeTruthy(); + + expect(collection.data).toHaveProperty('1'); + expect(collection.data['1']).toBe(dummyItem); + + expect(ComputedTracker.tracked).toHaveBeenCalledTimes(1); + expect(ComputedTracker.tracked).not.toHaveBeenCalledWith( + dummyItem.observer + ); + }); + + it('should create placeholder Item and add it to Collection (addToCollection = true)', () => { + const item = collection.createPlaceholderItem('2', true); + + expect(item).not.toBe(dummyItem); + expect(item.collection()).toBe(collection); + expect(item._key).toBe('2'); + expect(item._value).toStrictEqual({ id: '2', dummy: 'item' }); + expect(item.isPlaceholder).toBeTruthy(); + + expect(collection.data).toHaveProperty('2'); + expect(collection.data['2']).toStrictEqual(expect.any(Item)); + + expect(ComputedTracker.tracked).toHaveBeenCalledTimes(1); + expect(ComputedTracker.tracked).not.toHaveBeenCalledWith( + dummyItem.observer + ); + }); + }); + describe('getItemValue function tests', () => { let dummyItem: Item; @@ -2353,7 +2428,7 @@ describe('Collection Tests', () => { }); }); - describe('setData function tests', () => { + describe('assignData function tests', () => { let dummyItem1: Item; beforeEach(() => { @@ -2363,55 +2438,89 @@ describe('Collection Tests', () => { }; collection.size = 1; + jest.spyOn(collection, 'assignItem'); + dummyItem1.patch = jest.fn(); dummyItem1.set = jest.fn(); }); - it('should create new Item out of valid Data, rebuild Groups and increase size (default config)', () => { + it("should assign Item to Collection if it doesn't exist yet (default config)", () => { const response = collection.assignData({ id: 'dummyItem2', name: 'Hans', }); expect(response).toBeTruthy(); - expect(collection.data).toHaveProperty('dummyItem1'); + expect(collection.size).toBe(2); // Increased by assignItem + expect(collection.assignItem).toHaveBeenCalledWith(expect.any(Item), { + background: false, + }); + + // Check if Item, assignItem was called with, has the correct data expect(collection.data).toHaveProperty('dummyItem2'); - expect(collection.data['dummyItem2']).toBeInstanceOf(Item); expect(collection.data['dummyItem2']._value).toStrictEqual({ id: 'dummyItem2', name: 'Hans', }); - expect(collection.size).toBe(2); LogMock.hasNotLogged('error'); LogMock.hasNotLogged('warn'); }); - it("shouldn't create new Item if passed Data is no valid Object", () => { + it("should assign Item to Collection if it doesn't exist yet (config.background = true)", () => { + const response = collection.assignData( + { + id: 'dummyItem2', + name: 'Hans', + }, + { background: true } + ); + + expect(response).toBeTruthy(); + expect(collection.size).toBe(2); // Increased by assignItem + expect(collection.assignItem).toHaveBeenCalledWith(expect.any(Item), { + background: true, + }); + + // Check if Item, assignItem was called with, has the correct data + expect(collection.data).toHaveProperty('dummyItem2'); + expect(collection.data['dummyItem2']._value).toStrictEqual({ + id: 'dummyItem2', + name: 'Hans', + }); + + LogMock.hasNotLogged('error'); + LogMock.hasNotLogged('warn'); + }); + + it("shouldn't assign or update Item if passed data is no valid object", () => { const response = collection.assignData('noObject' as any); expect(response).toBeFalsy(); expect(collection.size).toBe(1); + expect(collection.assignItem).not.toHaveBeenCalled(); LogMock.hasLoggedCode('1B:03:05', [collection._key]); LogMock.hasNotLogged('warn'); }); - it('should create new Item with random primaryKey if passed Data has no primaryKey', () => { + it("should assign Item to Collection with random itemKey if data object doesn't contain valid itemKey", () => { jest.spyOn(Utils, 'generateId').mockReturnValueOnce('randomDummyId'); const response = collection.assignData({ name: 'Frank' } as any); expect(response).toBeTruthy(); - expect(response).toBeTruthy(); - expect(collection.data).toHaveProperty('dummyItem1'); + expect(collection.size).toBe(2); // Increased by assignItem + expect(collection.assignItem).toHaveBeenCalledWith(expect.any(Item), { + background: false, + }); + + // Check if Item, assignItem was called with, has the correct data expect(collection.data).toHaveProperty('randomDummyId'); - expect(collection.data['randomDummyId']).toBeInstanceOf(Item); expect(collection.data['randomDummyId']._value).toStrictEqual({ id: 'randomDummyId', name: 'Frank', }); - expect(collection.size).toBe(2); LogMock.hasNotLogged('error'); LogMock.hasLoggedCode('1B:02:05', [ @@ -2420,17 +2529,15 @@ describe('Collection Tests', () => { ]); }); - it("should update Item with valid Data, shouldn't rebuild Groups and shouldn't increase size (default config)", () => { + it('should update Item with valid data via set (default config)', () => { const response = collection.assignData({ id: 'dummyItem1', name: 'Dieter', }); expect(response).toBeTruthy(); - - expect(collection.data).toHaveProperty('dummyItem1'); - expect(collection.data['dummyItem1']).toBeInstanceOf(Item); expect(collection.size).toBe(1); + expect(collection.assignItem).not.toHaveBeenCalled(); expect(dummyItem1.set).toHaveBeenCalledWith( { id: 'dummyItem1', name: 'Dieter' }, @@ -2442,7 +2549,7 @@ describe('Collection Tests', () => { LogMock.hasNotLogged('warn'); }); - it("should update Item with valid Data, shouldn't rebuild Groups and shouldn't increase size (config.background = true)", () => { + it('should update Item with valid data via set (config.background = true)', () => { const response = collection.assignData( { id: 'dummyItem1', @@ -2452,10 +2559,8 @@ describe('Collection Tests', () => { ); expect(response).toBeTruthy(); - - expect(collection.data).toHaveProperty('dummyItem1'); - expect(collection.data['dummyItem1']).toBeInstanceOf(Item); expect(collection.size).toBe(1); + expect(collection.assignItem).not.toHaveBeenCalled(); expect(dummyItem1.set).toHaveBeenCalledWith( { id: 'dummyItem1', name: 'Dieter' }, @@ -2467,7 +2572,7 @@ describe('Collection Tests', () => { LogMock.hasNotLogged('warn'); }); - it("should update Item with valid Data, shouldn't rebuild Groups and shouldn't increase size (config.patch = true, background: true)", () => { + it('should update Item with valid data via patch (config.patch = true, background: true)', () => { const response = collection.assignData( { id: 'dummyItem1', @@ -2477,10 +2582,8 @@ describe('Collection Tests', () => { ); expect(response).toBeTruthy(); - - expect(collection.data).toHaveProperty('dummyItem1'); - expect(collection.data['dummyItem1']).toBeInstanceOf(Item); expect(collection.size).toBe(1); + expect(collection.assignItem).not.toHaveBeenCalled(); expect(dummyItem1.set).not.toHaveBeenCalled(); expect(dummyItem1.patch).toHaveBeenCalledWith( @@ -2492,9 +2595,8 @@ describe('Collection Tests', () => { LogMock.hasNotLogged('warn'); }); - it("should update placeholder Item with valid Data, shouldn't rebuild Groups and should increase size (default config)", () => { + it('should update placeholder Item with valid data and increase size', () => { dummyItem1.isPlaceholder = true; - collection.size = 0; const response = collection.assignData({ id: 'dummyItem1', @@ -2502,15 +2604,169 @@ describe('Collection Tests', () => { }); expect(response).toBeTruthy(); - - expect(collection.data).toHaveProperty('dummyItem1'); - expect(collection.data['dummyItem1']).toBeInstanceOf(Item); - expect(collection.size).toBe(1); + expect(collection.size).toBe(2); + expect(collection.assignItem).not.toHaveBeenCalled(); expect(dummyItem1.set).toHaveBeenCalledWith( { id: 'dummyItem1', name: 'Dieter' }, { background: false } ); + expect(dummyItem1.patch).not.toHaveBeenCalled(); + + LogMock.hasNotLogged('error'); + LogMock.hasNotLogged('warn'); + }); + }); + + describe('assignItem function tests', () => { + let dummyItem1: Item; + let toAddDummyItem2: Item; + + beforeEach(() => { + dummyItem1 = new Item(collection, { id: 'dummyItem1', name: 'Jeff' }); + toAddDummyItem2 = new Item(collection, { + id: 'dummyItem2', + name: 'Frank', + }); + collection.data = { + dummyItem1: dummyItem1, + }; + collection.size = 1; + + dummyItem1.patch = jest.fn(); + toAddDummyItem2.patch = jest.fn(); + collection.rebuildGroupsThatIncludeItemKey = jest.fn(); + }); + + it('should assign valid Item to Collection (default config)', () => { + const response = collection.assignItem(toAddDummyItem2); + + expect(response).toBeTruthy(); + expect(collection.size).toBe(2); + expect(collection.data).toHaveProperty('dummyItem2'); + expect(collection.data['dummyItem2']).toBe(toAddDummyItem2); + expect(collection.rebuildGroupsThatIncludeItemKey).toHaveBeenCalledWith( + 'dummyItem2', + { + background: false, + } + ); + + expect(toAddDummyItem2.patch).not.toHaveBeenCalled(); + + LogMock.hasNotLogged('error'); + LogMock.hasNotLogged('warn'); + }); + + it('should assign valid Item to Collection (config.background = true)', () => { + const response = collection.assignItem(toAddDummyItem2, { + background: true, + }); + + expect(response).toBeTruthy(); + expect(collection.size).toBe(2); + expect(collection.data).toHaveProperty('dummyItem2'); + expect(collection.data['dummyItem2']).toBe(toAddDummyItem2); + expect(collection.rebuildGroupsThatIncludeItemKey).toHaveBeenCalledWith( + 'dummyItem2', + { + background: true, + } + ); + + expect(toAddDummyItem2.patch).not.toHaveBeenCalled(); + + LogMock.hasNotLogged('error'); + LogMock.hasNotLogged('warn'); + }); + + it("should assign Item to Collection with random itemKey if data object doesn't contain valid itemKey (default config)", () => { + jest.spyOn(Utils, 'generateId').mockReturnValueOnce('randomDummyId'); + toAddDummyItem2._value = { dummy: 'data' } as any; + toAddDummyItem2._key = undefined; + + const response = collection.assignItem(toAddDummyItem2); + + expect(response).toBeTruthy(); + expect(collection.size).toBe(2); + expect(collection.data).toHaveProperty('randomDummyId'); + expect(collection.data['randomDummyId']).toBe(toAddDummyItem2); + expect(collection.rebuildGroupsThatIncludeItemKey).toHaveBeenCalledWith( + 'randomDummyId', + { + background: false, + } + ); + + expect(toAddDummyItem2.patch).toHaveBeenCalledWith( + { id: 'randomDummyId' }, + { background: false } + ); + expect(toAddDummyItem2._key).toBe('randomDummyId'); + + LogMock.hasNotLogged('error'); + LogMock.hasLoggedCode('1B:02:05', [ + collection._key, + collection.config.primaryKey, + ]); + }); + + it("shouldn't assign Item to Collection that belongs to another Collection", () => { + const anotherCollection = new Collection(dummyAgile, { + key: 'anotherCollection', + }); + toAddDummyItem2.collection = () => anotherCollection; + + const response = collection.assignItem(toAddDummyItem2); + + expect(response).toBeFalsy(); + expect(collection.size).toBe(1); + expect(collection.data).not.toHaveProperty('dummyItem2'); + expect( + collection.rebuildGroupsThatIncludeItemKey + ).not.toHaveBeenCalled(); + + expect(toAddDummyItem2.patch).not.toHaveBeenCalled(); + + LogMock.hasLoggedCode('1B:03:06', [ + collection._key, + anotherCollection._key, + ]); + LogMock.hasNotLogged('warn'); + }); + + it("shouldn't assign Item to Collection if an Item at itemKey already exists (default config)", () => { + const response = collection.assignItem(dummyItem1); + + expect(response).toBeTruthy(); + expect(collection.size).toBe(1); + expect(collection.data).toHaveProperty('dummyItem1'); + expect(collection.data['dummyItem1']).toBe(dummyItem1); + expect( + collection.rebuildGroupsThatIncludeItemKey + ).not.toHaveBeenCalled(); + + expect(dummyItem1.patch).not.toHaveBeenCalled(); + + LogMock.hasNotLogged('error'); + LogMock.hasNotLogged('warn'); + }); + + it('should assign Item to Collection if an Item at itemKey already exists (config.overwrite = true)', () => { + const response = collection.assignItem(dummyItem1, { overwrite: true }); + + expect(response).toBeTruthy(); + expect(collection.size).toBe(1); + expect(collection.data).toHaveProperty('dummyItem1'); + expect(collection.data['dummyItem1']).toBe(dummyItem1); + expect(collection.rebuildGroupsThatIncludeItemKey).toHaveBeenCalledWith( + 'dummyItem1', + { + background: false, + } + ); + + expect(dummyItem1.patch).not.toHaveBeenCalled(); LogMock.hasNotLogged('error'); LogMock.hasNotLogged('warn'); diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index d00a98ea..e3a2a889 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -124,7 +124,7 @@ export class Logger { // Tag //========================================================================================================= /** - * @private + * @internal * Only executes following 'command' if all given tags are included in allowedTags * @param tags - Tags */ diff --git a/packages/multieditor/src/multieditor.ts b/packages/multieditor/src/multieditor.ts index 573ca0ce..107a98b9 100644 --- a/packages/multieditor/src/multieditor.ts +++ b/packages/multieditor/src/multieditor.ts @@ -394,7 +394,7 @@ export class MultiEditor< // Get Validator //========================================================================================================= /** - * @private + * @internal * Get Validator of Item based on validateMethods * @param key - Key/Name of Item */ @@ -429,7 +429,7 @@ export class MultiEditor< // Validate //========================================================================================================= /** - * @private + * @internal * Validates Editor and updates its 'isValid' property */ public validate(): boolean { @@ -451,7 +451,7 @@ export class MultiEditor< // Can Assign Status To Item On Change //========================================================================================================= /** - * @private + * @internal * If Status can be assigned on Change * @param item - Item to which the Status should get applied */ @@ -470,7 +470,7 @@ export class MultiEditor< // Can Assign Status To Item On Submit //========================================================================================================= /** - * @private + * @internal * If Status can be assigned on Submit * @param item - Item to which the Status should get applied */ diff --git a/packages/proxytree/src/branch.ts b/packages/proxytree/src/branch.ts index f9c05f3e..bb13449e 100644 --- a/packages/proxytree/src/branch.ts +++ b/packages/proxytree/src/branch.ts @@ -46,7 +46,7 @@ export class Branch { } /** - * @private + * @internal * Record usage of an accessed property in the passed target object. * @param target - Target object in which a property at key was accessed * @param key - Key that was accessed in the target object diff --git a/packages/react/src/hocs/AgileHOC.ts b/packages/react/src/hocs/AgileHOC.ts index 30bbde0d..8249b15c 100644 --- a/packages/react/src/hocs/AgileHOC.ts +++ b/packages/react/src/hocs/AgileHOC.ts @@ -74,7 +74,7 @@ export function AgileHOC( // Create HOC //========================================================================================================= /** - * @private + * @internal * Creates Higher Order Component based on passed React Component that binds the deps to it * @param ReactComponent - React Component * @param agileInstance - Instance of Agile @@ -151,7 +151,7 @@ const createHOC = ( // Format Deps With No Safe Indicator //========================================================================================================= /** - * @private + * @internal * Extract Observers from dependencies which might not have an indicator. * If a indicator could be found it will be added to 'depsWithIndicator' otherwise to 'depsWithoutIndicator'. * @param deps - Dependencies to be formatted @@ -187,7 +187,7 @@ const formatDepsWithNoSafeIndicator = ( // Format Deps With Indicator //========================================================================================================= /** - * @private + * @internal * Extract Observers from dependencies which have an indicator through the object property key. * @param deps - Dependencies to be formatted */ diff --git a/packages/vue/src/bindAgileInstances.ts b/packages/vue/src/bindAgileInstances.ts index 1d439b44..0e89bdc9 100644 --- a/packages/vue/src/bindAgileInstances.ts +++ b/packages/vue/src/bindAgileInstances.ts @@ -50,7 +50,7 @@ export function bindAgileInstances( // Format Deps With No Safe Indicator //========================================================================================================= /** - * @private + * @internal * Extract Observers from dependencies which might not have an indicator. * If a indicator could be found it will be added to 'depsWithIndicator' otherwise to 'depsWithoutIndicator'. * @param deps - Dependencies to be formatted @@ -86,7 +86,7 @@ const formatDepsWithNoSafeIndicator = ( // Format Deps With Indicator //========================================================================================================= /** - * @private + * @internal * Extract Observers from dependencies which have an indicator through the object property key. * @param deps - Dependencies to be formatted */ From f31c043794069e693c656efe9506fa2df7309b40 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 30 May 2021 20:27:08 +0200 Subject: [PATCH 029/117] fixed persist tests --- .../tests/unit/collection/collection.test.ts | 19 ++++++++++--------- .../core/tests/unit/collection/item.test.ts | 9 +++++++-- packages/core/tests/unit/state/state.test.ts | 17 +++++++---------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index aeedde2b..de26cc2a 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -447,11 +447,13 @@ describe('Collection Tests', () => { let dummyGroup1: Group; let dummyGroup2: Group; let defaultGroup: Group; + let dummyItem: Item; beforeEach(() => { dummyGroup1 = new Group(collection); dummyGroup2 = new Group(collection); defaultGroup = new Group(collection); + dummyItem = new Item(collection, { id: '1', name: 'frank' }); collection.groups = { [collection.config.defaultGroupKey]: defaultGroup, @@ -460,6 +462,7 @@ describe('Collection Tests', () => { }; collection.assignData = jest.fn(); + collection.assignItem = jest.fn(); collection.createSelector = jest.fn(); collection.createGroup = jest.fn(); @@ -1703,19 +1706,17 @@ describe('Collection Tests', () => { }); }); - it('should overwrite existing persistent with a warning', () => { - collection.persistent = new CollectionPersistent(collection); + it("shouldn't overwrite existing persistent", () => { + const dummyPersistent = new CollectionPersistent(collection); + collection.persistent = dummyPersistent; + collection.isPersisted = true; + jest.clearAllMocks(); collection.persist('newPersistentKey'); - expect(collection.persistent).toBeInstanceOf(CollectionPersistent); + expect(collection.persistent).toBe(dummyPersistent); // expect(collection.persistent._key).toBe("newPersistentKey"); // Can not test because of Mocking Persistent - expect(CollectionPersistent).toHaveBeenCalledWith(collection, { - instantiate: true, - storageKeys: [], - key: 'newPersistentKey', - defaultStorageKey: null, - }); + expect(CollectionPersistent).not.toHaveBeenCalled(); }); }); diff --git a/packages/core/tests/unit/collection/item.test.ts b/packages/core/tests/unit/collection/item.test.ts index 32f61557..c3ccb9af 100644 --- a/packages/core/tests/unit/collection/item.test.ts +++ b/packages/core/tests/unit/collection/item.test.ts @@ -2,15 +2,20 @@ import { Item, Collection, Agile, StateObserver, State } from '../../../src'; import { LogMock } from '../../helper/logMock'; describe('Item Tests', () => { + interface ItemInterface { + id: string; + name: string; + } + let dummyAgile: Agile; - let dummyCollection: Collection; + let dummyCollection: Collection; beforeEach(() => { jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); - dummyCollection = new Collection(dummyAgile); + dummyCollection = new Collection(dummyAgile); jest.spyOn(Item.prototype, 'addRebuildGroupThatIncludeItemKeySideEffect'); }); diff --git a/packages/core/tests/unit/state/state.test.ts b/packages/core/tests/unit/state/state.test.ts index 66793053..dbfb84d5 100644 --- a/packages/core/tests/unit/state/state.test.ts +++ b/packages/core/tests/unit/state/state.test.ts @@ -638,20 +638,17 @@ describe('State Tests', () => { }); }); - it('should overwrite existing Persistent', () => { - const oldPersistent = new StatePersistent(numberState); - numberState.persistent = oldPersistent; + it("shouldn't overwrite existing Persistent", () => { + const dummyPersistent = new StatePersistent(numberState); + numberState.persistent = dummyPersistent; + numberState.isPersisted = true; + jest.clearAllMocks(); numberState.persist('newPersistentKey'); - expect(numberState.persistent).toBeInstanceOf(StatePersistent); + expect(numberState.persistent).toBe(dummyPersistent); // expect(numberState.persistent._key).toBe("newPersistentKey"); // Can not test because of Mocking Persistent - expect(StatePersistent).toHaveBeenCalledWith(numberState, { - instantiate: true, - storageKeys: [], - key: 'newPersistentKey', - defaultStorageKey: null, - }); + expect(StatePersistent).not.toHaveBeenCalled(); }); }); From 8a7ff482875245ba08131a5a2add8de749d917f6 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 31 May 2021 06:45:33 +0200 Subject: [PATCH 030/117] fixed collect method tests --- packages/core/src/collection/index.ts | 26 ++- packages/core/src/collection/item.ts | 2 +- .../tests/unit/collection/collection.test.ts | 197 ++++++++++++++---- 3 files changed, 174 insertions(+), 51 deletions(-) diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 6f6e1008..f62b9fa3 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -261,7 +261,7 @@ export class Collection { ); _data.forEach((data, index) => { - const itemKey = data[primaryKey]; + let itemKey; let success = false; // Assign Data or Item to Collection @@ -269,25 +269,28 @@ export class Collection { success = this.assignItem(data, { background: config.background, }); + itemKey = data._key; } else { success = this.assignData(data, { patch: config.patch, background: config.background, }); + itemKey = data[primaryKey]; } - if (!success) return this; - - // Add ItemKey to provided Groups - _groupKeys.forEach((groupKey) => { - this.getGroup(groupKey)?.add(itemKey, { - method: config.method, - background: config.background, + // Add ItemKey to provided Groups and create corresponding Selector + if (success) { + _groupKeys.forEach((groupKey) => { + this.getGroup(groupKey)?.add(itemKey, { + method: config.method, + background: config.background, + }); }); - }); - if (config.select) this.createSelector(itemKey, itemKey); - if (config.forEachItem) config.forEachItem(data, itemKey, index); + if (config.select) this.createSelector(itemKey, itemKey); + } + + if (config.forEachItem) config.forEachItem(data, itemKey, success, index); }); return this; @@ -1367,6 +1370,7 @@ export interface CollectConfigInterface { forEachItem?: ( data: DataType | Item, key: ItemKey, + success: boolean, index: number ) => void; background?: boolean; diff --git a/packages/core/src/collection/item.ts b/packages/core/src/collection/item.ts index d64f2ad4..28cae1da 100644 --- a/packages/core/src/collection/item.ts +++ b/packages/core/src/collection/item.ts @@ -1,7 +1,6 @@ import { State, Collection, - DefaultItem, StateKey, StateRuntimeJobConfigInterface, defineConfig, @@ -10,6 +9,7 @@ import { isValidObject, CollectionPersistent, StatePersistentConfigInterface, + DefaultItem, } from '../internal'; export class Item extends State< diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index de26cc2a..c79dcf57 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -447,13 +447,13 @@ describe('Collection Tests', () => { let dummyGroup1: Group; let dummyGroup2: Group; let defaultGroup: Group; - let dummyItem: Item; + let dummyItem5: Item; beforeEach(() => { dummyGroup1 = new Group(collection); dummyGroup2 = new Group(collection); defaultGroup = new Group(collection); - dummyItem = new Item(collection, { id: '1', name: 'frank' }); + dummyItem5 = new Item(collection, { id: '5', name: 'frank' }); collection.groups = { [collection.config.defaultGroupKey]: defaultGroup, @@ -471,7 +471,7 @@ describe('Collection Tests', () => { defaultGroup.add = jest.fn(); }); - it('should add Data to Collection and to default Group (default config)', () => { + it('should add data object to Collection and to default Group (default config)', () => { collection.assignData = jest.fn(() => true); collection.collect({ id: '1', name: 'frank' }); @@ -486,6 +486,8 @@ describe('Collection Tests', () => { background: false, } ); + expect(collection.assignItem).not.toHaveBeenCalled(); + expect(collection.createGroup).not.toHaveBeenCalled(); expect(dummyGroup1.add).not.toHaveBeenCalled(); @@ -498,7 +500,7 @@ describe('Collection Tests', () => { expect(collection.createSelector).not.toHaveBeenCalled(); }); - it('should add Data to Collection and to default Group (specific config)', () => { + it('should add data object to Collection and to default Group (specific config)', () => { collection.assignData = jest.fn(() => true); collection.collect({ id: '1', name: 'frank' }, [], { @@ -517,6 +519,8 @@ describe('Collection Tests', () => { background: true, } ); + expect(collection.assignItem).not.toHaveBeenCalled(); + expect(collection.createGroup).not.toHaveBeenCalled(); expect(dummyGroup1.add).not.toHaveBeenCalled(); @@ -529,14 +533,60 @@ describe('Collection Tests', () => { expect(collection.createSelector).not.toHaveBeenCalled(); }); - it('should add Data to Collection and to passed Groups + default Group (default config)', () => { + it('should add Item to Collection and to default Group (default config)', () => { + collection.assignItem = jest.fn(() => true); + + collection.collect(dummyItem5); + + expect(collection.assignData).not.toHaveBeenCalled(); + expect(collection.assignItem).toHaveBeenCalledWith(dummyItem5, { + background: false, + }); + + expect(collection.createGroup).not.toHaveBeenCalled(); + + expect(dummyGroup1.add).not.toHaveBeenCalled(); + expect(dummyGroup2.add).not.toHaveBeenCalled(); + expect(defaultGroup.add).toHaveBeenCalledWith('5', { + method: 'push', + background: false, + }); + + expect(collection.createSelector).not.toHaveBeenCalled(); + }); + + it('should add Item to Collection and to default Group (specific config)', () => { + collection.assignItem = jest.fn(() => true); + + collection.collect(dummyItem5, [], { + background: true, + method: 'unshift', + patch: true, + }); + + expect(collection.assignData).not.toHaveBeenCalled(); + expect(collection.assignItem).toHaveBeenCalledWith(dummyItem5, { + background: true, + }); + + expect(collection.createGroup).not.toHaveBeenCalled(); + + expect(dummyGroup1.add).not.toHaveBeenCalled(); + expect(dummyGroup2.add).not.toHaveBeenCalled(); + expect(defaultGroup.add).toHaveBeenCalledWith('5', { + method: 'unshift', + background: true, + }); + + expect(collection.createSelector).not.toHaveBeenCalled(); + }); + + it('should add data/item to Collection and to given + default Group (default config)', () => { collection.assignData = jest.fn(() => true); + collection.assignItem = jest.fn(() => true); collection.collect( - [ - { id: '1', name: 'frank' }, - { id: '2', name: 'hans' }, - ], + [{ id: '1', name: 'frank' }, dummyItem5, { id: '2', name: 'hans' }], ['dummyGroup1', 'dummyGroup2'] ); @@ -560,6 +610,10 @@ describe('Collection Tests', () => { background: false, } ); + expect(collection.assignItem).toHaveBeenCalledWith(dummyItem5, { + background: false, + }); + expect(collection.createGroup).not.toHaveBeenCalled(); expect(dummyGroup1.add).toHaveBeenCalledWith('1', { @@ -570,6 +624,10 @@ describe('Collection Tests', () => { method: 'push', background: false, }); + expect(dummyGroup1.add).toHaveBeenCalledWith('5', { + method: 'push', + background: false, + }); expect(dummyGroup2.add).toHaveBeenCalledWith('1', { method: 'push', background: false, @@ -578,6 +636,10 @@ describe('Collection Tests', () => { method: 'push', background: false, }); + expect(dummyGroup2.add).toHaveBeenCalledWith('5', { + method: 'push', + background: false, + }); expect(defaultGroup.add).toHaveBeenCalledWith('1', { method: 'push', background: false, @@ -586,17 +648,25 @@ describe('Collection Tests', () => { method: 'push', background: false, }); + expect(defaultGroup.add).toHaveBeenCalledWith('5', { + method: 'push', + background: false, + }); expect(collection.createSelector).not.toHaveBeenCalled(); }); - it("should call setData and shouldn't add Items to passed Groups if setData failed (default config)", () => { - collection.assignData = jest.fn(() => false); + it("should try to add data/item to Collection and shouldn't add it to passed Groups if adding data/item failed (default config)", () => { + collection.assignData = jest + .fn() + .mockReturnValueOnce(false) + .mockReturnValueOnce(true); + collection.assignItem = jest.fn(() => false); - collection.collect({ id: '1', name: 'frank' }, [ - 'dummyGroup1', - 'dummyGroup2', - ]); + collection.collect( + [{ id: '1', name: 'frank' }, dummyItem5, { id: '2', name: 'hans' }], + ['dummyGroup1', 'dummyGroup2'] + ); expect(collection.assignData).toHaveBeenCalledWith( { @@ -608,26 +678,72 @@ describe('Collection Tests', () => { background: false, } ); + expect(collection.assignData).toHaveBeenCalledWith( + { + id: '2', + name: 'hans', + }, + { + patch: false, + background: false, + } + ); + expect(collection.assignItem).toHaveBeenCalledWith(dummyItem5, { + background: false, + }); + expect(collection.createGroup).not.toHaveBeenCalled(); - expect(dummyGroup1.add).not.toHaveBeenCalled(); - expect(dummyGroup2.add).not.toHaveBeenCalled(); - expect(defaultGroup.add).not.toHaveBeenCalled(); + expect(dummyGroup1.add).not.toHaveBeenCalledWith('1', { + method: 'push', + background: false, + }); + expect(dummyGroup1.add).toHaveBeenCalledWith('2', { + method: 'push', + background: false, + }); + expect(dummyGroup1.add).not.toHaveBeenCalledWith('5', { + method: 'push', + background: false, + }); + expect(dummyGroup2.add).not.toHaveBeenCalledWith('1', { + method: 'push', + background: false, + }); + expect(dummyGroup2.add).toHaveBeenCalledWith('2', { + method: 'push', + background: false, + }); + expect(dummyGroup2.add).not.toHaveBeenCalledWith('5', { + method: 'push', + background: false, + }); + expect(defaultGroup.add).not.toHaveBeenCalledWith('1', { + method: 'push', + background: false, + }); + expect(defaultGroup.add).toHaveBeenCalledWith('2', { + method: 'push', + background: false, + }); + expect(defaultGroup.add).not.toHaveBeenCalledWith('5', { + method: 'push', + background: false, + }); expect(collection.createSelector).not.toHaveBeenCalled(); }); - it("should add Data to Collection and create Groups that doesn't exist yet (default config)", () => { - const notExistingGroup = new Group(collection); - notExistingGroup.add = jest.fn(); + it("should add data object to Collection and create Groups that doesn't exist yet (default config)", () => { + const newGroup = new Group(collection); + newGroup.add = jest.fn(); collection.assignData = jest.fn(() => true); collection.createGroup = jest.fn(function (groupKey) { - //@ts-ignore - this.groups[groupKey] = notExistingGroup; - return notExistingGroup as any; + collection.groups[groupKey] = newGroup; + return newGroup as any; }); - collection.collect({ id: '1', name: 'frank' }, 'notExistingGroup'); + collection.collect({ id: '1', name: 'frank' }, 'newGroup'); expect(collection.assignData).toHaveBeenCalledWith( { @@ -639,11 +755,11 @@ describe('Collection Tests', () => { background: false, } ); - expect(collection.createGroup).toHaveBeenCalledWith('notExistingGroup'); + expect(collection.createGroup).toHaveBeenCalledWith('newGroup'); expect(dummyGroup1.add).not.toHaveBeenCalled(); expect(dummyGroup2.add).not.toHaveBeenCalled(); - expect(notExistingGroup.add).toHaveBeenCalledWith('1', { + expect(newGroup.add).toHaveBeenCalledWith('1', { method: 'push', background: false, }); @@ -655,31 +771,31 @@ describe('Collection Tests', () => { expect(collection.createSelector).not.toHaveBeenCalled(); }); - it('should create Selector for each Item (config.select)', () => { + it('should add data object to Collection and create Selector for each Item (config.select)', () => { collection.assignData = jest.fn(() => true); + collection.assignItem = jest.fn(() => true); collection.collect( - [ - { id: '1', name: 'frank' }, - { id: '2', name: 'hans' }, - ], + [{ id: '1', name: 'frank' }, dummyItem5, { id: '2', name: 'hans' }], [], { select: true } ); expect(collection.createSelector).toHaveBeenCalledWith('1', '1'); + expect(collection.createSelector).toHaveBeenCalledWith('5', '5'); expect(collection.createSelector).toHaveBeenCalledWith('2', '2'); }); - it("should call 'forEachItem' for each Item (default config)", () => { - collection.assignData = jest.fn(() => true); + it("should add data object to Collection and call 'forEachItem()' for each Item (config.forEachItem)", () => { + collection.assignData = jest + .fn() + .mockReturnValueOnce(false) + .mockReturnValueOnce(true); + collection.assignItem = jest.fn(() => true); const forEachItemMock = jest.fn(); collection.collect( - [ - { id: '1', name: 'frank' }, - { id: '2', name: 'hans' }, - ], + [{ id: '1', name: 'frank' }, dummyItem5, { id: '2', name: 'hans' }], [], { forEachItem: forEachItemMock } ); @@ -687,12 +803,15 @@ describe('Collection Tests', () => { expect(forEachItemMock).toHaveBeenCalledWith( { id: '1', name: 'frank' }, '1', + false, 0 ); + expect(forEachItemMock).toHaveBeenCalledWith(dummyItem5, '5', true, 1); expect(forEachItemMock).toHaveBeenCalledWith( { id: '2', name: 'hans' }, '2', - 1 + true, + 2 ); }); }); From 1b3a5d873b528194e9c0a4c82524df632b8e5f61 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Tue, 1 Jun 2021 07:59:29 +0200 Subject: [PATCH 031/117] fixed typos --- packages/core/src/collection/index.ts | 104 +++++++++++------- packages/core/src/state/index.ts | 25 +++-- .../tests/unit/collection/collection.test.ts | 32 ++++-- 3 files changed, 97 insertions(+), 64 deletions(-) diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index f62b9fa3..6fae74ec 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -40,13 +40,18 @@ export class Collection { public isInstantiated = false; /** - * Class that holds a List of Objects with key and causes rerender on subscribed Components + * A Collection provides a reactive set of Information that we need to remember globally at a later point in time. + * While providing a toolkit to use and mutate this set of Information. * - * @public + * It is designed for arrays of data objects following the same pattern. * - * @param agileInstance - Instance of Agile the Collection belongs to + * Each of these data object must have a unique primaryKey to be correctly identified later. * - * @param config - Configuration + * [Learn more..](https://agile-ts.org/docs/core/collection/) + * + * @public + * @param agileInstance - Instance of Agile the Collection belongs to. + * @param config - Configuration object */ constructor(agileInstance: Agile, config: CollectionConfig = {}) { this.agileInstance = () => agileInstance; @@ -73,60 +78,68 @@ export class Collection { // Reselect Selector Items // Necessary because the selection of an Item - // hasn't worked with a not 'instantiated' Collection + // hasn't worked with a not 'instantiated' Collection before for (const key in this.selectors) this.selectors[key].reselect(); // Rebuild of Groups // Not necessary because if Items are added to the Collection, - // the Groups which contain these added Items get rebuilt. + // the Groups which contain these added Items are rebuilt. // for (const key in this.groups) this.groups[key].rebuild(); } /** + * Updates key/name identifier of Collection. + * * @public - * Set Key/Name of Collection + * @param value - New key/name identifier. */ public set key(value: CollectionKey | undefined) { this.setKey(value); } /** + * Returns key/name identifier of Collection. + * * @public - * Get Key/Name of Collection */ public get key(): CollectionKey | undefined { return this._key; } - //========================================================================================================= - // Set Key - //========================================================================================================= /** + * Updates key/name identifier of Collection. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#setkey) + * * @public - * Set Key/Name of Collection - * @param value - New Key/Name of Collection + * @param value - New key/name identifier. */ public setKey(value: CollectionKey | undefined) { const oldKey = this._key; - // Update State Key + // Update Collection key this._key = value; - // Update Key in Persistent (only if oldKey equal to persistentKey -> otherwise the PersistentKey got formatted and will be set where other) + // Update key in Persistent (only if oldKey equal to persistentKey + // because otherwise the persistentKey is detached from the Collection key + // -> not managed by Collection anymore) if (value && this.persistent?._key === oldKey) this.persistent?.setKey(value); return this; } - //========================================================================================================= - // Group - //========================================================================================================= /** + * Creates a new Group without associating it to the Collection. + * + * Therefore, this function is intended for use in the Collection configuration object, + * where the `constructor()` takes care of the associating. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#group) + * * @public - * Group - Holds Items of this Collection - * @param initialItems - Initial ItemKeys of Group - * @param config - Config + * @param initialItems - Initial keys of Items that the Group should represent. + * @param config - Configuration object */ public Group( initialItems?: Array, @@ -141,14 +154,17 @@ export class Collection { return new Group(this, initialItems, config); } - //========================================================================================================= - // Selector - //========================================================================================================= /** + * Creates a new Selector without associating it to the Collection. + * + * Therefore, this function is intended for use in the Collection configuration object, + * where the `constructor()` takes care of the associating. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#selector) + * * @public - * Selector - Represents an Item of this Collection - * @param initialKey - Key of Item that the Selector represents - * @param config - Config + * @param initialKey - Initial key of the Item that the Selector should represent. + * @param config - Configuration object */ public Selector( initialKey: ItemKey, @@ -163,18 +179,18 @@ export class Collection { return new Selector(this, initialKey, config); } - //========================================================================================================= - // Init Groups - //========================================================================================================= /** + * Sets up the give Groups or Group keys and initializes the default Group. + * The Groups are then assigned to the Collection after a successful set up. + * * @internal - * Instantiates Groups + * @param groups - Groups or Group keys to be setup. */ - public initGroups(groups: { [key: string]: Group } | string[]) { + public initGroups(groups: { [key: string]: Group } | string[]): void { if (!groups) return; let groupsObject: { [key: string]: Group } = {}; - // If groups is Array of GroupNames transform it to Group Object + // If groups is Array of Group names/keys, create the Groups based these keys if (Array.isArray(groups)) { groups.forEach((groupKey) => { groupsObject[groupKey] = new Group(this, [], { @@ -188,25 +204,25 @@ export class Collection { key: this.config.defaultGroupKey, }); - // Set Key/Name of Group to property Name + // Assign missing key/name to Group based on the property key for (const key in groupsObject) if (groupsObject[key]._key == null) groupsObject[key].setKey(key); this.groups = groupsObject; } - //========================================================================================================= - // Init Selectors - //========================================================================================================= /** + * Sets up the give Selectors or Selector keys + * and assigns them to the Collection if they are valid. + * * @internal - * Instantiates Selectors + * @param selectors - Selectors or Selector keys to be setup. */ public initSelectors(selectors: { [key: string]: Selector } | string[]) { if (!selectors) return; let selectorsObject: { [key: string]: Selector } = {}; - // If selectors is Array of SelectorNames transform it to Selector Object + // If selectors is Array of Selector names/keys, create the Selectors based these keys if (Array.isArray(selectors)) { selectors.forEach((selectorKey) => { selectorsObject[selectorKey] = new Selector( @@ -219,7 +235,7 @@ export class Collection { }); } else selectorsObject = selectors; - // Set Key/Name of Selector to property Name + // Assign missing key/name to Selector based on the property key for (const key in selectorsObject) if (selectorsObject[key]._key == null) selectorsObject[key].setKey(key); @@ -230,8 +246,14 @@ export class Collection { // Collect //========================================================================================================= /** + * todo + * collect data objects or whole items + * adds these data objects to the Collection + * Each to collect data object needs a unique primaryKey (identifier) + * + * + * * @public - * Collect Item/s * @param data - Data that gets added to Collection * @param groupKeys - Add collected Item/s to certain Groups * @param config - Config diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index c33afdf3..ce4e6eaf 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -103,39 +103,42 @@ export class State { } /** + * Updates key/name identifier of State. + * * @public - * Set Key/Name of State + * @param value - New key/name identifier. */ public set key(value: StateKey | undefined) { this.setKey(value); } /** + * Returns key/name identifier of State. + * * @public - * Get Key/Name of State */ public get key(): StateKey | undefined { return this._key; } - //========================================================================================================= - // Set Key - //========================================================================================================= /** - * @internal - * Updates Key/Name of State - * @param value - New Key/Name of State + * Updates key/name identifier of State. + * + * @public + * @param value - New key/name identifier. */ public setKey(value: StateKey | undefined): this { const oldKey = this._key; - // Update State Key + // Update State key this._key = value; - // Update Key in Observer + // Update key in Observer this.observer._key = value; - // Update Key in Persistent (only if oldKey equal to persistentKey -> otherwise the PersistentKey got formatted and will be set where other) + // Update key in Persistent (only if oldKey equal to persistentKey + // because otherwise the persistentKey is detached from the State key + // -> not managed by State anymore) if (value && this.persistent?._key === oldKey) this.persistent?.setKey(value); diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index c79dcf57..e8057b50 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -1555,7 +1555,7 @@ describe('Collection Tests', () => { ); }); - it("should return and track created reference Item if Item doesn't exist yet", () => { + it("should return and track created reference Item if searched Item doesn't exist yet", () => { const response = collection.getItemWithReference('notExistingItem'); expect(response).toBe(placeholderItem); @@ -1563,7 +1563,9 @@ describe('Collection Tests', () => { 'notExistingItem', true ); - expect(ComputedTracker.tracked).toHaveBeenCalledWith(response.observer); + expect(ComputedTracker.tracked).toHaveBeenCalledWith( + placeholderItem.observer + ); }); }); @@ -1626,6 +1628,7 @@ describe('Collection Tests', () => { expect(collection.data).toHaveProperty('2'); expect(collection.data['2']).toStrictEqual(expect.any(Item)); + expect(collection.data['2']._key).toBe('2'); expect(ComputedTracker.tracked).toHaveBeenCalledTimes(1); expect(ComputedTracker.tracked).not.toHaveBeenCalledWith( @@ -2571,12 +2574,12 @@ describe('Collection Tests', () => { }); expect(response).toBeTruthy(); - expect(collection.size).toBe(2); // Increased by assignItem + expect(collection.size).toBe(2); // Increased by assignItem() expect(collection.assignItem).toHaveBeenCalledWith(expect.any(Item), { background: false, }); - // Check if Item, assignItem was called with, has the correct data + // Check if Item, assignItem() was called with, has the correct data expect(collection.data).toHaveProperty('dummyItem2'); expect(collection.data['dummyItem2']._value).toStrictEqual({ id: 'dummyItem2', @@ -2597,12 +2600,12 @@ describe('Collection Tests', () => { ); expect(response).toBeTruthy(); - expect(collection.size).toBe(2); // Increased by assignItem + expect(collection.size).toBe(2); // Increased by assignItem() expect(collection.assignItem).toHaveBeenCalledWith(expect.any(Item), { background: true, }); - // Check if Item, assignItem was called with, has the correct data + // Check if Item, assignItem() was called with, has the correct data expect(collection.data).toHaveProperty('dummyItem2'); expect(collection.data['dummyItem2']._value).toStrictEqual({ id: 'dummyItem2', @@ -2630,12 +2633,12 @@ describe('Collection Tests', () => { const response = collection.assignData({ name: 'Frank' } as any); expect(response).toBeTruthy(); - expect(collection.size).toBe(2); // Increased by assignItem + expect(collection.size).toBe(2); // Increased by assignItem() expect(collection.assignItem).toHaveBeenCalledWith(expect.any(Item), { background: false, }); - // Check if Item, assignItem was called with, has the correct data + // Check if Item, assignItem() was called with, has the correct data expect(collection.data).toHaveProperty('randomDummyId'); expect(collection.data['randomDummyId']._value).toStrictEqual({ id: 'randomDummyId', @@ -2649,7 +2652,7 @@ describe('Collection Tests', () => { ]); }); - it('should update Item with valid data via set (default config)', () => { + it('should update existing Item with valid data via set (default config)', () => { const response = collection.assignData({ id: 'dummyItem1', name: 'Dieter', @@ -2669,7 +2672,7 @@ describe('Collection Tests', () => { LogMock.hasNotLogged('warn'); }); - it('should update Item with valid data via set (config.background = true)', () => { + it('should update existing Item with valid data via set (config.background = true)', () => { const response = collection.assignData( { id: 'dummyItem1', @@ -2692,7 +2695,7 @@ describe('Collection Tests', () => { LogMock.hasNotLogged('warn'); }); - it('should update Item with valid data via patch (config.patch = true, background: true)', () => { + it('should update existing Item with valid data via patch (config.patch = true, background: true)', () => { const response = collection.assignData( { id: 'dummyItem1', @@ -2715,7 +2718,7 @@ describe('Collection Tests', () => { LogMock.hasNotLogged('warn'); }); - it('should update placeholder Item with valid data and increase size', () => { + it('should update placeholder Item with valid data and increase Collection size (default config)', () => { dummyItem1.isPlaceholder = true; const response = collection.assignData({ @@ -2773,6 +2776,7 @@ describe('Collection Tests', () => { ); expect(toAddDummyItem2.patch).not.toHaveBeenCalled(); + expect(toAddDummyItem2._key).toBe(2); LogMock.hasNotLogged('error'); LogMock.hasNotLogged('warn'); @@ -2795,6 +2799,7 @@ describe('Collection Tests', () => { ); expect(toAddDummyItem2.patch).not.toHaveBeenCalled(); + expect(toAddDummyItem2._key).toBe(2); LogMock.hasNotLogged('error'); LogMock.hasNotLogged('warn'); @@ -2847,6 +2852,7 @@ describe('Collection Tests', () => { ).not.toHaveBeenCalled(); expect(toAddDummyItem2.patch).not.toHaveBeenCalled(); + expect(toAddDummyItem2._key).toBe(2); LogMock.hasLoggedCode('1B:03:06', [ collection._key, @@ -2867,6 +2873,7 @@ describe('Collection Tests', () => { ).not.toHaveBeenCalled(); expect(dummyItem1.patch).not.toHaveBeenCalled(); + expect(dummyItem1._key).toBe(1); LogMock.hasNotLogged('error'); LogMock.hasNotLogged('warn'); @@ -2887,6 +2894,7 @@ describe('Collection Tests', () => { ); expect(dummyItem1.patch).not.toHaveBeenCalled(); + expect(dummyItem1._key).toBe(2); LogMock.hasNotLogged('error'); LogMock.hasNotLogged('warn'); From 37d1495f6225aed50c42e4a1e3576719cca8ed38 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 2 Jun 2021 08:17:48 +0200 Subject: [PATCH 032/117] fixed typos --- packages/core/src/collection/index.ts | 120 ++++++++++++++------------ 1 file changed, 63 insertions(+), 57 deletions(-) diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 6fae74ec..15abcd55 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -138,7 +138,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#group) * * @public - * @param initialItems - Initial keys of Items that the Group should represent. + * @param initialItems - Initial keys of Items to be represented by the Group. * @param config - Configuration object */ public Group( @@ -163,7 +163,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#selector) * * @public - * @param initialKey - Initial key of the Item that the Selector should represent. + * @param initialKey - Initial key of Items to be represented by the Selector. * @param config - Configuration object */ public Selector( @@ -184,7 +184,7 @@ export class Collection { * The Groups are then assigned to the Collection after a successful set up. * * @internal - * @param groups - Groups or Group keys to be setup. + * @param groups - Entire Groups or Group keys to be set up. */ public initGroups(groups: { [key: string]: Group } | string[]): void { if (!groups) return; @@ -216,7 +216,7 @@ export class Collection { * and assigns them to the Collection if they are valid. * * @internal - * @param selectors - Selectors or Selector keys to be setup. + * @param selectors - Entire Selectors or Selector keys to be set up. */ public initSelectors(selectors: { [key: string]: Selector } | string[]) { if (!selectors) return; @@ -242,21 +242,26 @@ export class Collection { this.selectors = selectorsObject; } - //========================================================================================================= - // Collect - //========================================================================================================= /** - * todo - * collect data objects or whole items - * adds these data objects to the Collection - * Each to collect data object needs a unique primaryKey (identifier) + * Appends a new data object or whole Items following the same pattern to the end of the Collection. + * + * Each collected data object and Item requires a unique identifier at the primaryKey property + * to be properly identified later. By default, 'id' is the primaryKey property. + * + * For example, a valid data object would look like this: + * + * {id: 1, name: 'jeff'} * + * 'id': PrimaryKey property with a unique identifier '1' * + * 'name': A actual data property in this case 'jeff'. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#collect) * * @public - * @param data - Data that gets added to Collection - * @param groupKeys - Add collected Item/s to certain Groups - * @param config - Config + * @param data - Data object or Items to be added. + * @param groupKeys - Given data objects or Items to be added to certain Group/s. + * @param config - Configuration object */ public collect( data: DataType | Item | Array>, @@ -274,7 +279,7 @@ export class Collection { select: false, }); - // Add default GroupKey, because Items get always added to default Group + // Add default groupKey, since all Items are added to the default Group if (!_groupKeys.includes(defaultGroupKey)) _groupKeys.push(defaultGroupKey); // Create not existing Groups @@ -300,7 +305,7 @@ export class Collection { itemKey = data[primaryKey]; } - // Add ItemKey to provided Groups and create corresponding Selector + // Add itemKey to provided Groups and create corresponding Selector if (success) { _groupKeys.forEach((groupKey) => { this.getGroup(groupKey)?.add(itemKey, { @@ -318,15 +323,15 @@ export class Collection { return this; } - //========================================================================================================= - // Update - //========================================================================================================= /** + * Updates Item data object at give identifier key, if it exists. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#update) + * * @public - * Updates Item at provided Key - * @param itemKey - ItemKey of Item that gets updated - * @param changes - Changes that will be merged into the Item (flatMerge) - * @param config - Config + * @param itemKey - ItemKey of Item to be updated. + * @param changes - Object with changes to be merged into the Item data. + * @param config - Configuration object */ public update( itemKey: ItemKey, @@ -340,6 +345,7 @@ export class Collection { background: false, }); + // Validate passed data if (item == null) { LogCodeManager.log('1B:03:00', [itemKey, this._key]); return undefined; @@ -351,17 +357,17 @@ export class Collection { const oldItemKey = item._value[primaryKey]; const newItemKey = changes[primaryKey] || oldItemKey; - const updateItemKey = oldItemKey !== newItemKey; - // Update ItemKey - if (updateItemKey) + // Update itemKey if the new itemKey differs from the old one + if (oldItemKey !== newItemKey) this.updateItemKey(oldItemKey, newItemKey, { background: config.background, }); - // Patch changes into Item + // Patch changes into Item data object if (config.patch) { - // Delete primaryKey from 'changes' because if it has changed, it gets properly updated in 'updateItemKey' (see above) + // Delete primaryKey property from 'changes object' because if it has changed, + // it is correctly updated in the above called 'updateItemKey()' method if (changes[primaryKey]) delete changes[primaryKey]; let patchConfig: { addNewProperties?: boolean } = @@ -370,22 +376,19 @@ export class Collection { addNewProperties: true, }); - // Apply changes to Item item.patch(changes as any, { background: config.background, addNewProperties: patchConfig.addNewProperties, }); } - - // Set changes into Item - if (!config.patch) { - // To make sure that the primaryKey doesn't differ from the changes object primaryKey + // Apply changes to Item data object + else { + // Ensure that the current Item identifier isn't different from the 'changes object' itemKey if (changes[this.config.primaryKey] !== itemKey) { changes[this.config.primaryKey] = itemKey; LogCodeManager.log('1B:02:02', [], changes); } - // Apply changes to Item item.set(changes as any, { background: config.background, }); @@ -394,21 +397,20 @@ export class Collection { return item; } - //========================================================================================================= - // Create Group - //========================================================================================================= /** + * Creates a new Group and associates it to the Collection. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#createGroup) + * * @public - * Creates new Group that can hold Items of Collection - * @param groupKey - Name/Key of Group - * @param initialItems - Initial ItemKeys of Group + * @param groupKey - Unique Group identifier of the new Group. + * @param initialItems - Initial keys of Items to be represented by the Group. */ public createGroup( groupKey: GroupKey, initialItems: Array = [] ): Group { let group = this.getGroup(groupKey, { notExisting: true }); - if (!this.isInstantiated) LogCodeManager.log('1B:02:03'); // Check if Group already exists @@ -421,21 +423,22 @@ export class Collection { return group; } - // Create Group + // Create new Group group = new Group(this, initialItems, { key: groupKey }); this.groups[groupKey] = group; return group; } - //========================================================================================================= - // Has Group - //========================================================================================================= /** + * Returns a boolean indicating whether an Group with the specified groupKey + * exists in the Collection or not. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasgroup) + * * @public - * Check if Group exists in Collection - * @param groupKey - Key/Name of Group - * @param config - Config + * @param groupKey - Key/Name identifier of Group. + * @param config - Configuration object */ public hasGroup( groupKey: GroupKey | undefined, @@ -445,13 +448,13 @@ export class Collection { } /** - * Retrieves a single Group by key/name. + * Retrieves a single Group with the specified key/name identifier from the Collection. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroup) * * @public - * @param groupKey - key/name Group identifier - * @param config - Configuration + * @param groupKey - Key/Name identifier of Group. + * @param config - Configuration object */ public getGroup( groupKey: GroupKey | undefined, @@ -465,19 +468,22 @@ export class Collection { const group = groupKey ? this.groups[groupKey] : undefined; // Check if Group exists - if (group == null || (!config.notExisting && group.isPlaceholder)) + if (group == null || (!config.notExisting && group.exists)) return undefined; ComputedTracker.tracked(group.observer); return group; } - //========================================================================================================= - // Get Default Group - //========================================================================================================= /** + * Retrieves the default Group from the Collection. + * + * Every Collection has a default Group, + * which represents the main pattern of the Collection. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getdefaultgroup) + * * @public - * Get default Group of Collection */ public getDefaultGroup(): Group | undefined { return this.getGroup(this.config.defaultGroupKey); @@ -606,7 +612,7 @@ export class Collection { const selector = selectorKey ? this.selectors[selectorKey] : undefined; // Check if Selector exists - if (selector == null || (!config.notExisting && selector.isPlaceholder)) + if (selector == null || (!config.notExisting && selector.exists)) return undefined; ComputedTracker.tracked(selector.observer); From f81ce354cfbeaa71c3a16bc37c1393f86fef702c Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 2 Jun 2021 08:42:42 +0200 Subject: [PATCH 033/117] fixed collection tests --- packages/core/src/collection/index.ts | 4 +-- .../tests/unit/collection/collection.test.ts | 26 ++++++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 15abcd55..1e2f4c37 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -468,7 +468,7 @@ export class Collection { const group = groupKey ? this.groups[groupKey] : undefined; // Check if Group exists - if (group == null || (!config.notExisting && group.exists)) + if (group == null || (!config.notExisting && !group.exists)) return undefined; ComputedTracker.tracked(group.observer); @@ -612,7 +612,7 @@ export class Collection { const selector = selectorKey ? this.selectors[selectorKey] : undefined; // Check if Selector exists - if (selector == null || (!config.notExisting && selector.exists)) + if (selector == null || (!config.notExisting && !selector.exists)) return undefined; ComputedTracker.tracked(selector.observer); diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index e8057b50..500fb289 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -1321,9 +1321,15 @@ describe('Collection Tests', () => { describe('getSelector function tests', () => { let dummySelector: Selector; + let dummyItem1: Item; beforeEach(() => { - dummySelector = new Selector(collection, 'dummyItem', { + dummyItem1 = new Item(collection, { id: 'dummyItem1', name: 'frank' }); + collection.data = { + ['dummyItem1']: dummyItem1, + }; + + dummySelector = new Selector(collection, 'dummyItem1', { key: 'dummySelector', }); collection.selectors = { @@ -1374,9 +1380,15 @@ describe('Collection Tests', () => { describe('getSelectorWithReference function tests', () => { let dummySelector: Selector; + let dummyItem1: Item; beforeEach(() => { - dummySelector = new Selector(collection, 'dummyItem', { + dummyItem1 = new Item(collection, { id: 'dummyItem1', name: 'frank' }); + collection.data = { + ['dummyItem1']: dummyItem1, + }; + + dummySelector = new Selector(collection, 'dummyItem1', { key: 'dummySelector', }); collection.selectors = { @@ -2776,7 +2788,7 @@ describe('Collection Tests', () => { ); expect(toAddDummyItem2.patch).not.toHaveBeenCalled(); - expect(toAddDummyItem2._key).toBe(2); + expect(toAddDummyItem2._key).toBe('dummyItem2'); LogMock.hasNotLogged('error'); LogMock.hasNotLogged('warn'); @@ -2799,7 +2811,7 @@ describe('Collection Tests', () => { ); expect(toAddDummyItem2.patch).not.toHaveBeenCalled(); - expect(toAddDummyItem2._key).toBe(2); + expect(toAddDummyItem2._key).toBe('dummyItem2'); LogMock.hasNotLogged('error'); LogMock.hasNotLogged('warn'); @@ -2852,7 +2864,7 @@ describe('Collection Tests', () => { ).not.toHaveBeenCalled(); expect(toAddDummyItem2.patch).not.toHaveBeenCalled(); - expect(toAddDummyItem2._key).toBe(2); + expect(toAddDummyItem2._key).toBe('dummyItem2'); LogMock.hasLoggedCode('1B:03:06', [ collection._key, @@ -2873,7 +2885,7 @@ describe('Collection Tests', () => { ).not.toHaveBeenCalled(); expect(dummyItem1.patch).not.toHaveBeenCalled(); - expect(dummyItem1._key).toBe(1); + expect(dummyItem1._key).toBe('dummyItem1'); LogMock.hasNotLogged('error'); LogMock.hasNotLogged('warn'); @@ -2894,7 +2906,7 @@ describe('Collection Tests', () => { ); expect(dummyItem1.patch).not.toHaveBeenCalled(); - expect(dummyItem1._key).toBe(2); + expect(dummyItem1._key).toBe('dummyItem1'); LogMock.hasNotLogged('error'); LogMock.hasNotLogged('warn'); From 9a119d79d052be445bd2c59a706bcf0890cd19d1 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 2 Jun 2021 18:59:25 +0200 Subject: [PATCH 034/117] fixed typos --- .../src/collection/collection.persistent.ts | 2 +- packages/core/src/collection/index.ts | 526 ++++++++++-------- packages/core/src/state/index.ts | 43 +- 3 files changed, 331 insertions(+), 240 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 0ee6c8b3..9b73c336 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -314,7 +314,7 @@ export class CollectionPersistent< * Adds and removes Items from the Storage based on the Group value. * * @internal - * @param group - Group whose Items should be dynamically added and removed from the Storage. + * @param group - Group whose Items are to be dynamically added or removed from the Storage. * @param storageItemKey - Prefix key of persisted Collection Instances. | default = Persistent.key | */ public rebuildStorageSideEffect( diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 1e2f4c37..fbdedc79 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -28,24 +28,25 @@ export class Collection { public config: CollectionConfigInterface; private initialConfig: CreateCollectionConfigInterface; - public size = 0; // Amount of Items stored in Collection + public size = 0; // Amount of Items stored in the Collection public data: { [key: string]: Item } = {}; // Collection Data public _key?: CollectionKey; - public isPersisted = false; // If Collection can be stored in Agile Storage (-> successfully integrated persistent) - public persistent: CollectionPersistent | undefined; // Manages storing Collection Value into Storage + public isPersisted = false; // Whether Collection is persisted in any external Storage + public persistent: CollectionPersistent | undefined; // Manages persisting Collection 'value' public groups: { [key: string]: Group } = {}; public selectors: { [key: string]: Selector } = {}; - public isInstantiated = false; + public isInstantiated = false; // Whether the Collection is instantiated completely /** - * A Collection provides a reactive set of Information that we need to remember globally at a later point in time. + * A Collection provides a reactive set of Information + * that we need to remember globally at a later point in time. * While providing a toolkit to use and mutate this set of Information. * * It is designed for arrays of data objects following the same pattern. * - * Each of these data object must have a unique primaryKey to be correctly identified later. + * Each of these data object must have a unique `primaryKey` to be correctly identified later. * * [Learn more..](https://agile-ts.org/docs/core/collection/) * @@ -132,7 +133,7 @@ export class Collection { /** * Creates a new Group without associating it to the Collection. * - * Therefore, this function is intended for use in the Collection configuration object, + * This way of creating a Group is intended for use in the Collection configuration object, * where the `constructor()` takes care of the associating. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#group) @@ -157,13 +158,13 @@ export class Collection { /** * Creates a new Selector without associating it to the Collection. * - * Therefore, this function is intended for use in the Collection configuration object, + * This way of creating a Selector is intended for use in the Collection configuration object, * where the `constructor()` takes care of the associating. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#selector) * * @public - * @param initialKey - Initial key of Items to be represented by the Selector. + * @param initialKey - Initial key of Item to be represented by the Selector. * @param config - Configuration object */ public Selector( @@ -180,8 +181,11 @@ export class Collection { } /** - * Sets up the give Groups or Group keys and initializes the default Group. - * The Groups are then assigned to the Collection after a successful set up. + * Sets up the specified Groups or Group keys + * and assigns them to the Collection if they are valid. + * + * It also assigns the default Group to the Collection. + * The default Group reflects the default pattern of the Collection. * * @internal * @param groups - Entire Groups or Group keys to be set up. @@ -212,7 +216,7 @@ export class Collection { } /** - * Sets up the give Selectors or Selector keys + * Sets up the specified Selectors or Selector keys * and assigns them to the Collection if they are valid. * * @internal @@ -243,24 +247,23 @@ export class Collection { } /** - * Appends a new data object or whole Items following the same pattern to the end of the Collection. - * - * Each collected data object and Item requires a unique identifier at the primaryKey property - * to be properly identified later. By default, 'id' is the primaryKey property. - * - * For example, a valid data object would look like this: + * Appends new data objects following the same pattern to the end of the Collection. * - * {id: 1, name: 'jeff'} + * Each collected `data object` requires a unique identifier at the primaryKey property (by default 'id') + * to be correctly identified later. * - * 'id': PrimaryKey property with a unique identifier '1' - * - * 'name': A actual data property in this case 'jeff'. + * For example, if we collect some kind of user object, + * it must contain such unique identifier at 'id' + * to be added to the Collection. + * ``` + * MY_COLLECTION.collect({id: '1', name: 'jeff'}); + * ``` * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#collect) * * @public - * @param data - Data object or Items to be added. - * @param groupKeys - Given data objects or Items to be added to certain Group/s. + * @param data - Data objects or entire Items to be added. + * @param groupKeys - Group/s to which the specified data objects or Items are to be added. * @param config - Configuration object */ public collect( @@ -324,13 +327,14 @@ export class Collection { } /** - * Updates Item data object at give identifier key, if it exists. + * Updates the Item `data object` with the specified `object with changes`, if the Item exists. + * By default the `object with changes` is merged into the Item `data object`. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#update) * * @public - * @param itemKey - ItemKey of Item to be updated. - * @param changes - Object with changes to be merged into the Item data. + * @param itemKey - Key/Name identifier of Item to be updated. + * @param changes - Object with changes to be merged into the Item data object. * @param config - Configuration object */ public update( @@ -403,7 +407,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#createGroup) * * @public - * @param groupKey - Unique Group identifier of the new Group. + * @param groupKey - Unique identifier of the to create Group. * @param initialItems - Initial keys of Items to be represented by the Group. */ public createGroup( @@ -431,7 +435,7 @@ export class Collection { } /** - * Returns a boolean indicating whether an Group with the specified groupKey + * Returns a boolean indicating whether a Group with the specified `groupKey` * exists in the Collection or not. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasgroup) @@ -450,6 +454,8 @@ export class Collection { /** * Retrieves a single Group with the specified key/name identifier from the Collection. * + * If the to retrieve Group doesn't exist, `undefined` is returned. + * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroup) * * @public @@ -464,10 +470,10 @@ export class Collection { notExisting: false, }); - // Get Group + // Retrieve Group const group = groupKey ? this.groups[groupKey] : undefined; - // Check if Group exists + // Check if retrieved Group exists if (group == null || (!config.notExisting && !group.exists)) return undefined; @@ -478,8 +484,8 @@ export class Collection { /** * Retrieves the default Group from the Collection. * - * Every Collection has a default Group, - * which represents the main pattern of the Collection. + * Every Collection should have a default Group, + * which represents the default pattern of the Collection. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getdefaultgroup) * @@ -489,13 +495,17 @@ export class Collection { return this.getGroup(this.config.defaultGroupKey); } - //========================================================================================================= - // Get Group With Reference - //========================================================================================================= /** + * Retrieves a single Group with the specified key/name identifier from the Collection. + * + * If the to retrieve Group doesn't exist, a reference Group is returned. + * This has the advantage that Components that have the reference Group bound to themselves + * are rerenderd when the original Group is created. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupwithreference) + * * @public - * Get Group by Key/Name or a Reference to it if it doesn't exist yet - * @param groupKey - Name/Key of Group + * @param groupKey - Key/Name identifier of Group. */ public getGroupWithReference(groupKey: GroupKey): Group { let group = this.getGroup(groupKey, { notExisting: true }); @@ -513,35 +523,34 @@ export class Collection { return group; } - //========================================================================================================= - // Remove Group - //========================================================================================================= /** + * Removes a Group with the specified identifier from the Collection, + * if it exists in the Collection. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removegroup) + * * @public - * Removes Group by Key/Name - * @param groupKey - Name/Key of Group + * @param groupKey - Key/Name identifier of Group. */ public removeGroup(groupKey: GroupKey): this { - if (this.groups[groupKey] == null) return this; - delete this.groups[groupKey]; + if (this.groups[groupKey] != null) delete this.groups[groupKey]; return this; } - //========================================================================================================= - // Create Selector - //========================================================================================================= /** + * Creates a new Selector and associates it to the Collection. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#createSelector) + * * @public - * Creates new Selector that represents an Item of the Collection - * @param selectorKey - Name/Key of Selector - * @param itemKey - Key of Item which the Selector represents + * @param selectorKey - Unique identifier of the to create Selector. + * @param itemKey - Initial key of Item to be represented by the Selector. */ public createSelector( selectorKey: SelectorKey, itemKey: ItemKey ): Selector { let selector = this.getSelector(selectorKey, { notExisting: true }); - if (!this.isInstantiated) LogCodeManager.log('1B:02:04'); // Check if Selector already exists @@ -554,7 +563,7 @@ export class Collection { return selector; } - // Create Selector + // Create new Selector selector = new Selector(this, itemKey, { key: selectorKey, }); @@ -563,26 +572,34 @@ export class Collection { return selector; } - //========================================================================================================= - // Select - //========================================================================================================= /** + * Creates a new Selector and associates it to the Collection. + * + * The specified `itemKey` is used as the unique identifier key of the new Selector. + * ``` + * MY_COLLECTION.select('1'); + * // is equivalent to + * MY_COLLECTION.createSelector('1', '1'); + * ``` + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#select) + * * @public - * Creates new Selector that represents an Item of the Collection - * @param itemKey - Key of Item which the Selector represents + * @param itemKey - ItemKey to be selected. */ public select(itemKey: ItemKey): Selector { return this.createSelector(itemKey, itemKey); } - //========================================================================================================= - // Has Selector - //========================================================================================================= /** + * Returns a boolean indicating whether a Selector with the specified `selectorKey` + * exists in the Collection or not. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasselector) + * * @public - * Check if Selector exists in Collection - * @param selectorKey - Key/Name of Selector - * @param config - Config + * @param selectorKey - Key/Name identifier of Selector. + * @param config - Configuration object */ public hasSelector( selectorKey: SelectorKey | undefined, @@ -591,14 +608,16 @@ export class Collection { return !!this.getSelector(selectorKey, config); } - //========================================================================================================= - // Get Selector - //========================================================================================================= /** + * Retrieves a single Selector with the specified key/name identifier from the Collection. + * + * If the to retrieve Selector doesn't exist, `undefined` is returned. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselector) + * * @public - * Get Selector by Key/Name - * @param selectorKey - Key/Name of Selector - * @param config - Config + * @param selectorKey - Key/Name identifier of Selector. + * @param config - Configuration object */ public getSelector( selectorKey: SelectorKey | undefined, @@ -619,13 +638,17 @@ export class Collection { return selector; } - //========================================================================================================= - // Get Selector With Reference - //========================================================================================================= /** + * Retrieves a single Selector with the specified key/name identifier from the Collection. + * + * If the to retrieve Selector doesn't exist, a reference Selector is returned. + * This has the advantage that Components that have the reference Selector bound to themselves + * are rerenderd when the original Selector is created. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselectorwithreference) + * * @public - * Get Selector by Key/Name or a Reference to it if it doesn't exist yet - * @param selectorKey - Name/Key of Selector + * @param selectorKey - Key/Name identifier of Selector. */ public getSelectorWithReference( selectorKey: SelectorKey @@ -649,29 +672,32 @@ export class Collection { return selector; } - //========================================================================================================= - // Remove Selector - //========================================================================================================= /** + * Removes a Selector with the specified identifier from the Collection, + * if it exists in the Collection. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removeselector) + * * @public - * Removes Selector by Key/Name - * @param selectorKey - Name/Key of Selector + * @param selectorKey - Key/Name identifier of Selector. */ public removeSelector(selectorKey: SelectorKey): this { - if (this.selectors[selectorKey] == null) return this; - this.selectors[selectorKey].unselect(); // Unselects current selected Item - delete this.selectors[selectorKey]; + if (this.selectors[selectorKey] != null) { + this.selectors[selectorKey].unselect(); + delete this.selectors[selectorKey]; + } return this; } - //========================================================================================================= - // Has Item - //========================================================================================================= /** + * Returns a boolean indicating whether a Item with the specified `itemKey` + * exists in the Collection or not. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasitem) + * * @public - * Check if Item exists in Collection - * @param itemKey - Key/Name of Item - * @param config - Config + * @param itemKey - Key/Name identifier of Item. + * @param config - Configuration object */ public hasItem( itemKey: ItemKey | undefined, @@ -680,14 +706,16 @@ export class Collection { return !!this.getItem(itemKey, config); } - //========================================================================================================= - // Get Item by Id - //========================================================================================================= /** + * Retrieves a single Item with the specified key/name identifier from the Collection. + * + * If the to retrieve Item doesn't exist, `undefined` is returned. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitem) + * * @public - * Get Item by Key/Name - * @param itemKey - ItemKey of Item - * @param config - Config + * @param itemKey - Key/Name identifier of Item. + * @param config - Configuration object */ public getItem( itemKey: ItemKey | undefined, @@ -708,9 +736,16 @@ export class Collection { } /** + * Retrieves a single Item with the specified key/name identifier from the Collection. + * + * If the to retrieve Item doesn't exist, a reference Item is returned. + * This has the advantage that Components that have the reference Item bound to themselves + * are rerenderd when the original Item is created. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitemwithreference) + * * @public - * Get Item by Key/Name or a Reference to it if it doesn't exist yet - * @param itemKey - Key/Name of Item + * @param itemKey - Key/Name identifier of Item. */ public getItemWithReference(itemKey: ItemKey): Item { let item = this.getItem(itemKey, { notExisting: true }); @@ -724,16 +759,17 @@ export class Collection { /** * Creates a placeholder Item - * that can be used to hold a reference to an Item that doesn't exist yet. + * that can be used to hold a reference to a not existing Item. * * @internal - * @param itemKey - Key/Name identifier of the Item to be created. + * @param itemKey - Unique identifier of the to create placeholder Item. * @param addToCollection - Whether the created Item should be added to the Collection. */ public createPlaceholderItem( itemKey: ItemKey, addToCollection = false ): Item { + // Create placeholder Item const item = new Item( this, { @@ -743,6 +779,7 @@ export class Collection { { isPlaceholder: true } ); + // Add placeholder Item to Collection if ( addToCollection && !Object.prototype.hasOwnProperty.call(this.data, itemKey) @@ -753,14 +790,17 @@ export class Collection { return item; } - //========================================================================================================= - // Get Value by Id - //========================================================================================================= /** + * Retrieves the value (data object) of a single Item + * with the specified key/name identifier from the Collection. + * + * If the to retrieve Item doesn't exist, `undefined` is returned. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitemvalue) + * * @public - * Get Value of Item by Key/Name - * @param itemKey - ItemKey of Item that holds the Value - * @param config - Config + * @param itemKey - Key/Name identifier of Item. + * @param config - Configuration object */ public getItemValue( itemKey: ItemKey | undefined, @@ -771,13 +811,18 @@ export class Collection { return item.value; } - //========================================================================================================= - // Get All Items - //========================================================================================================= /** + * Retrieves all Items from the Collection. + * ``` + * MY_COLLECTION.getAllItems(); + * // is equivalent to + * MY_COLLECTION.getDefaultGroup().items; + * ``` + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getallitems) + * * @public - * Get all Items of Collection - * @param config - Config + * @param config - Configuration object */ public getAllItems(config: HasConfigInterface = {}): Array> { config = defineConfig(config, { @@ -793,40 +838,52 @@ export class Collection { } else { // Why defaultGroup Items and not all .exists === true Items? // Because the default Group keeps track of all existing Items - // It also does control the Collection output in useAgile() and should do it here too + // It also does control the Collection output in useAgile() + // and therefore should do it here too. items = defaultGroup?.items || []; } return items; } - //========================================================================================================= - // Get All Item Values - //========================================================================================================= /** + * Retrieves the values (data objects) of all Items from the Collection. + * ``` + * MY_COLLECTION.getAllItemValues(); + * // is equivalent to + * MY_COLLECTION.getDefaultGroup().output; + * ``` + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getallitemvalues) + * * @public - * Get all Values of Items in a Collection - * @param config - Config + * @param config - Configuration object */ public getAllItemValues(config: HasConfigInterface = {}): Array { const items = this.getAllItems(config); return items.map((item) => item.value); } - //========================================================================================================= - // Persist - //========================================================================================================= /** + * Preserves the Collection `value` in the corresponding external Storage. + * + * The Collection key/name is used as the unique identifier for the Persistent. + * If that is not desired, please specify a unique identifier for the Persistent. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#persist) + * * @public - * Stores Collection Value into Agile Storage permanently - * @param config - Config + * @param config - Configuration object */ public persist(config?: CollectionPersistentConfigInterface): this; /** + * Preserves the Collection `value` in the corresponding external Storage. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#persist) + * * @public - * Stores Collection Value into Agile Storage permanently - * @param key - Key/Name of created Persistent (Note: Key required if Collection has no set Key!) - * @param config - Config + * @param key - Key/Name identifier of Persistent. + * @param config - Configuration object */ public persist( key?: StorageKey, @@ -856,7 +913,7 @@ export class Collection { // Check if Collection is already persisted if (this.persistent != null && this.isPersisted) return this; - // Create persistent -> Persist Value + // Create Persistent -> persist value this.persistent = new CollectionPersistent(this, { instantiate: _config.loadValue, storageKeys: _config.storageKeys, @@ -867,67 +924,72 @@ export class Collection { return this; } - //========================================================================================================= - // On Load - //========================================================================================================= /** + * Fires immediately after the persisted `value` + * is loaded into the Collection from a corresponding external Storage. + * + * Registering this callback only makes sense + * when the Collection is [persisted](https://agile-ts.org/docs/core/collection/methods/#persist) in an external Storage. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#onload) + * * @public - * Callback Function that gets called if the persisted Value gets loaded into the Collection for the first Time - * Note: Only useful for persisted Collections! - * @param callback - Callback Function + * @param callback - Callback function */ public onLoad(callback: (success: boolean) => void): this { if (!this.persistent) return this; - // Check if Callback is valid Function + // Check if provided callback is valid function if (!isFunction(callback)) { LogCodeManager.log('00:03:01', ['OnLoad Callback', 'function']); return this; } + // Register provided callback this.persistent.onLoad = callback; - // If Collection is already 'isPersisted' the loading was successful -> callback can be called + // If Collection is already persisted ('isPersisted') fire provided callback immediately if (this.isPersisted) callback(true); return this; } - //========================================================================================================= - // Get Group Count - //========================================================================================================= /** + * Returns the count of registered Groups in the Collection. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupcount) + * * @public - * Get count of registered Groups in Collection */ public getGroupCount(): number { let size = 0; - for (const group in this.groups) size++; + Object.keys(this.groups).map(() => size++); return size; } - //========================================================================================================= - // Get Selector Count - //========================================================================================================= /** + * Returns the count of registered Selectors in the Collection. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselectorcount) + * * @public - * Get count of registered Selectors in Collection */ public getSelectorCount(): number { let size = 0; - for (const selector in this.selectors) size++; + Object.keys(this.selectors).map(() => size++); return size; } - //========================================================================================================= - // Reset - //========================================================================================================= /** + * Removes all Items from the Collection + * and resets the Groups and Selectors. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#reset) + * * @public - * Resets this Collection */ public reset(): this { - // Reset Data + // Reset data this.data = {}; this.size = 0; @@ -940,15 +1002,15 @@ export class Collection { return this; } - //========================================================================================================= - // Put - //========================================================================================================= /** + * Puts `itemKeys/s` into Group/s. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#put) + * * @public - * Puts ItemKey/s into Group/s (GroupKey/s) - * @param itemKeys - ItemKey/s that get added to provided Group/s - * @param groupKeys - Group/s to which the ItemKey/s get added - * @param config - Config + * @param itemKeys - `itemKey/s` to be put into the specified Group/s. + * @param groupKeys - Group identifier/s the specified `itemKey/s` are to put in. + * @param config - Configuration object */ public put( itemKeys: ItemKey | Array, @@ -958,7 +1020,7 @@ export class Collection { const _itemKeys = normalizeArray(itemKeys); const _groupKeys = normalizeArray(groupKeys); - // Add ItemKeys to Groups + // Assign itemKeys to Groups _groupKeys.forEach((groupKey) => { this.getGroup(groupKey)?.add(_itemKeys, config); }); @@ -966,16 +1028,16 @@ export class Collection { return this; } - //========================================================================================================= - // Move - //========================================================================================================= /** + * Moves specified `itemKey/s` from one Group to another Group. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#move) + * * @public - * Move ItemKey/s from one Group to another - * @param itemKeys - ItemKey/s that are moved - * @param oldGroupKey - GroupKey of the Group that currently keeps the Items at itemKey/s - * @param newGroupKey - GroupKey of the Group into which the Items at itemKey/s are moved - * @param config - Config + * @param itemKeys - `itemKey/s` to be moved. + * @param oldGroupKey - Group identifier of the Group the `itemKeys` are moved from. + * @param newGroupKey - Group identifier of the Group the `itemKeys` are moved in. + * @param config - Configuration object */ public move( itemKeys: ItemKey | Array, @@ -991,21 +1053,21 @@ export class Collection { removeProperties(config, ['method', 'overwrite']) ); - // Add itemKeys to new Group + // Assign itemKeys to new Group this.getGroup(newGroupKey)?.add(_itemKeys, config); return this; } - //========================================================================================================= - // Update Item Key - //========================================================================================================= /** + * Updates key/name identifier of Item + * and returns a boolean indicating + * whether the Item identifier was updated successfully. + * * @internal - * Updates Key/Name of Item in all Instances (Group, Selector, ..) - * @param oldItemKey - Old ItemKey - * @param newItemKey - New ItemKey - * @param config - Config + * @param oldItemKey - Old Item identifier. + * @param newItemKey - New Item identifier. + * @param config - Configuration object */ public updateItemKey( oldItemKey: ItemKey, @@ -1025,39 +1087,42 @@ export class Collection { return false; } - // Remove Item from old ItemKey and add Item to new ItemKey + // Update itemKey in data object delete this.data[oldItemKey]; this.data[newItemKey] = item; - // Update Key/Name of Item + // Update key/name of Item item.setKey(newItemKey, { background: config.background, }); - // Update persist Key of Item (Doesn't get updated by updating key of Item because PersistKey is special formatted) + // Update Persistent key of Item + // because it differs from the actual Item key + // and therefore isn't updated when the Item key is updated item.persistent?.setKey( CollectionPersistent.getItemStorageKey(newItemKey, this._key) ); - // Update ItemKey in Groups + // Update itemKey in Groups for (const groupKey in this.groups) { const group = this.getGroup(groupKey, { notExisting: true }); - if (!group?.has(oldItemKey)) continue; - group?.replace(oldItemKey, newItemKey, { background: config.background }); + if (group == null || !group.has(oldItemKey)) continue; + group.replace(oldItemKey, newItemKey, { background: config.background }); } - // Update ItemKey in Selectors + // Update itemKey in Selectors for (const selectorKey in this.selectors) { const selector = this.getSelector(selectorKey, { notExisting: true }); if (selector == null) continue; // Reselect Item in Selector that has selected the newItemKey - // Necessary because the reference placeholder Item got removed - // and replaced with the new Item (Item of which the primaryKey was renamed) - // -> needs to find new Item with the same itemKey + // Necessary because potential reference placeholder Item got overwritten + // with the new (renamed) Item + // -> has to find the new Item at selected itemKey + // since the placeholder Item got overwritten if (selector.hasSelected(newItemKey, false)) { selector.reselect({ - force: true, // Because ItemKeys are the same + force: true, // Because itemKeys are the same background: config.background, }); } @@ -1072,30 +1137,45 @@ export class Collection { return true; } - //========================================================================================================= - // Get GroupKeys That Have ItemKey - //========================================================================================================= /** + * Returns all identifier keys/names of Group/s representing the specified `itemKey`. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupkeysthathaveitemkey) + * * @public - * Gets GroupKeys that contain the passed ItemKey - * @param itemKey - ItemKey + * @param itemKey - `itemKey` to be contained in Group/s. */ public getGroupKeysThatHaveItemKey(itemKey: ItemKey): Array { const groupKeys: Array = []; for (const groupKey in this.groups) { - const group = this.getGroup(groupKey, { notExisting: true }); + const group = this.groups[groupKey]; if (group?.has(itemKey)) groupKeys.push(groupKey); } return groupKeys; } - //========================================================================================================= - // Remove - //========================================================================================================= /** + * Removes Item/s from: + * + * - `.everywhere()`: + * Removes Item/s from the entire Collection and all its Groups and Selectors (i.e. from everywhere) + * ``` + * MY_COLLECTION.remove('1').everywhere(); + * // is equivalent to + * MY_COLLECTION.removeItems('1'); + * ``` + * - `.fromGroups()`: + * Removes Item/s only from specified Groups. + * ``` + * MY_COLLECTION.remove('1').fromGroups(['1', '2']); + * // is equivalent to + * MY_COLLECTION.removeFromGroups('1', ['1', '2']); + * ``` + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#remove) + * * @public - * Remove Items from Collection - * @param itemKeys - ItemKey/s that get removed + * @param itemKeys - Item/s with identifier/s to be removed. */ public remove( itemKeys: ItemKey | Array @@ -1110,14 +1190,14 @@ export class Collection { }; } - //========================================================================================================= - // Remove From Groups - //========================================================================================================= /** + * Remove Item/s from Group/s. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removefromgroups) + * * @public - * Removes Item/s from Group/s - * @param itemKeys - ItemKey/s that get removed from Group/s - * @param groupKeys - GroupKey/s of Group/s form which the ItemKey/s will be removed + * @param itemKeys - Item/s with identifier/s to be removed from Group/s. + * @param groupKeys - Group/s with identifier/s the Item/s are to remove from. */ public removeFromGroups( itemKeys: ItemKey | Array, @@ -1129,7 +1209,7 @@ export class Collection { _itemKeys.forEach((itemKey) => { let removedFromGroupsCount = 0; - // Remove ItemKey from Groups + // Remove itemKey from Groups _groupKeys.forEach((groupKey) => { const group = this.getGroup(groupKey, { notExisting: true }); if (!group?.has(itemKey)) return; @@ -1137,7 +1217,8 @@ export class Collection { removedFromGroupsCount++; }); - // If Item got removed from every Groups the Item was in, remove it completely + // If the Item was removed from each Group in which it was represented, + // remove it completely if ( removedFromGroupsCount >= this.getGroupKeysThatHaveItemKey(itemKey).length @@ -1148,13 +1229,13 @@ export class Collection { return this; } - //========================================================================================================= - // Remove Items - //========================================================================================================= /** + * Removes Item/s from the entire Collection and all its Groups and Selectors + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removeitems) + * * @public - * Removes Item completely from Collection - * @param itemKeys - ItemKey/s of Item/s + * @param itemKeys - Item/s with identifier/s to be removed from the entire Collection. * @param config - Config */ public removeItems( @@ -1184,16 +1265,18 @@ export class Collection { // Remove Item from Collection delete this.data[itemKey]; - // Reselect or remove Selectors representing the removed Item + // Reselect or remove Selectors which have represented the removed Item for (const selectorKey in this.selectors) { const selector = this.getSelector(selectorKey, { notExisting: true }); - if (selector?.hasSelected(itemKey, false)) { + if (selector != null && selector.hasSelected(itemKey, false)) { if (config.removeSelector) { // Remove Selector - this.removeSelector(selector?._key ?? 'unknown'); + this.removeSelector(selector._key ?? 'unknown'); } else { - // Reselect Item in Selector (to create new dummyItem to hold a reference to this removed Item) - selector?.reselect({ force: true }); + // Reselect Item in Selector + // in order to create a new dummyItem + // to hold a reference to the now not existing Item + selector.reselect({ force: true }); } } } @@ -1318,14 +1401,12 @@ export class Collection { return true; } - //========================================================================================================= - // Rebuild Groups That Includes Item Key - //========================================================================================================= /** + * Rebuilds all Groups that include the specified `itemKey`. + * * @internal - * Rebuilds Groups that include the provided ItemKey - * @itemKey - Item Key - * @config - Config + * @itemKey - `itemKey` Groups must contain to be rebuilt. + * @config - Configuration object */ public rebuildGroupsThatIncludeItemKey( itemKey: ItemKey, @@ -1339,16 +1420,17 @@ export class Collection { }, }); - // Rebuild Groups that include ItemKey + // Rebuild Groups that include itemKey for (const groupKey in this.groups) { const group = this.getGroup(groupKey); if (group?.has(itemKey)) { - // group.rebuild(); Not necessary because a sideEffect of the Group is to rebuild it self + // Not necessary because a sideEffect of ingesting the Group is to rebuilt it self + // group.rebuild(); group?.ingest({ background: config?.background, - force: true, // because Group value doesn't change only the output changes + force: true, // because Group value didn't change, only the output changes sideEffects: config?.sideEffects, - storage: false, // because Group only rebuilds and doesn't change its value + storage: false, // because Group only rebuilds -> actual value hasn't changed }); } } diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index ce4e6eaf..ccb571be 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -370,20 +370,26 @@ export class State { return !!this.watchers[key]; } - //========================================================================================================= - // Persist - //========================================================================================================= /** + * Preserves the State `value` in the corresponding external Storage. + * + * The State key/name is used as the unique identifier for the Persistent. + * If that is not desired, please specify a unique identifier for the Persistent. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) + * * @public - * Stores State Value into Agile Storage permanently - * @param config - Config + * @param config - Configuration object */ public persist(config?: StatePersistentConfigInterface): this; /** + * Preserves the State `value` in the corresponding external Storage. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) + * * @public - * Stores State Value into Agile Storage permanently - * @param key - Key/Name of created Persistent (Note: Key required if State has no set Key!) - * @param config - Config + * @param key - Key/Name identifier of Persistent. + * @param config - Configuration object */ public persist( key?: PersistentKey, @@ -413,7 +419,7 @@ export class State { // Check if State is already persisted if (this.persistent != null && this.isPersisted) return this; - // Create persistent -> Persist Value + // Create Persistent -> persist value this.persistent = new StatePersistent(this, { instantiate: _config.loadValue, storageKeys: _config.storageKeys, @@ -424,27 +430,30 @@ export class State { return this; } - //========================================================================================================= - // On Load - //========================================================================================================= /** + * Fires immediately after the persisted `value` + * is loaded into the State from corresponding the external Storage. + * + * Registering this callback only makes sense when the State is [persisted](https://agile-ts.org/docs/core/state/methods/#persist). + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#onload) + * * @public - * Callback Function that gets called if the persisted Value gets loaded into the State for the first Time - * Note: Only useful for persisted States! - * @param callback - Callback Function + * @param callback - Callback function */ public onLoad(callback: (success: boolean) => void): this { if (!this.persistent) return this; - // Check if Callback is valid Function + // Check if provided callback is valid function if (!isFunction(callback)) { LogCodeManager.log('00:03:01', ['OnLoad Callback', 'function']); return this; } + // Register provided callback this.persistent.onLoad = callback; - // If State is already 'isPersisted' the loading was successful -> callback can be called + // If State is already persisted ('isPersisted') fire provided callback immediately if (this.isPersisted) callback(true); return this; From 6df7e3f1025e18426859eb1c0e8c00c9ca83f0fa Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 3 Jun 2021 07:19:32 +0200 Subject: [PATCH 035/117] fixed typos in Collection --- packages/core/src/collection/index.ts | 136 ++++++++++-------- packages/core/src/state/index.ts | 15 +- packages/core/src/storages/persistent.ts | 2 +- .../tests/unit/collection/collection.test.ts | 67 ++++++++- 4 files changed, 154 insertions(+), 66 deletions(-) diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index fbdedc79..a2b0a98a 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -40,7 +40,7 @@ export class Collection { public isInstantiated = false; // Whether the Collection is instantiated completely /** - * A Collection provides a reactive set of Information + * A Collection manages a reactive set of Information * that we need to remember globally at a later point in time. * While providing a toolkit to use and mutate this set of Information. * @@ -134,7 +134,10 @@ export class Collection { * Creates a new Group without associating it to the Collection. * * This way of creating a Group is intended for use in the Collection configuration object, - * where the `constructor()` takes care of the associating. + * where the `constructor()` takes care of the binding. + * + * After a successful initiation of the Collection we recommend using `createGroup()`, + * because it automatically connects the Group to the Collection. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#group) * @@ -159,7 +162,10 @@ export class Collection { * Creates a new Selector without associating it to the Collection. * * This way of creating a Selector is intended for use in the Collection configuration object, - * where the `constructor()` takes care of the associating. + * where the `constructor()` takes care of the binding. + * + * After a successful initiation of the Collection we recommend using `createSelector()`, + * because it automatically connects the Group to the Collection. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#selector) * @@ -184,7 +190,7 @@ export class Collection { * Sets up the specified Groups or Group keys * and assigns them to the Collection if they are valid. * - * It also assigns the default Group to the Collection. + * It also instantiates and assigns the default Group to the Collection. * The default Group reflects the default pattern of the Collection. * * @internal @@ -194,7 +200,7 @@ export class Collection { if (!groups) return; let groupsObject: { [key: string]: Group } = {}; - // If groups is Array of Group names/keys, create the Groups based these keys + // If groups is Array of Group keys/names, create the Groups based on these keys if (Array.isArray(groups)) { groups.forEach((groupKey) => { groupsObject[groupKey] = new Group(this, [], { @@ -226,7 +232,7 @@ export class Collection { if (!selectors) return; let selectorsObject: { [key: string]: Selector } = {}; - // If selectors is Array of Selector names/keys, create the Selectors based these keys + // If selectors is Array of Selector keys/names, create the Selectors based on these keys if (Array.isArray(selectors)) { selectors.forEach((selectorKey) => { selectorsObject[selectorKey] = new Selector( @@ -256,7 +262,8 @@ export class Collection { * it must contain such unique identifier at 'id' * to be added to the Collection. * ``` - * MY_COLLECTION.collect({id: '1', name: 'jeff'}); + * MY_COLLECTION.collect({id: '1', name: 'jeff'}); // valid + * MY_COLLECTION.collect({name: 'frank'}); // invalid * ``` * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#collect) @@ -328,7 +335,7 @@ export class Collection { /** * Updates the Item `data object` with the specified `object with changes`, if the Item exists. - * By default the `object with changes` is merged into the Item `data object`. + * By default the `object with changes` is merged into the Item `data object` at top level. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#update) * @@ -407,7 +414,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#createGroup) * * @public - * @param groupKey - Unique identifier of the to create Group. + * @param groupKey - Unique identifier of the Group to be created. * @param initialItems - Initial keys of Items to be represented by the Group. */ public createGroup( @@ -487,6 +494,8 @@ export class Collection { * Every Collection should have a default Group, * which represents the default pattern of the Collection. * + * If the default Group, for what ever reason, doesn't exist, `undefined` is returned. + * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getdefaultgroup) * * @public @@ -530,20 +539,33 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removegroup) * * @public - * @param groupKey - Key/Name identifier of Group. + * @param groupKey - Key/Name identifier of Group to be removed. */ public removeGroup(groupKey: GroupKey): this { if (this.groups[groupKey] != null) delete this.groups[groupKey]; return this; } + /** + * Returns the count of registered Groups in the Collection. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupcount) + * + * @public + */ + public getGroupCount(): number { + let size = 0; + Object.keys(this.groups).map(() => size++); + return size; + } + /** * Creates a new Selector and associates it to the Collection. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#createSelector) * * @public - * @param selectorKey - Unique identifier of the to create Selector. + * @param selectorKey - Unique identifier of Selector to be created. * @param itemKey - Initial key of Item to be represented by the Selector. */ public createSelector( @@ -585,7 +607,8 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#select) * * @public - * @param itemKey - ItemKey to be selected. + * @param itemKey - Initial key of Item to be represented by the Selector + * and used as unique identifier of the Selector. */ public select(itemKey: ItemKey): Selector { return this.createSelector(itemKey, itemKey); @@ -679,7 +702,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removeselector) * * @public - * @param selectorKey - Key/Name identifier of Selector. + * @param selectorKey - Key/Name identifier of Selector to be removed. */ public removeSelector(selectorKey: SelectorKey): this { if (this.selectors[selectorKey] != null) { @@ -689,6 +712,19 @@ export class Collection { return this; } + /** + * Returns the count of registered Selectors in the Collection. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselectorcount) + * + * @public + */ + public getSelectorCount(): number { + let size = 0; + Object.keys(this.selectors).map(() => size++); + return size; + } + /** * Returns a boolean indicating whether a Item with the specified `itemKey` * exists in the Collection or not. @@ -763,7 +799,7 @@ export class Collection { * * @internal * @param itemKey - Unique identifier of the to create placeholder Item. - * @param addToCollection - Whether the created Item should be added to the Collection. + * @param addToCollection - Whether to add the Item to be created to the Collection. */ public createPlaceholderItem( itemKey: ItemKey, @@ -773,7 +809,7 @@ export class Collection { const item = new Item( this, { - [this.config.primaryKey]: itemKey, // Setting PrimaryKey of Item to passed itemKey + [this.config.primaryKey]: itemKey, // Setting primaryKey of Item to passed itemKey dummy: 'item', } as any, { isPlaceholder: true } @@ -794,7 +830,7 @@ export class Collection { * Retrieves the value (data object) of a single Item * with the specified key/name identifier from the Collection. * - * If the to retrieve Item doesn't exist, `undefined` is returned. + * If the to retrieve Item containing the value doesn't exist, `undefined` is returned. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitemvalue) * @@ -832,13 +868,14 @@ export class Collection { const defaultGroup = this.getDefaultGroup(); let items: Array> = []; - // If config.notExisting transform this.data into array, otherwise return the default Group items + // If config.notExisting transform the data object into array since it contains all Items, + // otherwise return the default Group Items if (config.notExisting) { for (const key in this.data) items.push(this.data[key]); } else { - // Why defaultGroup Items and not all .exists === true Items? - // Because the default Group keeps track of all existing Items - // It also does control the Collection output in useAgile() + // Why default Group Items and not all '.exists === true' Items? + // Because the default Group keeps track of all existing Items. + // It also does control the Collection output in binding methods like 'useAgile()' // and therefore should do it here too. items = defaultGroup?.items || []; } @@ -868,7 +905,8 @@ export class Collection { * Preserves the Collection `value` in the corresponding external Storage. * * The Collection key/name is used as the unique identifier for the Persistent. - * If that is not desired, please specify a unique identifier for the Persistent. + * If that is not desired or the Collection has no unique identifier, + * please specify a separate unique identifier for the Persistent. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#persist) * @@ -879,6 +917,8 @@ export class Collection { /** * Preserves the Collection `value` in the corresponding external Storage. * + * The specified key is used as the unique identifier for the Persistent. + * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#persist) * * @public @@ -913,7 +953,7 @@ export class Collection { // Check if Collection is already persisted if (this.persistent != null && this.isPersisted) return this; - // Create Persistent -> persist value + // Create Persistent (-> persist value) this.persistent = new CollectionPersistent(this, { instantiate: _config.loadValue, storageKeys: _config.storageKeys, @@ -954,35 +994,9 @@ export class Collection { return this; } - /** - * Returns the count of registered Groups in the Collection. - * - * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupcount) - * - * @public - */ - public getGroupCount(): number { - let size = 0; - Object.keys(this.groups).map(() => size++); - return size; - } - - /** - * Returns the count of registered Selectors in the Collection. - * - * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselectorcount) - * - * @public - */ - public getSelectorCount(): number { - let size = 0; - Object.keys(this.selectors).map(() => size++); - return size; - } - /** * Removes all Items from the Collection - * and resets the Groups and Selectors. + * and resets all Groups and Selectors of the Collection. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#reset) * @@ -1009,7 +1023,7 @@ export class Collection { * * @public * @param itemKeys - `itemKey/s` to be put into the specified Group/s. - * @param groupKeys - Group identifier/s the specified `itemKey/s` are to put in. + * @param groupKeys - Identifier/s of the Group/s the specified `itemKey/s` are to put in. * @param config - Configuration object */ public put( @@ -1035,8 +1049,8 @@ export class Collection { * * @public * @param itemKeys - `itemKey/s` to be moved. - * @param oldGroupKey - Group identifier of the Group the `itemKeys` are moved from. - * @param newGroupKey - Group identifier of the Group the `itemKeys` are moved in. + * @param oldGroupKey - Identifier of the Group the `itemKey/s` are moved from. + * @param newGroupKey - Identifier of the Group the `itemKey/s` are moved in. * @param config - Configuration object */ public move( @@ -1097,11 +1111,17 @@ export class Collection { }); // Update Persistent key of Item - // because it differs from the actual Item key - // and therefore isn't updated when the Item key is updated - item.persistent?.setKey( - CollectionPersistent.getItemStorageKey(newItemKey, this._key) - ); + // if it follows the Item Storage Key pattern + // and therefore differs from the actual Item key + // (-> isn't automatically updated when the Item key is updated) + if ( + item.persistent != null && + item.persistent._key === + CollectionPersistent.getItemStorageKey(oldItemKey, this._key) + ) + item.persistent?.setKey( + CollectionPersistent.getItemStorageKey(newItemKey, this._key) + ); // Update itemKey in Groups for (const groupKey in this.groups) { @@ -1122,7 +1142,7 @@ export class Collection { // since the placeholder Item got overwritten if (selector.hasSelected(newItemKey, false)) { selector.reselect({ - force: true, // Because itemKeys are the same + force: true, // Because itemKeys are the same (but not the Items) background: config.background, }); } diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index ccb571be..cd412f67 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -139,8 +139,12 @@ export class State { // Update key in Persistent (only if oldKey equal to persistentKey // because otherwise the persistentKey is detached from the State key // -> not managed by State anymore) - if (value && this.persistent?._key === oldKey) - this.persistent?.setKey(value); + if ( + value != null && + this.persistent != null && + this.persistent._key === oldKey + ) + this.persistent.setKey(value); return this; } @@ -374,7 +378,8 @@ export class State { * Preserves the State `value` in the corresponding external Storage. * * The State key/name is used as the unique identifier for the Persistent. - * If that is not desired, please specify a unique identifier for the Persistent. + * If that is not desired or the State has no unique identifier, + * please specify a separate unique identifier for the Persistent. * * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) * @@ -385,6 +390,8 @@ export class State { /** * Preserves the State `value` in the corresponding external Storage. * + * The specified key is used as the unique identifier for the Persistent. + * * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) * * @public @@ -419,7 +426,7 @@ export class State { // Check if State is already persisted if (this.persistent != null && this.isPersisted) return this; - // Create Persistent -> persist value + // Create Persistent (-> persist value) this.persistent = new StatePersistent(this, { instantiate: _config.loadValue, storageKeys: _config.storageKeys, diff --git a/packages/core/src/storages/persistent.ts b/packages/core/src/storages/persistent.ts index 19f4cc09..2fa36634 100644 --- a/packages/core/src/storages/persistent.ts +++ b/packages/core/src/storages/persistent.ts @@ -92,7 +92,7 @@ export class Persistent { public instantiatePersistent( config: InstantiatePersistentConfigInterface = {} ) { - this._key = this.formatKey(config.key) || Persistent.placeHolderKey; + this._key = this.formatKey(config.key) ?? Persistent.placeHolderKey; this.assignStorageKeys(config.storageKeys, config.defaultStorageKey); this.validatePersistent(); } diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index 500fb289..8af0f72e 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -2118,7 +2118,13 @@ describe('Collection Tests', () => { dummySelector3.reselect = jest.fn(); }); - it('should update ItemKey in Collection, Selectors and Groups (default config)', () => { + it('should update ItemKey in Collection, Selectors, Groups and Persistent (default config)', () => { + if (dummyItem1.persistent) + dummyItem1.persistent._key = CollectionPersistent.getItemStorageKey( + dummyItem1._key, + collection._key + ); + const response = collection.updateItemKey('dummyItem1', 'newDummyItem'); expect(response).toBeTruthy(); @@ -2156,7 +2162,13 @@ describe('Collection Tests', () => { LogMock.hasNotLogged('warn'); }); - it('should update ItemKey in Collection, Selectors and Groups (specific config)', () => { + it('should update ItemKey in Collection, Selectors, Groups and Persistent (specific config)', () => { + if (dummyItem1.persistent) + dummyItem1.persistent._key = CollectionPersistent.getItemStorageKey( + dummyItem1._key, + collection._key + ); + const response = collection.updateItemKey( 'dummyItem1', 'newDummyItem', @@ -2196,7 +2208,13 @@ describe('Collection Tests', () => { LogMock.hasNotLogged('warn'); }); - it('should update ItemKey in Collection, dummy Selectors and dummy Groups (default config)', () => { + it('should update ItemKey in Collection, dummy Selectors, dummy Groups and Persistent (default config)', () => { + if (dummyItem1.persistent) + dummyItem1.persistent._key = CollectionPersistent.getItemStorageKey( + dummyItem1._key, + collection._key + ); + dummyGroup1.isPlaceholder = true; dummySelector1.isPlaceholder = true; @@ -2233,6 +2251,49 @@ describe('Collection Tests', () => { LogMock.hasNotLogged('warn'); }); + it( + 'should update ItemKey in Collection, Selectors, Groups ' + + "and shouldn't update it in Persistent if persist key doesn't follow the Item Storage Key pattern (default config)", + () => { + if (dummyItem1.persistent) + dummyItem1.persistent._key = 'randomPersistKey'; + + const response = collection.updateItemKey( + 'dummyItem1', + 'newDummyItem' + ); + + expect(response).toBeTruthy(); + + expect(dummyItem1.setKey).toHaveBeenCalledWith('newDummyItem', { + background: false, + }); + expect(dummyItem2.setKey).not.toHaveBeenCalled(); + expect(dummyItem1.persistent?.setKey).not.toHaveBeenCalled(); + expect(dummyItem2.persistent?.setKey).not.toHaveBeenCalled(); + + expect(dummyGroup1.replace).toHaveBeenCalledWith( + 'dummyItem1', + 'newDummyItem', + { + background: false, + } + ); + expect(dummyGroup2.replace).not.toHaveBeenCalled(); + + expect(dummySelector1.select).toHaveBeenCalledWith('newDummyItem', { + background: false, + }); + expect(dummySelector2.select).not.toHaveBeenCalled(); + expect(dummySelector3.reselect).toHaveBeenCalledWith({ + force: true, + background: false, + }); + + LogMock.hasNotLogged('warn'); + } + ); + it("shouldn't update ItemKey of Item that doesn't exist (default config)", () => { const response = collection.updateItemKey( 'notExistingItem', From cfd9f2c22b15e570443e2262f0f01e2b93f25109 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 3 Jun 2021 16:10:44 +0200 Subject: [PATCH 036/117] added interface descriptions in Collection --- packages/core/src/collection/group.ts | 4 +- packages/core/src/collection/index.ts | 266 ++++++++++++------ packages/core/src/computed/index.ts | 26 +- packages/core/src/state/index.ts | 15 +- packages/core/src/state/state.observer.ts | 3 + .../tests/unit/collection/collection.test.ts | 4 +- 6 files changed, 205 insertions(+), 113 deletions(-) diff --git a/packages/core/src/collection/group.ts b/packages/core/src/collection/group.ts index 98fa4ccc..a8f53b1c 100644 --- a/packages/core/src/collection/group.ts +++ b/packages/core/src/collection/group.ts @@ -56,7 +56,7 @@ export class Group extends State< */ public get output(): Array { ComputedTracker.tracked(this.observer); - return this._output; + return copy(this._output); } /** @@ -64,7 +64,7 @@ export class Group extends State< * Set Item Values of Group */ public set output(value: DataType[]) { - this._output = value; + this._output = copy(value); } /** diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index a2b0a98a..26399664 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -20,6 +20,7 @@ import { removeProperties, isFunction, LogCodeManager, + PatchOptionConfigInterface, } from '../internal'; export class Collection { @@ -1079,8 +1080,8 @@ export class Collection { * whether the Item identifier was updated successfully. * * @internal - * @param oldItemKey - Old Item identifier. - * @param newItemKey - New Item identifier. + * @param oldItemKey - Old key/name Item identifier. + * @param newItemKey - New key/name Item identifier. * @param config - Configuration object */ public updateItemKey( @@ -1110,8 +1111,7 @@ export class Collection { background: config.background, }); - // Update Persistent key of Item - // if it follows the Item Storage Key pattern + // Update Persistent key of Item if it follows the Item Storage Key pattern // and therefore differs from the actual Item key // (-> isn't automatically updated when the Item key is updated) if ( @@ -1135,14 +1135,14 @@ export class Collection { const selector = this.getSelector(selectorKey, { notExisting: true }); if (selector == null) continue; - // Reselect Item in Selector that has selected the newItemKey + // Reselect Item in Selector that has selected the newItemKey. // Necessary because potential reference placeholder Item got overwritten // with the new (renamed) Item // -> has to find the new Item at selected itemKey // since the placeholder Item got overwritten if (selector.hasSelected(newItemKey, false)) { selector.reselect({ - force: true, // Because itemKeys are the same (but not the Items) + force: true, // Because itemKeys are the same (but not the Items at this itemKey anymore) background: config.background, }); } @@ -1158,7 +1158,7 @@ export class Collection { } /** - * Returns all identifier keys/names of Group/s representing the specified `itemKey`. + * Returns all key/name identifiers of the Group/s containing the specified `itemKey`. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupkeysthathaveitemkey) * @@ -1216,8 +1216,8 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removefromgroups) * * @public - * @param itemKeys - Item/s with identifier/s to be removed from Group/s. - * @param groupKeys - Group/s with identifier/s the Item/s are to remove from. + * @param itemKeys - Identifier/s of Item/s to be removed from Group/s. + * @param groupKeys - Identifier/s of Group/s the Item/s are to remove from. */ public removeFromGroups( itemKeys: ItemKey | Array, @@ -1237,7 +1237,7 @@ export class Collection { removedFromGroupsCount++; }); - // If the Item was removed from each Group in which it was represented, + // If the Item was removed from each Group representing the Item, // remove it completely if ( removedFromGroupsCount >= @@ -1250,13 +1250,13 @@ export class Collection { } /** - * Removes Item/s from the entire Collection and all its Groups and Selectors + * Removes Item/s from the entire Collection and all the Collection's Groups and Selectors. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removeitems) * * @public - * @param itemKeys - Item/s with identifier/s to be removed from the entire Collection. - * @param config - Config + * @param itemKeys - Identifier/s of Item/s to be removed from the entire Collection. + * @param config - Configuration object */ public removeItems( itemKeys: ItemKey | Array, @@ -1308,9 +1308,13 @@ export class Collection { } /** - * Assigns provided data object to an already existing Item at itemKey. - * If Item at itemKey doesn't exist yet, - * a new Item with the data object as value is created and added to the Collection. + * Assigns the provided `data` object to an already existing Item + * with specified key/name identifier found in the `data` object. + * If the Item doesn't exist yet, a new Item with the `data` object as value + * is created and assigned to the Collection. + * + * Returns a boolean indicating + * whether the `data` object was assigned/updated successfully. * * @internal * @param data - Data object @@ -1332,7 +1336,7 @@ export class Collection { return false; } - // Check if data object contains valid itemKey + // Check if data object contains valid itemKey, // otherwise add random itemKey to Item if (!Object.prototype.hasOwnProperty.call(_data, primaryKey)) { LogCodeManager.log('1B:02:05', [this._key, primaryKey]); @@ -1357,14 +1361,18 @@ export class Collection { } // Increase size of Collection if Item was previously a placeholder - // (-> didn't officially exit in Collection) + // (-> hasn't officially existed in Collection before) if (wasPlaceholder) this.size++; return true; } /** - * Adds provided Item to the Collection. + * Assigns the specified Item to the Collection + * at the key/name identifier of the Item. + * + * And returns a boolean indicating + * whether the Item was assigned successfully. * * @internal * @param item - Item to be added. @@ -1382,7 +1390,7 @@ export class Collection { let itemKey = item._value[primaryKey]; let increaseCollectionSize = true; - // Check if Item has valid itemKey + // Check if Item has valid itemKey, // otherwise add random itemKey to Item if (!Object.prototype.hasOwnProperty.call(item._value, primaryKey)) { LogCodeManager.log('1B:02:05', [this._key, primaryKey]); @@ -1410,8 +1418,8 @@ export class Collection { this.data[itemKey] = item; // Rebuild Groups that include itemKey - // after adding Item to Collection - // (because otherwise it can't find the Item since it doesn't exist in Collection yet) + // after adding Item with itemKey to the Collection + // (because otherwise it can't find the Item as it isn't added yet) this.rebuildGroupsThatIncludeItemKey(itemKey, { background: config.background, }); @@ -1422,7 +1430,7 @@ export class Collection { } /** - * Rebuilds all Groups that include the specified `itemKey`. + * Rebuilds all Groups that contain the specified `itemKey`. * * @internal * @itemKey - `itemKey` Groups must contain to be rebuilt. @@ -1444,13 +1452,15 @@ export class Collection { for (const groupKey in this.groups) { const group = this.getGroup(groupKey); if (group?.has(itemKey)) { - // Not necessary because a sideEffect of ingesting the Group is to rebuilt it self + // Not necessary because a sideEffect of ingesting the Group + // into the runtime is to rebuilt itself // group.rebuild(); + group?.ingest({ background: config?.background, - force: true, // because Group value didn't change, only the output changes + force: true, // because Group value didn't change, only the output might change sideEffects: config?.sideEffects, - storage: false, // because Group only rebuilds -> actual value hasn't changed + storage: false, // because Group only rebuilds (-> actual persisted value hasn't changed) }); } } @@ -1461,129 +1471,203 @@ export type DefaultItem = Record; // same as { [key: string]: any } export type CollectionKey = string | number; export type ItemKey = string | number; -/** - * @param key - Key/Name of Collection - * @param groups - Groups of Collection - * @param selectors - Selectors of Collection - * @param primaryKey - Name of Property that holds the PrimaryKey (default = id) - * @param defaultGroupKey - Key/Name of Default Group that holds all collected Items - * @param initialData - Initial Data of Collection - */ export interface CreateCollectionConfigInterface { + /** + * Initial Groups of Collection. + * @default [] + */ groups?: { [key: string]: Group } | string[]; + /** + * Initial Selectors of Collection + * @default [] + */ selectors?: { [key: string]: Selector } | string[]; + /** + * Key/Name identifier of Collection. + * @default undefined + */ key?: CollectionKey; + /** + * Key/Name of the property + * which should represent the unique Item identifier + * in collected data objects. + * @default 'id' + */ primaryKey?: string; + /** + * Key/Name identifier of the default Group that is created shortly after instantiation. + * The default Group represents the default pattern of the Collection. + * @default 'default' + */ defaultGroupKey?: GroupKey; + /** + * Initial data objects of the Collection. + * @default [] + */ initialData?: Array; } -/** - * @param primaryKey - Name of Property that holds the PrimaryKey (default = id) - * @param defaultGroupKey - Key/Name of Default Group that holds all collected Items - */ +export type CollectionConfig = + | CreateCollectionConfigInterface + | (( + collection: Collection + ) => CreateCollectionConfigInterface); + export interface CollectionConfigInterface { + /** + * Key/Name of the property + * which should represent the unique Item identifier + * in collected data objects. + * @default 'id' + */ primaryKey: string; + /** + * Key/Name identifier of the default Group that is created shortly after instantiation. + * The default Group represents the default pattern of the Collection. + * @default 'default' + */ defaultGroupKey: ItemKey; } -/** - * @param patch - If Item gets patched into existing Item with the same Id - * @param method - Way of adding Item to Collection (push, unshift) - * @param forEachItem - Gets called for each Item that got collected - * @param background - If collecting an Item happens in the background (-> not causing any rerender) - * @param select - If collected Items get selected with a Selector - */ -export interface CollectConfigInterface { - patch?: boolean; +export interface CollectConfigInterface + extends AssignDataConfigInterface { + /** + * In which way the collected data should be added to the Collection. + * - 'push' = at the end + * - 'unshift' = at the beginning + * @default 'push' + */ method?: 'push' | 'unshift'; + /** + * Performs the specified action for each collected data object. + * @default undefined + */ forEachItem?: ( data: DataType | Item, key: ItemKey, success: boolean, index: number ) => void; - background?: boolean; + /** + * Whether a Selector should be created for each collected data object. + * @default false + */ select?: boolean; } -/** - * @param patch - If Data gets merged into the current Data - * @param background - If updating an Item happens in the background (-> not causing any rerender) - */ export interface UpdateConfigInterface { - patch?: boolean | { addNewProperties?: boolean }; + /** + * Whether the data object with changes should be merged into the existing Item data object + * or overwrite it entirely. + * @default true + */ + patch?: boolean | PatchOptionConfigInterface; + /** + * Whether the Item data object should be updated in background. + * So that the UI isn't notified of these changes and thus doesn't rerender. + * @default false + */ background?: boolean; } -/** - * @param background - If updating the primaryKey of an Item happens in the background (-> not causing any rerender) - */ export interface UpdateItemKeyConfigInterface { + /** + * Whether the Item key/name identifier should be updated in background. + * So that the UI isn't notified of these changes and thus doesn't rerender. + * @default false + */ background?: boolean; } -/** - * @param background - If assigning a new value happens in the background (-> not causing any rerender) - * @param force - Force creating and performing Job - * @param sideEffects - If Side Effects of Group gets executed - */ export interface RebuildGroupsThatIncludeItemKeyConfigInterface { + /** + * Whether the Group should be rebuilt in background. + * So that the UI isn't notified of these changes and thus doesn't rerender. + * @default false + */ background?: boolean; - force?: boolean; + /** + * Whether the defined side effects should be executed. + * @default true + */ sideEffects?: SideEffectConfigInterface; } -/** - * @param notExisting - If placeholder can be found - */ export interface HasConfigInterface { + /** + * Whether Items that do not officially exist, + * such as placeholder Items, can be found + * @default true + */ notExisting?: boolean; } -/** - * @param loadValue - If Persistent loads the persisted value into the Collection - * @param storageKeys - Key/Name of Storages which gets used to persist the Collection Value (NOTE: If not passed the default Storage will be used) - * @param defaultStorageKey - Default Storage Key (if not provided it takes the first index of storageKeys or the AgileTs default Storage) - */ export interface CollectionPersistentConfigInterface { + /** + * Whether the Persistent should automatically load + * the persisted value into the Collection after instantiation. + * @default true + */ loadValue?: boolean; + /** + * Key/Name identifier of Storages in which the Collection should be persisted. + * @default [AgileTs default Storage key] + */ storageKeys?: StorageKey[]; + /** + * Default Storage key of the specified Storage keys. + * The Collection value is loaded from the default Storage. + * The value is only loaded from the remaining Storages (storageKeys) + * if the loading of the default Storage failed. + * + * @default first index of the specified Storage keys or the AgileTs default Storage key + */ defaultStorageKey?: StorageKey; } -/** - * @property notExisting - If not existing Items like placeholder Items can be removed. - * Keep in mind that sometimes it won't remove the Item entirely - * because another Instance (like a Selector) needs to keep reference to it. - * https://github.com/agile-ts/agile/pull/152 - * @property removeSelector - If Selectors that have selected an Item to be removed, should be removed too - */ export interface RemoveItemsConfigInterface { + /** + * Whether not officially existing Items (such as placeholder Items) can be removed. + * Keep in mind that sometimes it won't remove the Item entirely + * as another Instance (like a Selector) might need to keep reference to it. + * https://github.com/agile-ts/agile/pull/152 + * @default false + */ notExisting?: boolean; + /** + * Whether to remove Selectors that have selected an Item to be removed. + * @default false + */ removeSelector?: boolean; } -/** - * @property patch - If Data gets patched into existing Item - * @property background - If assigning Data happens in background - */ export interface AssignDataConfigInterface { + /** + * When the Item identifier of the to assign data object already exists in the Collection, + * whether then the newly assigned data should be merged into the existing one + * or overwrite it entirely. + * @default true + */ patch?: boolean; + /** + * Whether to assign the data object to the Collection in background. + * So that the UI isn't notified of these changes and thus doesn't rerender. + * @default false + */ background?: boolean; } -/** - * @property overwrite - If old Item should be overwritten - * @property background - If assigning Data happens in background - */ export interface AssignItemConfigInterface { + /** + * If an Item with the Item identifier already exists, + * whether it should then be overwritten with the new Item. + * @default false + */ overwrite?: boolean; + /** + * Whether to assign the Item to the Collection in background. + * So that the UI isn't notified of these changes and thus doesn't rerender. + * @default false + */ background?: boolean; } - -export type CollectionConfig = - | CreateCollectionConfigInterface - | (( - collection: Collection - ) => CreateCollectionConfigInterface); diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index 1c0e6bdd..42731bef 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -183,29 +183,29 @@ export class Computed extends State< } } -/** - * @property computedDeps - Hard coded dependencies on which the Computed Class depends. - * | Default = [] | - */ export interface ComputedConfigInterface extends StateConfigInterface { + /** + * Hard-coded dependencies on which the Computed Class should depend. + * @default [] + */ computedDeps?: Array; } -/** - * @property autodetect - Whether dependencies used in the compute function should be detected automatically. - * | Default = true | - */ export interface ComputeConfigInterface { + /** + * Whether dependencies used in the compute function should be detected automatically. + * @default true + */ autodetect?: boolean; } -/** - * @param overwriteDeps - Whether the old hard coded dependencies - * should be entirely overwritten with the new hard coded dependencies or merged in. - * | Default = true | - */ export interface UpdateComputeFunctionConfigInterface extends RecomputeConfigInterface { + /** + * Whether the old hard-coded dependencies should be completely overwritten + * with the new ones or merged into the new ones. + * @default false + */ overwriteDeps?: boolean; } diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index cd412f67..745364b6 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -99,7 +99,7 @@ export class State { */ public get value(): ValueType { ComputedTracker.tracked(this.observer); - return this._value; + return copy(this._value); } /** @@ -719,10 +719,15 @@ export interface StateConfigInterface { isPlaceholder?: boolean; } -/** - * @param addNewProperties - If new Properties gets added to the State Value - */ -export interface PatchConfigInterface extends StateIngestConfigInterface { +export interface PatchConfigInterface + extends StateIngestConfigInterface, + PatchOptionConfigInterface {} + +export interface PatchOptionConfigInterface { + /** + * Whether to add new properties to the object during the merge. + * @default true + */ addNewProperties?: boolean; } diff --git a/packages/core/src/state/state.observer.ts b/packages/core/src/state/state.observer.ts index 32160c75..3da44fe9 100644 --- a/packages/core/src/state/state.observer.ts +++ b/packages/core/src/state/state.observer.ts @@ -124,6 +124,9 @@ export class StateObserver extends Observer { state._value = copy(job.observer.nextStateValue); state.nextStateValue = copy(job.observer.nextStateValue); + // https://www.geeksforgeeks.org/object-freeze-javascript/#:~:text=Object.freeze()%20Method&text=freeze()%20which%20is%20used,the%20prototype%20of%20the%20object. + // if (typeof state._value === 'object') Object.freeze(state._value); + // Overwrite old State Values if (job.config.overwrite) { state.initialStateValue = copy(state._value); diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index 8af0f72e..3ad578f8 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -1664,7 +1664,7 @@ describe('Collection Tests', () => { it('should return existing Item Value (default config)', () => { const response = collection.getItemValue('1'); - expect(response).toBe(dummyItem._value); + expect(response).toStrictEqual(dummyItem._value); expect(collection.getItem).toHaveBeenCalledWith('1', {}); }); @@ -1691,7 +1691,7 @@ describe('Collection Tests', () => { notExisting: true, }); - expect(response).toBe(dummyItem._value); + expect(response).toStrictEqual(dummyItem._value); expect(collection.getItem).toHaveBeenCalledWith('1', { notExisting: true, }); From 1dd9f3f2bf4ac3194397884a283de2b3a40229f1 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 3 Jun 2021 21:07:44 +0200 Subject: [PATCH 037/117] fixed typos --- packages/core/src/collection/group.ts | 28 ++++--- packages/core/src/collection/item.ts | 78 +++++++++++-------- .../core/tests/unit/collection/item.test.ts | 38 ++++++++- 3 files changed, 102 insertions(+), 42 deletions(-) diff --git a/packages/core/src/collection/group.ts b/packages/core/src/collection/group.ts index a8f53b1c..e8da86dd 100644 --- a/packages/core/src/collection/group.ts +++ b/packages/core/src/collection/group.ts @@ -234,20 +234,29 @@ export class Group extends State< return this; } - //========================================================================================================= - // Persist - //========================================================================================================= /** + * Preserves the Group `value` in the corresponding external Storage. + * + * The Group key/name is used as the unique identifier for the Persistent. + * If that is not desired or the Group has no unique identifier, + * please specify a separate unique identifier for the Persistent. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) + * * @public - * Stores Group Value into Agile Storage permanently - * @param config - Config + * @param config - Configuration object */ public persist(config?: GroupPersistConfigInterface): this; /** + * Preserves the Group `value` in the corresponding external Storage. + * + * The specified key is used as the unique identifier for the Persistent. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) + * * @public - * Stores Group Value into Agile Storage permanently - * @param key - Key/Name of created Persistent (Note: Key required if Group has no set Key!) - * @param config - Config + * @param key - Key/Name identifier of Persistent. + * @param config - Configuration object */ public persist( key?: PersistentKey, @@ -275,7 +284,7 @@ export class Group extends State< defaultStorageKey: null, }); - // Create storageItemKey based on Collection Name + // Create storageItemKey based on Collection key/name identifier if (_config.followCollectionPersistKeyPattern) { key = CollectionPersistent.getGroupStorageKey( key || this._key, @@ -283,6 +292,7 @@ export class Group extends State< ); } + // Persist Group super.persist(key, { loadValue: _config.loadValue, storageKeys: _config.storageKeys, diff --git a/packages/core/src/collection/item.ts b/packages/core/src/collection/item.ts index 28cae1da..b4a92569 100644 --- a/packages/core/src/collection/item.ts +++ b/packages/core/src/collection/item.ts @@ -15,16 +15,18 @@ import { export class Item extends State< DataType > { - static updateGroupSideEffectKey = 'rebuildGroup'; - public selectedBy: Set = new Set(); // Keys of Selectors that have selected this Item public collection: () => Collection; + static updateGroupSideEffectKey = 'rebuildGroup'; + public selectedBy: Set = new Set(); // Key/Name Identifiers of Selectors which have selected the Item + /** + * An extension of the State Class that represents a single data object of a Collection. + * * @public - * Item of Collection - * @param collection - Collection to which the Item belongs - * @param data - Data that the Item holds - * @param config - Config + * @param collection - Collection to which the Item belongs. + * @param data - Data object to be represented by the Item. + * @param config - Configuration object */ constructor( collection: Collection, @@ -33,24 +35,23 @@ export class Item extends State< ) { super(collection.agileInstance(), data, { isPlaceholder: config.isPlaceholder, - key: data[collection.config.primaryKey], // Set Key/Name of Item to primaryKey of Data + key: data[collection.config.primaryKey], // Set key/name of Item to identifier at primaryKey property }); this.collection = () => collection; - // Add rebuildGroupsThatIncludeItemKey to sideEffects to rebuild Groups that include this Item if it mutates - this.addRebuildGroupThatIncludeItemKeySideEffect( - this._key != null ? this._key : 'unknown' - ); + // Add 'rebuildGroupsThatIncludeItemKey' side effect + // in order to rebuild all Groups that include this Item whenever it mutates + if (this._key != null) { + this.addRebuildGroupThatIncludeItemKeySideEffect(this._key); + } } - //========================================================================================================= - // Set Key - //========================================================================================================= /** + * Updates key/name identifier of Persistent. + * * @internal - * Updates Key/Name of State - * @param value - New Key/Name of State - * @param config - Config + * @param value - New key/name identifier. + * @param config - Configuration object */ public setKey( value: StateKey | undefined, @@ -69,13 +70,12 @@ export class Item extends State< }); if (value == null) return this; - // Remove old rebuildGroupsThatIncludeItemKey sideEffect + // Update 'rebuildGroupsThatIncludeItemKey' side effect to the new itemKey this.removeSideEffect(Item.updateGroupSideEffectKey); - - // Add rebuildGroupsThatIncludeItemKey to sideEffects to rebuild Groups that include this Item if it mutates this.addRebuildGroupThatIncludeItemKeySideEffect(value); - // Update ItemKey in ItemValue (After updating the sideEffect because otherwise it calls the old sideEffect) + // Update itemKey in Item value + // (After updating the side effect, because otherwise it would call the old side effect) this.patch( { [this.collection().config.primaryKey]: value }, { @@ -89,20 +89,29 @@ export class Item extends State< return this; } - //========================================================================================================= - // Persist - //========================================================================================================= /** + * Preserves the Item `value` in the corresponding external Storage. + * + * The Item key/name is used as the unique identifier for the Persistent. + * If that is not desired or the Item has no unique identifier, + * please specify a separate unique identifier for the Persistent. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) + * * @public - * Stores Item Value into Agile Storage permanently - * @param config - Config + * @param config - Configuration object */ public persist(config?: ItemPersistConfigInterface): this; /** + * Preserves the Item `value` in the corresponding external Storage. + * + * The specified key is used as the unique identifier for the Persistent. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) + * * @public - * Stores Item Value into Agile Storage permanently - * @param key - Key/Name of created Persistent (Note: Key required if Item has no set Key!) - * @param config - Config + * @param key - Key/Name identifier of Persistent. + * @param config - Configuration object */ public persist( key?: PersistentKey, @@ -130,7 +139,7 @@ export class Item extends State< defaultStorageKey: null, }); - // Create storageItemKey based on Collection Name + // Create storageItemKey based on Collection key/name identifier if (_config.followCollectionPersistKeyPattern) { key = CollectionPersistent.getItemStorageKey( key || this._key, @@ -138,6 +147,7 @@ export class Item extends State< ); } + // Persist Item super.persist(key, { loadValue: _config.loadValue, storageKeys: _config.storageKeys, @@ -158,8 +168,12 @@ export class Item extends State< public addRebuildGroupThatIncludeItemKeySideEffect(itemKey: StateKey) { this.addSideEffect>( Item.updateGroupSideEffectKey, - (instance, config) => - instance.collection().rebuildGroupsThatIncludeItemKey(itemKey, config), + (instance, config) => { + // TODO optimise this because currently the whole Group rebuilds + // although only the Item value has changed which definitely needs no complete rebuild + // https://github.com/agile-ts/agile/issues/113 + instance.collection().rebuildGroupsThatIncludeItemKey(itemKey, config); + }, { weight: 100 } ); } diff --git a/packages/core/tests/unit/collection/item.test.ts b/packages/core/tests/unit/collection/item.test.ts index c3ccb9af..bbd902bf 100644 --- a/packages/core/tests/unit/collection/item.test.ts +++ b/packages/core/tests/unit/collection/item.test.ts @@ -94,8 +94,44 @@ describe('Item Tests', () => { expect(item.selectedBy.size).toBe(0); }); + it("should create Item and shouldn't add rebuild Group side effect to it if no itemKey was provided (default config)", () => { + // Overwrite addRebuildGroupThatIncludeItemKeySideEffect once to not call it + jest + .spyOn(Item.prototype, 'addRebuildGroupThatIncludeItemKeySideEffect') + .mockReturnValueOnce(undefined); + + const dummyData = { name: 'dummyName' }; + const item = new Item(dummyCollection, dummyData as any); + + expect(item.collection()).toBe(dummyCollection); + expect( + item.addRebuildGroupThatIncludeItemKeySideEffect + ).not.toHaveBeenCalled(); + + expect(item._key).toBeUndefined(); + expect(item.valueType).toBeUndefined(); + expect(item.isSet).toBeFalsy(); + expect(item.isPlaceholder).toBeFalsy(); + expect(item.initialStateValue).toStrictEqual(dummyData); + expect(item._value).toStrictEqual(dummyData); + expect(item.previousStateValue).toStrictEqual(dummyData); + expect(item.nextStateValue).toStrictEqual(dummyData); + expect(item.observer).toBeInstanceOf(StateObserver); + expect(item.observer.dependents.size).toBe(0); + expect(item.observer._key).toBe( + dummyData[dummyCollection.config.primaryKey] + ); + expect(item.sideEffects).toStrictEqual({}); + expect(item.computeValueMethod).toBeUndefined(); + expect(item.computeExistsMethod).toBeInstanceOf(Function); + expect(item.isPersisted).toBeFalsy(); + expect(item.persistent).toBeUndefined(); + expect(item.watchers).toStrictEqual({}); + expect(item.selectedBy.size).toBe(0); + }); + describe('Item Function Tests', () => { - let item: Item; + let item: Item; beforeEach(() => { item = new Item(dummyCollection, { id: 'dummyId', name: 'dummyName' }); From 74a86efb1ac60308e44b0d1ce50c7735eca6207e Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 4 Jun 2021 07:22:35 +0200 Subject: [PATCH 038/117] added item method descriptions --- packages/core/src/collection/group.ts | 10 +- packages/core/src/collection/index.ts | 37 +++--- packages/core/src/collection/item.ts | 46 ++++---- packages/core/src/computed/index.ts | 6 +- .../core/tests/unit/collection/group.test.ts | 98 +++++++++------- .../core/tests/unit/collection/item.test.ts | 107 +++++++++++++++++- 6 files changed, 211 insertions(+), 93 deletions(-) diff --git a/packages/core/src/collection/group.ts b/packages/core/src/collection/group.ts index e8da86dd..d4654f44 100644 --- a/packages/core/src/collection/group.ts +++ b/packages/core/src/collection/group.ts @@ -279,7 +279,7 @@ export class Group extends State< _config = defineConfig(_config, { loadValue: true, - followCollectionPattern: false, + followCollectionPersistKeyPattern: true, storageKeys: [], defaultStorageKey: null, }); @@ -375,10 +375,12 @@ export interface GroupConfigInterface { isPlaceholder?: boolean; } -/** - * @param useCollectionPattern - If Group storageKey follows the Collection Group StorageKey Pattern - */ export interface GroupPersistConfigInterface extends StatePersistentConfigInterface { + /** + * Whether to format the specified Storage key into the Collection Group Storage key pattern. + * `_${collectionKey}_group_${groupKey}` + * @default true + */ followCollectionPersistKeyPattern?: boolean; } diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 26399664..cf7c7366 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -1489,7 +1489,7 @@ export interface CreateCollectionConfigInterface { key?: CollectionKey; /** * Key/Name of the property - * which should represent the unique Item identifier + * which represents the unique Item identifier * in collected data objects. * @default 'id' */ @@ -1516,7 +1516,7 @@ export type CollectionConfig = export interface CollectionConfigInterface { /** * Key/Name of the property - * which should represent the unique Item identifier + * which represents the unique Item identifier * in collected data objects. * @default 'id' */ @@ -1549,7 +1549,7 @@ export interface CollectConfigInterface index: number ) => void; /** - * Whether a Selector should be created for each collected data object. + * Whether to create a Selector for each collected data object. * @default false */ select?: boolean; @@ -1557,13 +1557,13 @@ export interface CollectConfigInterface export interface UpdateConfigInterface { /** - * Whether the data object with changes should be merged into the existing Item data object - * or overwrite it entirely. + * Whether to merge the data object with changes into the existing Item data object + * or overwrite the existing Item data object entirely. * @default true */ patch?: boolean | PatchOptionConfigInterface; /** - * Whether the Item data object should be updated in background. + * Whether to update the data object in background. * So that the UI isn't notified of these changes and thus doesn't rerender. * @default false */ @@ -1572,7 +1572,7 @@ export interface UpdateConfigInterface { export interface UpdateItemKeyConfigInterface { /** - * Whether the Item key/name identifier should be updated in background. + * Whether to update the Item key/name identifier in background * So that the UI isn't notified of these changes and thus doesn't rerender. * @default false */ @@ -1581,13 +1581,13 @@ export interface UpdateItemKeyConfigInterface { export interface RebuildGroupsThatIncludeItemKeyConfigInterface { /** - * Whether the Group should be rebuilt in background. + * Whether to rebuilt the Group in background. * So that the UI isn't notified of these changes and thus doesn't rerender. * @default false */ background?: boolean; /** - * Whether the defined side effects should be executed. + * Whether to execute the defined side effects. * @default true */ sideEffects?: SideEffectConfigInterface; @@ -1605,19 +1605,20 @@ export interface HasConfigInterface { export interface CollectionPersistentConfigInterface { /** * Whether the Persistent should automatically load - * the persisted value into the Collection after instantiation. + * the persisted value into the Collection after its instantiation. * @default true */ loadValue?: boolean; /** - * Key/Name identifier of Storages in which the Collection should be persisted. + * Key/Name identifier of Storages + * in which the Collection value should be or is persisted. * @default [AgileTs default Storage key] */ storageKeys?: StorageKey[]; /** * Default Storage key of the specified Storage keys. - * The Collection value is loaded from the default Storage. - * The value is only loaded from the remaining Storages (storageKeys) + * The Collection value is loaded from the default Storage + * and only loaded from the remaining Storages (storageKeys) * if the loading of the default Storage failed. * * @default first index of the specified Storage keys or the AgileTs default Storage key @@ -1627,8 +1628,8 @@ export interface CollectionPersistentConfigInterface { export interface RemoveItemsConfigInterface { /** - * Whether not officially existing Items (such as placeholder Items) can be removed. - * Keep in mind that sometimes it won't remove the Item entirely + * Whether to remove not officially existing Items (such as placeholder Items). + * Keep in mind that sometimes it won't remove an Item entirely * as another Instance (like a Selector) might need to keep reference to it. * https://github.com/agile-ts/agile/pull/152 * @default false @@ -1644,8 +1645,8 @@ export interface RemoveItemsConfigInterface { export interface AssignDataConfigInterface { /** * When the Item identifier of the to assign data object already exists in the Collection, - * whether then the newly assigned data should be merged into the existing one - * or overwrite it entirely. + * whether to merge the newly assigned data into the existing one + * or overwrite the existing one entirely. * @default true */ patch?: boolean; @@ -1660,7 +1661,7 @@ export interface AssignDataConfigInterface { export interface AssignItemConfigInterface { /** * If an Item with the Item identifier already exists, - * whether it should then be overwritten with the new Item. + * whether to overwrite it entirely with the new one. * @default false */ overwrite?: boolean; diff --git a/packages/core/src/collection/item.ts b/packages/core/src/collection/item.ts index b4a92569..1abdf3c7 100644 --- a/packages/core/src/collection/item.ts +++ b/packages/core/src/collection/item.ts @@ -23,6 +23,8 @@ export class Item extends State< /** * An extension of the State Class that represents a single data object of a Collection. * + * It can be used independently, but is always synchronized with the Collection. + * * @public * @param collection - Collection to which the Item belongs. * @param data - Data object to be represented by the Item. @@ -47,7 +49,7 @@ export class Item extends State< } /** - * Updates key/name identifier of Persistent. + * Updates key/name identifier of Item. * * @internal * @param value - New key/name identifier. @@ -76,16 +78,8 @@ export class Item extends State< // Update itemKey in Item value // (After updating the side effect, because otherwise it would call the old side effect) - this.patch( - { [this.collection().config.primaryKey]: value }, - { - sideEffects: config.sideEffects, - background: config.background, - force: config.force, - storage: config.storage, - overwrite: config.overwrite, - } - ); + this.patch({ [this.collection().config.primaryKey]: value }, config); + return this; } @@ -134,7 +128,7 @@ export class Item extends State< _config = defineConfig(_config, { loadValue: true, - followCollectionPattern: false, + followCollectionPersistKeyPattern: true, storageKeys: [], defaultStorageKey: null, }); @@ -157,20 +151,20 @@ export class Item extends State< return this; } - //========================================================================================================= - // Add Rebuild Group That Include ItemKey SideEffect - //========================================================================================================= /** + * Adds the 'Rebuild Group that include Item Key' action to the Item side effects, + * so that the Groups which include the Item with the identifier `itemKey` + * are rebuilt when the Item changes. + * * @internal - * Adds rebuildGroupThatIncludeItemKey to the Item sideEffects - * @param itemKey - ItemKey at which the groups has to rebuild + * @param itemKey - Item identifier that has to be included in Groups so that these Groups can be rebuilt. */ public addRebuildGroupThatIncludeItemKeySideEffect(itemKey: StateKey) { this.addSideEffect>( Item.updateGroupSideEffectKey, (instance, config) => { // TODO optimise this because currently the whole Group rebuilds - // although only the Item value has changed which definitely needs no complete rebuild + // although only one Item value has changed which definitely needs no complete rebuild // https://github.com/agile-ts/agile/issues/113 instance.collection().rebuildGroupsThatIncludeItemKey(itemKey, config); }, @@ -179,17 +173,21 @@ export class Item extends State< } } -/** - * @param isPlaceholder - If Item is initially a Placeholder - */ export interface ItemConfigInterface { + /** + * Whether the Item should be a placeholder + * and therefore only exists in background. + * @default false + */ isPlaceholder?: boolean; } -/** - * @param useCollectionPattern - If Item storageKey follows the Collection Item StorageKey Pattern - */ export interface ItemPersistConfigInterface extends StatePersistentConfigInterface { + /** + * Whether to format the specified Storage key into the Collection Item Storage key pattern. + * `_${collectionKey}_item_${itemKey}` + * @default true + */ followCollectionPersistKeyPattern?: boolean; } diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index 42731bef..b5648374 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -193,7 +193,7 @@ export interface ComputedConfigInterface extends StateConfigInterface { export interface ComputeConfigInterface { /** - * Whether dependencies used in the compute function should be detected automatically. + * Whether to automatically detect used dependencies in the compute method. * @default true */ autodetect?: boolean; @@ -202,8 +202,8 @@ export interface ComputeConfigInterface { export interface UpdateComputeFunctionConfigInterface extends RecomputeConfigInterface { /** - * Whether the old hard-coded dependencies should be completely overwritten - * with the new ones or merged into the new ones. + * Whether to overwrite the old hard-coded dependencies with the new ones + * or merge them into the new ones. * @default false */ overwriteDeps?: boolean; diff --git a/packages/core/tests/unit/collection/group.test.ts b/packages/core/tests/unit/collection/group.test.ts index 01ef3502..ee05fce5 100644 --- a/packages/core/tests/unit/collection/group.test.ts +++ b/packages/core/tests/unit/collection/group.test.ts @@ -418,60 +418,48 @@ describe('Group Tests', () => { jest.spyOn(State.prototype, 'persist'); }); - it('should persist Group with GroupKey (default config)', () => { + it('should persist Group with formatted groupKey (default config)', () => { group.persist(); - expect(State.prototype.persist).toHaveBeenCalledWith(group._key, { - loadValue: true, - storageKeys: [], - defaultStorageKey: null, - }); + expect(State.prototype.persist).toHaveBeenCalledWith( + CollectionPersistent.getGroupStorageKey( + group._key, + dummyCollection._key + ), + { + loadValue: true, + storageKeys: [], + defaultStorageKey: null, + } + ); }); - it('should persist Group with GroupKey (specific config)', () => { + it('should persist Group with formatted groupKey (specific config)', () => { group.persist({ loadValue: false, storageKeys: ['test1', 'test2'], defaultStorageKey: 'test1', }); - expect(State.prototype.persist).toHaveBeenCalledWith(group._key, { - loadValue: false, - storageKeys: ['test1', 'test2'], - defaultStorageKey: 'test1', - }); + expect(State.prototype.persist).toHaveBeenCalledWith( + CollectionPersistent.getGroupStorageKey( + group._key, + dummyCollection._key + ), + { + loadValue: false, + storageKeys: ['test1', 'test2'], + defaultStorageKey: 'test1', + } + ); }); - it('should persist Group with passed Key (default config)', () => { + it('should persist Group with formatted specified key (default config)', () => { group.persist('dummyKey'); - expect(State.prototype.persist).toHaveBeenCalledWith('dummyKey', { - loadValue: true, - storageKeys: [], - defaultStorageKey: null, - }); - }); - - it('should persist Group with passed Key (specific config)', () => { - group.persist('dummyKey', { - loadValue: false, - storageKeys: ['test1', 'test2'], - defaultStorageKey: 'test1', - }); - - expect(State.prototype.persist).toHaveBeenCalledWith('dummyKey', { - loadValue: false, - storageKeys: ['test1', 'test2'], - defaultStorageKey: 'test1', - }); - }); - - it('should persist Group with formatted GroupKey (config.followCollectionPersistKeyPattern)', () => { - group.persist({ followCollectionPersistKeyPattern: true }); - expect(State.prototype.persist).toHaveBeenCalledWith( CollectionPersistent.getGroupStorageKey( - group._key, + 'dummyKey', dummyCollection._key ), { @@ -482,8 +470,12 @@ describe('Group Tests', () => { ); }); - it('should persist Group with formatted passed Key (config.followCollectionPersistKeyPattern)', () => { - group.persist('dummyKey', { followCollectionPersistKeyPattern: true }); + it('should persist Group with formatted specified key (specific config)', () => { + group.persist('dummyKey', { + loadValue: false, + storageKeys: ['test1', 'test2'], + defaultStorageKey: 'test1', + }); expect(State.prototype.persist).toHaveBeenCalledWith( CollectionPersistent.getGroupStorageKey( @@ -491,12 +483,32 @@ describe('Group Tests', () => { dummyCollection._key ), { - loadValue: true, - storageKeys: [], - defaultStorageKey: null, + loadValue: false, + storageKeys: ['test1', 'test2'], + defaultStorageKey: 'test1', } ); }); + + it('should persist Group with groupKey (config.followCollectionPersistKeyPattern = false)', () => { + group.persist({ followCollectionPersistKeyPattern: false }); + + expect(State.prototype.persist).toHaveBeenCalledWith(group._key, { + loadValue: true, + storageKeys: [], + defaultStorageKey: null, + }); + }); + + it('should persist Group with specified key (config.followCollectionPersistKeyPattern = false)', () => { + group.persist('dummyKey', { followCollectionPersistKeyPattern: false }); + + expect(State.prototype.persist).toHaveBeenCalledWith('dummyKey', { + loadValue: true, + storageKeys: [], + defaultStorageKey: null, + }); + }); }); describe('rebuild function tests', () => { diff --git a/packages/core/tests/unit/collection/item.test.ts b/packages/core/tests/unit/collection/item.test.ts index bbd902bf..80f44955 100644 --- a/packages/core/tests/unit/collection/item.test.ts +++ b/packages/core/tests/unit/collection/item.test.ts @@ -1,4 +1,11 @@ -import { Item, Collection, Agile, StateObserver, State } from '../../../src'; +import { + Item, + Collection, + Agile, + StateObserver, + State, + CollectionPersistent, +} from '../../../src'; import { LogMock } from '../../helper/logMock'; describe('Item Tests', () => { @@ -204,6 +211,104 @@ describe('Item Tests', () => { }); }); + describe('persist function tests', () => { + beforeEach(() => { + jest.spyOn(State.prototype, 'persist'); + }); + + it('should persist Item with formatted itemKey (default config)', () => { + item.persist(); + + expect(State.prototype.persist).toHaveBeenCalledWith( + CollectionPersistent.getItemStorageKey( + item._key, + dummyCollection._key + ), + { + loadValue: true, + storageKeys: [], + defaultStorageKey: null, + } + ); + }); + + it('should persist Item with formatted itemLeu (specific config)', () => { + item.persist({ + loadValue: false, + storageKeys: ['test1', 'test2'], + defaultStorageKey: 'test1', + }); + + expect(State.prototype.persist).toHaveBeenCalledWith( + CollectionPersistent.getItemStorageKey( + item._key, + dummyCollection._key + ), + { + loadValue: false, + storageKeys: ['test1', 'test2'], + defaultStorageKey: 'test1', + } + ); + }); + + it('should persist Item with formatted specified key (default config)', () => { + item.persist('dummyKey'); + + expect(State.prototype.persist).toHaveBeenCalledWith( + CollectionPersistent.getItemStorageKey( + 'dummyKey', + dummyCollection._key + ), + { + loadValue: true, + storageKeys: [], + defaultStorageKey: null, + } + ); + }); + + it('should persist Item with formatted specified key (specific config)', () => { + item.persist('dummyKey', { + loadValue: false, + storageKeys: ['test1', 'test2'], + defaultStorageKey: 'test1', + }); + + expect(State.prototype.persist).toHaveBeenCalledWith( + CollectionPersistent.getItemStorageKey( + 'dummyKey', + dummyCollection._key + ), + { + loadValue: false, + storageKeys: ['test1', 'test2'], + defaultStorageKey: 'test1', + } + ); + }); + + it('should persist Item with itemKey (config.followCollectionPersistKeyPattern = false)', () => { + item.persist({ followCollectionPersistKeyPattern: false }); + + expect(State.prototype.persist).toHaveBeenCalledWith(item._key, { + loadValue: true, + storageKeys: [], + defaultStorageKey: null, + }); + }); + + it('should persist Item with specified key (config.followCollectionPersistKeyPattern = false)', () => { + item.persist('dummyKey', { followCollectionPersistKeyPattern: false }); + + expect(State.prototype.persist).toHaveBeenCalledWith('dummyKey', { + loadValue: true, + storageKeys: [], + defaultStorageKey: null, + }); + }); + }); + describe('addRebuildGroupThatIncludeItemKeySideEffect function tests', () => { beforeEach(() => { dummyCollection.rebuildGroupsThatIncludeItemKey = jest.fn(); From af4e259637ba9e1f3f947acb26910a24bdb5e72b Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 4 Jun 2021 10:33:55 +0200 Subject: [PATCH 039/117] added descriptions to Group --- packages/core/src/collection/group.ts | 188 ++++++++++-------- packages/core/src/collection/index.ts | 70 +++---- packages/core/src/collection/item.ts | 6 +- packages/core/src/logCodeManager.ts | 6 + .../core/tests/unit/collection/group.test.ts | 19 +- .../core/tests/unit/collection/item.test.ts | 2 +- 6 files changed, 157 insertions(+), 134 deletions(-) diff --git a/packages/core/src/collection/group.ts b/packages/core/src/collection/group.ts index d4654f44..bdef020e 100644 --- a/packages/core/src/collection/group.ts +++ b/packages/core/src/collection/group.ts @@ -22,18 +22,25 @@ export class Group extends State< Array > { static rebuildGroupSideEffectKey = 'rebuildGroup'; - collection: () => Collection; // Collection the Group belongs to + collection: () => Collection; _output: Array = []; // Output of Group _items: Array<() => Item> = []; // Items of Group - notFoundItemKeys: Array = []; // Contains all keys of Group that can't be found in Collection + notFoundItemKeys: Array = []; // Contains all itemKeys that couldn't be found in the Collection /** + * An extension of the State Class that categorizes and preserves the ordering of structured data. + * It allows us to cluster together data from a Collection as an array of Item keys. + * + * Note that a Group doesn't store the actual Items. It only keeps track of the Item keys + * and retrieves the fitting Items when needed. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/group/) + * * @public - * Group - Holds Items of Collection - * @param collection - Collection to that the Group belongs - * @param initialItems - Initial Key of Items in this Group - * @param config - Config + * @param collection - Collection to which the Item belongs. + * @param initialItems - Identifiers of the Items to be clustered by the Group. + * @param config - Configuration object */ constructor( collection: Collection, @@ -43,78 +50,76 @@ export class Group extends State< super(collection.agileInstance(), initialItems || [], config); this.collection = () => collection; - // Add rebuild to sideEffects to rebuild Group on Value Change + // Add the 'Rebuild Group' action to the Group side effects + // in order to rebuild the Group whenever its value changes this.addSideEffect(Group.rebuildGroupSideEffectKey, () => this.rebuild()); - // Initial Rebuild + // Initial rebuild this.rebuild(); } /** + * Retrieves values of the Items clustered by the Group. + * * @public - * Get Item Values of Group */ public get output(): Array { ComputedTracker.tracked(this.observer); return copy(this._output); } - /** - * @public - * Set Item Values of Group - */ public set output(value: DataType[]) { - this._output = copy(value); + LogCodeManager.log('1C:03:00', [this._key]); } /** + * Retrieves Items clustered by the Group. + * * @public - * Get Items of Group */ public get items(): Array> { ComputedTracker.tracked(this.observer); return this._items.map((item) => item()); } - /** - * @public - * Set Items of Group - */ public set items(value: Array>) { - this._items = value.map((item) => () => item); + LogCodeManager.log('1C:03:01', [this._key]); } - //========================================================================================================= - // Has - //========================================================================================================= /** + * + * Returns a boolean indicating whether an Item with the specified `itemKey` + * is clustered in the Group or not. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/group/methods/#has) + * * @public - * Checks if Group contains ItemKey - * @param itemKey - ItemKey that gets checked + * @param itemKey - Key/Name identifier of the Item. */ public has(itemKey: ItemKey) { return this.value.findIndex((key) => key === itemKey) !== -1; } - //========================================================================================================= - // Size - //========================================================================================================= /** + * Returns the count of the Items clustered in the Group. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/group/properties#size) + * * @public - * Get size of Group (-> How many Items it contains) */ public get size(): number { return this.value.length; } - //========================================================================================================= - // Remove - //========================================================================================================= /** + * Removes an Item with the specified key/name identifier from the Group, + * if it exists in the Group. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/group/methods#remove) + * * @public - * Removes ItemKey/s from Group - * @param itemKeys - ItemKey/s that get removed from Group - * @param config - Config + * @param itemKeys - Key/Name identifier/s of the Item/s to be removed. + * @param config - Configuration object */ public remove( itemKeys: ItemKey | ItemKey[], @@ -125,7 +130,7 @@ export class Group extends State< const notExistingItemKeys: Array = []; let newGroupValue = copy(this.nextStateValue); - // Remove ItemKeys from Group + // Remove itemKeys from Group _itemKeys.forEach((itemKey) => { // Check if itemKey exists in Group if (!newGroupValue.includes(itemKey)) { @@ -134,18 +139,19 @@ export class Group extends State< return; } - // Check if ItemKey exists in Collection + // Check if itemKey exists in Collection if (!this.collection().getItem(itemKey)) notExistingItemKeysInCollection.push(itemKey); - // Remove ItemKey from Group + // Remove itemKey from Group newGroupValue = newGroupValue.filter((key) => key !== itemKey); }); - // Return if passed ItemKeys doesn't exist + // Return if none of the specified itemKeys exists if (notExistingItemKeys.length >= _itemKeys.length) return this; - // If all removed ItemKeys doesn't exist in Collection -> no rerender necessary since output doesn't change + // If all removed itemKeys don't exist in the Collection + // -> no rerender necessary since the output won't change if (notExistingItemKeysInCollection.length >= _itemKeys.length) config.background = true; @@ -154,14 +160,14 @@ export class Group extends State< return this; } - //========================================================================================================= - // Add - //========================================================================================================= /** + * Appends new Item/s to the end of the Group. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/group/methods#add) + * * @public - * Adds ItemKey/s to Group - * @param itemKeys - ItemKey/s that get added to the Group - * @param config - Config + * @param itemKeys - Key/Name identifier/s of Item/s to be added. + * @param config - Configuration object */ public add( itemKeys: ItemKey | ItemKey[], @@ -176,16 +182,14 @@ export class Group extends State< overwrite: false, }); - // Add ItemKeys to Group + // Add itemKeys to Group _itemKeys.forEach((itemKey) => { - const existsInGroup = newGroupValue.includes(itemKey); - - // Check if ItemKey exists in Collection + // Check if itemKey exists in Collection if (!this.collection().getItem(itemKey)) notExistingItemKeysInCollection.push(itemKey); - // Remove ItemKey from Group if it should get overwritten and already exists - if (existsInGroup) { + // Remove itemKey from Group if it should be overwritten and already exists + if (this.has(itemKey)) { if (config.overwrite) { newGroupValue = newGroupValue.filter((key) => key !== itemKey); } else { @@ -194,14 +198,15 @@ export class Group extends State< } } - // Add new ItemKey to Group + // Add new itemKey to Group newGroupValue[config.method || 'push'](itemKey); }); - // Return if passed ItemKeys already exist + // Return if all specified itemKeys already exist if (existingItemKeys.length >= _itemKeys.length) return this; - // If all added ItemKeys doesn't exist in Collection or already exist -> no rerender necessary since output doesn't change + // If all added itemKeys don't exist in the Collection + // -> no rerender necessary since the output won't change if ( notExistingItemKeysInCollection.concat(existingItemKeys).length >= _itemKeys.length @@ -213,20 +218,20 @@ export class Group extends State< return this; } - //========================================================================================================= - // Replace - //========================================================================================================= /** + * Replaces the old `itemKey` with a new specified `itemKey`. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/group/methods#replace) + * * @public - * Replaces oldItemKey with newItemKey - * @param oldItemKey - Old ItemKey - * @param newItemKey - New ItemKey - * @param config - Config + * @param oldItemKey - Old `itemKey` to be replaced. + * @param newItemKey - New `itemKey` which replaces the specified old `itemKey`. + * @param config - Configuration object */ public replace( oldItemKey: ItemKey, newItemKey: ItemKey, - config: StateRuntimeJobConfigInterface = {} + config: StateIngestConfigInterface = {} ): this { const newGroupValue = copy(this._value); newGroupValue.splice(newGroupValue.indexOf(oldItemKey), 1, newItemKey); @@ -302,30 +307,33 @@ export class Group extends State< return this; } - //========================================================================================================= - // Rebuild - //========================================================================================================= /** + * Rebuilds the entire `output` and `items` property of the Group. + * + * In doing so, it traverses the Group `value` (Item identifiers) + * and fetches the Items that belong to an Item identifier. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/group/methods#rebuild) + * * @internal - * Rebuilds Output and Items of Group */ public rebuild(): this { - const notFoundItemKeys: Array = []; // Item Keys that couldn't be found in Collection + const notFoundItemKeys: Array = []; // Item keys that couldn't be found in the Collection const groupItems: Array> = []; - // Don't rebuild Group if Collection is not properly instantiated + // Don't rebuild Group if Collection isn't correctly instantiated // (because only after a successful instantiation the Collection - // contains Items which are essential for a proper rebuild) + // contains the Items which are essential for a proper rebuild) if (!this.collection().isInstantiated) return this; - // Create groupItems by finding Item at ItemKey in Collection + // Fetch Items from Collection this._value.forEach((itemKey) => { const item = this.collection().getItem(itemKey); if (item != null) groupItems.push(item); else notFoundItemKeys.push(itemKey); }); - // Create groupOutput out of groupItems + // Get Item values of fetched Items const groupOutput = groupItems.map((item) => { return item.getPublicValue(); }); @@ -339,7 +347,7 @@ export class Group extends State< ); } - this.items = groupItems; + this._items = groupItems.map((item) => () => item); this._output = groupOutput; this.notFoundItemKeys = notFoundItemKeys; @@ -349,36 +357,44 @@ export class Group extends State< export type GroupKey = string | number; -/** - * @param method - Way of adding ItemKey to Group (push, unshift) - * @param overwrite - If adding ItemKey overwrites old ItemKey (-> otherwise it gets added to the end of the Group) - * @param background - If adding ItemKey happens in the background (-> not causing any rerender) - */ export interface GroupAddConfigInterface extends StateIngestConfigInterface { + /** + * In which way the `itemKey` should be added to the Group. + * - 'push' = at the end + * - 'unshift' = at the beginning + * @default 'push' + */ method?: 'unshift' | 'push'; + /** + * If the to add `itemKey` already exists, + * whether to overwrite its position with the position of the new `itemKey`. + * @default false + */ overwrite?: boolean; } -/** - * @param background - If removing ItemKey happens in the background (-> not causing any rerender) - */ -export interface GroupRemoveConfigInterface { - background?: boolean; -} - /** * @param key - Key/Name of Group * @param isPlaceholder - If Group is initially a Placeholder */ export interface GroupConfigInterface { + /** + * Key/Name identifier of Group. + * @default undefined + */ key?: GroupKey; + /** + * Whether the Group should be a placeholder + * and therefore should only exists in the background. + * @default false + */ isPlaceholder?: boolean; } export interface GroupPersistConfigInterface extends StatePersistentConfigInterface { /** - * Whether to format the specified Storage key into the Collection Group Storage key pattern. + * Whether to format the specified Storage key following the Collection Group Storage key pattern. * `_${collectionKey}_group_${groupKey}` * @default true */ diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index cf7c7366..92151144 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -29,7 +29,7 @@ export class Collection { public config: CollectionConfigInterface; private initialConfig: CreateCollectionConfigInterface; - public size = 0; // Amount of Items stored in the Collection + public size = 0; // Amount of the Items stored in the Collection public data: { [key: string]: Item } = {}; // Collection Data public _key?: CollectionKey; public isPersisted = false; // Whether Collection is persisted in any external Storage @@ -143,7 +143,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#group) * * @public - * @param initialItems - Initial keys of Items to be represented by the Group. + * @param initialItems - Key/Name identifiers of the Items to be clustered by the Group. * @param config - Configuration object */ public Group( @@ -171,7 +171,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#selector) * * @public - * @param initialKey - Initial key of Item to be represented by the Selector. + * @param initialKey - Key/Name identifier of the Item to be represented by the Selector. * @param config - Configuration object */ public Selector( @@ -341,7 +341,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#update) * * @public - * @param itemKey - Key/Name identifier of Item to be updated. + * @param itemKey - Key/Name identifier of the Item to be updated. * @param changes - Object with changes to be merged into the Item data object. * @param config - Configuration object */ @@ -416,7 +416,7 @@ export class Collection { * * @public * @param groupKey - Unique identifier of the Group to be created. - * @param initialItems - Initial keys of Items to be represented by the Group. + * @param initialItems - Key/Name identifiers of the Items to be clustered by the Group. */ public createGroup( groupKey: GroupKey, @@ -449,7 +449,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasgroup) * * @public - * @param groupKey - Key/Name identifier of Group. + * @param groupKey - Key/Name identifier of the Group. * @param config - Configuration object */ public hasGroup( @@ -467,7 +467,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroup) * * @public - * @param groupKey - Key/Name identifier of Group. + * @param groupKey - Key/Name identifier of the Group. * @param config - Configuration object */ public getGroup( @@ -515,7 +515,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getgroupwithreference) * * @public - * @param groupKey - Key/Name identifier of Group. + * @param groupKey - Key/Name identifier of the Group. */ public getGroupWithReference(groupKey: GroupKey): Group { let group = this.getGroup(groupKey, { notExisting: true }); @@ -534,13 +534,13 @@ export class Collection { } /** - * Removes a Group with the specified identifier from the Collection, + * Removes a Group with the specified key/name identifier from the Collection, * if it exists in the Collection. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removegroup) * * @public - * @param groupKey - Key/Name identifier of Group to be removed. + * @param groupKey - Key/Name identifier of the Group to be removed. */ public removeGroup(groupKey: GroupKey): this { if (this.groups[groupKey] != null) delete this.groups[groupKey]; @@ -566,8 +566,8 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#createSelector) * * @public - * @param selectorKey - Unique identifier of Selector to be created. - * @param itemKey - Initial key of Item to be represented by the Selector. + * @param selectorKey - Unique identifier of the Selector to be created. + * @param itemKey - Key/Name identifier of the Item to be represented by the Selector. */ public createSelector( selectorKey: SelectorKey, @@ -608,7 +608,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#select) * * @public - * @param itemKey - Initial key of Item to be represented by the Selector + * @param itemKey - Key/Name identifier of the Item to be represented by the Selector * and used as unique identifier of the Selector. */ public select(itemKey: ItemKey): Selector { @@ -622,7 +622,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasselector) * * @public - * @param selectorKey - Key/Name identifier of Selector. + * @param selectorKey - Key/Name identifier of the Selector. * @param config - Configuration object */ public hasSelector( @@ -640,7 +640,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselector) * * @public - * @param selectorKey - Key/Name identifier of Selector. + * @param selectorKey - Key/Name identifier of the Selector. * @param config - Configuration object */ public getSelector( @@ -672,7 +672,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getselectorwithreference) * * @public - * @param selectorKey - Key/Name identifier of Selector. + * @param selectorKey - Key/Name identifier of the Selector. */ public getSelectorWithReference( selectorKey: SelectorKey @@ -697,13 +697,13 @@ export class Collection { } /** - * Removes a Selector with the specified identifier from the Collection, + * Removes a Selector with the specified key/name identifier from the Collection, * if it exists in the Collection. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removeselector) * * @public - * @param selectorKey - Key/Name identifier of Selector to be removed. + * @param selectorKey - Key/Name identifier of the Selector to be removed. */ public removeSelector(selectorKey: SelectorKey): this { if (this.selectors[selectorKey] != null) { @@ -733,7 +733,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasitem) * * @public - * @param itemKey - Key/Name identifier of Item. + * @param itemKey - Key/Name identifier of the Item. * @param config - Configuration object */ public hasItem( @@ -751,7 +751,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitem) * * @public - * @param itemKey - Key/Name identifier of Item. + * @param itemKey - Key/Name identifier of the Item. * @param config - Configuration object */ public getItem( @@ -782,7 +782,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitemwithreference) * * @public - * @param itemKey - Key/Name identifier of Item. + * @param itemKey - Key/Name identifier of the Item. */ public getItemWithReference(itemKey: ItemKey): Item { let item = this.getItem(itemKey, { notExisting: true }); @@ -810,7 +810,7 @@ export class Collection { const item = new Item( this, { - [this.config.primaryKey]: itemKey, // Setting primaryKey of Item to passed itemKey + [this.config.primaryKey]: itemKey, // Setting primaryKey of the Item to passed itemKey dummy: 'item', } as any, { isPlaceholder: true } @@ -836,7 +836,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#getitemvalue) * * @public - * @param itemKey - Key/Name identifier of Item. + * @param itemKey - Key/Name identifier of the Item. * @param config - Configuration object */ public getItemValue( @@ -1024,7 +1024,7 @@ export class Collection { * * @public * @param itemKeys - `itemKey/s` to be put into the specified Group/s. - * @param groupKeys - Identifier/s of the Group/s the specified `itemKey/s` are to put in. + * @param groupKeys - Key/Name Identifier/s of the Group/s the specified `itemKey/s` are to put in. * @param config - Configuration object */ public put( @@ -1050,8 +1050,8 @@ export class Collection { * * @public * @param itemKeys - `itemKey/s` to be moved. - * @param oldGroupKey - Identifier of the Group the `itemKey/s` are moved from. - * @param newGroupKey - Identifier of the Group the `itemKey/s` are moved in. + * @param oldGroupKey - Key/Name Identifier of the Group the `itemKey/s` are moved from. + * @param newGroupKey - Key/Name Identifier of the Group the `itemKey/s` are moved in. * @param config - Configuration object */ public move( @@ -1075,7 +1075,7 @@ export class Collection { } /** - * Updates key/name identifier of Item + * Updates key/name identifier of the Item * and returns a boolean indicating * whether the Item identifier was updated successfully. * @@ -1106,12 +1106,12 @@ export class Collection { delete this.data[oldItemKey]; this.data[newItemKey] = item; - // Update key/name of Item + // Update key/name of the Item item.setKey(newItemKey, { background: config.background, }); - // Update Persistent key of Item if it follows the Item Storage Key pattern + // Update Persistent key of the Item if it follows the Item Storage Key pattern // and therefore differs from the actual Item key // (-> isn't automatically updated when the Item key is updated) if ( @@ -1211,13 +1211,13 @@ export class Collection { } /** - * Remove Item/s from Group/s. + * Remove Item/s from specified Group/s. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removefromgroups) * * @public - * @param itemKeys - Identifier/s of Item/s to be removed from Group/s. - * @param groupKeys - Identifier/s of Group/s the Item/s are to remove from. + * @param itemKeys - Key/Name Identifier/s of the Item/s to be removed from the Group/s. + * @param groupKeys - Key/Name Identifier/s of the Group/s the Item/s are to remove from. */ public removeFromGroups( itemKeys: ItemKey | Array, @@ -1229,7 +1229,7 @@ export class Collection { _itemKeys.forEach((itemKey) => { let removedFromGroupsCount = 0; - // Remove itemKey from Groups + // Remove itemKey from the Groups _groupKeys.forEach((groupKey) => { const group = this.getGroup(groupKey, { notExisting: true }); if (!group?.has(itemKey)) return; @@ -1255,7 +1255,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#removeitems) * * @public - * @param itemKeys - Identifier/s of Item/s to be removed from the entire Collection. + * @param itemKeys - Key/Name identifier/s of the Item/s to be removed from the entire Collection. * @param config - Configuration object */ public removeItems( @@ -1273,7 +1273,7 @@ export class Collection { if (item == null) return; const wasPlaceholder = item.isPlaceholder; - // Remove Item from Groups + // Remove Item from the Groups for (const groupKey in this.groups) { const group = this.getGroup(groupKey, { notExisting: true }); if (group?.has(itemKey)) group?.remove(itemKey); diff --git a/packages/core/src/collection/item.ts b/packages/core/src/collection/item.ts index 1abdf3c7..cc4c5ad6 100644 --- a/packages/core/src/collection/item.ts +++ b/packages/core/src/collection/item.ts @@ -157,7 +157,7 @@ export class Item extends State< * are rebuilt when the Item changes. * * @internal - * @param itemKey - Item identifier that has to be included in Groups so that these Groups can be rebuilt. + * @param itemKey - Item identifier that has to be included in Groups so that these Groups are rebuilt. */ public addRebuildGroupThatIncludeItemKeySideEffect(itemKey: StateKey) { this.addSideEffect>( @@ -176,7 +176,7 @@ export class Item extends State< export interface ItemConfigInterface { /** * Whether the Item should be a placeholder - * and therefore only exists in background. + * and therefore should only exists in the background. * @default false */ isPlaceholder?: boolean; @@ -185,7 +185,7 @@ export interface ItemConfigInterface { export interface ItemPersistConfigInterface extends StatePersistentConfigInterface { /** - * Whether to format the specified Storage key into the Collection Item Storage key pattern. + * Whether to format the specified Storage key following the Collection Item Storage key pattern. * `_${collectionKey}_item_${itemKey}` * @default true */ diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index 8ac05a98..1e99b165 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -144,6 +144,12 @@ const logCodeMessages = { '1C:02:00': "Couldn't find some Items in the Collection '${0}' " + "during the rebuild of the Group '${1}' output.", + '1C:03:00': + "The 'output' property of the Group '${0}' is a automatically generated readonly property " + + 'that can only be mutated by the Group itself!', + '1C:03:01': + "The 'item' property of the Group '${0}' is a automatically generated readonly property " + + 'that can only be mutated by the Group itself!', // Utils '20:03:00': 'Failed to get Agile Instance from', diff --git a/packages/core/tests/unit/collection/group.test.ts b/packages/core/tests/unit/collection/group.test.ts index ee05fce5..72ea153b 100644 --- a/packages/core/tests/unit/collection/group.test.ts +++ b/packages/core/tests/unit/collection/group.test.ts @@ -182,16 +182,16 @@ describe('Group Tests', () => { }); describe('output set function tests', () => { - it('should set output to passed value', () => { + it("shouldn't set output to passed value and print error", () => { + group._output = null as any; + group.output = [ { id: '12', name: 'Hans der 3' }, { id: '99', name: 'Frank' }, ]; - expect(group._output).toStrictEqual([ - { id: '12', name: 'Hans der 3' }, - { id: '99', name: 'Frank' }, - ]); + expect(group._output).toStrictEqual(null); + expect(LogMock.hasLoggedCode('1C:03:00', [group._key])); }); }); @@ -211,12 +211,13 @@ describe('Group Tests', () => { }); describe('item set function tests', () => { - it('should set items to passed value', () => { + it("shouldn't set items to passed value and print error", () => { + group._items = null as any; + group.items = [dummyItem1, dummyItem2]; - expect(group._items.length).toBe(2); - expect(group._items[0]()).toBe(dummyItem1); - expect(group._items[1]()).toBe(dummyItem2); + expect(group._items).toStrictEqual(null); + expect(LogMock.hasLoggedCode('1C:03:01', [group._key])); }); }); diff --git a/packages/core/tests/unit/collection/item.test.ts b/packages/core/tests/unit/collection/item.test.ts index 80f44955..3e55cff0 100644 --- a/packages/core/tests/unit/collection/item.test.ts +++ b/packages/core/tests/unit/collection/item.test.ts @@ -232,7 +232,7 @@ describe('Item Tests', () => { ); }); - it('should persist Item with formatted itemLeu (specific config)', () => { + it('should persist Item with formatted itemKey (specific config)', () => { item.persist({ loadValue: false, storageKeys: ['test1', 'test2'], From 44966aa1820810bf620fce95c6fd5310b56c497c Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 4 Jun 2021 12:00:37 +0200 Subject: [PATCH 040/117] refactored the selector method descriptions --- packages/core/src/collection/group.ts | 16 +- packages/core/src/collection/index.ts | 4 + packages/core/src/collection/item.ts | 2 +- packages/core/src/collection/selector.ts | 187 +++++++++++------- .../tests/unit/collection/selector.test.ts | 74 ++++--- 5 files changed, 183 insertions(+), 100 deletions(-) diff --git a/packages/core/src/collection/group.ts b/packages/core/src/collection/group.ts index bdef020e..840fb6c8 100644 --- a/packages/core/src/collection/group.ts +++ b/packages/core/src/collection/group.ts @@ -21,9 +21,10 @@ import { export class Group extends State< Array > { - static rebuildGroupSideEffectKey = 'rebuildGroup'; collection: () => Collection; + static rebuildGroupSideEffectKey = 'rebuildGroup'; + _output: Array = []; // Output of Group _items: Array<() => Item> = []; // Items of Group notFoundItemKeys: Array = []; // Contains all itemKeys that couldn't be found in the Collection @@ -38,8 +39,8 @@ export class Group extends State< * [Learn more..](https://agile-ts.org/docs/core/collection/group/) * * @public - * @param collection - Collection to which the Item belongs. - * @param initialItems - Identifiers of the Items to be clustered by the Group. + * @param collection - Collection to which the Group belongs. + * @param initialItems - Key/Name identifiers of the Items to be clustered by the Group. * @param config - Configuration object */ constructor( @@ -61,6 +62,8 @@ export class Group extends State< /** * Retrieves values of the Items clustered by the Group. * + * [Learn more..](https://agile-ts.org/docs/core/collection/group/properties#output) + * * @public */ public get output(): Array { @@ -75,6 +78,8 @@ export class Group extends State< /** * Retrieves Items clustered by the Group. * + * [Learn more..](https://agile-ts.org/docs/core/collection/group/properties#items) + * * @public */ public get items(): Array> { @@ -87,7 +92,6 @@ export class Group extends State< } /** - * * Returns a boolean indicating whether an Item with the specified `itemKey` * is clustered in the Group or not. * @@ -321,7 +325,7 @@ export class Group extends State< const notFoundItemKeys: Array = []; // Item keys that couldn't be found in the Collection const groupItems: Array> = []; - // Don't rebuild Group if Collection isn't correctly instantiated + // Don't rebuild Group if Collection isn't correctly instantiated yet // (because only after a successful instantiation the Collection // contains the Items which are essential for a proper rebuild) if (!this.collection().isInstantiated) return this; @@ -385,7 +389,7 @@ export interface GroupConfigInterface { key?: GroupKey; /** * Whether the Group should be a placeholder - * and therefore should only exists in the background. + * and therefore should only exist in the background. * @default false */ isPlaceholder?: boolean; diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 92151144..a60d959f 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -92,6 +92,8 @@ export class Collection { /** * Updates key/name identifier of Collection. * + * [Learn more..](https://agile-ts.org/docs/core/collection/properties#key) + * * @public * @param value - New key/name identifier. */ @@ -102,6 +104,8 @@ export class Collection { /** * Returns key/name identifier of Collection. * + * [Learn more..](https://agile-ts.org/docs/core/collection/properties#key) + * * @public */ public get key(): CollectionKey | undefined { diff --git a/packages/core/src/collection/item.ts b/packages/core/src/collection/item.ts index cc4c5ad6..eab17158 100644 --- a/packages/core/src/collection/item.ts +++ b/packages/core/src/collection/item.ts @@ -176,7 +176,7 @@ export class Item extends State< export interface ItemConfigInterface { /** * Whether the Item should be a placeholder - * and therefore should only exists in the background. + * and therefore should only exist in the background. * @default false */ isPlaceholder?: boolean; diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index deaf17b7..a0b6ee1b 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -11,19 +11,28 @@ import { export class Selector extends State< DataType | undefined > { + public collection: () => Collection; + static unknownItemPlaceholderKey = '__UNKNOWN__ITEM__KEY__'; static rebuildSelectorSideEffectKey = 'rebuildSelector'; static rebuildItemSideEffectKey = 'rebuildItem'; - public collection: () => Collection; - public item: Item | undefined; - public _itemKey: ItemKey; // Key of Item the Selector represents + + public _item: Item | undefined; // Item the Selector represents + public _itemKey: ItemKey; // Key/Name identifier of the Item the Selector represents /** + * A Selector represents an Item from a Collection in the long term. + * It can be mutated dynamically and remains in sync with the Collection. + * + * Components that need one piece of data from a Collection such as the "current user" + * would benefit from using Selectors. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/selector) + * * @public - * Represents Item of Collection - * @param collection - Collection that contains the Item - * @param itemKey - ItemKey of Item that the Selector represents - * @param config - Config + * @param collection - Collection to which the Selector belongs. + * @param itemKey - Key/Name identifier of the Item to be represented by the Selector. + * @param config - Configuration object */ constructor( collection: Collection, @@ -35,41 +44,71 @@ export class Selector extends State< }); super(collection.agileInstance(), undefined, config); this.collection = () => collection; - this.item = undefined; + this._item = undefined; this._itemKey = !config.isPlaceholder ? itemKey : Selector.unknownItemPlaceholderKey; this._key = config?.key; this.isPlaceholder = true; // Because hasn't selected any Item yet - // Initial Select + // Initial select of the Item if (!config.isPlaceholder) this.select(itemKey, { overwrite: true }); } /** + * Retrieves the `itemKey` currently selected by the Selector. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/selector/properties#itemkey) + * * @public - * Set ItemKey that the Selector represents + */ + public get itemKey(): ItemKey { + return this._itemKey; + } + + /** + * Updates the currently selected Item of the Selector. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/selector/properties#itemkey) + * + * @public + * @param value - New key/name identifier of the Item to be represented by the Selector. */ public set itemKey(value: ItemKey) { this.select(value); } /** + * Retrieves the Item currently selected by the Selector. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/selector/properties#item) + * * @public - * Get ItemKey that the Selector represents */ - public get itemKey() { - return this._itemKey; + public get item(): Item | undefined { + return this._item; } - //========================================================================================================= - // Select - //========================================================================================================= /** + * Updates the currently selected Item of the Selector. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/selector/properties#item) + * * @public - * Select new ItemKey - * @param itemKey - New ItemKey - * @param config - Config + * @param value - New Item to be represented by the Selector. + */ + public set item(value: Item | undefined) { + if (value?._key) this.select(value._key); + } + + /** + * Updates the currently selected Item of the Selector. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/selector/methods#select) + * + * @public + * @param itemKey - New key/name identifier of the Item to be represented by the Selector. + * @param config - Configuration object */ public select( itemKey: ItemKey, @@ -82,11 +121,11 @@ export class Selector extends State< exclude: [], }, force: false, - overwrite: this.item?.isPlaceholder ?? false, + overwrite: this._item?.isPlaceholder ?? false, storage: true, }); - // Don't select Item if Collection is not properly instantiated + // Don't select Item if Collection is not correctly instantiated yet // (because only after a successful instantiation the Collection // contains the Items which are essential for a proper selection) if ( @@ -98,27 +137,29 @@ export class Selector extends State< // Unselect old Item this.unselect({ background: true }); - // Get new Item + // Retrieve new Item from Collection const newItem = this.collection().getItemWithReference(itemKey); // Select new Item this._itemKey = itemKey; - this.item = newItem; + this._item = newItem; newItem.selectedBy.add(this._key as any); - // Add SideEffect to newItem, that rebuild this Selector depending on the current Item Value + // Add side effect to the newly selected Item + // that rebuilds the Selector depending on the current Item value newItem.addSideEffect( Selector.rebuildSelectorSideEffectKey, (instance, config) => this.rebuildSelector(config), { weight: 100 } ); - // Add sideEffect to Selector, that updates the Item Value if this Value got updated + // Add side effect to Selector + // that updates the Item value depending on the current Selector value this.addSideEffect>( Selector.rebuildItemSideEffectKey, (instance, config) => { - if (!instance.item?.isPlaceholder) - instance.item?.set(instance._value as any, { + if (!instance._item?.isPlaceholder) + instance._item?.set(instance._value as any, { ...config, ...{ sideEffects: { @@ -131,21 +172,24 @@ export class Selector extends State< { weight: 90 } ); - // Rebuild Selector for instantiating new 'selected' ItemKey properly + // Rebuild Selector for 'instantiating' the newly selected Item properly this.rebuildSelector(config); return this; } - //========================================================================================================= - // Reselect - //========================================================================================================= /** + * Reselects the currently selected Item. + * + * This might be helpful if the Selector failed to select the Item correctly + * and therefore should try to select it again. + * + * You can use the 'hasSelected()' method to check whether the Item is selected correctly. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/selector/methods#reselect) + * * @public - * Reselects current Item - * Might help if the Selector failed to select an Item correctly. - * You can check with 'hasSelected()' if an Item got correctly selected. - * @param config - Config + * @param config - Configuration object */ public reselect(config: StateRuntimeJobConfigInterface = {}): this { if ( @@ -156,18 +200,19 @@ export class Selector extends State< return this; } - //========================================================================================================= - // Unselect - //========================================================================================================= /** + * Unselects the currently selected Item. + * + * Therefore, it sets the `itemKey` and `item` property to `undefined`, + * since the Selector no longer represents any Item. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/selector/methods#unselect) + * * @public - * Unselects current selected Item. - * Often not necessary because by selecting a new Item, - * the old Item is automatically unselected. - * @param config - Config + * @param config - Configuration object */ public unselect(config: StateRuntimeJobConfigInterface = {}): this { - // Because this.item might be outdated + // Retrieve Item from Collection because 'this._item' might be outdated const item = this.collection().getItem(this._itemKey, { notExisting: true, }); @@ -180,53 +225,54 @@ export class Selector extends State< if (item.isPlaceholder) delete this.collection().data[this._itemKey]; } - // Reset and rebuild Selector - this.item = undefined; + // Reset Selector + this._item = undefined; this._itemKey = Selector.unknownItemPlaceholderKey; this.rebuildSelector(config); - this.isPlaceholder = true; return this; } - //========================================================================================================= - // Has Selected - //========================================================================================================= /** - * Checks if Selector has correctly selected the Item at the passed itemKey - * @param itemKey - ItemKey - * @param correctlySelected - If it should consider only correctly selected Items + * Returns a boolean indicating whether an Item with the specified `itemKey` + * is selected by the Selector or not. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/selector/methods#hasselected) + * + * @public + * @param itemKey - Key/Name identifier of the Item. + * @param correctlySelected - Whether the Item has to be selected correctly. */ public hasSelected(itemKey: ItemKey, correctlySelected = true): boolean { if (correctlySelected) { return ( this._itemKey === itemKey && - this.item != null && - this.item.selectedBy.has(this._key as any) + this._item != null && + this._item.selectedBy.has(this._key as any) ); } return this._itemKey === itemKey; } - //========================================================================================================= - // Rebuild Selector - //========================================================================================================= /** + * Rebuilds the Selector, + * which updates the Selector value based on the Item value. + * + * [Learn more..](https://agile-ts.org/docs/core/collection/selector/methods#rebuild) + * * @public - * Rebuilds Selector, - * which updates the Selector value based on the Item value - * @param config - Config + * @param config - Configuration object */ public rebuildSelector(config: StateRuntimeJobConfigInterface = {}): this { - // Set Selector Value to undefined if Item doesn't exist - if (this.item == null || this.item.isPlaceholder) { + // Assign 'undefined' to the Selector value if no Item is set + if (this._item == null || this._item.isPlaceholder) { this.set(undefined, config); return this; } - // Set Selector Value to updated Item Value - this.set(this.item._value, config); + // Assign the current Item value to the Selector value + this.set(this._item._value, config); return this; } @@ -234,11 +280,16 @@ export class Selector extends State< export type SelectorKey = string | number; -/** - * @param key - Key/Name of Selector - * @param isPlaceholder - If Selector is initially a Placeholder - */ export interface SelectorConfigInterface { + /** + * Key/Name identifier of Selector. + * @default undefined + */ key?: SelectorKey; + /** + * Whether the Selector should be a placeholder + * and therefore should only exist in the background. + * @default false + */ isPlaceholder?: boolean; } diff --git a/packages/core/tests/unit/collection/selector.test.ts b/packages/core/tests/unit/collection/selector.test.ts index 03f088d0..52185ec5 100644 --- a/packages/core/tests/unit/collection/selector.test.ts +++ b/packages/core/tests/unit/collection/selector.test.ts @@ -29,7 +29,7 @@ describe('Selector Tests', () => { const selector = new Selector(dummyCollection, 'dummyItemKey'); expect(selector.collection()).toBe(dummyCollection); - expect(selector.item).toBeUndefined(); + expect(selector._item).toBeUndefined(); expect(selector._itemKey).toBe('dummyItemKey'); expect(selector.select).toHaveBeenCalledWith('dummyItemKey', { overwrite: true, @@ -65,7 +65,7 @@ describe('Selector Tests', () => { }); expect(selector.collection()).toBe(dummyCollection); - expect(selector.item).toBeUndefined(); + expect(selector._item).toBeUndefined(); expect(selector._itemKey).toBe('dummyItemKey'); expect(selector.select).toHaveBeenCalledWith('dummyItemKey', { overwrite: true, @@ -101,7 +101,7 @@ describe('Selector Tests', () => { }); expect(selector.collection()).toBe(dummyCollection); - expect(selector.item).toBeUndefined(); + expect(selector._item).toBeUndefined(); expect(selector._itemKey).toBe(Selector.unknownItemPlaceholderKey); expect(selector.select).not.toHaveBeenCalled(); @@ -143,21 +143,45 @@ describe('Selector Tests', () => { describe('itemKey set function tests', () => { it('should call select function with passed value', () => { selector.select = jest.fn(); + selector._itemKey = null as any; selector.itemKey = 'newItemKey'; expect(selector.select).toHaveBeenCalledWith('newItemKey'); + expect(selector._itemKey).toBeNull(); }); }); describe('itemKey get function tests', () => { - it('should return current ItemKey of Selector', () => { + it('should return currently selected ItemKey of Selector', () => { selector._itemKey = 'coolItemKey'; expect(selector.itemKey).toBe('coolItemKey'); }); }); + describe('item set function tests', () => { + it('should call select function with passed Item identifier', () => { + selector.select = jest.fn(); + selector._item = null as any; + + dummyItem1._key = 'AReallyCoolKey'; + + selector.item = dummyItem1; + + expect(selector.select).toHaveBeenCalledWith('AReallyCoolKey'); + expect(selector._item).toBeNull(); + }); + }); + + describe('item get function tests', () => { + it('should return currently selected Item of Selector', () => { + selector._item = dummyItem1; + + expect(selector.item).toBe(dummyItem1); + }); + }); + describe('select function tests', () => { let dummyItem2: Item; @@ -185,7 +209,7 @@ describe('Selector Tests', () => { ); expect(selector._itemKey).toBe('dummyItem2'); - expect(selector.item).toBe(dummyItem2); + expect(selector._item).toBe(dummyItem2); expect(selector.unselect).toHaveBeenCalledWith({ background: true }); expect(selector.rebuildSelector).toHaveBeenCalledWith({ background: false, @@ -233,7 +257,7 @@ describe('Selector Tests', () => { ); expect(selector._itemKey).toBe('dummyItem2'); - expect(selector.item).toBe(dummyItem2); + expect(selector._item).toBe(dummyItem2); expect(selector.unselect).toHaveBeenCalledWith({ background: true }); expect(selector.rebuildSelector).toHaveBeenCalledWith({ background: true, @@ -270,7 +294,7 @@ describe('Selector Tests', () => { expect(dummyCollection.getItemWithReference).not.toHaveBeenCalled(); expect(selector._itemKey).toBe('dummyItem1'); - expect(selector.item).toBe(dummyItem1); + expect(selector._item).toBe(dummyItem1); expect(selector.unselect).not.toHaveBeenCalled(); expect(selector.rebuildSelector).not.toHaveBeenCalled(); expect(selector.addSideEffect).not.toHaveBeenCalled(); @@ -292,7 +316,7 @@ describe('Selector Tests', () => { ); expect(selector._itemKey).toBe('dummyItem1'); - expect(selector.item).toBe(dummyItem1); + expect(selector._item).toBe(dummyItem1); expect(selector.unselect).toHaveBeenCalledWith({ background: true }); expect(selector.rebuildSelector).toHaveBeenCalledWith({ background: false, @@ -331,7 +355,7 @@ describe('Selector Tests', () => { expect(dummyCollection.getItemWithReference).not.toHaveBeenCalled(); expect(selector._itemKey).toBe('dummyItem1'); - expect(selector.item).toBe(dummyItem1); + expect(selector._item).toBe(dummyItem1); expect(selector.unselect).not.toHaveBeenCalled(); expect(selector.rebuildSelector).not.toHaveBeenCalled(); expect(selector.addSideEffect).not.toHaveBeenCalled(); @@ -353,7 +377,7 @@ describe('Selector Tests', () => { ); expect(selector._itemKey).toBe('dummyItem2'); - expect(selector.item).toBe(dummyItem2); + expect(selector._item).toBe(dummyItem2); expect(selector.unselect).toHaveBeenCalledWith({ background: true }); expect(selector.rebuildSelector).toHaveBeenCalledWith({ background: false, @@ -394,7 +418,7 @@ describe('Selector Tests', () => { 'dummyItem2' ); expect(selector._itemKey).toBe('dummyItem2'); - expect(selector.item).toBe(dummyItem2); + expect(selector._item).toBe(dummyItem2); expect(selector.unselect).toHaveBeenCalledWith({ background: true }); expect(selector.rebuildSelector).toHaveBeenCalledWith({ background: false, @@ -435,7 +459,7 @@ describe('Selector Tests', () => { 'dummyItem2' ); expect(selector._itemKey).toBe('dummyItem2'); - expect(selector.item).toBe(dummyItem2); + expect(selector._item).toBe(dummyItem2); expect(selector.unselect).toHaveBeenCalledWith({ background: true }); expect(selector.rebuildSelector).toHaveBeenCalledWith({ background: false, @@ -589,7 +613,7 @@ describe('Selector Tests', () => { Selector.rebuildSelectorSideEffectKey ); - expect(selector.item).toBeUndefined(); + expect(selector._item).toBeUndefined(); expect(selector._itemKey).toBe(Selector.unknownItemPlaceholderKey); expect(selector.isPlaceholder).toBeTruthy(); expect(selector.rebuildSelector).toHaveBeenCalledWith({}); @@ -609,7 +633,7 @@ describe('Selector Tests', () => { Selector.rebuildSelectorSideEffectKey ); - expect(selector.item).toBeUndefined(); + expect(selector._item).toBeUndefined(); expect(selector._itemKey).toBe(Selector.unknownItemPlaceholderKey); expect(selector.isPlaceholder).toBeTruthy(); expect(selector.rebuildSelector).toHaveBeenCalledWith({ @@ -631,7 +655,7 @@ describe('Selector Tests', () => { Selector.rebuildSelectorSideEffectKey ); - expect(selector.item).toBeUndefined(); + expect(selector._item).toBeUndefined(); expect(selector._itemKey).toBe(Selector.unknownItemPlaceholderKey); expect(selector.isPlaceholder).toBeTruthy(); expect(selector.rebuildSelector).toHaveBeenCalledWith({}); @@ -646,7 +670,7 @@ describe('Selector Tests', () => { }); it('should return true if Selector has selected itemKey correctly and Item isSelected', () => { - if (selector.item) selector.item.selectedBy.add(selector._key as any); + if (selector._item) selector._item.selectedBy.add(selector._key as any); expect(selector.hasSelected('dummyItemKey')).toBeTruthy(); }); @@ -660,25 +684,25 @@ describe('Selector Tests', () => { }); it("should return false if Selector hasn't selected itemKey correctly (item = undefined)", () => { - selector.item = undefined; + selector._item = undefined; expect(selector.hasSelected('dummyItemKey')).toBeFalsy(); }); it("should return true if Selector hasn't selected itemKey correctly (item = undefined, correctlySelected = false)", () => { - selector.item = undefined; + selector._item = undefined; expect(selector.hasSelected('dummyItemKey', false)).toBeTruthy(); }); it("should return false if Selector has selected itemKey correctly and Item isn't isSelected", () => { - if (selector.item) selector.item.selectedBy = new Set(); + if (selector._item) selector._item.selectedBy = new Set(); expect(selector.hasSelected('dummyItemKey')).toBeFalsy(); }); it("should return true if Selector has selected itemKey correctly and Item isn't isSelected (correctlySelected = false)", () => { - if (selector.item) selector.item.selectedBy = new Set(); + if (selector._item) selector._item.selectedBy = new Set(); expect(selector.hasSelected('dummyItemKey', false)).toBeTruthy(); }); @@ -690,15 +714,15 @@ describe('Selector Tests', () => { }); it('should set selector value to item value (default config)', () => { - selector.item = dummyItem1; + selector._item = dummyItem1; selector.rebuildSelector(); - expect(selector.set).toHaveBeenCalledWith(selector.item._value, {}); + expect(selector.set).toHaveBeenCalledWith(selector._item._value, {}); }); it('should set selector value to item value (specific config)', () => { - selector.item = dummyItem1; + selector._item = dummyItem1; selector.rebuildSelector({ sideEffects: { @@ -708,7 +732,7 @@ describe('Selector Tests', () => { force: true, }); - expect(selector.set).toHaveBeenCalledWith(selector.item._value, { + expect(selector.set).toHaveBeenCalledWith(selector._item._value, { sideEffects: { enabled: false, }, @@ -718,7 +742,7 @@ describe('Selector Tests', () => { }); it('should set selector value to undefined if Item is undefined (default config)', () => { - selector.item = undefined; + selector._item = undefined; selector.rebuildSelector(); From 8c14b1212b8ba9567f467e2ce3ab1fd03c292882 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 4 Jun 2021 14:39:59 +0200 Subject: [PATCH 041/117] fixed typos --- packages/core/src/collection/group.ts | 33 +++++++++---------- packages/core/src/collection/index.ts | 7 ++-- packages/core/src/collection/item.ts | 14 ++++---- packages/core/src/collection/selector.ts | 21 +++++++----- .../tests/unit/collection/selector.test.ts | 8 ++--- 5 files changed, 42 insertions(+), 41 deletions(-) diff --git a/packages/core/src/collection/group.ts b/packages/core/src/collection/group.ts index 840fb6c8..adfe93f6 100644 --- a/packages/core/src/collection/group.ts +++ b/packages/core/src/collection/group.ts @@ -12,7 +12,6 @@ import { isValidObject, PersistentKey, ComputedTracker, - StateRuntimeJobConfigInterface, StateIngestConfigInterface, removeProperties, LogCodeManager, @@ -25,9 +24,9 @@ export class Group extends State< static rebuildGroupSideEffectKey = 'rebuildGroup'; - _output: Array = []; // Output of Group - _items: Array<() => Item> = []; // Items of Group - notFoundItemKeys: Array = []; // Contains all itemKeys that couldn't be found in the Collection + _output: Array = []; // Item values represented by the Group + _items: Array<() => Item> = []; // Items represented by the Group + notFoundItemKeys: Array = []; // Contains all Item identifiers for Items that couldn't be found in the Collection /** * An extension of the State Class that categorizes and preserves the ordering of structured data. @@ -51,8 +50,8 @@ export class Group extends State< super(collection.agileInstance(), initialItems || [], config); this.collection = () => collection; - // Add the 'Rebuild Group' action to the Group side effects - // in order to rebuild the Group whenever its value changes + // Add side effect to Group + // that rebuilds the Group whenever the Group value changes this.addSideEffect(Group.rebuildGroupSideEffectKey, () => this.rebuild()); // Initial rebuild @@ -60,7 +59,7 @@ export class Group extends State< } /** - * Retrieves values of the Items clustered by the Group. + * Retrieves the values of the Items clustered by the Group. * * [Learn more..](https://agile-ts.org/docs/core/collection/group/properties#output) * @@ -76,7 +75,7 @@ export class Group extends State< } /** - * Retrieves Items clustered by the Group. + * Retrieves the Items clustered by the Group. * * [Learn more..](https://agile-ts.org/docs/core/collection/group/properties#items) * @@ -105,7 +104,7 @@ export class Group extends State< } /** - * Returns the count of the Items clustered in the Group. + * Returns the count of Items clustered by the Group. * * [Learn more..](https://agile-ts.org/docs/core/collection/group/properties#size) * @@ -192,7 +191,8 @@ export class Group extends State< if (!this.collection().getItem(itemKey)) notExistingItemKeysInCollection.push(itemKey); - // Remove itemKey from Group if it should be overwritten and already exists + // Remove itemKey from Group + // if it should be overwritten and already exists if (this.has(itemKey)) { if (config.overwrite) { newGroupValue = newGroupValue.filter((key) => key !== itemKey); @@ -229,7 +229,7 @@ export class Group extends State< * * @public * @param oldItemKey - Old `itemKey` to be replaced. - * @param newItemKey - New `itemKey` which replaces the specified old `itemKey`. + * @param newItemKey - New `itemKey` to replace the before specified old `itemKey`. * @param config - Configuration object */ public replace( @@ -315,7 +315,7 @@ export class Group extends State< * Rebuilds the entire `output` and `items` property of the Group. * * In doing so, it traverses the Group `value` (Item identifiers) - * and fetches the Items that belong to an Item identifier. + * and fetches the fitting Items accordingly. * * [Learn more..](https://agile-ts.org/docs/core/collection/group/methods#rebuild) * @@ -337,7 +337,7 @@ export class Group extends State< else notFoundItemKeys.push(itemKey); }); - // Get Item values of fetched Items + // Extract Item values from the retrieved Items const groupOutput = groupItems.map((item) => { return item.getPublicValue(); }); @@ -366,21 +366,18 @@ export interface GroupAddConfigInterface extends StateIngestConfigInterface { * In which way the `itemKey` should be added to the Group. * - 'push' = at the end * - 'unshift' = at the beginning + * https://www.tutorialspoint.com/what-are-the-differences-between-unshift-and-push-methods-in-javascript * @default 'push' */ method?: 'unshift' | 'push'; /** * If the to add `itemKey` already exists, - * whether to overwrite its position with the position of the new `itemKey`. + * whether its position should be overwritten with the position of the new `itemKey`. * @default false */ overwrite?: boolean; } -/** - * @param key - Key/Name of Group - * @param isPlaceholder - If Group is initially a Placeholder - */ export interface GroupConfigInterface { /** * Key/Name identifier of Group. diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index a60d959f..67b74ecc 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -90,7 +90,7 @@ export class Collection { } /** - * Updates key/name identifier of Collection. + * Updates the key/name identifier of Collection. * * [Learn more..](https://agile-ts.org/docs/core/collection/properties#key) * @@ -102,7 +102,7 @@ export class Collection { } /** - * Returns key/name identifier of Collection. + * Returns the key/name identifier of Collection. * * [Learn more..](https://agile-ts.org/docs/core/collection/properties#key) * @@ -113,7 +113,7 @@ export class Collection { } /** - * Updates key/name identifier of Collection. + * Updates the key/name identifier of Collection. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#setkey) * @@ -1539,6 +1539,7 @@ export interface CollectConfigInterface * In which way the collected data should be added to the Collection. * - 'push' = at the end * - 'unshift' = at the beginning + * https://www.tutorialspoint.com/what-are-the-differences-between-unshift-and-push-methods-in-javascript * @default 'push' */ method?: 'push' | 'unshift'; diff --git a/packages/core/src/collection/item.ts b/packages/core/src/collection/item.ts index eab17158..d78b4626 100644 --- a/packages/core/src/collection/item.ts +++ b/packages/core/src/collection/item.ts @@ -41,15 +41,15 @@ export class Item extends State< }); this.collection = () => collection; - // Add 'rebuildGroupsThatIncludeItemKey' side effect - // in order to rebuild all Groups that include this Item whenever it mutates + // Add side effect to Item + // that rebuilds all Groups containing the Item whenever it changes if (this._key != null) { this.addRebuildGroupThatIncludeItemKeySideEffect(this._key); } } /** - * Updates key/name identifier of Item. + * Updates the key/name identifier of Item. * * @internal * @param value - New key/name identifier. @@ -152,12 +152,12 @@ export class Item extends State< } /** - * Adds the 'Rebuild Group that include Item Key' action to the Item side effects, - * so that the Groups which include the Item with the identifier `itemKey` - * are rebuilt when the Item changes. + * Adds side effect to Item + * that rebuilds all Groups containing the specified Item identifier + * whenever the Item changes. * * @internal - * @param itemKey - Item identifier that has to be included in Groups so that these Groups are rebuilt. + * @param itemKey - Item identifier that has to be contained in Groups. */ public addRebuildGroupThatIncludeItemKeySideEffect(itemKey: StateKey) { this.addSideEffect>( diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index a0b6ee1b..203cc1f8 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -67,7 +67,8 @@ export class Selector extends State< } /** - * Updates the currently selected Item of the Selector. + * Updates the currently selected Item of the Selector + * based on the specified `itemKey`. * * [Learn more..](https://agile-ts.org/docs/core/collection/selector/properties#itemkey) * @@ -90,7 +91,8 @@ export class Selector extends State< } /** - * Updates the currently selected Item of the Selector. + * Updates the currently selected Item of the Selector + * based on the specified Item. * * [Learn more..](https://agile-ts.org/docs/core/collection/selector/properties#item) * @@ -146,7 +148,7 @@ export class Selector extends State< newItem.selectedBy.add(this._key as any); // Add side effect to the newly selected Item - // that rebuilds the Selector depending on the current Item value + // that rebuilds the Selector value depending on the current Item value newItem.addSideEffect( Selector.rebuildSelectorSideEffectKey, (instance, config) => this.rebuildSelector(config), @@ -172,7 +174,7 @@ export class Selector extends State< { weight: 90 } ); - // Rebuild Selector for 'instantiating' the newly selected Item properly + // Rebuild the Selector to properly 'instantiate' the newly selected Item this.rebuildSelector(config); return this; @@ -181,10 +183,11 @@ export class Selector extends State< /** * Reselects the currently selected Item. * - * This might be helpful if the Selector failed to select the Item correctly + * This might be helpful if the Selector failed to select the Item correctly before * and therefore should try to select it again. * - * You can use the 'hasSelected()' method to check whether the Item is selected correctly. + * You can use the 'hasSelected()' method to check + * whether the 'selected' Item is selected correctly. * * [Learn more..](https://agile-ts.org/docs/core/collection/selector/methods#reselect) * @@ -212,7 +215,7 @@ export class Selector extends State< * @param config - Configuration object */ public unselect(config: StateRuntimeJobConfigInterface = {}): this { - // Retrieve Item from Collection because 'this._item' might be outdated + // Retrieve Item from the Collection because 'this._item' might be outdated const item = this.collection().getItem(this._itemKey, { notExisting: true, }); @@ -256,8 +259,8 @@ export class Selector extends State< } /** - * Rebuilds the Selector, - * which updates the Selector value based on the Item value. + * Rebuilds the Selector. + * During this process, it updates the Selector `value` based on the Item `value`. * * [Learn more..](https://agile-ts.org/docs/core/collection/selector/methods#rebuild) * diff --git a/packages/core/tests/unit/collection/selector.test.ts b/packages/core/tests/unit/collection/selector.test.ts index 52185ec5..cb4b2f87 100644 --- a/packages/core/tests/unit/collection/selector.test.ts +++ b/packages/core/tests/unit/collection/selector.test.ts @@ -141,7 +141,7 @@ describe('Selector Tests', () => { }); describe('itemKey set function tests', () => { - it('should call select function with passed value', () => { + it('should call the select() method with the passed value', () => { selector.select = jest.fn(); selector._itemKey = null as any; @@ -153,7 +153,7 @@ describe('Selector Tests', () => { }); describe('itemKey get function tests', () => { - it('should return currently selected ItemKey of Selector', () => { + it('should return the identifier of the Item currently selected by the Selector', () => { selector._itemKey = 'coolItemKey'; expect(selector.itemKey).toBe('coolItemKey'); @@ -161,7 +161,7 @@ describe('Selector Tests', () => { }); describe('item set function tests', () => { - it('should call select function with passed Item identifier', () => { + it('should call the select() method with the Item identifier of the specified Item', () => { selector.select = jest.fn(); selector._item = null as any; @@ -175,7 +175,7 @@ describe('Selector Tests', () => { }); describe('item get function tests', () => { - it('should return currently selected Item of Selector', () => { + it('should return the currently selected Item of the Selector', () => { selector._item = dummyItem1; expect(selector.item).toBe(dummyItem1); From 9313d8c9a313776bfd35ca801f6446be9c2048b0 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 4 Jun 2021 15:09:27 +0200 Subject: [PATCH 042/117] fixed Collection persistent storage key issue --- .../core/src/collection/collection.persistent.ts | 6 ++++++ packages/core/src/collection/group.ts | 6 +++--- .../collection/collection.persistent.test.ts | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 9b73c336..b3831b6c 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -132,6 +132,7 @@ export class CollectionPersistent< loadValue: false, defaultStorageKey: this.config.defaultStorageKey || undefined, storageKeys: this.storageKeys, + followCollectionPersistKeyPattern: false, // Because of the dynamic 'storageItemKey', the key is already formatted above }); if (defaultGroup.persistent?.ready) await defaultGroup.persistent.initialLoading(); @@ -149,6 +150,7 @@ export class CollectionPersistent< item.persist(itemStorageKey, { defaultStorageKey: this.config.defaultStorageKey || undefined, storageKeys: this.storageKeys, + followCollectionPersistKeyPattern: false, // Because of the dynamic 'storageItemKey', the key is already formatted above }); } // Persist and therefore load not present Item @@ -162,6 +164,7 @@ export class CollectionPersistent< loadValue: false, defaultStorageKey: this.config.defaultStorageKey || undefined, storageKeys: this.storageKeys, + followCollectionPersistKeyPattern: false, // Because of the dynamic 'storageItemKey', the key is already formatted above }); if (dummyItem?.persistent?.ready) { const loadedPersistedValueIntoItem = await dummyItem.persistent.loadPersistedValue( @@ -210,6 +213,7 @@ export class CollectionPersistent< defaultGroup.persist(defaultGroupStorageKey, { defaultStorageKey: this.config.defaultStorageKey || undefined, storageKeys: this.storageKeys, + followCollectionPersistKeyPattern: false, // Because of the dynamic 'storageItemKey', the key is already formatted above }); // Persist Items found in the default Group's value @@ -222,6 +226,7 @@ export class CollectionPersistent< item?.persist(itemStorageKey, { defaultStorageKey: this.config.defaultStorageKey || undefined, storageKeys: this.storageKeys, + followCollectionPersistKeyPattern: false,// Because of the dynamic 'storageItemKey', the key is already formatted above }); } @@ -347,6 +352,7 @@ export class CollectionPersistent< item.persist(itemStorageKey, { defaultStorageKey: this.config.defaultStorageKey || undefined, storageKeys: this.storageKeys, + followCollectionPersistKeyPattern: false, // Because of the dynamic 'storageItemKey', the key is already formatted above }); }); diff --git a/packages/core/src/collection/group.ts b/packages/core/src/collection/group.ts index adfe93f6..716cbb08 100644 --- a/packages/core/src/collection/group.ts +++ b/packages/core/src/collection/group.ts @@ -191,9 +191,9 @@ export class Group extends State< if (!this.collection().getItem(itemKey)) notExistingItemKeysInCollection.push(itemKey); - // Remove itemKey from Group - // if it should be overwritten and already exists - if (this.has(itemKey)) { + // Remove itemKey temporary from newGroupValue + // if it should be overwritten and already exists in the newGroupValue + if (newGroupValue.includes(itemKey)) { if (config.overwrite) { newGroupValue = newGroupValue.filter((key) => key !== itemKey); } else { diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index 7c10829d..0a1044c5 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -364,6 +364,7 @@ describe('CollectionPersistent Tests', () => { loadValue: false, defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); expect(dummyDefaultGroup.persistent?.initialLoading).toHaveBeenCalled(); @@ -377,6 +378,7 @@ describe('CollectionPersistent Tests', () => { { defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); @@ -438,6 +440,7 @@ describe('CollectionPersistent Tests', () => { loadValue: false, defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); expect(dummyDefaultGroup.persistent?.initialLoading).toHaveBeenCalled(); @@ -455,6 +458,7 @@ describe('CollectionPersistent Tests', () => { loadValue: false, defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); expect(placeholderItem2.persist).toHaveBeenCalledWith( @@ -466,6 +470,7 @@ describe('CollectionPersistent Tests', () => { loadValue: false, defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); expect(placeholderItem3.persist).toHaveBeenCalledWith( @@ -477,6 +482,7 @@ describe('CollectionPersistent Tests', () => { loadValue: false, defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); expect(dummyCollection.assignItem).toHaveBeenCalledWith( @@ -539,6 +545,7 @@ describe('CollectionPersistent Tests', () => { loadValue: false, defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); expect( @@ -551,6 +558,7 @@ describe('CollectionPersistent Tests', () => { { defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); @@ -566,6 +574,7 @@ describe('CollectionPersistent Tests', () => { loadValue: false, defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); expect(dummyCollection.assignItem).toHaveBeenCalledWith( @@ -708,6 +717,7 @@ describe('CollectionPersistent Tests', () => { { defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); @@ -719,6 +729,7 @@ describe('CollectionPersistent Tests', () => { { defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); expect(dummyItem3.persist).toHaveBeenCalledWith( @@ -729,6 +740,7 @@ describe('CollectionPersistent Tests', () => { { defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); @@ -757,6 +769,7 @@ describe('CollectionPersistent Tests', () => { { defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); @@ -765,6 +778,7 @@ describe('CollectionPersistent Tests', () => { { defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); expect(dummyItem3.persist).toHaveBeenCalledWith( @@ -772,6 +786,7 @@ describe('CollectionPersistent Tests', () => { { defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); @@ -1180,6 +1195,7 @@ describe('CollectionPersistent Tests', () => { { defaultStorageKey: collectionPersistent.config.defaultStorageKey, storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, } ); From a6de58f4be9252d4f8d405ee9bb5f9e9fda1908d Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 5 Jun 2021 20:09:35 +0200 Subject: [PATCH 043/117] fixed typos --- packages/core/src/collection/group.ts | 4 +- packages/core/src/collection/index.ts | 24 +- packages/core/src/collection/selector.ts | 2 +- packages/core/src/computed/index.ts | 6 +- packages/core/src/state/index.ts | 381 ++++++++++--------- packages/core/tests/unit/state/state.test.ts | 43 ++- packages/utils/src/index.ts | 6 +- 7 files changed, 256 insertions(+), 210 deletions(-) diff --git a/packages/core/src/collection/group.ts b/packages/core/src/collection/group.ts index 716cbb08..8452d83b 100644 --- a/packages/core/src/collection/group.ts +++ b/packages/core/src/collection/group.ts @@ -59,7 +59,7 @@ export class Group extends State< } /** - * Retrieves the values of the Items clustered by the Group. + * Returns the values of the Items clustered by the Group. * * [Learn more..](https://agile-ts.org/docs/core/collection/group/properties#output) * @@ -75,7 +75,7 @@ export class Group extends State< } /** - * Retrieves the Items clustered by the Group. + * Returns the Items clustered by the Group. * * [Learn more..](https://agile-ts.org/docs/core/collection/group/properties#items) * diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 67b74ecc..45ab7394 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -29,16 +29,16 @@ export class Collection { public config: CollectionConfigInterface; private initialConfig: CreateCollectionConfigInterface; - public size = 0; // Amount of the Items stored in the Collection - public data: { [key: string]: Item } = {}; // Collection Data public _key?: CollectionKey; - public isPersisted = false; // Whether Collection is persisted in any external Storage - public persistent: CollectionPersistent | undefined; // Manages persisting Collection 'value' + public size = 0; // Amount of the Items stored in the Collection + public data: { [key: string]: Item } = {}; // Items stored in the Collection + public isPersisted = false; // Whether the Collection is persisted in an external Storage + public persistent: CollectionPersistent | undefined; // Manages persisting the Collection 'value' - public groups: { [key: string]: Group } = {}; - public selectors: { [key: string]: Selector } = {}; + public groups: { [key: string]: Group } = {}; // Groups of Collection + public selectors: { [key: string]: Selector } = {}; // Selectors of Collection - public isInstantiated = false; // Whether the Collection is instantiated completely + public isInstantiated = false; // Whether the Collection was instantiated correctly /** * A Collection manages a reactive set of Information @@ -49,6 +49,8 @@ export class Collection { * * Each of these data object must have a unique `primaryKey` to be correctly identified later. * + * You can create as many global Collections as you need. + * * [Learn more..](https://agile-ts.org/docs/core/collection/) * * @public @@ -90,7 +92,7 @@ export class Collection { } /** - * Updates the key/name identifier of Collection. + * Updates the key/name identifier of the Collection. * * [Learn more..](https://agile-ts.org/docs/core/collection/properties#key) * @@ -102,7 +104,7 @@ export class Collection { } /** - * Returns the key/name identifier of Collection. + * Returns the key/name identifier of the Collection. * * [Learn more..](https://agile-ts.org/docs/core/collection/properties#key) * @@ -113,7 +115,7 @@ export class Collection { } /** - * Updates the key/name identifier of Collection. + * Updates the key/name identifier of the Collection. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#setkey) * @@ -361,7 +363,7 @@ export class Collection { background: false, }); - // Validate passed data + // Check if the given conditions are suitable for a update action if (item == null) { LogCodeManager.log('1B:03:00', [itemKey, this._key]); return undefined; diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index 203cc1f8..b6afe661 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -56,7 +56,7 @@ export class Selector extends State< } /** - * Retrieves the `itemKey` currently selected by the Selector. + * Returns the `itemKey` currently selected by the Selector. * * [Learn more..](https://agile-ts.org/docs/core/collection/selector/properties#itemkey) * diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index b5648374..2bb0852d 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -17,9 +17,9 @@ export class Computed extends State< > { public agileInstance: () => Agile; - public computeFunction: () => ComputedValueType; // Function to compute the computed value - public deps: Array = []; // All dependencies the Computed depends on (including hardCoded and autoDetected dependencies) - public hardCodedDeps: Array = []; // Only hardCoded dependencies the Computed depends + public computeFunction: () => ComputedValueType; // Function to compute the Computed Class value + public deps: Array = []; // All dependencies the Computed Class depends on (including hardCoded and automatically detected dependencies) + public hardCodedDeps: Array = []; // Only hardCoded dependencies the Computed Class depends /** * An extension of the State Class that computes its value based on a compute function. diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index 745364b6..282b9a4f 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -23,39 +23,42 @@ export class State { public agileInstance: () => Agile; public _key?: StateKey; - public valueType?: string; // primitive Type of State Value (for JS users) - public isSet = false; // If value is not the same as initialValue + public valueType?: string; // Primitive type which constrains the State value (for basic typesafety in Javascript) + public isSet = false; // Whether the current value differs from the initial value public isPlaceholder = false; - public initialStateValue: ValueType; - public _value: ValueType; // Current Value of State - public previousStateValue: ValueType; - public nextStateValue: ValueType; // Represents the next Value of the State (mostly used internal) - public observer: StateObserver; // Handles deps and subs of State and is like an interface to the Runtime + public initialStateValue: ValueType; // First value assigned to the State + public _value: ValueType; // Current value of the State + public previousStateValue: ValueType; // Previous value of the State + public nextStateValue: ValueType; // Next value of the State (which can be used for dynamic State updates) + + // Handles dependencies to other States and subscriptions of UI-Components + // and serves as an interface to the runtime + public observer: StateObserver; public sideEffects: { [key: string]: SideEffectInterface>; - } = {}; // SideEffects of State (will be executed in Runtime) - public computeValueMethod?: ComputeValueMethod; - public computeExistsMethod: ComputeExistsMethod; + } = {}; // Side effects of changing the State value + public computeValueMethod?: ComputeValueMethod; // Method for dynamically computing the State value + public computeExistsMethod: ComputeExistsMethod; // Method for dynamically computing the existence of the State - public isPersisted = false; // If State can be stored in Agile Storage (-> successfully integrated persistent) - public persistent: StatePersistent | undefined; // Manages storing State Value into Storage + public isPersisted = false; // Whether the State is persisted in an external Storage + public persistent: StatePersistent | undefined; // Manages persisting the State value - public watchers: { [key: string]: StateWatcherCallback } = {}; + public watchers: { [key: string]: StateWatcherCallback } = {}; // Callbacks that are fired on State changes - public currentInterval?: NodeJS.Timer | number; + public currentInterval?: NodeJS.Timer | number; // The current active interval /** - * @public - * State - Class that holds one Value and causes rerender on subscribed Components - * - * @param agileInstance - An instance of Agile + * A State manages a piece of Information + * that we need to remember globally at a later point int time. + * While providing a toolkit to use and mutate this set of Information. * - * @param initialValue - Initial Value of State + * You can create as many global States as you need. * - * @param config - Configuration - * - * @typeparam ValueType - Type of a the value the State represents + * @public + * @param agileInstance - Instance of Agile the State belongs to. + * @param initialValue - Initial value of State. + * @param config - Configuration object */ constructor( agileInstance: Agile, @@ -81,21 +84,28 @@ export class State { return v != null; }; - // Initial Set + // Set State value to specified initial value if (!config.isPlaceholder) this.set(initialValue, { overwrite: true }); } /** + * Updates the value of the State. + * + * [Learn more..](https://agile-ts.org/docs/core/state/properties#value) + * * @public - * Set Value of State + * @param value - New State value. */ public set value(value: ValueType) { this.set(value); } /** + * Returns a reference-free version of the State value. + * + * [Learn more..](https://agile-ts.org/docs/core/state/properties#value) + * * @public - * Get Value of State */ public get value(): ValueType { ComputedTracker.tracked(this.observer); @@ -103,7 +113,9 @@ export class State { } /** - * Updates key/name identifier of State. + * Updates the key/name identifier of the State. + * + * [Learn more..](https://agile-ts.org/docs/core/state/properties#key) * * @public * @param value - New key/name identifier. @@ -113,7 +125,9 @@ export class State { } /** - * Returns key/name identifier of State. + * Returns the key/name identifier of the State. + * + * [Learn more..](https://agile-ts.org/docs/core/state/properties#key) * * @public */ @@ -122,7 +136,9 @@ export class State { } /** - * Updates key/name identifier of State. + * Updates the key/name identifier of the State. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#setkey) * * @public * @param value - New key/name identifier. @@ -133,30 +149,26 @@ export class State { // Update State key this._key = value; - // Update key in Observer + // Update key of Observer this.observer._key = value; // Update key in Persistent (only if oldKey equal to persistentKey // because otherwise the persistentKey is detached from the State key // -> not managed by State anymore) - if ( - value != null && - this.persistent != null && - this.persistent._key === oldKey - ) - this.persistent.setKey(value); + if (value && this.persistent?._key === oldKey) + this.persistent?.setKey(value); return this; } - //========================================================================================================= - // Set - //========================================================================================================= /** + * Assigns a new value to the State. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#set) + * * @public - * Updates Value of State - * @param value - new State Value - * @param config - Config + * @param value - New State value + * @param config - Configuration object */ public set( value: ValueType | ((value: ValueType) => ValueType), @@ -169,7 +181,7 @@ export class State { ? (value as any)(copy(this._value)) : value; - // Check value has correct Type (js) + // Check value has correct type (Javascript) if (!this.hasCorrectType(_value)) { LogCodeManager.log(config.force ? '14:02:00' : '14:03:00', [ typeof _value, @@ -178,38 +190,42 @@ export class State { if (!config.force) return this; } - // Ingest new value into Runtime + // Ingest the State with the new value into the runtime this.observer.ingestValue(_value, config); return this; } - //========================================================================================================= - // Ingest - //========================================================================================================= /** + * Ingests the State without any specified new value into the runtime. + * + * Since no new value was defined either the State value is computed (Computed Class) + * or the `nextStateValue` is taken. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#ingest) + * * @internal - * Ingests nextStateValue, computedValue into Runtime - * @param config - Config + * @param config - Configuration object */ public ingest(config: StateIngestConfigInterface = {}): this { this.observer.ingest(config); return this; } - //========================================================================================================= - // Type - //========================================================================================================= /** + * Assigns a primitive type to the State + * which constrains the State value on the specified type + * to ensure basic typesafety in Javascript. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#type) + * * @public - * Assign primitive type to State Value - * Note: This function is mainly thought for JS users - * @param type - wished Type ('String', 'Boolean', 'Array', 'Object', 'Number') + * @param type - Primitive type the State value must follow (`String`, `Boolean`, `Array`, `Object`, `Number`). */ public type(type: any): this { const supportedTypes = ['String', 'Boolean', 'Array', 'Object', 'Number']; - // Check if type is a supported Type + // Check if type is a supported type if (!supportedTypes.includes(type.name)) { LogCodeManager.log('14:03:01', [type]); return this; @@ -219,41 +235,45 @@ export class State { return this; } - //========================================================================================================= - // Undo - //========================================================================================================= /** + * Undoes the latest State value change. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#undo) + * * @public - * Undoes latest State Value change - * @param config - Config + * @param config - Configuration object */ public undo(config: StateIngestConfigInterface = {}): this { this.set(this.previousStateValue, config); return this; } - //========================================================================================================= - // Reset - //========================================================================================================= /** + * Resets the State value to its initial value. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#reset) + * * @public - * Resets State to its initial Value - * @param config - Config + * @param config - Configuration object */ public reset(config: StateIngestConfigInterface = {}): this { this.set(this.initialStateValue, config); return this; } - //========================================================================================================= - // Patch - //========================================================================================================= /** + * Merges the specified `targetWithChanges` object into the current State value. + * This merge can be different for different value combinations: + * - If the current State value is an `object`, it does partial update for the object. + * - If the current State value is an `array` an the argument is an array too, + * it concatenates the current value with the value of the argument. + * - If the current State value is not a `object` or an `array` it prints an warning. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#patch) + * * @public - * Patches Object with changes into State Value - * Note: Only useful if State is an Object - * @param targetWithChanges - Object that holds changes which get patched into State Value - * @param config - Config + * @param targetWithChanges - Object to be merged into the current State value. + * @param config - Configuration object */ public patch( targetWithChanges: Object, @@ -263,44 +283,58 @@ export class State { addNewProperties: true, }); + // Check if the given conditions are suitable for a patch action if (!isValidObject(this.nextStateValue, true)) { LogCodeManager.log('14:03:02'); return this; } - if (!isValidObject(targetWithChanges, true)) { LogCodeManager.log('00:03:01', ['TargetWithChanges', 'object']); return this; } - // Merge targetWithChanges into nextStateValue - this.nextStateValue = flatMerge( - copy(this.nextStateValue), - targetWithChanges, - { addNewProperties: config.addNewProperties } - ); + // Merge targetWithChanges object into the nextStateValue + if ( + Array.isArray(targetWithChanges) && + Array.isArray(this.nextStateValue) + ) { + this.nextStateValue = [ + ...this.nextStateValue, + ...targetWithChanges, + ] as any; + } else { + this.nextStateValue = flatMerge( + this.nextStateValue, + targetWithChanges, + { addNewProperties: config.addNewProperties } + ); + } - // Ingest updated nextStateValue into Runtime + // Ingest updated 'nextStateValue' into runtime this.ingest(removeProperties(config, ['addNewProperties'])); return this; } - //========================================================================================================= - // Watch - //========================================================================================================= /** + * Fires on each State value change. + * + * Returns the key/name identifier of the created watcher callback. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#watch) + * * @public - * Watches State and detects State changes - * @param callback - Callback Function that gets called if the State Value changes - * @return Key of Watcher + * @param callback - Callback function */ public watch(callback: StateWatcherCallback): string; /** + * Fires on each State value change. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#watch) + * * @public - * Watches State and detects State changes - * @param key - Key/Name of Watcher Function - * @param callback - Callback Function that gets called if the State Value changes + * @param key - Key/Name identifier of the watcher callback. + * @param callback - Callback function */ public watch(key: string, callback: StateWatcherCallback): this; public watch( @@ -319,13 +353,13 @@ export class State { _callback = callback as StateWatcherCallback; } - // Check if Callback is valid Function + // Check if specified callback is a valid function if (!isFunction(_callback)) { LogCodeManager.log('00:03:01', ['Watcher Callback', 'function']); return this; } - // Check if watcherKey is already occupied + // Check if watcher with key is already occupied if (this.watchers[key]) { LogCodeManager.log('14:03:03', [key]); return this; @@ -335,13 +369,13 @@ export class State { return generateKey ? key : this; } - //========================================================================================================= - // Remove Watcher - //========================================================================================================= /** + * Removes a watcher callback with the specified key/name identifier from the State. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#removewatcher) + * * @public - * Removes Watcher at given Key - * @param key - Key of Watcher that gets removed + * @param key - Key/Name identifier of the watcher callback to be removed. */ public removeWatcher(key: string): this { delete this.watchers[key]; @@ -349,9 +383,25 @@ export class State { } /** + * Returns a boolean indicating whether a watcher callback with the specified `key` + * exists in the State or not. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#haswatcher) + * * @public - * Creates a Watcher that gets once called when the State Value changes for the first time and than destroys itself - * @param callback - Callback Function that gets called if the State Value changes + * @param key - Key/Name identifier of the watcher callback. + */ + public hasWatcher(key: string): boolean { + return !!this.watchers[key]; + } + + /** + * Fires on the first State value assignment and then destroys itself. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#oninaugurated) + * + * @public + * @param callback - Callback function */ public onInaugurated(callback: StateWatcherCallback): this { const watcherKey = 'InauguratedWatcherKey'; @@ -362,18 +412,6 @@ export class State { return this; } - //========================================================================================================= - // Has Watcher - //========================================================================================================= - /** - * @public - * Checks if watcher at given Key exists - * @param key - Key/Name of Watcher - */ - public hasWatcher(key: string): boolean { - return !!this.watchers[key]; - } - /** * Preserves the State `value` in the corresponding external Storage. * @@ -466,20 +504,21 @@ export class State { return this; } - //========================================================================================================= - // Interval - //========================================================================================================= /** + * Repeatedly calls a function or executes a code snippet, with a fixed time delay between each call. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#interval) + * * @public - * Calls callback at certain intervals in milliseconds and assigns the callback return value to the State - * @param callback- Callback that is called on each interval and should return the new State value - * @param ms - The intervals in milliseconds + * @param handler - A function to be executed every delay milliseconds. + * @param delay - The time, in milliseconds (thousandths of a second), + * the timer should delay in between executions of the specified function. */ public interval( - callback: (value: ValueType) => ValueType, - ms?: number + handler: (value: ValueType) => ValueType, + delay?: number ): this { - if (!isFunction(callback)) { + if (!isFunction(handler)) { LogCodeManager.log('00:03:01', ['Interval Callback', 'function']); return this; } @@ -489,18 +528,19 @@ export class State { } this.currentInterval = setInterval(() => { - this.set(callback(this._value)); - }, ms ?? 1000); + this.set(handler(this._value)); + }, delay ?? 1000); return this; } - //========================================================================================================= - // Clear Interval - //========================================================================================================= /** + * Cancels a active timed, repeating action + * which was previously established by a call to `interval()`. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#clearinterval) + * * @public - * Clears the current Interval */ public clearInterval(): void { if (this.currentInterval) { @@ -509,35 +549,24 @@ export class State { } } - //========================================================================================================= - // Copy - //========================================================================================================= - /** - * @public - * Creates fresh copy of State Value (-> No reference to State Value) - */ - public copy(): ValueType { - return copy(this.value); - } - - //========================================================================================================= - // Exists - //========================================================================================================= /** + * Returns a boolean indicating whether the State exists. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#exists) + * * @public - * Checks if State exists */ public get exists(): boolean { return !this.isPlaceholder && this.computeExistsMethod(this.value); } - //========================================================================================================= - // Compute Exists - //========================================================================================================= /** + * TODO + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#computeexists) + * * @public - * Function that computes the exists status of the State - * @param method - Computed Function + * @param method - Computed function */ public computeExists(method: ComputeExistsMethod): this { if (!isFunction(method)) { @@ -549,25 +578,46 @@ export class State { return this; } - //========================================================================================================= - // Is - //========================================================================================================= /** + * TODO + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#computevalue) + * + * @public + * @param method - Computed function + */ + public computeValue(method: ComputeValueMethod): this { + if (!isFunction(method)) { + LogCodeManager.log('00:03:01', ['Compute Value Method', 'function']); + return this; + } + this.computeValueMethod = method; + + // Initial compute + this.set(method(this.nextStateValue)); + + return this; + } + + /** + * Returns a boolean indicating whether the specified value is equal to the current State value. + * Equivalent to '===' with the difference that it looks at the value + * and not on the reference. + * * @public - * Equivalent to === - * @param value - Value that gets checked if its equals to the State Value + * @param value - Value to be compared with the current State value. */ public is(value: ValueType): boolean { return equal(value, this.value); } - //========================================================================================================= - // Is Not - //========================================================================================================= /** + * Returns a boolean indicating whether the specified value is not equal to the current State value. + * Equivalent to '!==' with the difference that it looks at the value + * and not on the reference. + * * @public - * Equivalent to !== - * @param value - Value that gets checked if its not equals to the State Value + * @param value - Value to be compared with the current State value. */ public isNot(value: ValueType): boolean { return notEqual(value, this.value); @@ -590,27 +640,6 @@ export class State { return this; } - //========================================================================================================= - // Compute Value - //========================================================================================================= - /** - * @public - * Function that recomputes State Value if it changes - * @param method - Computed Function - */ - public computeValue(method: ComputeValueMethod): this { - if (!isFunction(method)) { - LogCodeManager.log('00:03:01', ['Compute Value Method', 'function']); - return this; - } - this.computeValueMethod = method; - - // Initial compute - this.set(method(this.nextStateValue)); - - return this; - } - //========================================================================================================= // Add SideEffect //========================================================================================================= diff --git a/packages/core/tests/unit/state/state.test.ts b/packages/core/tests/unit/state/state.test.ts index dbfb84d5..bff8000e 100644 --- a/packages/core/tests/unit/state/state.test.ts +++ b/packages/core/tests/unit/state/state.test.ts @@ -404,24 +404,25 @@ describe('State Tests', () => { beforeEach(() => { objectState.ingest = jest.fn(); numberState.ingest = jest.fn(); + arrayState.ingest = jest.fn(); jest.spyOn(Utils, 'flatMerge'); }); - it("shouldn't patch and ingest passed object based value into a not object based State (default config)", () => { + it("shouldn't patch specified object value into a not object based State (default config)", () => { numberState.patch({ changed: 'object' }); LogMock.hasLoggedCode('14:03:02'); expect(objectState.ingest).not.toHaveBeenCalled(); }); - it("shouldn't patch and ingest passed not object based value into object based State (default config)", () => { + it("shouldn't patch specified non object value into a object based State (default config)", () => { objectState.patch('number' as any); LogMock.hasLoggedCode('00:03:01', ['TargetWithChanges', 'object']); expect(objectState.ingest).not.toHaveBeenCalled(); }); - it('should patch and ingest passed object based value into a object based State (default config)', () => { + it('should patch specified object value into a object based State (default config)', () => { objectState.patch({ name: 'frank' }); expect(Utils.flatMerge).toHaveBeenCalledWith( @@ -436,7 +437,7 @@ describe('State Tests', () => { expect(objectState.ingest).toHaveBeenCalledWith({}); }); - it('should patch and ingest passed object based value into a object based State (specific config)', () => { + it('should patch specified object value into a object based State (specific config)', () => { objectState.patch( { name: 'frank' }, { @@ -468,6 +469,30 @@ describe('State Tests', () => { }, }); }); + + it('should patch specified array value into a array based State (default config)', () => { + arrayState.patch(['hi']); + + expect(Utils.flatMerge).not.toHaveBeenCalled(); + expect(arrayState.nextStateValue).toStrictEqual(['jeff', 'hi']); + expect(arrayState.ingest).toHaveBeenCalledWith({}); + }); + + it('should patch specified array value into a object based State', () => { + objectState.patch(['hi'], { addNewProperties: true }); + + expect(Utils.flatMerge).toHaveBeenCalledWith( + { age: 10, name: 'jeff' }, + ['hi'], + { addNewProperties: true } + ); + expect(objectState.nextStateValue).toStrictEqual({ + 0: 'hi', + age: 10, + name: 'jeff', + }); + expect(objectState.ingest).toHaveBeenCalledWith({}); + }); }); describe('watch function tests', () => { @@ -807,16 +832,6 @@ describe('State Tests', () => { }); }); - describe('copy function tests', () => { - it('should return a reference free copy of the current State Value', () => { - jest.spyOn(Utils, 'copy'); - const value = numberState.copy(); - - expect(value).toBe(10); - expect(Utils.copy).toHaveBeenCalledWith(10); - }); - }); - describe('exists get function tests', () => { it('should return true if State is no placeholder and computeExistsMethod returns true', () => { numberState.computeExistsMethod = jest.fn().mockReturnValueOnce(true); diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 4746c07c..e02d5cc3 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -198,13 +198,13 @@ export function flatMerge( // Copy Source to avoid References const _source = copy(source); - if (!_source) return _source; + if (_source == null) return _source; // Merge Changes Object into Source Object const keys = Object.keys(changes); keys.forEach((property) => { - if (!config.addNewProperties && !_source[property]) return; - _source[property] = changes[property]; + if (config.addNewProperties && _source[property] != null) + _source[property] = changes[property]; }); return _source; From 61b2ee64e0fc6d7f1d19c11379e97560327ec926 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 6 Jun 2021 07:40:02 +0200 Subject: [PATCH 044/117] fixed typos --- packages/core/src/collection/group.ts | 2 +- packages/core/src/collection/index.ts | 5 +- packages/core/src/collection/selector.ts | 2 +- packages/core/src/logCodeManager.ts | 2 +- packages/core/src/state/index.ts | 187 ++++++++++++------- packages/core/src/state/state.observer.ts | 2 +- packages/core/tests/unit/state/state.test.ts | 51 ++++- packages/react/src/hooks/useAgile.ts | 2 +- 8 files changed, 172 insertions(+), 81 deletions(-) diff --git a/packages/core/src/collection/group.ts b/packages/core/src/collection/group.ts index 8452d83b..0ac01960 100644 --- a/packages/core/src/collection/group.ts +++ b/packages/core/src/collection/group.ts @@ -380,7 +380,7 @@ export interface GroupAddConfigInterface extends StateIngestConfigInterface { export interface GroupConfigInterface { /** - * Key/Name identifier of Group. + * Key/Name identifier of the Group. * @default undefined */ key?: GroupKey; diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 45ab7394..f8c13c6a 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -1489,7 +1489,7 @@ export interface CreateCollectionConfigInterface { */ selectors?: { [key: string]: Selector } | string[]; /** - * Key/Name identifier of Collection. + * Key/Name identifier of the Collection. * @default undefined */ key?: CollectionKey; @@ -1625,9 +1625,8 @@ export interface CollectionPersistentConfigInterface { /** * Default Storage key of the specified Storage keys. * The Collection value is loaded from the default Storage - * and only loaded from the remaining Storages (storageKeys) + * and is only loaded from the remaining Storages (storageKeys) * if the loading of the default Storage failed. - * * @default first index of the specified Storage keys or the AgileTs default Storage key */ defaultStorageKey?: StorageKey; diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index b6afe661..15a32b72 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -285,7 +285,7 @@ export type SelectorKey = string | number; export interface SelectorConfigInterface { /** - * Key/Name identifier of Selector. + * Key/Name identifier of the Selector. * @default undefined */ key?: SelectorKey; diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index 1e99b165..700b0a26 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -65,7 +65,7 @@ const logCodeMessages = { '14:03:02': "The 'patch()' method works only in object based States!", '14:03:03': "Watcher Callback with the key/name '${0}' already exists!", '14:03:04': 'Only one Interval can be active at once!', - '14:03:05': "The 'invert()' method works only in boolean based States!", + '14:03:05': "Failed to invert value of the type '${0}'!", // SubController '15:01:00': "Unregistered 'Callback' based Subscription.", diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index 282b9a4f..34f69297 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -224,13 +224,10 @@ export class State { */ public type(type: any): this { const supportedTypes = ['String', 'Boolean', 'Array', 'Object', 'Number']; - - // Check if type is a supported type if (!supportedTypes.includes(type.name)) { LogCodeManager.log('14:03:01', [type]); return this; } - this.valueType = type.name.toLowerCase(); return this; } @@ -389,7 +386,7 @@ export class State { * [Learn more..](https://agile-ts.org/docs/core/state/methods/#haswatcher) * * @public - * @param key - Key/Name identifier of the watcher callback. + * @param key - Key/Name identifier of the watcher callback to be checked for existence. */ public hasWatcher(key: string): boolean { return !!this.watchers[key]; @@ -488,24 +485,23 @@ export class State { */ public onLoad(callback: (success: boolean) => void): this { if (!this.persistent) return this; - - // Check if provided callback is valid function if (!isFunction(callback)) { LogCodeManager.log('00:03:01', ['OnLoad Callback', 'function']); return this; } - // Register provided callback + // Register specified callback this.persistent.onLoad = callback; - // If State is already persisted ('isPersisted') fire provided callback immediately + // If State is already persisted ('isPersisted') fire specified callback immediately if (this.isPersisted) callback(true); return this; } /** - * Repeatedly calls a function or executes a code snippet, with a fixed time delay between each call. + * Repeatedly calls a function or executes a code snippet, + * with a fixed time delay between each call. * * [Learn more..](https://agile-ts.org/docs/core/state/methods/#interval) * @@ -527,6 +523,7 @@ export class State { return this; } + // Create interval this.currentInterval = setInterval(() => { this.set(handler(this._value)); }, delay ?? 1000); @@ -552,6 +549,9 @@ export class State { /** * Returns a boolean indicating whether the State exists. * + * It calculates the value based on the `computeExistsMethod()` + * and whether the State is a placeholder. + * * [Learn more..](https://agile-ts.org/docs/core/state/methods/#exists) * * @public @@ -561,12 +561,15 @@ export class State { } /** - * TODO + * Defines the method used to compute the existence of the State. + * + * It is retrieved on each `exists()` method call + * to determine whether the State exists or not. * * [Learn more..](https://agile-ts.org/docs/core/state/methods/#computeexists) * * @public - * @param method - Computed function + * @param method - Method to compute the existence of the State. */ public computeExists(method: ComputeExistsMethod): this { if (!isFunction(method)) { @@ -579,12 +582,16 @@ export class State { } /** - * TODO + * Defines the method used to compute the value of the State. + * + * It is retrieved on each State value change, + * in order to compute the new State value + * based on the specified compute method. * * [Learn more..](https://agile-ts.org/docs/core/state/methods/#computevalue) * * @public - * @param method - Computed function + * @param method - Method to compute the value of the State. */ public computeValue(method: ComputeValueMethod): this { if (!isFunction(method)) { @@ -601,8 +608,9 @@ export class State { /** * Returns a boolean indicating whether the specified value is equal to the current State value. - * Equivalent to '===' with the difference that it looks at the value - * and not on the reference. + * + * Equivalent to `===` with the difference that it looks at the value + * and not on the reference in the case of objects. * * @public * @param value - Value to be compared with the current State value. @@ -613,8 +621,9 @@ export class State { /** * Returns a boolean indicating whether the specified value is not equal to the current State value. - * Equivalent to '!==' with the difference that it looks at the value - * and not on the reference. + * + * Equivalent to `!==` with the difference that it looks at the value + * and not on the reference in the case of objects. * * @public * @param value - Value to be compared with the current State value. @@ -623,32 +632,52 @@ export class State { return notEqual(value, this.value); } - //========================================================================================================= - // Invert - //========================================================================================================= /** + * Inverts the current State value. + * + * Some examples are: + * - `'jeff'` -> `'ffej'` + * - `true` -> `false` + * - `[1, 2, 3]` -> `[3, 2, 1]` + * - `10` -> `-10` + * * @public - * Inverts State Value - * Note: Only useful with boolean based States */ public invert(): this { - if (typeof this._value === 'boolean') { - this.set(!this._value as any); - } else { - LogCodeManager.log('14:03:05'); + switch (typeof this.nextStateValue) { + case 'boolean': + this.set(!this.nextStateValue as any); + break; + case 'object': + if (Array.isArray(this.nextStateValue)) + this.set(this.nextStateValue.reverse() as any); + break; + case 'string': + this.set(this.nextStateValue.split('').reverse().join('') as any); + break; + case 'number': + this.set((this.nextStateValue * -1) as any); + break; + default: + LogCodeManager.log('14:03:05', [typeof this.nextStateValue]); } + return this; } - //========================================================================================================= - // Add SideEffect - //========================================================================================================= /** + * + * Registers a `callback` function that is executed during the `runtime` + * as a side effect of State changes. + * + * For example it is called when the State value changes from 'jeff' to 'hans'. + * + * A typical side effect could be the updating of the external Storage value. + * * @internal - * Adds SideEffect to State - * @param key - Key/Name of SideEffect - * @param callback - Callback Function that gets called on every State Value change - * @param config - Config + * @param key - Key/Name identifier of the to register side effect. + * @param callback - Callback function to be called on each State value change. + * @param config - Configuration object */ public addSideEffect>( key: string, @@ -669,26 +698,27 @@ export class State { return this; } - //========================================================================================================= - // Remove SideEffect - //========================================================================================================= /** + * Removes a side effect callback with the specified key/name identifier from the State. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#removesideeffect) + * * @internal - * Removes SideEffect at given Key - * @param key - Key of the SideEffect that gets removed + * @param key - Key/Name identifier of the side effect callback to be removed. */ public removeSideEffect(key: string): this { delete this.sideEffects[key]; return this; } - //========================================================================================================= - // Has SideEffect - //========================================================================================================= /** + * Returns a boolean indicating whether a side effect callback with the specified `key` + * exists in the State or not. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#hassideeffect) + * * @internal - * Checks if sideEffect at given Key exists - * @param key - Key of SideEffect + * @param key - Key/Name identifier of the side effect callback to be checked for existence. */ public hasSideEffect(key: string): boolean { return !!this.sideEffects[key]; @@ -709,26 +739,23 @@ export class State { return type === this.valueType; } - //========================================================================================================= - // Get Public Value - //========================================================================================================= /** + * Returns the public value of the State. + * * @internal - * Returns public Value of State */ public getPublicValue(): ValueType { - // If State Value is used internally and output represents the real state value (for instance in Group) + // If State value is used internally + // and output represents the public State value (for instance in Group) if (this['output'] !== undefined) return this['output']; return this._value; } - //========================================================================================================= - // Get Persistable Value - //========================================================================================================= /** + * Returns the persistable value of the State. + * * @internal - * Returns Value that gets written into the Agile Storage */ public getPersistableValue(): any { return this._value; @@ -743,8 +770,21 @@ export type StateKey = string | number; * @param isPlaceholder - If State is initially a Placeholder */ export interface StateConfigInterface { + /** + * Key/Name identifier of the State. + * @default undefined + */ key?: StateKey; + /** + * Observers that depend on the State. + * @default undefined + */ dependents?: Array; + /** + * Whether the State should be a placeholder + * and therefore should only exist in the background. + * @default false + */ isPlaceholder?: boolean; } @@ -760,14 +800,26 @@ export interface PatchOptionConfigInterface { addNewProperties?: boolean; } -/** - * @param loadValue - If Persistent loads the persisted value into the State - * @param storageKeys - Key/Name of Storages which gets used to persist the State Value (NOTE: If not passed the default Storage will be used) - * @param defaultStorageKey - Default Storage Key (if not provided it takes the first index of storageKeys or the AgileTs default Storage) - */ export interface StatePersistentConfigInterface { + /** + * Whether the Persistent should automatically load + * the persisted value into the State after its instantiation. + * @default true + */ loadValue?: boolean; + /** + * Key/Name identifier of Storages + * in which the State value should be or is persisted. + * @default [AgileTs default Storage key] + */ storageKeys?: StorageKey[]; + /** + * Default Storage key of the specified Storage keys. + * The State value is loaded from the default Storage + * and is only loaded from the remaining Storages (storageKeys) + * if the loading of the default Storage failed. + * @default first index of the specified Storage keys or the AgileTs default Storage key + */ defaultStorageKey?: StorageKey; } @@ -782,18 +834,25 @@ export type SideEffectFunctionType> = ( } ) => void; -/** - * @param callback - Callback Function that gets called on every State Value change - * @param weight - When the sideEffect gets executed. The higher, the earlier it gets executed. - */ export interface SideEffectInterface> { + /** + * Callback function to be called on every State value change + * @return () => {} + */ callback: SideEffectFunctionType; + /** + * Weight of the side effect. + * Determines the order of execution of the registered side effects. + * The higher the weight, the earlier it is executed. + */ weight: number; } -/** - * @param weight - When the sideEffect gets executed. The higher, the earlier it gets executed. - */ export interface AddSideEffectConfigInterface { + /** + * Weight of the side effect. + * Determines the order of execution of the registered side effects. + * The higher the weight, the earlier it is executed. + */ weight?: number; } diff --git a/packages/core/src/state/state.observer.ts b/packages/core/src/state/state.observer.ts index 3da44fe9..fd0182dc 100644 --- a/packages/core/src/state/state.observer.ts +++ b/packages/core/src/state/state.observer.ts @@ -143,8 +143,8 @@ export class StateObserver extends Observer { // because sometimes (for instance in a Group State) the publicValue() is not the .value (nextStateValue) property. // The Observer value is at some point the public Value because Integrations like React are using it as return value. // For example 'useAgile()' returns the Observer.value and not the State.value. + job.observer.previousValue = copy(job.observer.value); job.observer.value = copy(state.getPublicValue()); - job.observer.previousValue = copy(state.previousStateValue); } //========================================================================================================= diff --git a/packages/core/tests/unit/state/state.test.ts b/packages/core/tests/unit/state/state.test.ts index bff8000e..84cccca9 100644 --- a/packages/core/tests/unit/state/state.test.ts +++ b/packages/core/tests/unit/state/state.test.ts @@ -924,22 +924,55 @@ describe('State Tests', () => { }); describe('invert function tests', () => { + let dummyState: State; + beforeEach(() => { - numberState.set = jest.fn(); - booleanState.set = jest.fn(); + dummyState = new State(dummyAgile, null); + + dummyState.set = jest.fn(); }); - it('should invert current value of a boolean based State', () => { - booleanState.invert(); + it('should invert value of the type boolean', () => { + dummyState.nextStateValue = false; - expect(booleanState.set).toHaveBeenCalledWith(true); + dummyState.invert(); + + expect(dummyState.set).toHaveBeenCalledWith(true); }); - it("shouldn't invert current value if not boolean based State and should print a error", () => { - numberState.invert(); + it('should invert value of the type number', () => { + dummyState.nextStateValue = 10; - expect(numberState.set).not.toHaveBeenCalled(); - LogMock.hasLoggedCode('14:03:05'); + dummyState.invert(); + + expect(dummyState.set).toHaveBeenCalledWith(-10); + }); + + it('should invert value of the type array', () => { + dummyState.nextStateValue = ['1', '2', '3']; + + dummyState.invert(); + + expect(dummyState.set).toHaveBeenCalledWith(['3', '2', '1']); + }); + + it('should invert value of the type string', () => { + dummyState.nextStateValue = 'jeff'; + + dummyState.invert(); + + expect(dummyState.set).toHaveBeenCalledWith('ffej'); + }); + + it("shouldn't invert not invertible types like function, null, undefined, object", () => { + dummyState.nextStateValue = () => { + // empty + }; + + dummyState.invert(); + + expect(dummyState.set).not.toHaveBeenCalled(); + LogMock.hasLoggedCode('14:03:05', ['function']); }); }); diff --git a/packages/react/src/hooks/useAgile.ts b/packages/react/src/hooks/useAgile.ts index f38d04d3..7061ed61 100644 --- a/packages/react/src/hooks/useAgile.ts +++ b/packages/react/src/hooks/useAgile.ts @@ -56,7 +56,7 @@ export function useAgile< // Creates Return Value of Hook, depending if deps are in Array shape or not const getReturnValue = ( - depsArray: (State | Observer | undefined)[] + depsArray: (Observer | undefined)[] ): AgileHookArrayType | AgileHookType => { const handleReturn = ( dep: State | Observer | undefined From 305f55340f4bb7f18d148233e9bae1f535e902df Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 6 Jun 2021 12:15:00 +0200 Subject: [PATCH 045/117] refactored the selector method descriptions --- .../src/collection/collection.persistent.ts | 3 +- packages/core/src/collection/group.ts | 11 +- packages/core/src/collection/index.ts | 39 +++-- packages/core/src/collection/item.ts | 5 +- packages/core/src/collection/selector.ts | 7 +- packages/core/src/computed/index.ts | 10 +- packages/core/src/logCodeManager.ts | 5 +- packages/core/src/state/index.ts | 142 +++++++++--------- packages/core/tests/unit/state/state.test.ts | 15 +- packages/utils/src/index.ts | 5 +- 10 files changed, 129 insertions(+), 113 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index b3831b6c..d7e099a8 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -16,6 +16,7 @@ import { export class CollectionPersistent< DataType extends Object = DefaultItem > extends Persistent { + // Collection the Persistent belongs to public collection: () => Collection; static defaultGroupSideEffectKey = 'rebuildGroupStorageValue'; @@ -226,7 +227,7 @@ export class CollectionPersistent< item?.persist(itemStorageKey, { defaultStorageKey: this.config.defaultStorageKey || undefined, storageKeys: this.storageKeys, - followCollectionPersistKeyPattern: false,// Because of the dynamic 'storageItemKey', the key is already formatted above + followCollectionPersistKeyPattern: false, // Because of the dynamic 'storageItemKey', the key is already formatted above }); } diff --git a/packages/core/src/collection/group.ts b/packages/core/src/collection/group.ts index 0ac01960..52dd9b6e 100644 --- a/packages/core/src/collection/group.ts +++ b/packages/core/src/collection/group.ts @@ -20,13 +20,18 @@ import { export class Group extends State< Array > { + // Collection the Group belongs to collection: () => Collection; static rebuildGroupSideEffectKey = 'rebuildGroup'; - _output: Array = []; // Item values represented by the Group - _items: Array<() => Item> = []; // Items represented by the Group - notFoundItemKeys: Array = []; // Contains all Item identifiers for Items that couldn't be found in the Collection + // Item values represented by the Group + _output: Array = []; + // Items represented by the Group + _items: Array<() => Item> = []; + + // Keeps track of all Item identifiers for Items that couldn't be found in the Collection + notFoundItemKeys: Array = []; /** * An extension of the State Class that categorizes and preserves the ordering of structured data. diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index f8c13c6a..4e944901 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -24,21 +24,30 @@ import { } from '../internal'; export class Collection { + // Agile Instance the Collection belongs to public agileInstance: () => Agile; public config: CollectionConfigInterface; private initialConfig: CreateCollectionConfigInterface; + // Key/Name identifier of the Collection public _key?: CollectionKey; - public size = 0; // Amount of the Items stored in the Collection - public data: { [key: string]: Item } = {}; // Items stored in the Collection - public isPersisted = false; // Whether the Collection is persisted in an external Storage - public persistent: CollectionPersistent | undefined; // Manages persisting the Collection 'value' + // Amount of the Items stored in the Collection + public size = 0; + // Items stored in the Collection + public data: { [key: string]: Item } = {}; + // Whether the Collection is persisted in an external Storage + public isPersisted = false; + // Manages the permanent persistent in external Storages + public persistent: CollectionPersistent | undefined; - public groups: { [key: string]: Group } = {}; // Groups of Collection - public selectors: { [key: string]: Selector } = {}; // Selectors of Collection + // Registered Groups of Collection + public groups: { [key: string]: Group } = {}; + // Registered Selectors of Collection + public selectors: { [key: string]: Selector } = {}; - public isInstantiated = false; // Whether the Collection was instantiated correctly + // Whether the Collection was instantiated correctly + public isInstantiated = false; /** * A Collection manages a reactive set of Information @@ -128,7 +137,7 @@ export class Collection { // Update Collection key this._key = value; - // Update key in Persistent (only if oldKey equal to persistentKey + // Update key in Persistent (only if oldKey is equal to persistentKey // because otherwise the persistentKey is detached from the Collection key // -> not managed by Collection anymore) if (value && this.persistent?._key === oldKey) @@ -455,7 +464,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasgroup) * * @public - * @param groupKey - Key/Name identifier of the Group. + * @param groupKey - Key/Name identifier of the Group to be checked for existence. * @param config - Configuration object */ public hasGroup( @@ -628,7 +637,7 @@ export class Collection { * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#hasselector) * * @public - * @param selectorKey - Key/Name identifier of the Selector. + * @param selectorKey - Key/Name identifier of the Selector to be checked for existence. * @param config - Configuration object */ public hasSelector( @@ -975,27 +984,25 @@ export class Collection { * Fires immediately after the persisted `value` * is loaded into the Collection from a corresponding external Storage. * - * Registering this callback only makes sense + * Registering such callback function makes only sense * when the Collection is [persisted](https://agile-ts.org/docs/core/collection/methods/#persist) in an external Storage. * * [Learn more..](https://agile-ts.org/docs/core/collection/methods/#onload) * * @public - * @param callback - Callback function + * @param callback - A function to be executed after the externally persisted `value` was loaded into the Collection. */ public onLoad(callback: (success: boolean) => void): this { if (!this.persistent) return this; - - // Check if provided callback is valid function if (!isFunction(callback)) { LogCodeManager.log('00:03:01', ['OnLoad Callback', 'function']); return this; } - // Register provided callback + // Register specified callback this.persistent.onLoad = callback; - // If Collection is already persisted ('isPersisted') fire provided callback immediately + // If Collection is already persisted ('isPersisted') fire specified callback immediately if (this.isPersisted) callback(true); return this; diff --git a/packages/core/src/collection/item.ts b/packages/core/src/collection/item.ts index d78b4626..5375b143 100644 --- a/packages/core/src/collection/item.ts +++ b/packages/core/src/collection/item.ts @@ -15,10 +15,13 @@ import { export class Item extends State< DataType > { + // Collection the Group belongs to public collection: () => Collection; static updateGroupSideEffectKey = 'rebuildGroup'; - public selectedBy: Set = new Set(); // Key/Name Identifiers of Selectors which have selected the Item + + // Key/Name identifiers of Selectors which have selected the Item + public selectedBy: Set = new Set(); /** * An extension of the State Class that represents a single data object of a Collection. diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index 15a32b72..1366f24d 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -11,14 +11,17 @@ import { export class Selector extends State< DataType | undefined > { + // Collection the Selector belongs to public collection: () => Collection; static unknownItemPlaceholderKey = '__UNKNOWN__ITEM__KEY__'; static rebuildSelectorSideEffectKey = 'rebuildSelector'; static rebuildItemSideEffectKey = 'rebuildItem'; - public _item: Item | undefined; // Item the Selector represents - public _itemKey: ItemKey; // Key/Name identifier of the Item the Selector represents + // Item the Selector represents + public _item: Item | undefined; + // Key/Name identifier of the Item the Selector represents + public _itemKey: ItemKey; /** * A Selector represents an Item from a Collection in the long term. diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index 2bb0852d..983fc42c 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -15,11 +15,15 @@ import { export class Computed extends State< ComputedValueType > { + // Agile Instance the Computed belongs to public agileInstance: () => Agile; - public computeFunction: () => ComputedValueType; // Function to compute the Computed Class value - public deps: Array = []; // All dependencies the Computed Class depends on (including hardCoded and automatically detected dependencies) - public hardCodedDeps: Array = []; // Only hardCoded dependencies the Computed Class depends + // Function to compute the Computed Class value + public computeFunction: () => ComputedValueType; + // All dependencies the Computed Class depends on (including hardCoded and automatically detected dependencies) + public deps: Array = []; + // Only hardCoded dependencies the Computed Class depends on + public hardCodedDeps: Array = []; /** * An extension of the State Class that computes its value based on a compute function. diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index 700b0a26..28694b93 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -63,9 +63,8 @@ const logCodeMessages = { '14:03:01': "'${1}' is a not supported type! Supported types are: String, Boolean, Array, Object, Number.", '14:03:02': "The 'patch()' method works only in object based States!", - '14:03:03': "Watcher Callback with the key/name '${0}' already exists!", - '14:03:04': 'Only one Interval can be active at once!', - '14:03:05': "Failed to invert value of the type '${0}'!", + '14:03:03': 'Only one Interval can be active at once!', + '14:03:04': "Failed to invert value of the type '${0}'!", // SubController '15:01:00': "Unregistered 'Callback' based Subscription.", diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index 34f69297..0fbe3bb3 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -20,37 +20,54 @@ import { } from '../internal'; export class State { + // Agile Instance the State belongs to public agileInstance: () => Agile; + // Key/Name identifier of the State public _key?: StateKey; - public valueType?: string; // Primitive type which constrains the State value (for basic typesafety in Javascript) - public isSet = false; // Whether the current value differs from the initial value + // Primitive type which constrains the State value (for basic typesafety in Javascript) + public valueType?: string; + // Whether the current value differs from the initial value + public isSet = false; + // Whether the State is a placeholder and only exist in the background public isPlaceholder = false; - public initialStateValue: ValueType; // First value assigned to the State - public _value: ValueType; // Current value of the State - public previousStateValue: ValueType; // Previous value of the State - public nextStateValue: ValueType; // Next value of the State (which can be used for dynamic State updates) - - // Handles dependencies to other States and subscriptions of UI-Components - // and serves as an interface to the runtime + // First value assigned to the State + public initialStateValue: ValueType; + // Current value of the State + public _value: ValueType; + // Previous value of the State + public previousStateValue: ValueType; + // Next value of the State (which can be used for dynamic State updates) + public nextStateValue: ValueType; + + // Manages dependencies to other States and subscriptions of UI-Components. + // It also serves as an interface to the runtime. public observer: StateObserver; + // Registered side effects of changing the State value public sideEffects: { [key: string]: SideEffectInterface>; - } = {}; // Side effects of changing the State value - public computeValueMethod?: ComputeValueMethod; // Method for dynamically computing the State value - public computeExistsMethod: ComputeExistsMethod; // Method for dynamically computing the existence of the State + } = {}; + + // Method for dynamically computing the State value + public computeValueMethod?: ComputeValueMethod; + // Method for dynamically computing the existence of the State + public computeExistsMethod: ComputeExistsMethod; - public isPersisted = false; // Whether the State is persisted in an external Storage - public persistent: StatePersistent | undefined; // Manages persisting the State value + // Whether the State is persisted in an external Storage + public isPersisted = false; + // Manages the permanent persistent in external Storages + public persistent: StatePersistent | undefined; - public watchers: { [key: string]: StateWatcherCallback } = {}; // Callbacks that are fired on State changes + // Registered callbacks that are fired on each State value change + public watchers: { [key: string]: StateWatcherCallback } = {}; - public currentInterval?: NodeJS.Timer | number; // The current active interval + // When an interval is active, the 'intervalId' to clear the interval is temporary stored here + public currentInterval?: NodeJS.Timer | number; /** * A State manages a piece of Information - * that we need to remember globally at a later point int time. + * that we need to remember globally at a later point in time. * While providing a toolkit to use and mutate this set of Information. * * You can create as many global States as you need. @@ -89,7 +106,8 @@ export class State { } /** - * Updates the value of the State. + * Assigns a new value to the State + * and rerenders all subscribed Components. * * [Learn more..](https://agile-ts.org/docs/core/state/properties#value) * @@ -101,7 +119,7 @@ export class State { } /** - * Returns a reference-free version of the State value. + * Returns a reference-free version of the current State value. * * [Learn more..](https://agile-ts.org/docs/core/state/properties#value) * @@ -152,7 +170,7 @@ export class State { // Update key of Observer this.observer._key = value; - // Update key in Persistent (only if oldKey equal to persistentKey + // Update key in Persistent (only if oldKey is equal to persistentKey // because otherwise the persistentKey is detached from the State key // -> not managed by State anymore) if (value && this.persistent?._key === oldKey) @@ -162,7 +180,8 @@ export class State { } /** - * Assigns a new value to the State. + * Assigns a new value to the State + * and rerenders all subscribed Components. * * [Learn more..](https://agile-ts.org/docs/core/state/methods/#set) * @@ -181,7 +200,7 @@ export class State { ? (value as any)(copy(this._value)) : value; - // Check value has correct type (Javascript) + // Check if value has correct type (Javascript) if (!this.hasCorrectType(_value)) { LogCodeManager.log(config.force ? '14:02:00' : '14:03:00', [ typeof _value, @@ -199,7 +218,8 @@ export class State { /** * Ingests the State without any specified new value into the runtime. * - * Since no new value was defined either the State value is computed (Computed Class) + * Since no new value was defined either the State value is computed + * based on a compute method (Computed Class) * or the `nextStateValue` is taken. * * [Learn more..](https://agile-ts.org/docs/core/state/methods/#ingest) @@ -260,11 +280,11 @@ export class State { /** * Merges the specified `targetWithChanges` object into the current State value. - * This merge can be different for different value combinations: - * - If the current State value is an `object`, it does partial update for the object. - * - If the current State value is an `array` an the argument is an array too, - * it concatenates the current value with the value of the argument. - * - If the current State value is not a `object` or an `array` it prints an warning. + * This merge can differ for different value combinations: + * - If the current State value is an `object`, it does a partial update for the object. + * - If the current State value is an `array` and the specified argument is an array too, + * it concatenates the current State value with the value of the argument. + * - If the current State value is neither an `object` nor an `array`, the patch can't be performed. * * [Learn more..](https://agile-ts.org/docs/core/state/methods/#patch) * @@ -321,7 +341,7 @@ export class State { * [Learn more..](https://agile-ts.org/docs/core/state/methods/#watch) * * @public - * @param callback - Callback function + * @param callback - A function to be executed on each State value change. */ public watch(callback: StateWatcherCallback): string; /** @@ -331,7 +351,7 @@ export class State { * * @public * @param key - Key/Name identifier of the watcher callback. - * @param callback - Callback function + * @param callback - A function to be executed on each State value change. */ public watch(key: string, callback: StateWatcherCallback): this; public watch( @@ -350,18 +370,10 @@ export class State { _callback = callback as StateWatcherCallback; } - // Check if specified callback is a valid function if (!isFunction(_callback)) { LogCodeManager.log('00:03:01', ['Watcher Callback', 'function']); return this; } - - // Check if watcher with key is already occupied - if (this.watchers[key]) { - LogCodeManager.log('14:03:03', [key]); - return this; - } - this.watchers[key] = _callback; return generateKey ? key : this; } @@ -393,12 +405,12 @@ export class State { } /** - * Fires on the first State value assignment and then destroys itself. + * Fires on the initial State value assignment and then destroys itself. * * [Learn more..](https://agile-ts.org/docs/core/state/methods/#oninaugurated) * * @public - * @param callback - Callback function + * @param callback - A function to be executed after the first State value assignment. */ public onInaugurated(callback: StateWatcherCallback): this { const watcherKey = 'InauguratedWatcherKey'; @@ -474,14 +486,15 @@ export class State { /** * Fires immediately after the persisted `value` - * is loaded into the State from corresponding the external Storage. + * is loaded into the State from a corresponding external Storage. * - * Registering this callback only makes sense when the State is [persisted](https://agile-ts.org/docs/core/state/methods/#persist). + * Registering such callback function makes only sense + * when the State is [persisted](https://agile-ts.org/docs/core/state/methods/#persist). * * [Learn more..](https://agile-ts.org/docs/core/state/methods/#onload) * * @public - * @param callback - Callback function + * @param callback - A function to be executed after the externally persisted `value` was loaded into the State. */ public onLoad(callback: (success: boolean) => void): this { if (!this.persistent) return this; @@ -500,7 +513,7 @@ export class State { } /** - * Repeatedly calls a function or executes a code snippet, + * Repeatedly calls the specified callback function, * with a fixed time delay between each call. * * [Learn more..](https://agile-ts.org/docs/core/state/methods/#interval) @@ -519,15 +532,12 @@ export class State { return this; } if (this.currentInterval) { - LogCodeManager.log('14:03:04', [], this.currentInterval); + LogCodeManager.log('14:03:03', [], this.currentInterval); return this; } - - // Create interval this.currentInterval = setInterval(() => { this.set(handler(this._value)); }, delay ?? 1000); - return this; } @@ -577,7 +587,6 @@ export class State { return this; } this.computeExistsMethod = method; - return this; } @@ -659,24 +668,24 @@ export class State { this.set((this.nextStateValue * -1) as any); break; default: - LogCodeManager.log('14:03:05', [typeof this.nextStateValue]); + LogCodeManager.log('14:03:04', [typeof this.nextStateValue]); } - return this; } /** * - * Registers a `callback` function that is executed during the `runtime` + * Registers a `callback` function that is executed in the `runtime` * as a side effect of State changes. * - * For example it is called when the State value changes from 'jeff' to 'hans'. + * For example, it is called when the State value changes from 'jeff' to 'hans'. * - * A typical side effect could be the updating of the external Storage value. + * A typical side effect of a State change + * could be the updating of the external Storage value. * * @internal * @param key - Key/Name identifier of the to register side effect. - * @param callback - Callback function to be called on each State value change. + * @param callback - Callback function to be fired on each State value change. * @param config - Configuration object */ public addSideEffect>( @@ -724,14 +733,12 @@ export class State { return !!this.sideEffects[key]; } - //========================================================================================================= - // Is Correct Type - //========================================================================================================= /** + * Returns a boolean indicating whether the passed value + * is of the before defined State `valueType` or not. + * * @internal - * Checks if Value has correct valueType (js) - * Note: If no valueType set, it returns true - * @param value - Value that gets checked for its correct Type + * @param value - Value to be checked for the correct type. */ public hasCorrectType(value: any): boolean { if (!this.valueType) return true; @@ -764,11 +771,6 @@ export class State { export type StateKey = string | number; -/** - * @param key - Key/Name of State - * @param deps - Initial deps of State - * @param isPlaceholder - If State is initially a Placeholder - */ export interface StateConfigInterface { /** * Key/Name identifier of the State. @@ -777,7 +779,7 @@ export interface StateConfigInterface { key?: StateKey; /** * Observers that depend on the State. - * @default undefined + * @default [] */ dependents?: Array; /** @@ -836,13 +838,13 @@ export type SideEffectFunctionType> = ( export interface SideEffectInterface> { /** - * Callback function to be called on every State value change + * Callback function to be called on every State value change. * @return () => {} */ callback: SideEffectFunctionType; /** * Weight of the side effect. - * Determines the order of execution of the registered side effects. + * The weight determines the order of execution of the registered side effects. * The higher the weight, the earlier it is executed. */ weight: number; @@ -851,7 +853,7 @@ export interface SideEffectInterface> { export interface AddSideEffectConfigInterface { /** * Weight of the side effect. - * Determines the order of execution of the registered side effects. + * The weight determines the order of execution of the registered side effects. * The higher the weight, the earlier it is executed. */ weight?: number; diff --git a/packages/core/tests/unit/state/state.test.ts b/packages/core/tests/unit/state/state.test.ts index 84cccca9..b3914e79 100644 --- a/packages/core/tests/unit/state/state.test.ts +++ b/packages/core/tests/unit/state/state.test.ts @@ -532,17 +532,6 @@ describe('State Tests', () => { expect(numberState.watchers).not.toHaveProperty('dummyKey'); LogMock.hasLoggedCode('00:03:01', ['Watcher Callback', 'function']); }); - - it("shouldn't add passed watcherFunction to watchers at passed key if passed key is already occupied", () => { - numberState.watchers['dummyKey'] = dummyCallbackFunction2; - - const response = numberState.watch('dummyKey', dummyCallbackFunction1); - - expect(response).toBe(numberState); - expect(numberState.watchers).toHaveProperty('dummyKey'); - expect(numberState.watchers['dummyKey']).toBe(dummyCallbackFunction2); - LogMock.hasLoggedCode('14:03:03', ['dummyKey']); - }); }); describe('removeWatcher function tests', () => { @@ -789,7 +778,7 @@ describe('State Tests', () => { 3000 ); expect(numberState.currentInterval).toStrictEqual(currentInterval); - LogMock.hasLoggedCode('14:03:04', [], numberState.currentInterval); + LogMock.hasLoggedCode('14:03:03', [], numberState.currentInterval); }); it("shouldn't set invalid interval callback function", () => { @@ -972,7 +961,7 @@ describe('State Tests', () => { dummyState.invert(); expect(dummyState.set).not.toHaveBeenCalled(); - LogMock.hasLoggedCode('14:03:05', ['function']); + LogMock.hasLoggedCode('14:03:04', ['function']); }); }); diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index e02d5cc3..e9104543 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -203,7 +203,10 @@ export function flatMerge( // Merge Changes Object into Source Object const keys = Object.keys(changes); keys.forEach((property) => { - if (config.addNewProperties && _source[property] != null) + if ( + (!config.addNewProperties && _source[property] != null) || + config.addNewProperties + ) _source[property] = changes[property]; }); From d9249255d6e352e2b1d8a21aab9189d5a46f3c48 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 6 Jun 2021 16:29:36 +0200 Subject: [PATCH 046/117] added weakmap based selectors to SubscriptionContainer --- packages/core/src/collection/index.ts | 4 +- packages/core/src/runtime/index.ts | 124 +++++--------- .../CallbackSubscriptionContainer.ts | 21 ++- .../ComponentSubscriptionContainer.ts | 29 +++- .../container/SubscriptionContainer.ts | 158 ++++++++++++++---- .../runtime/subscription/sub.controller.ts | 3 +- packages/core/src/state/index.ts | 2 +- .../core/tests/unit/runtime/runtime.test.ts | 60 +++---- .../CallbackSubscriptionContainer.test.ts | 6 +- .../ComponentSubscriptionContainer.test.ts | 6 +- .../container/SubscriptionContainer.test.ts | 12 +- .../subscription/sub.controller.test.ts | 10 +- packages/react/src/hooks/useAgile.ts | 35 +--- 13 files changed, 258 insertions(+), 212 deletions(-) diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 4e944901..e2d38d20 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -1486,12 +1486,12 @@ export type ItemKey = string | number; export interface CreateCollectionConfigInterface { /** - * Initial Groups of Collection. + * Initial Groups of the Collection. * @default [] */ groups?: { [key: string]: Group } | string[]; /** - * Initial Selectors of Collection + * Initial Selectors of the Collection * @default [] */ selectors?: { [key: string]: Selector } | string[]; diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index e3b1c409..036cc20a 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -155,17 +155,20 @@ export class Runtime { return; } - // Handle Object based Subscription - if (subscriptionContainer.isObjectBased) - this.handleObjectBasedSubscription(subscriptionContainer, job); - - // Check if subscriptionContainer should be updated - const updateSubscriptionContainer = subscriptionContainer.proxyBased - ? this.handleProxyBasedSubscription(subscriptionContainer, job) - : true; + let updateSubscriptionContainer = true; + + // Handle Selectors + if (subscriptionContainer.hasSelectors) { + updateSubscriptionContainer = this.handleSelectors( + subscriptionContainer, + job + ); + } - if (updateSubscriptionContainer) + if (updateSubscriptionContainer) { + subscriptionContainer.updatedSubscribers.push(job.observer); subscriptionsToUpdate.add(subscriptionContainer); + } job.subscriptionContainersToUpdate.delete(subscriptionContainer); }); @@ -183,8 +186,10 @@ export class Runtime { if (subscriptionContainer instanceof ComponentSubscriptionContainer) this.agileInstance().integrations.update( subscriptionContainer.component, - this.getObjectBasedProps(subscriptionContainer) + this.getUpdatedObserverValues(subscriptionContainer) ); + + subscriptionContainer.updatedSubscribers = []; }); Agile.logger.if @@ -194,56 +199,27 @@ export class Runtime { return true; } - //========================================================================================================= - // Handle Object Based Subscription - //========================================================================================================= /** + * Returns a key map with Observer values that have been updated. + * * @internal - * Finds key of Observer (Job) in subsObject and adds it to 'changedObjectKeys' * @param subscriptionContainer - Object based SubscriptionContainer - * @param job - Job that holds the searched Observer */ - public handleObjectBasedSubscription( - subscriptionContainer: SubscriptionContainer, - job: RuntimeJob - ): void { - let foundKey: string | null = null; - - // Check if SubscriptionContainer is Object based - if (!subscriptionContainer.isObjectBased) return; - - // Find Key of Job Observer in SubscriptionContainer - for (const key in subscriptionContainer.subsObject) - if (subscriptionContainer.subsObject[key] === job.observer) - foundKey = key; - - if (foundKey) subscriptionContainer.observerKeysToUpdate.push(foundKey); - } - - //========================================================================================================= - // Get Object Based Props - //========================================================================================================= - /** - * @internal - * Builds Object out of changedObjectKeys with Observer Value - * @param subscriptionContainer - Object based SubscriptionContainer - */ - public getObjectBasedProps( + public getUpdatedObserverValues( subscriptionContainer: SubscriptionContainer ): { [key: string]: any } { const props: { [key: string]: any } = {}; - // Map trough observerKeysToUpdate and build object out of Observer value - if (subscriptionContainer.subsObject) - for (const updatedKey of subscriptionContainer.observerKeysToUpdate) - props[updatedKey] = subscriptionContainer.subsObject[updatedKey]?.value; - - subscriptionContainer.observerKeysToUpdate = []; + // Map 'Observer To Update' values into the props object + for (const observer of subscriptionContainer.updatedSubscribers) { + const key = subscriptionContainer.subscriberKeysWeakMap.get(observer); + if (key != null) props[key] = observer.value; + } return props; } //========================================================================================================= - // Handle Proxy Based Subscription + // Handle Selectors //========================================================================================================= /** * @internal @@ -256,47 +232,25 @@ export class Runtime { * -> If a from the Proxy Tree detected property differs from the same property in the previous value * or the passed subscriptionContainer isn't properly proxy based */ - public handleProxyBasedSubscription( + public handleSelectors( subscriptionContainer: SubscriptionContainer, job: RuntimeJob ): boolean { - // Return true because in this cases the subscriptionContainer isn't properly proxyBased - if ( - !subscriptionContainer.proxyBased || - !job.observer._key || - !subscriptionContainer.proxyKeyMap[job.observer._key] - ) - return true; - - const paths = subscriptionContainer.proxyKeyMap[job.observer._key].paths; - - if (paths) { - for (const path of paths) { - // Get property in new Value located at path - let newValue = job.observer.value; - let newValueDeepness = 0; - for (const branch of path) { - if (!isValidObject(newValue, true)) break; - newValue = newValue[branch]; - newValueDeepness++; - } + // TODO add Selector support for Object based subscriptions + const selectors = subscriptionContainer.selectorsWeakMap.get(job.observer) + ?.selectors; - // Get property in previous Value located at path - let previousValue = job.observer.previousValue; - let previousValueDeepness = 0; - for (const branch of path) { - if (!isValidObject(previousValue, true)) break; - previousValue = previousValue[branch]; - previousValueDeepness++; - } - - // Check if found values differ - if ( - notEqual(newValue, previousValue) || - newValueDeepness !== previousValueDeepness - ) { - return true; - } + // Return true because in this cases the subscriptionContainer isn't properly proxyBased + if (!subscriptionContainer.hasSelectors || !selectors) return true; + + const previousValue = job.observer.previousValue; + const newValue = job.observer.value; + for (const selector of selectors) { + if ( + notEqual(selector(newValue), selector(previousValue)) + // || newValueDeepness !== previousValueDeepness // Not possible to check + ) { + return true; } } diff --git a/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts index e9771a0b..6b3339b5 100644 --- a/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts @@ -5,14 +5,27 @@ import { } from '../../../internal'; export class CallbackSubscriptionContainer extends SubscriptionContainer { + /** + * Callback function to trigger a rerender + * on the Component the Subscription Container represents. + */ public callback: Function; /** + * Subscription Container for callback based subscriptions. + * + * In a callback based subscription, a rerender is triggered on the Component via a specified callback function. + * + * The Callback Subscription Container doesn't keep track of the Component itself. + * It only knows how to trigger a rerender on the particular Component through the callback function. + * + * [Learn more..](https://agile-ts.org/docs/core/integration#callback-based) + * * @internal - * CallbackSubscriptionContainer - Subscription Container for Callback based Subscriptions - * @param callback - Callback Function that causes rerender on Component that is subscribed by Agile - * @param subs - Initial Subscriptions - * @param config - Config + * @param callback - Callback function to cause a rerender on the Component + * to be represented by the Subscription Container. + * @param subs - Observers to be subscribed to the Subscription Container. + * @param config - Configuration object */ constructor( callback: Function, diff --git a/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts index d71c3e2f..b161f70a 100644 --- a/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts @@ -4,18 +4,33 @@ import { SubscriptionContainerConfigInterface, } from '../../../internal'; -export class ComponentSubscriptionContainer extends SubscriptionContainer { - public component: any; +export class ComponentSubscriptionContainer< + C = any +> extends SubscriptionContainer { + /** + * Component the Subscription Container represents. + */ + public component: C; /** + * Subscription Container for component based subscriptions. + * + * In a component based subscription, a rerender is triggered on the Component via muting a local + * State Management instance of the Component. + * For example in a React Class Component the `this.state` property. + * + * The Component Subscription Container keeps track of the Component itself, + * in order to synchronize the Component State Management instance with the subscribed Observer values. + * + * [Learn more..](https://agile-ts.org/docs/core/integration#component-based) + * * @internal - * ComponentSubscriptionContainer - SubscriptionContainer for Component based Subscription - * @param component - Component that is subscribed by Agile - * @param subs - Initial Subscriptions - * @param config - Config + * @param component - Component to be represent by the Subscription Container. + * @param subs - Observers to be subscribed to the Subscription Container. + * @param config - Configuration object */ constructor( - component: any, + component: C, subs: Array = [], config: SubscriptionContainerConfigInterface = {} ) { diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index ef7c7bb5..58aa26df 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -1,73 +1,157 @@ import { defineConfig, generateId, - notEqual, + isValidObject, Observer, } from '../../../internal'; export class SubscriptionContainer { + /** + * Key/Name identifier of the Subscription Container. + */ public key?: SubscriptionContainerKeyType; + /** + * Whether the Subscription Container + * and the Component the Subscription Container represents are ready. + * So that the Subscription Container can trigger rerenders on the Component. + */ public ready = false; - public subscribers: Set; // Observers that are Subscribed to this SubscriptionContainer (Component) - - // Represents the paths to the accessed properties of the State/s this SubscriptionContainer represents - public proxyKeyMap: ProxyKeyMapInterface; - public proxyBased = false; + /** + * Observers that have subscribed the Subscription Container. + * + * The Observers use the Subscription Container + * as an interface to the Component the Subscription Container represents + * in order to cause rerenders on the Component. + */ + public subscribers: Set; + /** + * Temporary stores the subscribed Observers, + * that have been updated and are running through the runtime. + */ + public updatedSubscribers: Array = []; - // For Object based Subscription + /** + * Whether the Subscription Container is object based. + * + * A Observer is object based when the subscribed Observers were provided in a Observer key map. + * ``` + * { + * state1: Observer, + * state2: Observer + * } + * ``` + * Thus each Observer has its unique key stored in the 'subscribersWeakMap'. + * + * Often Component based Subscriptions are object based, + * because each Observer requires a unique identifier + * to properly merge the Observer value into the local State Management instance. + */ public isObjectBased = false; - public observerKeysToUpdate: Array = []; // Holds temporary keys of Observers that got updated (Note: keys based on 'subsObject') - public subsObject?: { [key: string]: Observer }; // Same as subs but in Object shape + /** + * Weak map for storing a key identifier for each Observer. + */ + public subscriberKeysWeakMap: WeakMap; + + /** + * Weak map representing Selectors of the Subscription Container. + */ + public selectorsWeakMap: SelectorWeakMapType; /** - * @internal * SubscriptionContainer - Represents Component/(Way to rerender Component) that is subscribed by Observer/s (Agile) * -> Used to cause rerender on Component - * @param subs - Initial Subscriptions - * @param config - Config + * + * + * A Subscription Container is like an interface to the Components. + * + * When a subscribed Observer value mutates a rerender is triggered on the Component + * through the Subscription Container. + * + * @internal + * @param subs - Observers to be subscribed to the Subscription Container. + * @param config - Configuration object */ constructor( subs: Array = [], config: SubscriptionContainerConfigInterface = {} ) { config = defineConfig(config, { - proxyKeyMap: {}, + proxyWeakMap: new WeakMap(), + selectorWeakMap: new WeakMap(), key: generateId(), }); this.subscribers = new Set(subs); this.key = config.key; - this.proxyKeyMap = config.proxyKeyMap as any; - this.proxyBased = notEqual(this.proxyKeyMap, {}); + this.subscriberKeysWeakMap = new WeakMap(); + + // Create for each proxy path a Selector, + // which selects the property at the path + const selectorWeakMap: SelectorWeakMapType = config.selectorWeakMap as any; + + for (const observer of subs) { + const paths = config.proxyWeakMap?.get(observer)?.paths; + + if (paths != null) { + const selectors: SelectorMethodType[] = []; + for (const path of paths) { + selectors.push((value) => { + let _value = value; + for (const branch of path) { + if (!isValidObject(_value, true)) break; + _value = _value[branch]; + } + return _value; + }); + } + selectorWeakMap.set(observer, { selectors }); + } + } + + this.selectorsWeakMap = selectorWeakMap; } } export type SubscriptionContainerKeyType = string | number; -/** - * @param proxyKeyMap - A keymap with a 2 dimensional arrays with paths/routes to particular properties in the State at key. - * The subscriptionContainer will then only rerender the Component, when a property at a given path changes. - * Not anymore if anything in the State object mutates, although it might not even be displayed in the Component. - * For example: - * { - * myState1: {paths: [['data', 'name']]}, - * myState2: {paths: [['car', 'speed']]} - * } - * Now the subscriptionContain will only trigger a rerender on the Component - * if 'data.name' in myState1 or 'car.speed' in myState2 changes. - * If, for instance, 'data.age' in myState1 mutates it won't trigger a rerender, - * since 'data.age' isn't represented in the proxyKeyMap. - * - * These particular paths can be tracked with the ProxyTree. - * https://github.com/agile-ts/agile/tree/master/packages/proxytree - * @param key - Key/Name of Subscription Container - */ export interface SubscriptionContainerConfigInterface { - proxyKeyMap?: ProxyKeyMapInterface; + /** + * Key/Name identifier of Subscription Container + * @default undefined + */ key?: SubscriptionContainerKeyType; + /** + * A keymap with a 2 dimensional arrays representing paths/routes to particular properties in the State at key. + * The subscriptionContainer will then only rerender the Component, when a property at a given path changes. + * Not anymore if anything in the State object mutates, although it might not even be displayed in the Component. + * For example: + * { + * myState1: {paths: [['data', 'name']]}, + * myState2: {paths: [['car', 'speed']]} + * } + * Now the subscriptionContain will only trigger a rerender on the Component + * if 'data.name' in myState1 or 'car.speed' in myState2 changes. + * If, for instance, 'data.age' in myState1 mutates it won't trigger a rerender, + * since 'data.age' isn't represented in the proxyKeyMap. + * + * These particular paths can be tracked with the ProxyTree. + * https://github.com/agile-ts/agile/tree/master/packages/proxytree + * + * @default {} + */ + proxyWeakMap?: ProxyWeakMapType; + /** + * TODO + * @default undefined + */ + selectorWeakMap?: SelectorWeakMapType; } -export interface ProxyKeyMapInterface { - [key: string]: { paths: string[][] }; -} +export type ProxyWeakMapType = WeakMap; + +export type SelectorWeakMapType = WeakMap< + Observer, + { selectors: SelectorMethodType[] } +>; +export type SelectorMethodType = (value: T) => any; diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index fbd9b37c..c66eb1e1 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -61,7 +61,8 @@ export class SubController { // Set SubscriptionContainer to Object based subscriptionContainer.isObjectBased = true; - subscriptionContainer.subsObject = subs; + for (const key in subs) + subscriptionContainer.subscriberKeysWeakMap.set(subs[key], key); // Register subs and build props object for (const key in subs) { diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index 0fbe3bb3..1e70c201 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -74,7 +74,7 @@ export class State { * * @public * @param agileInstance - Instance of Agile the State belongs to. - * @param initialValue - Initial value of State. + * @param initialValue - Initial value of the State. * @param config - Configuration object */ constructor( diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts index 95781f72..f129fed4 100644 --- a/packages/core/tests/unit/runtime/runtime.test.ts +++ b/packages/core/tests/unit/runtime/runtime.test.ts @@ -236,7 +236,7 @@ describe('Runtime Tests', () => { jest.spyOn(dummyAgile.integrations, 'update'); jest.spyOn(runtime, 'handleObjectBasedSubscription'); - jest.spyOn(runtime, 'handleProxyBasedSubscription'); + jest.spyOn(runtime, 'handleSelectors'); }); it('should return false if agile has no integration', () => { @@ -272,7 +272,7 @@ describe('Runtime Tests', () => { expect(runtime.jobsToRerender).toStrictEqual([]); expect(runtime.notReadyJobsToRerender.size).toBe(0); - expect(runtime.handleProxyBasedSubscription).not.toHaveBeenCalled(); + expect(runtime.handleSelectors).not.toHaveBeenCalled(); expect(dummyAgile.integrations.update).toHaveBeenCalledTimes(1); expect(dummyAgile.integrations.update).toHaveBeenCalledWith( @@ -299,7 +299,7 @@ describe('Runtime Tests', () => { expect(runtime.jobsToRerender).toStrictEqual([]); expect(runtime.notReadyJobsToRerender.size).toBe(0); - expect(runtime.handleProxyBasedSubscription).not.toHaveBeenCalled(); + expect(runtime.handleSelectors).not.toHaveBeenCalled(); expect(rCallbackSubContainer.callback).toHaveBeenCalledTimes(1); expect(rCallbackSubJob.subscriptionContainersToUpdate.size).toBe(0); @@ -309,11 +309,9 @@ describe('Runtime Tests', () => { }); it('should update ready proxy, callback based SubscriptionContainer if handleProxyBasedSubscriptions() returns true', () => { - jest - .spyOn(runtime, 'handleProxyBasedSubscription') - .mockReturnValueOnce(true); + jest.spyOn(runtime, 'handleSelectors').mockReturnValueOnce(true); dummyAgile.hasIntegration = jest.fn(() => true); - rCallbackSubContainer.proxyBased = true; + rCallbackSubContainer.isProxyBased = true; rCallbackSubContainer.proxyKeyMap = dummyProxyKeyMap; runtime.jobsToRerender.push(rCallbackSubJob); @@ -321,7 +319,7 @@ describe('Runtime Tests', () => { expect(runtime.jobsToRerender).toStrictEqual([]); expect(runtime.notReadyJobsToRerender.size).toBe(0); - expect(runtime.handleProxyBasedSubscription).toHaveBeenCalledWith( + expect(runtime.handleSelectors).toHaveBeenCalledWith( rCallbackSubContainer, rCallbackSubJob ); @@ -334,11 +332,9 @@ describe('Runtime Tests', () => { }); it("shouldn't update ready proxy, callback based SubscriptionContainer if handleProxyBasedSubscriptions() returns false", () => { - jest - .spyOn(runtime, 'handleProxyBasedSubscription') - .mockReturnValueOnce(false); + jest.spyOn(runtime, 'handleSelectors').mockReturnValueOnce(false); dummyAgile.hasIntegration = jest.fn(() => true); - rCallbackSubContainer.proxyBased = true; + rCallbackSubContainer.isProxyBased = true; rCallbackSubContainer.proxyKeyMap = dummyProxyKeyMap; runtime.jobsToRerender.push(rCallbackSubJob); @@ -346,7 +342,7 @@ describe('Runtime Tests', () => { expect(runtime.jobsToRerender).toStrictEqual([]); expect(runtime.notReadyJobsToRerender.size).toBe(0); - expect(runtime.handleProxyBasedSubscription).toHaveBeenCalledWith( + expect(runtime.handleSelectors).toHaveBeenCalledWith( rCallbackSubContainer, rCallbackSubJob ); @@ -561,9 +557,7 @@ describe('Runtime Tests', () => { arrayJob ); - expect(arraySubscriptionContainer.observerKeysToUpdate).toStrictEqual( - [] - ); + expect(arraySubscriptionContainer.updatedSubscribers).toStrictEqual([]); }); it('should add Job Observer to changedObjectKeys in SubscriptionContainer', () => { @@ -572,7 +566,7 @@ describe('Runtime Tests', () => { objectJob1 ); - expect(objectSubscriptionContainer.observerKeysToUpdate).toStrictEqual([ + expect(objectSubscriptionContainer.updatedSubscribers).toStrictEqual([ 'observer1', ]); }); @@ -598,18 +592,18 @@ describe('Runtime Tests', () => { }); it('should build Observer Value Object out of observerKeysToUpdate and Value of Observer', () => { - subscriptionContainer.observerKeysToUpdate.push('observer1'); - subscriptionContainer.observerKeysToUpdate.push('observer2'); - subscriptionContainer.observerKeysToUpdate.push('observer3'); + subscriptionContainer.updatedSubscribers.push('observer1'); + subscriptionContainer.updatedSubscribers.push('observer2'); + subscriptionContainer.updatedSubscribers.push('observer3'); - const props = runtime.getObjectBasedProps(subscriptionContainer); + const props = runtime.getUpdatedObserverValues(subscriptionContainer); expect(props).toStrictEqual({ observer1: 'dummyObserverValue1', observer2: undefined, observer3: 'dummyObserverValue3', }); - expect(subscriptionContainer.observerKeysToUpdate).toStrictEqual([]); + expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); }); }); @@ -640,7 +634,7 @@ describe('Runtime Tests', () => { key: 'dummyObserverValue1', data: { name: 'jeff' }, }; - objectSubscriptionContainer.proxyBased = true; + objectSubscriptionContainer.isProxyBased = true; objectSubscriptionContainer.proxyKeyMap = { [dummyObserver1._key || 'unknown']: { paths: [['data', 'name']] }, }; @@ -672,7 +666,7 @@ describe('Runtime Tests', () => { data: { name: 'hans' }, }, ]; - arraySubscriptionContainer.proxyBased = true; + arraySubscriptionContainer.isProxyBased = true; arraySubscriptionContainer.proxyKeyMap = { [dummyObserver2._key || 'unknown']: { paths: [['0', 'data', 'name']], @@ -688,9 +682,9 @@ describe('Runtime Tests', () => { }); it("should return true if subscriptionContainer isn't proxy based", () => { - objectSubscriptionContainer.proxyBased = false; + objectSubscriptionContainer.isProxyBased = false; - const response = runtime.handleProxyBasedSubscription( + const response = runtime.handleSelectors( objectSubscriptionContainer, objectJob ); @@ -702,7 +696,7 @@ describe('Runtime Tests', () => { it('should return true if observer the job represents has no key', () => { objectJob.observer._key = undefined; - const response = runtime.handleProxyBasedSubscription( + const response = runtime.handleSelectors( objectSubscriptionContainer, objectJob ); @@ -716,7 +710,7 @@ describe('Runtime Tests', () => { unknownKey: { paths: [['a', 'b']] }, }; - const response = runtime.handleProxyBasedSubscription( + const response = runtime.handleSelectors( objectSubscriptionContainer, objectJob ); @@ -731,7 +725,7 @@ describe('Runtime Tests', () => { data: { name: 'hans' }, }; - const response = runtime.handleProxyBasedSubscription( + const response = runtime.handleSelectors( objectSubscriptionContainer, objectJob ); @@ -744,7 +738,7 @@ describe('Runtime Tests', () => { }); it("should return false if used property hasn't changed (object value)", () => { - const response = runtime.handleProxyBasedSubscription( + const response = runtime.handleSelectors( objectSubscriptionContainer, objectJob ); @@ -765,7 +759,7 @@ describe('Runtime Tests', () => { data: { name: undefined }, }; - const response = runtime.handleProxyBasedSubscription( + const response = runtime.handleSelectors( objectSubscriptionContainer, objectJob ); @@ -786,7 +780,7 @@ describe('Runtime Tests', () => { }, ]; - const response = runtime.handleProxyBasedSubscription( + const response = runtime.handleSelectors( arraySubscriptionContainer, arrayJob ); @@ -799,7 +793,7 @@ describe('Runtime Tests', () => { }); it("should return false if used property hasn't changed (array value)", () => { - const response = runtime.handleProxyBasedSubscription( + const response = runtime.handleSelectors( arraySubscriptionContainer, arrayJob ); diff --git a/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts index 080c0439..80688764 100644 --- a/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts @@ -38,11 +38,11 @@ describe('CallbackSubscriptionContainer Tests', () => { expect(subscriptionContainer.subscribers.has(dummyObserver1)).toBeTruthy(); expect(subscriptionContainer.subscribers.has(dummyObserver2)).toBeTruthy(); expect(subscriptionContainer.isObjectBased).toBeFalsy(); - expect(subscriptionContainer.observerKeysToUpdate).toStrictEqual([]); - expect(subscriptionContainer.subsObject).toBeUndefined(); + expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); + expect(subscriptionContainer.subscriberKeysWeakMap).toBeUndefined(); expect(subscriptionContainer.proxyKeyMap).toStrictEqual({ myState: { paths: [['hi']] }, }); - expect(subscriptionContainer.proxyBased).toBeTruthy(); + expect(subscriptionContainer.isProxyBased).toBeTruthy(); }); }); diff --git a/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts index 04362b53..786c115a 100644 --- a/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts @@ -36,11 +36,11 @@ describe('ComponentSubscriptionContainer Tests', () => { expect(subscriptionContainer.subscribers.has(dummyObserver1)).toBeTruthy(); expect(subscriptionContainer.subscribers.has(dummyObserver2)).toBeTruthy(); expect(subscriptionContainer.isObjectBased).toBeFalsy(); - expect(subscriptionContainer.observerKeysToUpdate).toStrictEqual([]); - expect(subscriptionContainer.subsObject).toBeUndefined(); + expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); + expect(subscriptionContainer.subscriberKeysWeakMap).toBeUndefined(); expect(subscriptionContainer.proxyKeyMap).toStrictEqual({ myState: { paths: [['hi']] }, }); - expect(subscriptionContainer.proxyBased).toBeTruthy(); + expect(subscriptionContainer.isProxyBased).toBeTruthy(); }); }); diff --git a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts index 3c9c566a..ebc5fa91 100644 --- a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts @@ -25,10 +25,10 @@ describe('SubscriptionContainer Tests', () => { expect(subscriptionContainer.ready).toBeFalsy(); expect(subscriptionContainer.subscribers.size).toBe(0); expect(subscriptionContainer.isObjectBased).toBeFalsy(); - expect(subscriptionContainer.observerKeysToUpdate).toStrictEqual([]); - expect(subscriptionContainer.subsObject).toBeUndefined(); + expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); + expect(subscriptionContainer.subscriberKeysWeakMap).toBeUndefined(); expect(subscriptionContainer.proxyKeyMap).toStrictEqual({}); - expect(subscriptionContainer.proxyBased).toBeFalsy(); + expect(subscriptionContainer.isProxyBased).toBeFalsy(); }); it('should create SubscriptionContainer (specific config)', () => { @@ -43,11 +43,11 @@ describe('SubscriptionContainer Tests', () => { expect(subscriptionContainer.subscribers.has(dummyObserver1)).toBeTruthy(); expect(subscriptionContainer.subscribers.has(dummyObserver2)).toBeTruthy(); expect(subscriptionContainer.isObjectBased).toBeFalsy(); - expect(subscriptionContainer.observerKeysToUpdate).toStrictEqual([]); - expect(subscriptionContainer.subsObject).toBeUndefined(); + expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); + expect(subscriptionContainer.subscriberKeysWeakMap).toBeUndefined(); expect(subscriptionContainer.proxyKeyMap).toStrictEqual({ myState: { paths: [['a', 'b']] }, }); - expect(subscriptionContainer.proxyBased).toBeTruthy(); + expect(subscriptionContainer.isProxyBased).toBeTruthy(); }); }); diff --git a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts index 0dee1506..eb1fbc90 100644 --- a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts +++ b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts @@ -84,7 +84,7 @@ describe('SubController Tests', () => { ); expect(dummySubscriptionContainer.isObjectBased).toBeTruthy(); - expect(dummySubscriptionContainer.subsObject).toStrictEqual({ + expect(dummySubscriptionContainer.subscriberKeysWeakMap).toStrictEqual({ dummyObserver1: dummyObserver1, dummyObserver2: dummyObserver2, }); @@ -144,7 +144,9 @@ describe('SubController Tests', () => { ); expect(dummySubscriptionContainer.isObjectBased).toBeFalsy(); - expect(dummySubscriptionContainer.subsObject).toBeUndefined(); + expect( + dummySubscriptionContainer.subscriberKeysWeakMap + ).toBeUndefined(); expect(dummySubscriptionContainer.subscribers.size).toBe(2); expect( @@ -544,7 +546,7 @@ describe('SubController Tests', () => { // Note:This 'issue' happens in multiple parts of the AgileTs test expect(callbackSubscriptionContainer.key).toBe('randomKey'); expect(callbackSubscriptionContainer.proxyKeyMap).toStrictEqual({}); - expect(callbackSubscriptionContainer.proxyBased).toBeFalsy(); + expect(callbackSubscriptionContainer.isProxyBased).toBeFalsy(); expect(callbackSubscriptionContainer.subscribers.size).toBe(2); expect( @@ -584,7 +586,7 @@ describe('SubController Tests', () => { expect(callbackSubscriptionContainer.proxyKeyMap).toStrictEqual({ jeff: { paths: [[]] }, }); - expect(callbackSubscriptionContainer.proxyBased).toBeTruthy(); + expect(callbackSubscriptionContainer.isProxyBased).toBeTruthy(); expect(callbackSubscriptionContainer.subscribers.size).toBe(2); expect( diff --git a/packages/react/src/hooks/useAgile.ts b/packages/react/src/hooks/useAgile.ts index 7061ed61..ec4ccc17 100644 --- a/packages/react/src/hooks/useAgile.ts +++ b/packages/react/src/hooks/useAgile.ts @@ -12,6 +12,7 @@ import { isValidObject, ProxyKeyMapInterface, generateId, + ProxyWeakMapType, } from '@agile-ts/core'; import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; import { ProxyTree } from '@agile-ts/proxytree'; @@ -47,7 +48,7 @@ export function useAgile< config: AgileHookConfigInterface = {} ): AgileHookArrayType | AgileHookType { const depsArray = extractObservers(deps); - const proxyTreeMap: ProxyTreeMapInterface = {}; + const proxyWeakMap: ProxyWeakMapType = new WeakMap(); config = defineConfig(config, { proxyBased: false, key: generateId(), @@ -58,23 +59,16 @@ export function useAgile< const getReturnValue = ( depsArray: (Observer | undefined)[] ): AgileHookArrayType | AgileHookType => { - const handleReturn = ( - dep: State | Observer | undefined - ): AgileHookType => { + const handleReturn = (dep: Observer | undefined): AgileHookType => { const value = dep?.value; - const depKey = dep?.key; // If proxyBased and value is object wrap Proxy around it to track used properties if (config.proxyBased && isValidObject(value, true)) { - if (depKey) { - const proxyTree = new ProxyTree(value); - proxyTreeMap[depKey] = proxyTree; - return proxyTree.proxy; - } - Agile.logger.warn( - 'Keep in mind that without a key no Proxy can be wrapped around the dependency value!', - dep - ); + const proxyTree = new ProxyTree(value); + proxyWeakMap.set(dep, { + paths: proxyTree.getUsedRoutes() as any, + }); + return proxyTree.proxy; } return dep?.value; @@ -112,24 +106,13 @@ export function useAgile< (dep): dep is Observer => dep !== undefined ); - // Build Proxy Key Map - const proxyKeyMap: ProxyKeyMapInterface = {}; - if (config.proxyBased) { - for (const proxyTreeKey in proxyTreeMap) { - const proxyTree = proxyTreeMap[proxyTreeKey]; - proxyKeyMap[proxyTreeKey] = { - paths: proxyTree.getUsedRoutes() as any, - }; - } - } - // Create Callback based Subscription const subscriptionContainer = agileInstance.subController.subscribeWithSubsArray( () => { forceRender(); }, observers, - { key: config.key, proxyKeyMap, waitForMount: false } + { key: config.key, proxyWeakMap, waitForMount: false } ); // Unsubscribe Callback based Subscription on Unmount From c5025166f4d54aece5ee05e79118b0f925818131 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 6 Jun 2021 17:37:36 +0200 Subject: [PATCH 047/117] fixed typos --- packages/core/src/runtime/index.ts | 14 +++++--------- .../container/SubscriptionContainer.ts | 4 ++++ packages/react/src/hooks/useAgile.ts | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index 036cc20a..e2c27103 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -158,12 +158,10 @@ export class Runtime { let updateSubscriptionContainer = true; // Handle Selectors - if (subscriptionContainer.hasSelectors) { - updateSubscriptionContainer = this.handleSelectors( - subscriptionContainer, - job - ); - } + updateSubscriptionContainer = this.handleSelectors( + subscriptionContainer, + job + ); if (updateSubscriptionContainer) { subscriptionContainer.updatedSubscribers.push(job.observer); @@ -239,9 +237,7 @@ export class Runtime { // TODO add Selector support for Object based subscriptions const selectors = subscriptionContainer.selectorsWeakMap.get(job.observer) ?.selectors; - - // Return true because in this cases the subscriptionContainer isn't properly proxyBased - if (!subscriptionContainer.hasSelectors || !selectors) return true; + if (!selectors) return true; const previousValue = job.observer.previousValue; const newValue = job.observer.value; diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index 58aa26df..0e88f932 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -50,11 +50,15 @@ export class SubscriptionContainer { public isObjectBased = false; /** * Weak map for storing a key identifier for each Observer. + * + * https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap */ public subscriberKeysWeakMap: WeakMap; /** * Weak map representing Selectors of the Subscription Container. + * + * https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap */ public selectorsWeakMap: SelectorWeakMapType; diff --git a/packages/react/src/hooks/useAgile.ts b/packages/react/src/hooks/useAgile.ts index ec4ccc17..cb6fe4fc 100644 --- a/packages/react/src/hooks/useAgile.ts +++ b/packages/react/src/hooks/useAgile.ts @@ -10,7 +10,6 @@ import { SubscriptionContainerKeyType, defineConfig, isValidObject, - ProxyKeyMapInterface, generateId, ProxyWeakMapType, } from '@agile-ts/core'; @@ -60,7 +59,8 @@ export function useAgile< depsArray: (Observer | undefined)[] ): AgileHookArrayType | AgileHookType => { const handleReturn = (dep: Observer | undefined): AgileHookType => { - const value = dep?.value; + if (dep == null) return undefined as any; + const value = dep.value; // If proxyBased and value is object wrap Proxy around it to track used properties if (config.proxyBased && isValidObject(value, true)) { @@ -71,7 +71,7 @@ export function useAgile< return proxyTree.proxy; } - return dep?.value; + return dep.value; }; // Handle single dep From 1d44eb93676278357982126034d65f878a993adf Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 6 Jun 2021 18:41:59 +0200 Subject: [PATCH 048/117] fixed some more typos --- packages/core/src/runtime/index.ts | 10 +- .../container/SubscriptionContainer.ts | 95 +++++++++++++------ packages/react/src/hooks/useAgile.ts | 32 +++++-- 3 files changed, 95 insertions(+), 42 deletions(-) diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index e2c27103..c9606c47 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -234,20 +234,24 @@ export class Runtime { subscriptionContainer: SubscriptionContainer, job: RuntimeJob ): boolean { - // TODO add Selector support for Object based subscriptions const selectors = subscriptionContainer.selectorsWeakMap.get(job.observer) ?.selectors; + + // If no selector functions found, return true + // because no specific part of the Observer was selected + // -> The Subscription Container should update + // no matter what was updated in the Observer if (!selectors) return true; + // Check if a selected part of Observer value has changed const previousValue = job.observer.previousValue; const newValue = job.observer.value; for (const selector of selectors) { if ( notEqual(selector(newValue), selector(previousValue)) // || newValueDeepness !== previousValueDeepness // Not possible to check - ) { + ) return true; - } } return false; diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index 0e88f932..0d26264e 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -13,21 +13,24 @@ export class SubscriptionContainer { /** * Whether the Subscription Container * and the Component the Subscription Container represents are ready. - * So that the Subscription Container can trigger rerenders on the Component. + * + * When both are ready, the Subscription Container is allowed to trigger rerenders on the Component. */ public ready = false; /** * Observers that have subscribed the Subscription Container. * - * The Observers use the Subscription Container + * The subscribed Observers use the Subscription Container * as an interface to the Component the Subscription Container represents * in order to cause rerenders on the Component. + * + * [Learn more..](https://agile-ts.org/docs/core/integration#-subscriptions) */ public subscribers: Set; /** * Temporary stores the subscribed Observers, - * that have been updated and are running through the runtime. + * that were updated and are currently running through the runtime. */ public updatedSubscribers: Array = []; @@ -41,7 +44,7 @@ export class SubscriptionContainer { * state2: Observer * } * ``` - * Thus each Observer has its unique key stored in the 'subscribersWeakMap'. + * Thus each Observer has its 'external' unique key stored in the 'subscribersWeakMap'. * * Often Component based Subscriptions are object based, * because each Observer requires a unique identifier @@ -49,28 +52,30 @@ export class SubscriptionContainer { */ public isObjectBased = false; /** - * Weak map for storing a key identifier for each Observer. + * Weak map for storing a 'external' key identifier for each Observer. * * https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap */ public subscriberKeysWeakMap: WeakMap; /** - * Weak map representing Selectors of the Subscription Container. + * Weak Map storing selector functions for subscribed Observer. + * + * A selector functions allows the partly subscription to the Observer value. + * So only if the selected part changes, the Subscription Container + * rerenders the Component it represents. * * https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap */ public selectorsWeakMap: SelectorWeakMapType; /** - * SubscriptionContainer - Represents Component/(Way to rerender Component) that is subscribed by Observer/s (Agile) - * -> Used to cause rerender on Component + * A Subscription Container is an interface to a UI-Component, + * that can be subscribed by multiple Observers. * - * - * A Subscription Container is like an interface to the Components. - * - * When a subscribed Observer value mutates a rerender is triggered on the Component - * through the Subscription Container. + * These Observers use the Subscription Container + * to trigger a rerender on the Component it represents, + * when their value change. * * @internal * @param subs - Observers to be subscribed to the Subscription Container. @@ -94,9 +99,31 @@ export class SubscriptionContainer { // which selects the property at the path const selectorWeakMap: SelectorWeakMapType = config.selectorWeakMap as any; - for (const observer of subs) { - const paths = config.proxyWeakMap?.get(observer)?.paths; + // Assign selector functions based on the Proxy Weak Map + this.assignProxySelectors( + selectorWeakMap, + config.proxyWeakMap as any, + subs + ); + this.selectorsWeakMap = selectorWeakMap; + } + + /** + * Assigns selector functions created based on the paths of the Proxy Weak Map + * to the Selector Weak Map. + * + * @param selectorWeakMap + * @param proxyWeakMap + * @param subs + */ + public assignProxySelectors( + selectorWeakMap: SelectorWeakMapType, + proxyWeakMap: ProxyWeakMapType, + subs: Array + ): void { + for (const observer of subs) { + const paths = proxyWeakMap.get(observer)?.paths; if (paths != null) { const selectors: SelectorMethodType[] = []; for (const path of paths) { @@ -112,8 +139,6 @@ export class SubscriptionContainer { selectorWeakMap.set(observer, { selectors }); } } - - this.selectorsWeakMap = selectorWeakMap; } } @@ -126,28 +151,36 @@ export interface SubscriptionContainerConfigInterface { */ key?: SubscriptionContainerKeyType; /** - * A keymap with a 2 dimensional arrays representing paths/routes to particular properties in the State at key. - * The subscriptionContainer will then only rerender the Component, when a property at a given path changes. - * Not anymore if anything in the State object mutates, although it might not even be displayed in the Component. + * A Weak Map with a 2 dimensional arrays representing paths/routes + * to particular properties in the Observer. + * + * The Component the Subscription Container represents + * is then only rerendered, when a property at a specified path changes. + * Not anymore if anything in the Observer object mutates, + * although it might not even be displayed in the Component. + * * For example: - * { - * myState1: {paths: [['data', 'name']]}, - * myState2: {paths: [['car', 'speed']]} + * ``` + * WeakMap: { + * Observer1: {paths: [['data', 'name']]}, + * Observer2: {paths: [['car', 'speed']]} * } - * Now the subscriptionContain will only trigger a rerender on the Component - * if 'data.name' in myState1 or 'car.speed' in myState2 changes. - * If, for instance, 'data.age' in myState1 mutates it won't trigger a rerender, - * since 'data.age' isn't represented in the proxyKeyMap. + * ``` + * Now the Subscription Container will only trigger a rerender on the Component + * if 'data.name' in Observer1 or 'car.speed' in Observer2 changes. + * If, for instance, 'data.age' in Observer1 mutates it won't trigger a rerender, + * since 'data.age' isn't represented in the Proxy Weak Map. * - * These particular paths can be tracked with the ProxyTree. + * These particular paths were tracked via the ProxyTree. * https://github.com/agile-ts/agile/tree/master/packages/proxytree * - * @default {} + * @default new WeakMap() */ proxyWeakMap?: ProxyWeakMapType; /** - * TODO - * @default undefined + * A Weak Map with an array of selector functions for the Observer + * + * @default new WeakMap() */ selectorWeakMap?: SelectorWeakMapType; } diff --git a/packages/react/src/hooks/useAgile.ts b/packages/react/src/hooks/useAgile.ts index cb6fe4fc..f3fe5e03 100644 --- a/packages/react/src/hooks/useAgile.ts +++ b/packages/react/src/hooks/useAgile.ts @@ -47,14 +47,14 @@ export function useAgile< config: AgileHookConfigInterface = {} ): AgileHookArrayType | AgileHookType { const depsArray = extractObservers(deps); - const proxyWeakMap: ProxyWeakMapType = new WeakMap(); + const proxyTreeWeakMap = new WeakMap(); config = defineConfig(config, { proxyBased: false, key: generateId(), agileInstance: null, }); - // Creates Return Value of Hook, depending if deps are in Array shape or not + // Creates Return Value of Hook, depending whether deps are in Array shape or not const getReturnValue = ( depsArray: (Observer | undefined)[] ): AgileHookArrayType | AgileHookType => { @@ -62,16 +62,15 @@ export function useAgile< if (dep == null) return undefined as any; const value = dep.value; - // If proxyBased and value is object wrap Proxy around it to track used properties + // If proxyBased and value is of type object. + // Wrap a Proxy around the object to track the used properties if (config.proxyBased && isValidObject(value, true)) { const proxyTree = new ProxyTree(value); - proxyWeakMap.set(dep, { - paths: proxyTree.getUsedRoutes() as any, - }); + proxyTreeWeakMap.set(dep, proxyTree); return proxyTree.proxy; } - return dep.value; + return value; }; // Handle single dep @@ -79,7 +78,7 @@ export function useAgile< return handleReturn(depsArray[0]); } - // Handle dep array + // Handle deps array return depsArray.map((dep) => { return handleReturn(dep); }) as AgileHookArrayType; @@ -106,6 +105,23 @@ export function useAgile< (dep): dep is Observer => dep !== undefined ); + // Build Proxy Path WeakMap Map based on the Proxy Tree WeakMap + // by extracting the routes of the Tree + // Building the Path WeakMap in the 'useIsomorphicLayoutEffect' + // because the 'useIsomorphicLayoutEffect' is called after the rerender + // -> In the Component used paths got successfully tracked + const proxyWeakMap: ProxyWeakMapType = new WeakMap(); + if (config.proxyBased) { + for (const observer of observers) { + const proxyTree = proxyTreeWeakMap.get(observer); + if (proxyTree != null) { + proxyWeakMap.set(observer, { + paths: proxyTree.getUsedRoutes() as any, + }); + } + } + } + // Create Callback based Subscription const subscriptionContainer = agileInstance.subController.subscribeWithSubsArray( () => { From aa98046df37d2f199600b45aee50179b4d8107f2 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 6 Jun 2021 20:25:17 +0200 Subject: [PATCH 049/117] fixed typos --- packages/core/src/runtime/index.ts | 129 +++++++++++------- .../container/SubscriptionContainer.ts | 14 +- 2 files changed, 90 insertions(+), 53 deletions(-) diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index c9606c47..6565ab04 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -6,75 +6,93 @@ import { ComponentSubscriptionContainer, defineConfig, notEqual, - isValidObject, LogCodeManager, } from '../internal'; export class Runtime { + // Agile Instance the Runtime belongs to public agileInstance: () => Agile; - // Queue system + // Job that is currently performed public currentJob: RuntimeJob | null = null; + // Jobs to perform public jobQueue: Array = []; - public notReadyJobsToRerender: Set = new Set(); // Jobs that got performed but aren't ready to get rerendered (wait for mount) - public jobsToRerender: Array = []; // Jobs that are performed and will be rendered + + // Jobs that were performed and are ready to rerender + public jobsToRerender: Array = []; + // Jobs that were performed and should rerender + // but the Subscription Container isn't ready to rerender it yet + // For example if the UI-Component isn't mounted yet. + public notReadyJobsToRerender: Set = new Set(); + + // Whether Jobs are currently performed + public isPerformingJobs = false; /** + * The Runtime queues and performs ingested Observer change Jobs. + * + * It prevents race conditions and combines Job Subscription Container rerenders. + * * @internal - * Runtime - Performs ingested Observers - * @param agileInstance - An instance of Agile + * @param agileInstance - Instance of Agile the Runtime belongs to. */ constructor(agileInstance: Agile) { this.agileInstance = () => agileInstance; } - //========================================================================================================= - // Ingest - //========================================================================================================= /** + * Adds the specified Job to the Job queue, + * where it will be performed when it is its turn. + * * @internal - * Ingests Job into Runtime that gets performed - * @param job - Job - * @param config - Config + * @param job - Job to be performed. + * @param config - Configuration object */ public ingest(job: RuntimeJob, config: IngestConfigInterface = {}): void { config = defineConfig(config, { - perform: true, + perform: !this.isPerformingJobs, }); + // Add specified Job to the queue this.jobQueue.push(job); Agile.logger.if .tag(['runtime']) .info(LogCodeManager.getLog('16:01:00', [job._key]), job); - // Perform Job + // Run first Job from the queue if (config.perform) { const performJob = this.jobQueue.shift(); if (performJob) this.perform(performJob); } } - //========================================================================================================= - // Perform - //========================================================================================================= /** + * Performs the specified Job + * and adds it to the rerender queue if necessary. + * + * After the execution it checks if there is still a Job in the queue. + * If so, the next Job in the queue is performed. + * If not, the `jobsToRerender` queue will be started to work off. + * * @internal - * Performs Job and adds it to the rerender queue if necessary - * @param job - Job that gets performed + * @param job - Job to be performed. */ public perform(job: RuntimeJob): void { + this.isPerformingJobs = true; this.currentJob = job; // Perform Job job.observer.perform(job); job.performed = true; - // Ingest Dependents of Observer into Runtime + // Ingest dependents of the Observer into runtime, + // since they depend on the Observer and might have been changed job.observer.dependents.forEach((observer) => observer.ingest({ perform: false }) ); + // Add Job to rerender queue and reset current Job property if (job.rerender) this.jobsToRerender.push(job); this.currentJob = null; @@ -82,11 +100,13 @@ export class Runtime { .tag(['runtime']) .info(LogCodeManager.getLog('16:01:01', [job._key]), job); - // Perform Jobs as long as Jobs are left in queue, if no job left update/rerender Subscribers of jobsToRerender + // Perform Jobs as long as Jobs are left in the queue + // If no job left start updating/rerendering Subscribers of jobsToRerender if (this.jobQueue.length > 0) { const performJob = this.jobQueue.shift(); if (performJob) this.perform(performJob); } else { + this.isPerformingJobs = false; if (this.jobsToRerender.length > 0) { // https://stackoverflow.com/questions/9083594/call-settimeout-without-delay setTimeout(() => { @@ -96,13 +116,13 @@ export class Runtime { } } - //========================================================================================================= - // Update Subscribers - //========================================================================================================= /** + * Executes the `jobsToRerender` queue + * and updates (causes rerender on) the Subscription Container (subscribed Component) + * of each Job Observer. + * * @internal - * Updates/Rerenders all Subscribed Components (SubscriptionContainer) of the Job (Observer) - * @return If any subscriptionContainer got updated (-> triggered a rerender on the Component it represents) + * @return A boolean indicating whether any Subscription Container was updated. */ public updateSubscribers(): boolean { if (!this.agileInstance().hasIntegration()) { @@ -116,20 +136,20 @@ export class Runtime { ) return false; - // Subscriptions that has to be updated/rerendered - // A Set() to combine several equal SubscriptionContainers into one (optimizes rerender) - // (Even better would be to combine SubscriptionContainer based on the Component, - // since a Component can have multiple SubscriptionContainers) + // Subscription Containers that have to be updated (perform rerender on Component it represents). + // Using a 'Set()' to combine several equal SubscriptionContainers into one (rerender optimisation). const subscriptionsToUpdate = new Set(); - // Build final jobsToRerender array based on new jobsToRerender and not ready jobsToRerender + // Build final 'jobsToRerender' array + // based on the new 'jobsToRerender' array and the 'notReadyJobsToRerender' array. const jobsToRerender = this.jobsToRerender.concat( Array.from(this.notReadyJobsToRerender) ); this.notReadyJobsToRerender = new Set(); this.jobsToRerender = []; - // Check if Job SubscriptionContainers should be updated and if so add them to the subscriptionsToUpdate array + // Check if Job Subscription Container of Jobs should be updated + // and if so add it to the 'subscriptionsToUpdate' array jobsToRerender.forEach((job) => { job.subscriptionContainersToUpdate.forEach((subscriptionContainer) => { if (!subscriptionContainer.ready) { @@ -155,7 +175,7 @@ export class Runtime { return; } - let updateSubscriptionContainer = true; + let updateSubscriptionContainer; // Handle Selectors updateSubscriptionContainer = this.handleSelectors( @@ -163,6 +183,15 @@ export class Runtime { job ); + // Check if Subscription Container with same componentId is already in the 'subscriptionToUpdate' queue + // (rerender optimisation) + updateSubscriptionContainer = + updateSubscriptionContainer && + Array.from(subscriptionsToUpdate).findIndex( + (sc) => sc.componentId === subscriptionContainer.componentId + ) === -1; + + // Add Subscription Container to the 'subscriptionsToUpdate' queue if (updateSubscriptionContainer) { subscriptionContainer.updatedSubscribers.push(job.observer); subscriptionsToUpdate.add(subscriptionContainer); @@ -198,37 +227,34 @@ export class Runtime { } /** - * Returns a key map with Observer values that have been updated. + * Maps the values of the updated Observers into a key map. * * @internal - * @param subscriptionContainer - Object based SubscriptionContainer + * @param subscriptionContainer - Subscription Container from which the 'updatedSubscribers' are to be mapped to a key map. */ public getUpdatedObserverValues( subscriptionContainer: SubscriptionContainer ): { [key: string]: any } { const props: { [key: string]: any } = {}; - // Map 'Observer To Update' values into the props object + // Map updated Observer values into the props key map for (const observer of subscriptionContainer.updatedSubscribers) { - const key = subscriptionContainer.subscriberKeysWeakMap.get(observer); + const key = + subscriptionContainer.subscriberKeysWeakMap.get(observer) ?? + subscriptionContainer.key; if (key != null) props[key] = observer.value; } return props; } - //========================================================================================================= - // Handle Selectors - //========================================================================================================= /** - * @internal - * Checks if the subscriptionContainer should be updated. + * Returns a boolean indicating whether the Subscription Container can be updated or not. * Therefore it reviews the '.value' and the '.previousValue' property of the Observer the Job represents. - * If a property at the proxy detected path differs, the subscriptionContainer is allowed to update. - * @param subscriptionContainer - SubscriptionContainer - * @param job - Job - * @return {boolean} If the subscriptionContainer should be updated - * -> If a from the Proxy Tree detected property differs from the same property in the previous value - * or the passed subscriptionContainer isn't properly proxy based + * If a selected property differs, the Subscription Container is allowed to update/rerender. + * + * @internal + * @param subscriptionContainer - Subscription Container to be checked if it can update. + * @param job - Job the Subscription Container belongs to. */ public handleSelectors( subscriptionContainer: SubscriptionContainer, @@ -258,9 +284,10 @@ export class Runtime { } } -/** - * @param perform - If Job gets performed immediately - */ export interface IngestConfigInterface { + /** + * Whether the ingested Job should be performed immediately + * or added to the queue first and then executed when it is his turn. + */ perform?: boolean; } diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index 0d26264e..33f80747 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -17,6 +17,7 @@ export class SubscriptionContainer { * When both are ready, the Subscription Container is allowed to trigger rerenders on the Component. */ public ready = false; + public componentId?: ComponentIdType; /** * Observers that have subscribed the Subscription Container. @@ -30,7 +31,8 @@ export class SubscriptionContainer { public subscribers: Set; /** * Temporary stores the subscribed Observers, - * that were updated and are currently running through the runtime. + * that were performed by the runtime + * and are currently running through the update Subscription Container process. */ public updatedSubscribers: Array = []; @@ -93,6 +95,7 @@ export class SubscriptionContainer { this.subscribers = new Set(subs); this.key = config.key; + this.componentId = config?.componentId; this.subscriberKeysWeakMap = new WeakMap(); // Create for each proxy path a Selector, @@ -146,10 +149,15 @@ export type SubscriptionContainerKeyType = string | number; export interface SubscriptionContainerConfigInterface { /** - * Key/Name identifier of Subscription Container + * Key/Name identifier of the Subscription Container * @default undefined */ key?: SubscriptionContainerKeyType; + /** + * Key/Name identifier of the Component the Subscription Container represents. + * @default undefined + */ + componentId?: ComponentIdType; /** * A Weak Map with a 2 dimensional arrays representing paths/routes * to particular properties in the Observer. @@ -192,3 +200,5 @@ export type SelectorWeakMapType = WeakMap< { selectors: SelectorMethodType[] } >; export type SelectorMethodType = (value: T) => any; + +export type ComponentIdType = string | number; From 91c6ae33aca74a22bfcf85c3764c84bafe3082d5 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 7 Jun 2021 08:01:32 +0200 Subject: [PATCH 050/117] fixed typos --- packages/core/src/runtime/index.ts | 7 +-- .../CallbackSubscriptionContainer.ts | 8 +-- .../ComponentSubscriptionContainer.ts | 22 ++++++-- .../container/SubscriptionContainer.ts | 41 +++++++++------ .../runtime/subscription/sub.controller.ts | 50 +++++++++++++------ 5 files changed, 86 insertions(+), 42 deletions(-) diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index 6565ab04..e4db26b2 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -227,7 +227,7 @@ export class Runtime { } /** - * Maps the values of the updated Observers into a key map. + * Maps the values of updated Observers (updatedSubscribers) into a key map. * * @internal * @param subscriptionContainer - Subscription Container from which the 'updatedSubscribers' are to be mapped to a key map. @@ -249,8 +249,9 @@ export class Runtime { /** * Returns a boolean indicating whether the Subscription Container can be updated or not. - * Therefore it reviews the '.value' and the '.previousValue' property of the Observer the Job represents. - * If a selected property differs, the Subscription Container is allowed to update/rerender. + * + * Therefore it reviews the '.value' and the '.previousValue' property of the Observer represented by the Job. + * If a selected property differs, the Subscription Container is allowed to update/rerender (returns true). * * @internal * @param subscriptionContainer - Subscription Container to be checked if it can update. diff --git a/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts index 6b3339b5..eb3b0913 100644 --- a/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts @@ -7,17 +7,19 @@ import { export class CallbackSubscriptionContainer extends SubscriptionContainer { /** * Callback function to trigger a rerender - * on the Component the Subscription Container represents. + * on the Component represented by the Subscription Container. */ public callback: Function; /** * Subscription Container for callback based subscriptions. * - * In a callback based subscription, a rerender is triggered on the Component via a specified callback function. + * In a callback based subscription, a rerender is triggered on the Component + * using a specified callback function. * * The Callback Subscription Container doesn't keep track of the Component itself. - * It only knows how to trigger a rerender on the particular Component through the callback function. + * It only knows how to trigger a rerender + * on the particular Component through the callback function. * * [Learn more..](https://agile-ts.org/docs/core/integration#callback-based) * diff --git a/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts index b161f70a..81789168 100644 --- a/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts @@ -8,19 +8,33 @@ export class ComponentSubscriptionContainer< C = any > extends SubscriptionContainer { /** - * Component the Subscription Container represents. + * Component the Subscription Container represents + * and mutates to cause rerender on it. */ public component: C; /** * Subscription Container for component based subscriptions. * - * In a component based subscription, a rerender is triggered on the Component via muting a local - * State Management instance of the Component. + * In a component based subscription, a rerender is triggered on the Component + * by muting a local State Management instance/property of the Component. * For example in a React Class Component the `this.state` property. * * The Component Subscription Container keeps track of the Component itself, - * in order to synchronize the Component State Management instance with the subscribed Observer values. + * to synchronize the Component State Management instance with the subscribed Observer values. + * + * For this to work well, a component subscription is often object based + * so that each observer has a uniq key. + * ``` + * // Object based (guaranteed unique key) + * { + * state1: Observer, + * state2: Observer + * } + * + * // Array based (no guaranteed unique key) + * [Observer, Observer] + * ``` * * [Learn more..](https://agile-ts.org/docs/core/integration#component-based) * diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index 33f80747..8c18ff08 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -14,17 +14,22 @@ export class SubscriptionContainer { * Whether the Subscription Container * and the Component the Subscription Container represents are ready. * - * When both are ready, the Subscription Container is allowed to trigger rerenders on the Component. + * When both are ready, the Subscription Container is allowed + * to trigger rerenders on the Component based on its type. (Component or Callback based) */ public ready = false; + /** + * Id of the Component the Subscription Container represents. + */ public componentId?: ComponentIdType; /** * Observers that have subscribed the Subscription Container. * * The subscribed Observers use the Subscription Container - * as an interface to the Component the Subscription Container represents - * in order to cause rerenders on the Component. + * as an interface to the Component it represents. + * Through the Subscription Container, they can then trigger rerenders + * on the Component when their value changes. * * [Learn more..](https://agile-ts.org/docs/core/integration#-subscriptions) */ @@ -33,13 +38,16 @@ export class SubscriptionContainer { * Temporary stores the subscribed Observers, * that were performed by the runtime * and are currently running through the update Subscription Container process. + * + * This is used for example, to merge the changed Observer values + * into the Component's local State Management instance for a Component based Subscription. */ public updatedSubscribers: Array = []; /** * Whether the Subscription Container is object based. * - * A Observer is object based when the subscribed Observers were provided in a Observer key map. + * A Observer is object based when the subscribed Observers were provided in an Observer key map. * ``` * { * state1: Observer, @@ -50,22 +58,22 @@ export class SubscriptionContainer { * * Often Component based Subscriptions are object based, * because each Observer requires a unique identifier - * to properly merge the Observer value into the local State Management instance. + * to properly merge the Observer value into the Component's local State Management instance. */ public isObjectBased = false; /** - * Weak map for storing a 'external' key identifier for each Observer. + * Weak map for storing 'external' key identifiers for Observer. * * https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap */ public subscriberKeysWeakMap: WeakMap; /** - * Weak Map storing selector functions for subscribed Observer. + * Weak Map for storing selector functions of subscribed Observer. * - * A selector functions allows the partly subscription to the Observer value. - * So only if the selected part changes, the Subscription Container - * rerenders the Component it represents. + * A selector functions allows the partly subscription to an Observer value. + * Only if the selected Observe value part changes, + * the Subscription Container rerenders the Component it represents. * * https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap */ @@ -98,17 +106,14 @@ export class SubscriptionContainer { this.componentId = config?.componentId; this.subscriberKeysWeakMap = new WeakMap(); - // Create for each proxy path a Selector, - // which selects the property at the path + // Create for each specified proxy path a selector function, + // which selects the property at the path end const selectorWeakMap: SelectorWeakMapType = config.selectorWeakMap as any; - - // Assign selector functions based on the Proxy Weak Map this.assignProxySelectors( selectorWeakMap, config.proxyWeakMap as any, subs ); - this.selectorsWeakMap = selectorWeakMap; } @@ -186,7 +191,11 @@ export interface SubscriptionContainerConfigInterface { */ proxyWeakMap?: ProxyWeakMapType; /** - * A Weak Map with an array of selector functions for the Observer + * A Weak Map with an array of selector functions for Observers. + * + * A selector functions allows the partly subscription to an Observer value. + * Only if the selected Observe value part changes, + * the Subscription Container rerenders the Component it represents. * * @default new WeakMap() */ diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index c66eb1e1..121b3d02 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -12,31 +12,48 @@ import { } from '../../internal'; export class SubController { + // Agile Instance the Runtime belongs to public agileInstance: () => Agile; - public componentSubs: Set = new Set(); // Holds all registered Component based Subscriptions - public callbackSubs: Set = new Set(); // Holds all registered Callback based Subscriptions + // Represents all registered Component based Subscriptions + public componentSubs: Set = new Set(); + // Represents all registered Callback based Subscriptions + public callbackSubs: Set = new Set(); - public mountedComponents: Set = new Set(); // Holds all mounted Components (only if agileInstance.config.mount = true) + // Keeps track of all mounted Components (only if agileInstance.config.mount = true) + public mountedComponents: Set = new Set(); /** + * Manages the subscription to UI-Components. + * * @internal - * SubController - Handles subscriptions to Components - * @param agileInstance - An instance of Agile + * @param agileInstance - Instance of Agile the Subscription Container belongs to. */ public constructor(agileInstance: Agile) { this.agileInstance = () => agileInstance; } - //========================================================================================================= - // Subscribe with Subs Object - //========================================================================================================= /** + * Subscribes the in an object specified Observers to a Component represented by the 'integrationInstance'. + * Such subscription ensures that the Observer is able to trigger rerenders on the Component + * for example if its value changes. + * + * There are two ways of causing a rerender through the 'integrationInstance' on the Component. + * - 1. Via a callback function which triggers a rerender + * on the Component when it is called. (Callback based Subscription) + * [Learn more..](https://agile-ts.org/docs/core/integration/#callback-based) + * - 2. Via the Component itself. + * For example by mutating the local State Management property + * of the Component. (Component based Subscription) + * [Learn more..](https://agile-ts.org/docs/core/integration/#component-based) + * + * The Component (way of rerendering the Component) is then represented by a created Subscription Container + * that is added to the Observer and serves like an interface to the Component. + * * @internal - * Subscribe with Object shaped Subscriptions - * @param integrationInstance - Callback Function or Component - * @param subs - Initial Subscription Object - * @param config - Config + * @param integrationInstance - Callback function or Component Instance for triggering a rerender on a UI-Component. + * @param subs - Observers to be subscribed to the Subscription Container in object shape. + * @param config - Configuration object */ public subscribeWithSubsObject( integrationInstance: any, @@ -81,11 +98,12 @@ export class SubController { // Subscribe with Subs Array //========================================================================================================= /** - * @internal * Subscribe with Array shaped Subscriptions - * @param integrationInstance - Callback Function or Component - * @param subs - Initial Subscription Array - * @param config - Config + * + * @internal + * @param integrationInstance - Callback function or Component Instance for triggering a rerender on a UI-Component. + * @param subs - Observers to be subscribed to the Subscription Container in array shape. + * @param config - Configuration object */ public subscribeWithSubsArray( integrationInstance: any, From bd95ed8d6dde90920edfc24c94f8cdbdab254516 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Mon, 7 Jun 2021 17:59:33 +0200 Subject: [PATCH 051/117] fixed typos --- .../runtime/subscription/sub.controller.ts | 84 +++++++++++-------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index 121b3d02..589b35b5 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -34,21 +34,25 @@ export class SubController { } /** - * Subscribes the in an object specified Observers to a Component represented by the 'integrationInstance'. - * Such subscription ensures that the Observer is able to trigger rerenders on the Component - * for example if its value changes. + * Creates a so called Subscription Container which represents an UI-Component in AgileTs. + * Such Subscription Container know how to trigger a rerender on the UI-Component it represents + * through the provided 'integrationInstance'. * - * There are two ways of causing a rerender through the 'integrationInstance' on the Component. - * - 1. Via a callback function which triggers a rerender - * on the Component when it is called. (Callback based Subscription) + * There exist two different ways on how the Subscription Container can cause a rerender on the Component. + * - 1. Via a callback function that triggers a rerender on the Component. (Callback based Subscription) * [Learn more..](https://agile-ts.org/docs/core/integration/#callback-based) - * - 2. Via the Component itself. - * For example by mutating the local State Management property - * of the Component. (Component based Subscription) + * - 2. Via the Component instance itself. + * For example by mutating a local State Management property + * of the Component Instance. (Component based Subscription) * [Learn more..](https://agile-ts.org/docs/core/integration/#component-based) * - * The Component (way of rerendering the Component) is then represented by a created Subscription Container - * that is added to the Observer and serves like an interface to the Component. + * The in an object specified Observers are then automatically subscribed + * to the created Subscription Container and thus to the Component the Subscription Container represents. + * + * The advantage of subscribing the Observer via a object keymap, + * is that each Observer has its own unique key identifier. + * Such key can for example required when merging the Observer value at key into + * a local Component State Management property. * * @internal * @param integrationInstance - Callback function or Component Instance for triggering a rerender on a UI-Component. @@ -69,19 +73,21 @@ export class SubController { const subsArray: Observer[] = []; for (const key in subs) subsArray.push(subs[key]); - // Register Subscription -> decide weather subscriptionInstance is callback or component based + // Create a rerender interface to Component + // via the specified 'integrationInstance' (Subscription Container) const subscriptionContainer = this.registerSubscription( integrationInstance, subsArray, config ); - // Set SubscriptionContainer to Object based + // Set SubscriptionContainer to object based + // and assign property keys to the 'subscriberKeysWeakMap' subscriptionContainer.isObjectBased = true; for (const key in subs) subscriptionContainer.subscriberKeysWeakMap.set(subs[key], key); - // Register subs and build props object + // Subscribe Observer to the created Subscription Container and build props object for (const key in subs) { const observer = subs[key]; observer.subscribe(subscriptionContainer); @@ -94,13 +100,23 @@ export class SubController { }; } - //========================================================================================================= - // Subscribe with Subs Array - //========================================================================================================= /** - * Subscribe with Array shaped Subscriptions + * Creates a so called Subscription Container which represents an UI-Component in AgileTs. + * Such Subscription Container know how to trigger a rerender on the UI-Component it represents + * through the provided 'integrationInstance'. + * + * There exist two different ways on how the Subscription Container can cause a rerender on the Component. + * - 1. Via a callback function that triggers a rerender on the Component. (Callback based Subscription) + * [Learn more..](https://agile-ts.org/docs/core/integration/#callback-based) + * - 2. Via the Component instance itself. + * For example by mutating a local State Management property + * of the Component Instance. (Component based Subscription) + * [Learn more..](https://agile-ts.org/docs/core/integration/#component-based) * - * @internal + * The in an array specified Observers are then automatically subscribed + * to the created Subscription Container and thus to the Component the Subscription Container represents. + * + * @internal * @param integrationInstance - Callback function or Component Instance for triggering a rerender on a UI-Component. * @param subs - Observers to be subscribed to the Subscription Container in array shape. * @param config - Configuration object @@ -110,26 +126,28 @@ export class SubController { subs: Array = [], config: RegisterSubscriptionConfigInterface = {} ): SubscriptionContainer { - // Register Subscription -> decide weather subscriptionInstance is callback or component based + // Create a rerender interface to Component + // via the specified 'integrationInstance' (Subscription Container) const subscriptionContainer = this.registerSubscription( integrationInstance, subs, config ); - // Register subs + // Subscribe Observer to the created Subscription Container subs.forEach((observer) => observer.subscribe(subscriptionContainer)); return subscriptionContainer; } - //========================================================================================================= - // Unsubscribe - //========================================================================================================= /** + * Unsubscribe the from the specified 'subscriptionInstance' + * extracted SubscriptionContainer from all Observers that + * are subscribed to it. + * * @internal - * Unsubscribes SubscriptionContainer(Component) - * @param subscriptionInstance - SubscriptionContainer or Component that holds an SubscriptionContainer + * @param subscriptionInstance - Subscription Container + * or an UI-Component holding a instance of a Subscription Container */ public unsubscribe(subscriptionInstance: any) { // Helper function to remove SubscriptionContainer from Observer @@ -216,17 +234,9 @@ export class SubController { config = defineConfig(config, { waitForMount: this.agileInstance().config.waitForMount, }); - if (isFunction(integrationInstance)) - return this.registerCallbackSubscription( - integrationInstance, - subs, - config - ); - return this.registerComponentSubscription( - integrationInstance, - subs, - config - ); + return isFunction(integrationInstance) + ? this.registerCallbackSubscription(integrationInstance, subs, config) + : this.registerComponentSubscription(integrationInstance, subs, config); } //========================================================================================================= From f458fd1e5eeffb5ef12973f24dd9380f718c388e Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 7 Jun 2021 20:28:42 +0200 Subject: [PATCH 052/117] fixed typos --- packages/core/src/runtime/index.ts | 45 +++-- .../CallbackSubscriptionContainer.ts | 9 +- .../ComponentSubscriptionContainer.ts | 23 ++- .../container/SubscriptionContainer.ts | 20 +- .../runtime/subscription/sub.controller.ts | 187 ++++++++---------- .../subscription/sub.controller.test.ts | 62 +++--- packages/react/src/hooks/useAgile.ts | 9 +- 7 files changed, 181 insertions(+), 174 deletions(-) diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index e4db26b2..f6267a0a 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -13,25 +13,34 @@ export class Runtime { // Agile Instance the Runtime belongs to public agileInstance: () => Agile; - // Job that is currently performed + // Job that is currently being performed public currentJob: RuntimeJob | null = null; - // Jobs to perform + // Jobs to be performed public jobQueue: Array = []; // Jobs that were performed and are ready to rerender public jobsToRerender: Array = []; - // Jobs that were performed and should rerender - // but the Subscription Container isn't ready to rerender it yet - // For example if the UI-Component isn't mounted yet. + // Jobs that were performed and should be rerendered. + // However their Subscription Container isn't ready to rerender yet. + // For example when the UI-Component isn't mounted yet. public notReadyJobsToRerender: Set = new Set(); - // Whether Jobs are currently performed + // Whether the job queue is currently being actively processed public isPerformingJobs = false; /** - * The Runtime queues and performs ingested Observer change Jobs. + * The Runtime executes and queues ingested Observer based Jobs + * to prevent race conditions and optimized rerenders of subscribed Components. * - * It prevents race conditions and combines Job Subscription Container rerenders. + * Each provided Job will be executed when it is its turn + * by calling the Job Observer's 'perform()' method. + * + * After a successful execution the Job is added to a rerender queue, + * which is firstly put into the browser's 'Bucket' and executed when resources are left. + * + * The rerender queue is designed for optimizing the render count + * by combining rerender Jobs of the same Component + * and ingoing rerender requests for unmounted Components. * * @internal * @param agileInstance - Instance of Agile the Runtime belongs to. @@ -41,7 +50,7 @@ export class Runtime { } /** - * Adds the specified Job to the Job queue, + * Adds the specified Observer based Job to the internal Job queue, * where it will be performed when it is its turn. * * @internal @@ -71,9 +80,9 @@ export class Runtime { * Performs the specified Job * and adds it to the rerender queue if necessary. * - * After the execution it checks if there is still a Job in the queue. - * If so, the next Job in the queue is performed. - * If not, the `jobsToRerender` queue will be started to work off. + * After the execution of the Job it checks if there are still Jobs left in the queue. + * - If so, the next Job in the queue is performed. + * - If not, the `jobsToRerender` queue will be started to work off. * * @internal * @param job - Job to be performed. @@ -87,7 +96,7 @@ export class Runtime { job.performed = true; // Ingest dependents of the Observer into runtime, - // since they depend on the Observer and might have been changed + // since they depend on the Observer and have properly changed too job.observer.dependents.forEach((observer) => observer.ingest({ perform: false }) ); @@ -100,8 +109,9 @@ export class Runtime { .tag(['runtime']) .info(LogCodeManager.getLog('16:01:01', [job._key]), job); - // Perform Jobs as long as Jobs are left in the queue - // If no job left start updating/rerendering Subscribers of jobsToRerender + // Perform Jobs as long as Jobs are left in the queue. + // If no Job is left start updating/rerendering Subscribers + // of the Job based on the 'jobsToRerender' queue. if (this.jobQueue.length > 0) { const performJob = this.jobQueue.shift(); if (performJob) this.perform(performJob); @@ -121,8 +131,9 @@ export class Runtime { * and updates (causes rerender on) the Subscription Container (subscribed Component) * of each Job Observer. * + * It returns a boolean indicating whether any Subscription Container was updated. + * * @internal - * @return A boolean indicating whether any Subscription Container was updated. */ public updateSubscribers(): boolean { if (!this.agileInstance().hasIntegration()) { @@ -229,6 +240,8 @@ export class Runtime { /** * Maps the values of updated Observers (updatedSubscribers) into a key map. * + * The key is extracted from the Observer itself or from the Subscription Containers 'subscriberKeysWeakMap'. + * * @internal * @param subscriptionContainer - Subscription Container from which the 'updatedSubscribers' are to be mapped to a key map. */ diff --git a/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts index eb3b0913..aad3736b 100644 --- a/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts @@ -12,14 +12,11 @@ export class CallbackSubscriptionContainer extends SubscriptionContainer { public callback: Function; /** - * Subscription Container for callback based subscriptions. - * - * In a callback based subscription, a rerender is triggered on the Component - * using a specified callback function. + * A Callback Subscription Container represents a UI-Component in AgileTs + * and triggers a rerender on the UI-Component via a specified callback function. * * The Callback Subscription Container doesn't keep track of the Component itself. - * It only knows how to trigger a rerender - * on the particular Component through the callback function. + * It only knows how to trigger a rerender on it via the callback function. * * [Learn more..](https://agile-ts.org/docs/core/integration#callback-based) * diff --git a/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts index 81789168..1d10b209 100644 --- a/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts @@ -14,17 +14,16 @@ export class ComponentSubscriptionContainer< public component: C; /** - * Subscription Container for component based subscriptions. - * - * In a component based subscription, a rerender is triggered on the Component - * by muting a local State Management instance/property of the Component. - * For example in a React Class Component the `this.state` property. + * A Component Subscription Container represents a UI-Component in AgileTs + * and triggers a rerender on the UI-Component by muting the specified Component Instance. + * For example by updating a local State Management property of the Component. + * (like in a React Class Components the `this.state` property) * * The Component Subscription Container keeps track of the Component itself, - * to synchronize the Component State Management instance with the subscribed Observer values. + * to mutate it accordingly so that a rerender is triggered. * - * For this to work well, a component subscription is often object based - * so that each observer has a uniq key. + * For this to work well, a Component Subscription Container is often object based. + * Meaning that each Observer was provided in a object keymap with a unique key identifier. * ``` * // Object based (guaranteed unique key) * { @@ -35,6 +34,14 @@ export class ComponentSubscriptionContainer< * // Array based (no guaranteed unique key) * [Observer, Observer] * ``` + * Thus the Integrations 'updateMethod' method can be called + * with an complete object of changed Observer values. + * ``` + * { + * state1: Observer.value, + * state2: Observer.value + * } + * ``` * * [Learn more..](https://agile-ts.org/docs/core/integration#component-based) * diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index 8c18ff08..9e4a5540 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -80,12 +80,12 @@ export class SubscriptionContainer { public selectorsWeakMap: SelectorWeakMapType; /** - * A Subscription Container is an interface to a UI-Component, + * A Subscription Container represents a UI-Component in AgileTs * that can be subscribed by multiple Observers. * - * These Observers use the Subscription Container - * to trigger a rerender on the Component it represents, - * when their value change. + * These Observers use the Subscription Container as an interface + * to trigger a rerender on the UI-Component it represents, + * for example when their value has changed. * * @internal * @param subs - Observers to be subscribed to the Subscription Container. @@ -106,7 +106,7 @@ export class SubscriptionContainer { this.componentId = config?.componentId; this.subscriberKeysWeakMap = new WeakMap(); - // Create for each specified proxy path a selector function, + // Create a selector function for each specified proxy path, // which selects the property at the path end const selectorWeakMap: SelectorWeakMapType = config.selectorWeakMap as any; this.assignProxySelectors( @@ -118,12 +118,12 @@ export class SubscriptionContainer { } /** - * Assigns selector functions created based on the paths of the Proxy Weak Map - * to the Selector Weak Map. + * Assigns selector functions created based on the paths of the provided Proxy Weak Map + * to the specified `selectorWeakMap`. * - * @param selectorWeakMap - * @param proxyWeakMap - * @param subs + * @param selectorWeakMap - Selector Weak Map the created proxy selectors are added to. + * @param proxyWeakMap - Proxy Weak Map containing proxy paths for specified Observers in `subs`. + * @param subs - Observers whose values are to be selected based on the specified `proxyWeakMap`. */ public assignProxySelectors( selectorWeakMap: SelectorWeakMapType, diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index 589b35b5..292df72f 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -12,12 +12,12 @@ import { } from '../../internal'; export class SubController { - // Agile Instance the Runtime belongs to + // Agile Instance the SubController belongs to public agileInstance: () => Agile; - // Represents all registered Component based Subscriptions + // Keeps track of all registered Component based Subscriptions public componentSubs: Set = new Set(); - // Represents all registered Callback based Subscriptions + // Keeps track of all registered Callback based Subscriptions public callbackSubs: Set = new Set(); // Keeps track of all mounted Components (only if agileInstance.config.mount = true) @@ -36,14 +36,13 @@ export class SubController { /** * Creates a so called Subscription Container which represents an UI-Component in AgileTs. * Such Subscription Container know how to trigger a rerender on the UI-Component it represents - * through the provided 'integrationInstance'. + * through the provided `integrationInstance`. * * There exist two different ways on how the Subscription Container can cause a rerender on the Component. - * - 1. Via a callback function that triggers a rerender on the Component. (Callback based Subscription) + * - 1. Via a callback function that directly triggers a rerender on the Component. (Callback based Subscription) * [Learn more..](https://agile-ts.org/docs/core/integration/#callback-based) * - 2. Via the Component instance itself. - * For example by mutating a local State Management property - * of the Component Instance. (Component based Subscription) + * For example by mutating a local State Management property. (Component based Subscription) * [Learn more..](https://agile-ts.org/docs/core/integration/#component-based) * * The in an object specified Observers are then automatically subscribed @@ -51,12 +50,15 @@ export class SubController { * * The advantage of subscribing the Observer via a object keymap, * is that each Observer has its own unique key identifier. - * Such key can for example required when merging the Observer value at key into + * Such key identifier is for example required when merging the Observer value into * a local Component State Management property. + * ``` + * this.state = {...this.state, {state1: Observer1.value, state2: Observer2.value}} + * ``` * * @internal * @param integrationInstance - Callback function or Component Instance for triggering a rerender on a UI-Component. - * @param subs - Observers to be subscribed to the Subscription Container in object shape. + * @param subs - Observers to be subscribed to the Subscription Container. * @param config - Configuration object */ public subscribeWithSubsObject( @@ -69,15 +71,9 @@ export class SubController { } { const props: { [key: string]: Observer['value'] } = {}; - // Create subsArray - const subsArray: Observer[] = []; - for (const key in subs) subsArray.push(subs[key]); - - // Create a rerender interface to Component - // via the specified 'integrationInstance' (Subscription Container) - const subscriptionContainer = this.registerSubscription( + // Create Subscription Container + const subscriptionContainer = this.createSubscriptionContainer( integrationInstance, - subsArray, config ); @@ -87,7 +83,8 @@ export class SubController { for (const key in subs) subscriptionContainer.subscriberKeysWeakMap.set(subs[key], key); - // Subscribe Observer to the created Subscription Container and build props object + // Subscribe Observers to the created Subscription Container + // and build a Observer value keymap for (const key in subs) { const observer = subs[key]; observer.subscribe(subscriptionContainer); @@ -103,14 +100,13 @@ export class SubController { /** * Creates a so called Subscription Container which represents an UI-Component in AgileTs. * Such Subscription Container know how to trigger a rerender on the UI-Component it represents - * through the provided 'integrationInstance'. + * through the provided `integrationInstance`. * * There exist two different ways on how the Subscription Container can cause a rerender on the Component. - * - 1. Via a callback function that triggers a rerender on the Component. (Callback based Subscription) + * - 1. Via a callback function that directly triggers a rerender on the Component. (Callback based Subscription) * [Learn more..](https://agile-ts.org/docs/core/integration/#callback-based) * - 2. Via the Component instance itself. - * For example by mutating a local State Management property - * of the Component Instance. (Component based Subscription) + * For example by mutating a local State Management property. (Component based Subscription) * [Learn more..](https://agile-ts.org/docs/core/integration/#component-based) * * The in an array specified Observers are then automatically subscribed @@ -118,7 +114,7 @@ export class SubController { * * @internal * @param integrationInstance - Callback function or Component Instance for triggering a rerender on a UI-Component. - * @param subs - Observers to be subscribed to the Subscription Container in array shape. + * @param subs - Observers to be subscribed to the Subscription Container. * @param config - Configuration object */ public subscribeWithSubsArray( @@ -126,41 +122,39 @@ export class SubController { subs: Array = [], config: RegisterSubscriptionConfigInterface = {} ): SubscriptionContainer { - // Create a rerender interface to Component - // via the specified 'integrationInstance' (Subscription Container) - const subscriptionContainer = this.registerSubscription( + // Create Subscription Container + const subscriptionContainer = this.createSubscriptionContainer( integrationInstance, - subs, config ); - // Subscribe Observer to the created Subscription Container + // Subscribe Observers to the created Subscription Container subs.forEach((observer) => observer.subscribe(subscriptionContainer)); return subscriptionContainer; } /** - * Unsubscribe the from the specified 'subscriptionInstance' - * extracted SubscriptionContainer from all Observers that - * are subscribed to it. + * Unsubscribe the Subscription Container extracted from the specified 'subscriptionInstance' + * from all Observers that were subscribed to it. + * + * We should always unsubscribe a Subscription Container when it isn't in use anymore, + * for example when the Component it represented has been unmounted. * * @internal * @param subscriptionInstance - Subscription Container - * or an UI-Component holding a instance of a Subscription Container + * or an UI-Component that contains an instance of the Subscription Container to be unsubscribed. */ public unsubscribe(subscriptionInstance: any) { - // Helper function to remove SubscriptionContainer from Observer + // Helper function to remove Subscription Container from Observer const unsub = (subscriptionContainer: SubscriptionContainer) => { subscriptionContainer.ready = false; - - // Remove SubscriptionContainers from Observer subscriptionContainer.subscribers.forEach((observer) => { observer.unsubscribe(subscriptionContainer); }); }; - // Unsubscribe callback based Subscription + // Unsubscribe callback based Subscription Container if (subscriptionInstance instanceof CallbackSubscriptionContainer) { unsub(subscriptionInstance); this.callbackSubs.delete(subscriptionInstance); @@ -171,7 +165,7 @@ export class SubController { return; } - // Unsubscribe component based Subscription + // Unsubscribe component based Subscription Container if (subscriptionInstance instanceof ComponentSubscriptionContainer) { unsub(subscriptionInstance); this.componentSubs.delete(subscriptionInstance); @@ -182,24 +176,9 @@ export class SubController { return; } - // Unsubscribe component based Subscription with subscriptionInstance that holds a componentSubscriptionContainer - if (subscriptionInstance.componentSubscriptionContainer) { - unsub( - subscriptionInstance.componentSubscriptionContainer as ComponentSubscriptionContainer - ); - this.componentSubs.delete( - subscriptionInstance.componentSubscriptionContainer - ); - - Agile.logger.if - .tag(['runtime', 'subscription']) - .info(LogCodeManager.getLog('15:01:01'), subscriptionInstance); - return; - } - - // Unsubscribe component based Subscription with subscriptionInstance that holds componentSubscriptionContainers + // Unsubscribe component based Subscription Container extracted from the 'componentSubscriptionContainers' property if ( - subscriptionInstance.componentSubscriptionContainers && + subscriptionInstance['componentSubscriptionContainers'] !== null && Array.isArray(subscriptionInstance.componentSubscriptionContainers) ) { subscriptionInstance.componentSubscriptionContainers.forEach( @@ -216,60 +195,59 @@ export class SubController { } } - //========================================================================================================= - // Register Subscription - //========================================================================================================= /** + * Returns a Component or Callback based Subscription Container + * based on the specified `integrationInstance`. + * * @internal - * Registers SubscriptionContainer and decides weather integrationInstance is a callback or component based Subscription - * @param integrationInstance - Callback Function or Component - * @param subs - Initial Subscriptions - * @param config - Config + * @param integrationInstance - Callback function or Component Instance for triggering a rerender on a UI-Component. + * @param config - Configuration object */ - public registerSubscription( + public createSubscriptionContainer( integrationInstance: any, - subs: Array = [], config: RegisterSubscriptionConfigInterface = {} ): SubscriptionContainer { config = defineConfig(config, { waitForMount: this.agileInstance().config.waitForMount, }); return isFunction(integrationInstance) - ? this.registerCallbackSubscription(integrationInstance, subs, config) - : this.registerComponentSubscription(integrationInstance, subs, config); + ? this.createCallbackSubscriptionContainer(integrationInstance, config) + : this.createComponentSubscriptionContainer(integrationInstance, config); } //========================================================================================================= - // Register Component Subscription + // Create Component Subscription Container //========================================================================================================= /** - * @internal + * Returns a newly created Component based Subscription Container. + * * Registers Component based Subscription and applies SubscriptionContainer to Component. * If an instance called 'subscriptionContainers' exists in Component it will push the new SubscriptionContainer to this Array, * otherwise it creates a new Instance called 'subscriptionContainer' which holds the new SubscriptionContainer - * @param componentInstance - Component that got subscribed by Observer/s - * @param subs - Initial Subscriptions - * @param config - Config + * + * @internal + * @param componentInstance - Component Instance for triggering a rerender on a UI-Component. + * @param config - Configuration object. */ - public registerComponentSubscription( + public createComponentSubscriptionContainer( componentInstance: any, - subs: Array = [], config: RegisterSubscriptionConfigInterface = {} ): ComponentSubscriptionContainer { const componentSubscriptionContainer = new ComponentSubscriptionContainer( componentInstance, - subs, + [], removeProperties(config, ['waitForMount']) ); this.componentSubs.add(componentSubscriptionContainer); - // Set to ready if not waiting for component to mount + // Define ready state of Subscription Container if (config.waitForMount) { if (this.mountedComponents.has(componentInstance)) componentSubscriptionContainer.ready = true; } else componentSubscriptionContainer.ready = true; - // Add subscriptionContainer to Component, to have an instance of it there (necessary to unsubscribe SubscriptionContainer later) + // Add subscriptionContainer to Component, to have an instance of it there + // (Required to unsubscribe the Subscription Container later via the Component Instance) if ( componentInstance.componentSubscriptionContainers && Array.isArray(componentInstance.componentSubscriptionContainers) @@ -278,7 +256,9 @@ export class SubController { componentSubscriptionContainer ); else - componentInstance.componentSubscriptionContainer = componentSubscriptionContainer; + componentInstance.componentSubscriptionContainers = [ + componentSubscriptionContainer, + ]; Agile.logger.if .tag(['runtime', 'subscription']) @@ -287,24 +267,20 @@ export class SubController { return componentSubscriptionContainer; } - //========================================================================================================= - // Register Callback Subscription - //========================================================================================================= /** + * Returns a newly created Callback based Subscription Container. + * * @internal - * Registers Callback based Subscription - * @param callbackFunction - Callback Function that causes rerender on Component which got subscribed by Observer/s - * @param subs - Initial Subscriptions - * @param config - Config + * @param callbackFunction - Callback function for triggering a rerender on a UI-Component. + * @param config - Configuration object */ - public registerCallbackSubscription( + public createCallbackSubscriptionContainer( callbackFunction: () => void, - subs: Array = [], config: RegisterSubscriptionConfigInterface = {} ): CallbackSubscriptionContainer { const callbackSubscriptionContainer = new CallbackSubscriptionContainer( callbackFunction, - subs, + [], removeProperties(config, ['waitForMount']) ); this.callbackSubs.add(callbackSubscriptionContainer); @@ -317,42 +293,49 @@ export class SubController { return callbackSubscriptionContainer; } - //========================================================================================================= - // Mount - //========================================================================================================= /** + * Mounts Component based Subscription Container. + * * @internal - * Mounts Component based SubscriptionContainer * @param componentInstance - SubscriptionContainer(Component) that gets mounted */ public mount(componentInstance: any) { - if (componentInstance.componentSubscriptionContainer) - componentInstance.componentSubscriptionContainer.ready = true; + if ( + componentInstance.componentSubscriptionContainers && + Array.isArray(componentInstance.componentSubscriptionContainers) + ) + componentInstance.componentSubscriptionContainers.map( + (c) => (c.ready = true) + ); this.mountedComponents.add(componentInstance); } - //========================================================================================================= - // Unmount - //========================================================================================================= /** + * Unmounts Component based Subscription Containers. + * * @internal - * Unmounts Component based SubscriptionContainer * @param componentInstance - SubscriptionContainer(Component) that gets unmounted */ public unmount(componentInstance: any) { - if (componentInstance.componentSubscriptionContainer) - componentInstance.componentSubscriptionContainer.ready = false; + if ( + componentInstance.componentSubscriptionContainers && + Array.isArray(componentInstance.componentSubscriptionContainers) + ) + componentInstance.componentSubscriptionContainers.map( + (c) => (c.ready = false) + ); this.mountedComponents.delete(componentInstance); } } -/** - * @param waitForMount - Whether the subscriptionContainer should only become ready - * when the Component has been mounted. (default = agileInstance.config.waitForMount) - */ interface RegisterSubscriptionConfigInterface extends SubscriptionContainerConfigInterface { + /** + * Whether the Subscription Container should only become ready + * when the Component has been mounted. + * @default agileInstance.config.waitForMount + */ waitForMount?: boolean; } diff --git a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts index eb1fbc90..77634bd9 100644 --- a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts +++ b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts @@ -45,7 +45,7 @@ describe('SubController Tests', () => { dummySubscriptionContainer = new SubscriptionContainer(); dummyObserver1.value = 'myCoolValue'; - subController.registerSubscription = jest.fn( + subController.createSubscriptionContainer = jest.fn( () => dummySubscriptionContainer ); jest.spyOn(dummyObserver1, 'subscribe'); @@ -73,7 +73,7 @@ describe('SubController Tests', () => { subscriptionContainer: dummySubscriptionContainer, }); - expect(subController.registerSubscription).toHaveBeenCalledWith( + expect(subController.createSubscriptionContainer).toHaveBeenCalledWith( dummyIntegration, [dummyObserver1, dummyObserver2], { @@ -113,7 +113,7 @@ describe('SubController Tests', () => { beforeEach(() => { dummySubscriptionContainer = new SubscriptionContainer(); - subController.registerSubscription = jest.fn( + subController.createSubscriptionContainer = jest.fn( () => dummySubscriptionContainer ); jest.spyOn(dummyObserver1, 'subscribe'); @@ -133,7 +133,7 @@ describe('SubController Tests', () => { expect(subscribeWithSubsArrayResponse).toBe(dummySubscriptionContainer); - expect(subController.registerSubscription).toHaveBeenCalledWith( + expect(subController.createSubscriptionContainer).toHaveBeenCalledWith( dummyIntegration, [dummyObserver1, dummyObserver2], { @@ -175,7 +175,7 @@ describe('SubController Tests', () => { const dummyIntegration = () => { /* empty function */ }; - const callbackSubscriptionContainer = subController.registerCallbackSubscription( + const callbackSubscriptionContainer = subController.createCallbackSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2] ); @@ -196,7 +196,7 @@ describe('SubController Tests', () => { const dummyIntegration: any = { dummy: 'integration', }; - const componentSubscriptionContainer = subController.registerComponentSubscription( + const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2] ); @@ -217,7 +217,7 @@ describe('SubController Tests', () => { const dummyIntegration: any = { dummy: 'integration', }; - const componentSubscriptionContainer = subController.registerComponentSubscription( + const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2] ); @@ -239,11 +239,11 @@ describe('SubController Tests', () => { dummy: 'integration', componentSubscriptionContainers: [], }; - const componentSubscriptionContainer = subController.registerComponentSubscription( + const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2] ); - const componentSubscriptionContainer2 = subController.registerComponentSubscription( + const componentSubscriptionContainer2 = subController.createComponentSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2] ); @@ -277,10 +277,10 @@ describe('SubController Tests', () => { dummySubscriptionContainer = new SubscriptionContainer(); dummyAgile.config.waitForMount = 'dummyWaitForMount' as any; - subController.registerCallbackSubscription = jest.fn( + subController.createCallbackSubscriptionContainer = jest.fn( () => dummySubscriptionContainer as CallbackSubscriptionContainer ); - subController.registerComponentSubscription = jest.fn( + subController.createComponentSubscriptionContainer = jest.fn( () => dummySubscriptionContainer as ComponentSubscriptionContainer ); }); @@ -290,21 +290,21 @@ describe('SubController Tests', () => { /* empty function */ }; - const subscriptionContainer = subController.registerSubscription( + const subscriptionContainer = subController.createSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2] ); expect(subscriptionContainer).toBe(dummySubscriptionContainer); expect( - subController.registerCallbackSubscription + subController.createCallbackSubscriptionContainer ).toHaveBeenCalledWith( dummyIntegration, [dummyObserver1, dummyObserver2], { waitForMount: dummyAgile.config.waitForMount } ); expect( - subController.registerComponentSubscription + subController.createComponentSubscriptionContainer ).not.toHaveBeenCalled(); }); @@ -313,7 +313,7 @@ describe('SubController Tests', () => { /* empty function */ }; - const subscriptionContainer = subController.registerSubscription( + const subscriptionContainer = subController.createSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2], { key: 'niceKey', proxyKeyMap: {}, waitForMount: false } @@ -321,42 +321,42 @@ describe('SubController Tests', () => { expect(subscriptionContainer).toBe(dummySubscriptionContainer); expect( - subController.registerCallbackSubscription + subController.createCallbackSubscriptionContainer ).toHaveBeenCalledWith( dummyIntegration, [dummyObserver1, dummyObserver2], { key: 'niceKey', proxyKeyMap: {}, waitForMount: false } ); expect( - subController.registerComponentSubscription + subController.createComponentSubscriptionContainer ).not.toHaveBeenCalled(); }); it('should call registerComponentSubscription if passed integrationInstance is not a Function (default config)', () => { const dummyIntegration = { dummy: 'integration' }; - const subscriptionContainer = subController.registerSubscription( + const subscriptionContainer = subController.createSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2] ); expect(subscriptionContainer).toBe(dummySubscriptionContainer); expect( - subController.registerComponentSubscription + subController.createComponentSubscriptionContainer ).toHaveBeenCalledWith( dummyIntegration, [dummyObserver1, dummyObserver2], { waitForMount: dummyAgile.config.waitForMount } ); expect( - subController.registerCallbackSubscription + subController.createCallbackSubscriptionContainer ).not.toHaveBeenCalled(); }); it('should call registerComponentSubscription if passed integrationInstance is not a Function (specific config)', () => { const dummyIntegration = { dummy: 'integration' }; - const subscriptionContainer = subController.registerSubscription( + const subscriptionContainer = subController.createSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2], { key: 'niceKey', proxyKeyMap: {}, waitForMount: false } @@ -364,14 +364,14 @@ describe('SubController Tests', () => { expect(subscriptionContainer).toBe(dummySubscriptionContainer); expect( - subController.registerComponentSubscription + subController.createComponentSubscriptionContainer ).toHaveBeenCalledWith( dummyIntegration, [dummyObserver1, dummyObserver2], { key: 'niceKey', proxyKeyMap: {}, waitForMount: false } ); expect( - subController.registerCallbackSubscription + subController.createCallbackSubscriptionContainer ).not.toHaveBeenCalled(); }); }); @@ -380,7 +380,7 @@ describe('SubController Tests', () => { it('should return ready componentSubscriptionContainer and add it to dummyIntegration at componentSubscriptionContainer (config.waitForMount = false)', () => { const dummyIntegration: any = { dummy: 'integration' }; - const componentSubscriptionContainer = subController.registerComponentSubscription( + const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2], { waitForMount: false } @@ -418,7 +418,7 @@ describe('SubController Tests', () => { componentSubscriptionContainers: [], }; - const componentSubscriptionContainer = subController.registerComponentSubscription( + const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2], { waitForMount: false } @@ -457,7 +457,7 @@ describe('SubController Tests', () => { dummy: 'integration', }; - const componentSubscriptionContainer = subController.registerComponentSubscription( + const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2], { waitForMount: true } @@ -491,7 +491,7 @@ describe('SubController Tests', () => { }; subController.mount(dummyIntegration); - const componentSubscriptionContainer = subController.registerComponentSubscription( + const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2], { waitForMount: true } @@ -527,7 +527,7 @@ describe('SubController Tests', () => { /* empty function */ }; - const callbackSubscriptionContainer = subController.registerCallbackSubscription( + const callbackSubscriptionContainer = subController.createCallbackSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2] ); @@ -567,7 +567,7 @@ describe('SubController Tests', () => { /* empty function */ }; - const callbackSubscriptionContainer = subController.registerCallbackSubscription( + const callbackSubscriptionContainer = subController.createCallbackSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2], { @@ -611,7 +611,7 @@ describe('SubController Tests', () => { beforeEach(() => { dummyAgile.config.waitForMount = true; - componentSubscriptionContainer = subController.registerComponentSubscription( + componentSubscriptionContainer = subController.createComponentSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2] ); @@ -636,7 +636,7 @@ describe('SubController Tests', () => { beforeEach(() => { dummyAgile.config.waitForMount = true; - componentSubscriptionContainer = subController.registerComponentSubscription( + componentSubscriptionContainer = subController.createComponentSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2] ); diff --git a/packages/react/src/hooks/useAgile.ts b/packages/react/src/hooks/useAgile.ts index f3fe5e03..5ddb00f1 100644 --- a/packages/react/src/hooks/useAgile.ts +++ b/packages/react/src/hooks/useAgile.ts @@ -12,6 +12,7 @@ import { isValidObject, generateId, ProxyWeakMapType, + ComponentIdType, } from '@agile-ts/core'; import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; import { ProxyTree } from '@agile-ts/proxytree'; @@ -128,7 +129,12 @@ export function useAgile< forceRender(); }, observers, - { key: config.key, proxyWeakMap, waitForMount: false } + { + key: config.key, + proxyWeakMap, + waitForMount: false, + componentId: config.componentId, + } ); // Unsubscribe Callback based Subscription on Unmount @@ -184,6 +190,7 @@ interface AgileHookConfigInterface { key?: SubscriptionContainerKeyType; agileInstance?: Agile; proxyBased?: boolean; + componentId?: ComponentIdType; } interface ProxyTreeMapInterface { From ea35b95e0586c690f86ddac298d028bf50f6b556 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Tue, 8 Jun 2021 08:48:05 +0200 Subject: [PATCH 053/117] fixed typos --- packages/core/src/runtime/index.ts | 51 ++++---- packages/core/src/runtime/observer.ts | 119 +++++++++++------- packages/core/src/runtime/runtime.job.ts | 83 +++++++++--- .../ComponentSubscriptionContainer.ts | 17 ++- .../container/SubscriptionContainer.ts | 62 +++++---- .../runtime/subscription/sub.controller.ts | 66 +++++----- 6 files changed, 240 insertions(+), 158 deletions(-) diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index f6267a0a..e4d982bb 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -30,7 +30,7 @@ export class Runtime { /** * The Runtime executes and queues ingested Observer based Jobs - * to prevent race conditions and optimized rerenders of subscribed Components. + * to prevent race conditions and optimized rerender of subscribed Components. * * Each provided Job will be executed when it is its turn * by calling the Job Observer's 'perform()' method. @@ -40,7 +40,7 @@ export class Runtime { * * The rerender queue is designed for optimizing the render count * by combining rerender Jobs of the same Component - * and ingoing rerender requests for unmounted Components. + * and ignoring rerender requests for unmounted Components. * * @internal * @param agileInstance - Instance of Agile the Runtime belongs to. @@ -53,7 +53,11 @@ export class Runtime { * Adds the specified Observer based Job to the internal Job queue, * where it will be performed when it is its turn. * - * @internal + * After a successful execution it is added to the rerender queue, + * where all the Observer's subscribed Subscription Containers + * cause rerender on Components the Observer is represented in. + * + * @public * @param job - Job to be performed. * @param config - Configuration object */ @@ -80,7 +84,8 @@ export class Runtime { * Performs the specified Job * and adds it to the rerender queue if necessary. * - * After the execution of the Job it checks if there are still Jobs left in the queue. + * After the execution of the provided Job it is checked whether + * there are still Jobs left in the Job queue. * - If so, the next Job in the queue is performed. * - If not, the `jobsToRerender` queue will be started to work off. * @@ -127,8 +132,8 @@ export class Runtime { } /** - * Executes the `jobsToRerender` queue - * and updates (causes rerender on) the Subscription Container (subscribed Component) + * Works of the `jobsToRerender` queue by updating (causing rerender on) + * the Subscription Container (subscribed Component) * of each Job Observer. * * It returns a boolean indicating whether any Subscription Container was updated. @@ -147,7 +152,7 @@ export class Runtime { ) return false; - // Subscription Containers that have to be updated (perform rerender on Component it represents). + // Subscription Containers that have to be updated. // Using a 'Set()' to combine several equal SubscriptionContainers into one (rerender optimisation). const subscriptionsToUpdate = new Set(); @@ -188,14 +193,15 @@ export class Runtime { let updateSubscriptionContainer; - // Handle Selectors + // Handle Selectors of Subscription Container + // (-> check if a selected part of the Observer value has changed) updateSubscriptionContainer = this.handleSelectors( subscriptionContainer, job ); - // Check if Subscription Container with same componentId is already in the 'subscriptionToUpdate' queue - // (rerender optimisation) + // Check if Subscription Container with same 'componentId' + // is already in the 'subscriptionToUpdate' queue (rerender optimisation) updateSubscriptionContainer = updateSubscriptionContainer && Array.from(subscriptionsToUpdate).findIndex( @@ -214,13 +220,13 @@ export class Runtime { if (subscriptionsToUpdate.size <= 0) return false; - // Update Subscription Containers (trigger rerender on subscribed Component) + // Update Subscription Containers (trigger rerender on Components they represent) subscriptionsToUpdate.forEach((subscriptionContainer) => { // Call 'callback function' if Callback based Subscription if (subscriptionContainer instanceof CallbackSubscriptionContainer) subscriptionContainer.callback(); - // Call 'update method' if Component based Subscription + // Call 'update method' in Integrations if Component based Subscription if (subscriptionContainer instanceof ComponentSubscriptionContainer) this.agileInstance().integrations.update( subscriptionContainer.component, @@ -238,19 +244,19 @@ export class Runtime { } /** - * Maps the values of updated Observers (updatedSubscribers) into a key map. + * Maps the values of updated Observers (`updatedSubscribers`) + * of the specified Subscription Container into a key map. * - * The key is extracted from the Observer itself or from the Subscription Containers 'subscriberKeysWeakMap'. + * The key containing the Observer value is extracted from the Observer itself + * or from the Subscription Container's `subscriberKeysWeakMap`. * * @internal - * @param subscriptionContainer - Subscription Container from which the 'updatedSubscribers' are to be mapped to a key map. + * @param subscriptionContainer - Subscription Container from which the `updatedSubscribers` are to be mapped to a key map. */ public getUpdatedObserverValues( subscriptionContainer: SubscriptionContainer ): { [key: string]: any } { const props: { [key: string]: any } = {}; - - // Map updated Observer values into the props key map for (const observer of subscriptionContainer.updatedSubscribers) { const key = subscriptionContainer.subscriberKeysWeakMap.get(observer) ?? @@ -261,14 +267,15 @@ export class Runtime { } /** - * Returns a boolean indicating whether the Subscription Container can be updated or not. + * Returns a boolean indicating whether the specified Subscription Container can be updated or not + * based on the selector functions (`selectorsWeakMap`) of the Subscription Container. * - * Therefore it reviews the '.value' and the '.previousValue' property of the Observer represented by the Job. + * This is done by checking the '.value' and the '.previousValue' property of the Observer represented by the Job. * If a selected property differs, the Subscription Container is allowed to update/rerender (returns true). * * @internal * @param subscriptionContainer - Subscription Container to be checked if it can update. - * @param job - Job the Subscription Container belongs to. + * @param job - Job containing the Observer which has subscribed the Subscription Container. */ public handleSelectors( subscriptionContainer: SubscriptionContainer, @@ -281,7 +288,7 @@ export class Runtime { // because no specific part of the Observer was selected // -> The Subscription Container should update // no matter what was updated in the Observer - if (!selectors) return true; + if (selectors == null) return true; // Check if a selected part of Observer value has changed const previousValue = job.observer.previousValue; @@ -289,7 +296,7 @@ export class Runtime { for (const selector of selectors) { if ( notEqual(selector(newValue), selector(previousValue)) - // || newValueDeepness !== previousValueDeepness // Not possible to check + // || newValueDeepness !== previousValueDeepness // Not possible to check the object deepness ) return true; } diff --git a/packages/core/src/runtime/observer.ts b/packages/core/src/runtime/observer.ts index 5710bd18..cfe7e7ce 100644 --- a/packages/core/src/runtime/observer.ts +++ b/packages/core/src/runtime/observer.ts @@ -12,20 +12,32 @@ import { export type ObserverKey = string | number; export class Observer { + // Agile Instance the Observer belongs to public agileInstance: () => Agile; + // Key/Name identifier of the Subscription Container public _key?: ObserverKey; - public dependents: Set = new Set(); // Observers that depend on this Observer - public subscribedTo: Set = new Set(); // SubscriptionContainers (Components) that this Observer is subscribed to - public value?: ValueType; // Value of Observer - public previousValue?: ValueType; // Previous Value of Observer + // Observers that depend on this Observer + public dependents: Set = new Set(); + // Subscription Containers (Components) the Observer is subscribed to + public subscribedTo: Set = new Set(); + // Current value of Observer + public value?: ValueType; + // Previous value of Observer + public previousValue?: ValueType; /** + * Handles the subscriptions to Subscription Containers (Components) + * and keeps track of dependencies. + * + * All Agile Classes that can be bound a UI-Component have their own Observer + * which manages the above mentioned things for them. + * + * The Observer is no standalone class and should be extended from a 'real' Observer. + * * @internal - * Observer - Handles subscriptions and dependencies of an Agile Class and is like an instance to the Runtime - * Note: No stand alone class!! - * @param agileInstance - An instance of Agile - * @param config - Config + * @param agileInstance - Instance of Agile the Observer belongs to. + * @param config - Configuration object */ constructor( agileInstance: Agile, @@ -46,28 +58,31 @@ export class Observer { } /** - * @internal - * Set Key/Name of Observer + * Updates the key/name identifier of the Observer. + * + * @public + * @param value - New key/name identifier. */ public set key(value: StateKey | undefined) { this._key = value; } /** - * @internal - * Get Key/Name of Observer + * Returns the key/name identifier of the State. + * + * @public */ public get key(): StateKey | undefined { return this._key; } - //========================================================================================================= - // Ingest - //========================================================================================================= /** - * @internal - * Ingests Observer into Runtime - * @param config - Configuration + * Ingests the Observer into the runtime, + * by creating a Runtime Job + * and adding the Observer to the created Job. + * + * @public + * @param config - Configuration object */ public ingest(config: ObserverIngestConfigInterface = {}): void { config = defineConfig(config, { @@ -93,54 +108,56 @@ export class Observer { }); } - //========================================================================================================= - // Perform - //========================================================================================================= /** - * @internal - * Performs Job of Runtime - * @param job - Job that gets performed + * Method executed by the Runtime to perform the Runtime Job, + * previously ingested (`ingest()`) by the Observer. + * + * @public + * @param job - Runtime Job to be performed. */ public perform(job: RuntimeJob): void { LogCodeManager.log('17:03:00'); } - //========================================================================================================= - // Depend - //========================================================================================================= /** - * @internal - * Adds Dependent to Observer which gets ingested into the Runtime whenever this Observer mutates - * @param observer - Observer that will depend on this Observer + * Adds specified Observer to the dependents of this Observer. + * + * Every time this Observer is ingested into the Runtime, + * the dependent Observers are ingested into the Runtime too. + * + * @public + * @param observer - Observer to depends on this Observer. */ public depend(observer: Observer): void { if (!this.dependents.has(observer)) this.dependents.add(observer); } - //========================================================================================================= - // Subscribe - //========================================================================================================= /** - * @internal - * Adds Subscription to Observer - * @param subscriptionContainer - SubscriptionContainer(Component) that gets subscribed by this Observer + * Subscribes Observer to the specified Subscription Container (Component). + * + * Every time this Observer is ingested into the Runtime, + * a rerender might be triggered on the Component the Subscription Container represents. + * + * @public + * @param subscriptionContainer - Subscription Container to which the Observer should subscribe. */ public subscribe(subscriptionContainer: SubscriptionContainer): void { if (!this.subscribedTo.has(subscriptionContainer)) { this.subscribedTo.add(subscriptionContainer); - // Add this to subscriptionContainer to keep track of the Observers the subscriptionContainer hold + // Add Observer to Subscription Container + // to keep track of the Observers that have subscribed the Subscription Container. + // For example to unsubscribe the subscribed Observers + // when the Subscription Container (Component) unmounts. subscriptionContainer.subscribers.add(this); } } - //========================================================================================================= - // Unsubscribe - //========================================================================================================= /** - * @internal - * Removes Subscription from Observer - * @param subscriptionContainer - SubscriptionContainer(Component) that gets unsubscribed by this Observer + * Unsubscribes Observer from specified Subscription Container (Component). + * + * @public + * @param subscriptionContainer - Subscription Container that the Observer should unsubscribe. */ public unsubscribe(subscriptionContainer: SubscriptionContainer): void { if (this.subscribedTo.has(subscriptionContainer)) { @@ -157,9 +174,25 @@ export class Observer { * @param value - Initial Value of Observer */ export interface CreateObserverConfigInterface { + /** + * Initial Observers that depend on this Observer. + * @default [] + */ dependents?: Array; + /** + * Initial Subscription Container the Observer is subscribed to. + * @default [] + */ subs?: Array; + /** + * Key/Name identifier of the Observer. + * @default undefined + */ key?: ObserverKey; + /** + * Initial value of the Observer. + * @defualt undefined + */ value?: ValueType; } diff --git a/packages/core/src/runtime/runtime.job.ts b/packages/core/src/runtime/runtime.job.ts index a3e559ea..25420d8d 100644 --- a/packages/core/src/runtime/runtime.job.ts +++ b/packages/core/src/runtime/runtime.job.ts @@ -1,19 +1,27 @@ import { Observer, defineConfig, SubscriptionContainer } from '../internal'; export class RuntimeJob { - public _key?: RuntimeJobKey; public config: RuntimeJobConfigInterface; - public observer: ObserverType; // Observer the Job represents - public rerender: boolean; // If Job will cause rerender on subscriptionContainer in Observer - public performed = false; // If Job has been performed by Runtime - public subscriptionContainersToUpdate = new Set(); // SubscriptionContainer (from Observer) that have to be updated/rerendered - public triesToUpdate = 0; // How often not ready subscriptionContainers of this Job have been tried to update + + // Key/Name identifier of the Subscription Container + public _key?: RuntimeJobKey; + // Observer the Job represents + public observer: ObserverType; + // Whether the Subscription Containers (Components) of the Observer can be re-rendered + public rerender: boolean; + // Whether the Job has been performed by the runtime + public performed = false; + // Subscription Container of the Observer that have to be updated/re-rendered + public subscriptionContainersToUpdate = new Set(); + // How often not ready Subscription Container of the Observer have been tried to update + public triesToUpdate = 0; /** + * A Job that contains an Observer to be executed by the runtime. + * * @internal - * Job - Represents Observer that gets performed by the Runtime - * @param observer - Observer - * @param config - Config + * @param observer - Observer to be represented by the Runtime Job. + * @param config - Configuration object */ constructor( observer: ObserverType, @@ -42,22 +50,34 @@ export class RuntimeJob { this.subscriptionContainersToUpdate = new Set(observer.subscribedTo); } - public get key(): RuntimeJobKey | undefined { - return this._key; - } - + /** + * Updates the key/name identifier of the Runtime Job. + * + * @public + * @param value - New key/name identifier. + */ public set key(value: RuntimeJobKey | undefined) { this._key = value; } + + /** + * Returns the key/name identifier of the Runtime Job. + * + * @public + */ + public get key(): RuntimeJobKey | undefined { + return this._key; + } } export type RuntimeJobKey = string | number; -/** - * @param key - Key/Name of RuntimeJob - */ export interface CreateRuntimeJobConfigInterface extends RuntimeJobConfigInterface { + /** + * Key/Name identifier of the Runtime Job. + * @default undefined + */ key?: RuntimeJobKey; } @@ -70,17 +90,40 @@ export interface CreateRuntimeJobConfigInterface * But be aware that this can lead to an overflow of 'old' Jobs after some time. (affects performance) */ export interface RuntimeJobConfigInterface { + /** + * Whether to perform the Runtime Job in background. + * So that the UI isn't notified of these changes and thus doesn't rerender. + * @default false + */ background?: boolean; + /** + * Configuration of the execution of defined side effects. + * @default {enabled: true, exclude: []} + */ sideEffects?: SideEffectConfigInterface; + /** + * Whether the Runtime Job should be forced through the runtime + * although it might be useless from the viewpoint of the runtime. + * @default false + */ force?: boolean; + /** + * How often the runtime should try to update not ready Subscription Containers of the Observer the Job represents. + * If 'null' the runtime tries to update the not ready Subscription Container until they are ready (infinite). + * @default 3 + */ numberOfTriesToUpdate?: number | null; } -/** - * @param enabled - If SideEffects get executed - * @param exclude - SideEffect at Keys that doesn't get executed - */ export interface SideEffectConfigInterface { + /** + * Whether to execute the defined side effects + * @default true + */ enabled?: boolean; + /** + * Side effect key identifier that won't be executed. + * @default [] + */ exclude?: string[]; } diff --git a/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts index 1d10b209..9e1daeef 100644 --- a/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts @@ -35,18 +35,23 @@ export class ComponentSubscriptionContainer< * [Observer, Observer] * ``` * Thus the Integrations 'updateMethod' method can be called - * with an complete object of changed Observer values. + * with an complete object of changed Observer values. * ``` - * { - * state1: Observer.value, - * state2: Observer.value - * } + * updateMethod: (componentInstance, updatedData) => { + * console.log(componentInstance); // Returns [this.component] + * console.log(updatedData); // Returns changed Observer values (see below) + * // { + * // state1: Observer.value, + * // state2: Observer.value + * // } + * } * ``` * * [Learn more..](https://agile-ts.org/docs/core/integration#component-based) * * @internal - * @param component - Component to be represent by the Subscription Container. + * @param component - Component to be represented by the Subscription Container + * and mutated via the Integration method 'updateMethod()' to trigger rerender on it. * @param subs - Observers to be subscribed to the Subscription Container. * @param config - Configuration object */ diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index 9e4a5540..e5791cf9 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -15,11 +15,11 @@ export class SubscriptionContainer { * and the Component the Subscription Container represents are ready. * * When both are ready, the Subscription Container is allowed - * to trigger rerenders on the Component based on its type. (Component or Callback based) + * to trigger rerender on the Component. */ public ready = false; /** - * Id of the Component the Subscription Container represents. + * Unique identifier of the Component the Subscription Container represents. */ public componentId?: ComponentIdType; @@ -28,8 +28,8 @@ export class SubscriptionContainer { * * The subscribed Observers use the Subscription Container * as an interface to the Component it represents. - * Through the Subscription Container, they can then trigger rerenders - * on the Component when their value changes. + * Through the Subscription Container, they can easily trigger rerender + * on the Component, for example, when their value changes. * * [Learn more..](https://agile-ts.org/docs/core/integration#-subscriptions) */ @@ -37,17 +37,15 @@ export class SubscriptionContainer { /** * Temporary stores the subscribed Observers, * that were performed by the runtime - * and are currently running through the update Subscription Container process. - * - * This is used for example, to merge the changed Observer values - * into the Component's local State Management instance for a Component based Subscription. + * and are currently running through the update Subscription Container (rerender) process. */ public updatedSubscribers: Array = []; /** * Whether the Subscription Container is object based. * - * A Observer is object based when the subscribed Observers were provided in an Observer key map. + * An Observer is object based when the subscribed Observers + * have been provided in an Observer key map. * ``` * { * state1: Observer, @@ -58,22 +56,22 @@ export class SubscriptionContainer { * * Often Component based Subscriptions are object based, * because each Observer requires a unique identifier - * to properly merge the Observer value into the Component's local State Management instance. + * to be properly represented in the 'updatedData' object sent to the Integration 'updateMethod()'. */ public isObjectBased = false; /** - * Weak map for storing 'external' key identifiers for Observer. + * Weak map for storing 'external' key identifiers for subscribed Observers. * * https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap */ public subscriberKeysWeakMap: WeakMap; /** - * Weak Map for storing selector functions of subscribed Observer. + * Weak Map for storing selector functions for subscribed Observers. * - * A selector functions allows the partly subscription to an Observer value. - * Only if the selected Observe value part changes, - * the Subscription Container rerenders the Component it represents. + * A selector function allows partial subscription to an Observer value. + * Only when the selected Observer value part changes, + * the Subscription Container rerender the Component. * * https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap */ @@ -81,11 +79,11 @@ export class SubscriptionContainer { /** * A Subscription Container represents a UI-Component in AgileTs - * that can be subscribed by multiple Observers. + * that can be subscribed by multiple Observer Instances. * * These Observers use the Subscription Container as an interface * to trigger a rerender on the UI-Component it represents, - * for example when their value has changed. + * for example, when their value has changed. * * @internal * @param subs - Observers to be subscribed to the Subscription Container. @@ -106,8 +104,8 @@ export class SubscriptionContainer { this.componentId = config?.componentId; this.subscriberKeysWeakMap = new WeakMap(); - // Create a selector function for each specified proxy path, - // which selects the property at the path end + // Create a selector function for each specified proxy path + // that selects the property at the path end const selectorWeakMap: SelectorWeakMapType = config.selectorWeakMap as any; this.assignProxySelectors( selectorWeakMap, @@ -118,10 +116,10 @@ export class SubscriptionContainer { } /** - * Assigns selector functions created based on the paths of the provided Proxy Weak Map - * to the specified `selectorWeakMap`. + * Assigns to the specified `selectorWeakMap` selector functions + * created based on the paths of the specified Proxy WeakMap. * - * @param selectorWeakMap - Selector Weak Map the created proxy selectors are added to. + * @param selectorWeakMap - Selector WeakMap the created proxy selector functions are added to. * @param proxyWeakMap - Proxy Weak Map containing proxy paths for specified Observers in `subs`. * @param subs - Observers whose values are to be selected based on the specified `proxyWeakMap`. */ @@ -159,18 +157,18 @@ export interface SubscriptionContainerConfigInterface { */ key?: SubscriptionContainerKeyType; /** - * Key/Name identifier of the Component the Subscription Container represents. + * Key/Name identifier of the Component to be represented by the Subscription Container. * @default undefined */ componentId?: ComponentIdType; /** - * A Weak Map with a 2 dimensional arrays representing paths/routes - * to particular properties in the Observer. + * A Weak Map with a set of paths to certain properties + * in a Observer value for Observers. * - * The Component the Subscription Container represents - * is then only rerendered, when a property at a specified path changes. - * Not anymore if anything in the Observer object mutates, - * although it might not even be displayed in the Component. + * These paths are then selected via selector functions + * which allow the partly subscription to an Observer value. + * Only if the selected Observer value part changes, + * the Subscription Container rerender the Component. * * For example: * ``` @@ -191,11 +189,11 @@ export interface SubscriptionContainerConfigInterface { */ proxyWeakMap?: ProxyWeakMapType; /** - * A Weak Map with an array of selector functions for Observers. + * A Weak Map with a set of selector functions for Observers. * * A selector functions allows the partly subscription to an Observer value. - * Only if the selected Observe value part changes, - * the Subscription Container rerenders the Component it represents. + * Only if the selected Observer value part changes, + * the Subscription Container rerender the Component. * * @default new WeakMap() */ diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index 292df72f..37b56192 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -24,10 +24,13 @@ export class SubController { public mountedComponents: Set = new Set(); /** - * Manages the subscription to UI-Components. + * The Subscription Controller manages the subscription to UI-Components. + * + * Thus it creates Subscription Containers (Interfaces to UI-Components) + * and assigns them to Observers, so that the Observers can easily trigger rerender on Components. * * @internal - * @param agileInstance - Instance of Agile the Subscription Container belongs to. + * @param agileInstance - Instance of Agile the Subscription Controller belongs to. */ public constructor(agileInstance: Agile) { this.agileInstance = () => agileInstance; @@ -38,7 +41,7 @@ export class SubController { * Such Subscription Container know how to trigger a rerender on the UI-Component it represents * through the provided `integrationInstance`. * - * There exist two different ways on how the Subscription Container can cause a rerender on the Component. + * There exist two different ways the Subscription Container can cause a rerender on the Component. * - 1. Via a callback function that directly triggers a rerender on the Component. (Callback based Subscription) * [Learn more..](https://agile-ts.org/docs/core/integration/#callback-based) * - 2. Via the Component instance itself. @@ -46,7 +49,7 @@ export class SubController { * [Learn more..](https://agile-ts.org/docs/core/integration/#component-based) * * The in an object specified Observers are then automatically subscribed - * to the created Subscription Container and thus to the Component the Subscription Container represents. + * to the created Subscription Container and thus to the Component it represents. * * The advantage of subscribing the Observer via a object keymap, * is that each Observer has its own unique key identifier. @@ -56,9 +59,9 @@ export class SubController { * this.state = {...this.state, {state1: Observer1.value, state2: Observer2.value}} * ``` * - * @internal - * @param integrationInstance - Callback function or Component Instance for triggering a rerender on a UI-Component. - * @param subs - Observers to be subscribed to the Subscription Container. + * @public + * @param integrationInstance - Callback function or Component Instance to trigger a rerender on a UI-Component. + * @param subs - Observers to be subscribed to the to create Subscription Container. * @param config - Configuration object */ public subscribeWithSubsObject( @@ -84,7 +87,7 @@ export class SubController { subscriptionContainer.subscriberKeysWeakMap.set(subs[key], key); // Subscribe Observers to the created Subscription Container - // and build a Observer value keymap + // and build a Observer value keymap called props for (const key in subs) { const observer = subs[key]; observer.subscribe(subscriptionContainer); @@ -102,7 +105,7 @@ export class SubController { * Such Subscription Container know how to trigger a rerender on the UI-Component it represents * through the provided `integrationInstance`. * - * There exist two different ways on how the Subscription Container can cause a rerender on the Component. + * There exist two different ways the Subscription Container can cause a rerender on the Component. * - 1. Via a callback function that directly triggers a rerender on the Component. (Callback based Subscription) * [Learn more..](https://agile-ts.org/docs/core/integration/#callback-based) * - 2. Via the Component instance itself. @@ -110,11 +113,11 @@ export class SubController { * [Learn more..](https://agile-ts.org/docs/core/integration/#component-based) * * The in an array specified Observers are then automatically subscribed - * to the created Subscription Container and thus to the Component the Subscription Container represents. + * to the created Subscription Container and thus to the Component it represents. * - * @internal - * @param integrationInstance - Callback function or Component Instance for triggering a rerender on a UI-Component. - * @param subs - Observers to be subscribed to the Subscription Container. + * @public + * @param integrationInstance - Callback function or Component Instance to trigger a rerender on a UI-Component. + * @param subs - Observers to be subscribed to the to create Subscription Container. * @param config - Configuration object */ public subscribeWithSubsArray( @@ -138,12 +141,12 @@ export class SubController { * Unsubscribe the Subscription Container extracted from the specified 'subscriptionInstance' * from all Observers that were subscribed to it. * - * We should always unsubscribe a Subscription Container when it isn't in use anymore, - * for example when the Component it represented has been unmounted. + * We should always unregister a Subscription Container when it is no longer in use, + * for example when the Component it represents has been unmounted. * - * @internal + * @public * @param subscriptionInstance - Subscription Container - * or an UI-Component that contains an instance of the Subscription Container to be unsubscribed. + * or a UI-Component that contains an instance of a Subscription Container to be unsubscribed. */ public unsubscribe(subscriptionInstance: any) { // Helper function to remove Subscription Container from Observer @@ -196,11 +199,11 @@ export class SubController { } /** - * Returns a Component or Callback based Subscription Container + * Returns a newly created Component or Callback based Subscription Container * based on the specified `integrationInstance`. * * @internal - * @param integrationInstance - Callback function or Component Instance for triggering a rerender on a UI-Component. + * @param integrationInstance - Callback function or Component Instance to trigger a rerender on a UI-Component. * @param config - Configuration object */ public createSubscriptionContainer( @@ -215,18 +218,11 @@ export class SubController { : this.createComponentSubscriptionContainer(integrationInstance, config); } - //========================================================================================================= - // Create Component Subscription Container - //========================================================================================================= /** * Returns a newly created Component based Subscription Container. * - * Registers Component based Subscription and applies SubscriptionContainer to Component. - * If an instance called 'subscriptionContainers' exists in Component it will push the new SubscriptionContainer to this Array, - * otherwise it creates a new Instance called 'subscriptionContainer' which holds the new SubscriptionContainer - * * @internal - * @param componentInstance - Component Instance for triggering a rerender on a UI-Component. + * @param componentInstance - Component Instance to trigger a rerender on a UI-Component. * @param config - Configuration object. */ public createComponentSubscriptionContainer( @@ -247,7 +243,7 @@ export class SubController { } else componentSubscriptionContainer.ready = true; // Add subscriptionContainer to Component, to have an instance of it there - // (Required to unsubscribe the Subscription Container later via the Component Instance) + // (For example, required to unsubscribe the Subscription Container via the Component Instance) if ( componentInstance.componentSubscriptionContainers && Array.isArray(componentInstance.componentSubscriptionContainers) @@ -271,7 +267,7 @@ export class SubController { * Returns a newly created Callback based Subscription Container. * * @internal - * @param callbackFunction - Callback function for triggering a rerender on a UI-Component. + * @param callbackFunction - Callback function to trigger a rerender on a UI-Component. * @param config - Configuration object */ public createCallbackSubscriptionContainer( @@ -296,8 +292,8 @@ export class SubController { /** * Mounts Component based Subscription Container. * - * @internal - * @param componentInstance - SubscriptionContainer(Component) that gets mounted + * @public + * @param componentInstance - Component Instance containing a Subscription Container to be mounted */ public mount(componentInstance: any) { if ( @@ -314,8 +310,8 @@ export class SubController { /** * Unmounts Component based Subscription Containers. * - * @internal - * @param componentInstance - SubscriptionContainer(Component) that gets unmounted + * @public + * @param componentInstance - Component Instance containing a Subscription Container to be unmounted */ public unmount(componentInstance: any) { if ( @@ -333,8 +329,8 @@ export class SubController { interface RegisterSubscriptionConfigInterface extends SubscriptionContainerConfigInterface { /** - * Whether the Subscription Container should only become ready - * when the Component has been mounted. + * Whether the Subscription Container should be ready only + * when the Component it represents has been mounted. * @default agileInstance.config.waitForMount */ waitForMount?: boolean; From 2e25a44bdfb9ef64d985561f4a74b9f64bd56581 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Tue, 8 Jun 2021 17:44:28 +0200 Subject: [PATCH 054/117] optimized subscription controller --- .../functional-component-ts/package.json | 2 +- .../develop/functional-component-ts/yarn.lock | 46 +++--- packages/core/src/runtime/index.ts | 11 +- packages/core/src/runtime/observer.ts | 11 +- .../CallbackSubscriptionContainer.ts | 2 +- .../ComponentSubscriptionContainer.ts | 2 +- .../container/SubscriptionContainer.ts | 109 ++++++++----- .../runtime/subscription/sub.controller.ts | 145 ++++++++---------- .../core/tests/unit/runtime/runtime.test.ts | 57 +------ .../CallbackSubscriptionContainer.test.ts | 25 ++- .../ComponentSubscriptionContainer.test.ts | 25 ++- .../container/SubscriptionContainer.test.ts | 75 +++++++-- .../subscription/sub.controller.test.ts | 20 +-- packages/event/src/hooks/useEvent.ts | 2 +- packages/react/src/hocs/AgileHOC.ts | 10 +- packages/react/src/hooks/useAgile.ts | 2 +- packages/vue/src/bindAgileInstances.ts | 16 +- 17 files changed, 300 insertions(+), 260 deletions(-) diff --git a/examples/react/develop/functional-component-ts/package.json b/examples/react/develop/functional-component-ts/package.json index be742bb8..08b5324c 100644 --- a/examples/react/develop/functional-component-ts/package.json +++ b/examples/react/develop/functional-component-ts/package.json @@ -30,7 +30,7 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "install:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/api @agile-ts/multieditor @agile-ts/event & yarn install" + "install:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/api @agile-ts/multieditor @agile-ts/event @agile-ts/proxytree & yarn install" }, "eslintConfig": { "extends": "react-app" diff --git a/examples/react/develop/functional-component-ts/yarn.lock b/examples/react/develop/functional-component-ts/yarn.lock index 54955758..21b151e8 100644 --- a/examples/react/develop/functional-component-ts/yarn.lock +++ b/examples/react/develop/functional-component-ts/yarn.lock @@ -3,46 +3,46 @@ "@agile-ts/api@file:.yalc/@agile-ts/api": - version "0.0.16" + version "0.0.18" dependencies: - "@agile-ts/utils" "^0.0.2" + "@agile-ts/utils" "^0.0.4" "@agile-ts/core@file:.yalc/@agile-ts/core": - version "0.0.15" + version "0.0.17" dependencies: - "@agile-ts/logger" "^0.0.2" - "@agile-ts/utils" "^0.0.2" + "@agile-ts/logger" "^0.0.4" + "@agile-ts/utils" "^0.0.4" "@agile-ts/event@file:.yalc/@agile-ts/event": - version "0.0.5" + version "0.0.7" -"@agile-ts/logger@^0.0.2": - version "0.0.2" - resolved "https://registry.yarnpkg.com/@agile-ts/logger/-/logger-0.0.2.tgz#80a726531dd63ca7d1c9a123383e57b5501efbb0" - integrity sha512-rJJ5pqXtOriYxjuZPhHs2J9N1FnIaAZqItCw0MXW9/5od/uhJ28aiG7w9RUBZts9SjDcICYEfjFMcTJ/kYJsMg== +"@agile-ts/logger@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@agile-ts/logger/-/logger-0.0.4.tgz#7f4d82ef8f03b13089af0878c360575c43f0962d" + integrity sha512-qm0obAKqJMaPKM+c76gktRXyw3OL1v39AnhMZ0FBGwJqHWU+fLRkCzlQwjaROCr3F1XP01Lc/Ls3efF0WzyEPw== dependencies: - "@agile-ts/utils" "^0.0.2" + "@agile-ts/utils" "^0.0.4" "@agile-ts/multieditor@file:.yalc/@agile-ts/multieditor": - version "0.0.15" + version "0.0.17" -"@agile-ts/proxytree@^0.0.2": - version "0.0.2" - resolved "https://registry.yarnpkg.com/@agile-ts/proxytree/-/proxytree-0.0.2.tgz#516ed19ee8d58aeecb291788a1e47be3dc23df8c" - integrity sha512-PbSiChF0GcUoWnrbnHauzBxZ5r/+4pZSZWpYjkBcIFa48DgTtFzg5DfQzsW3Rc1Y0QSFGYqcZOvCK1xAjLIQ2g== +"@agile-ts/proxytree@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@agile-ts/proxytree/-/proxytree-0.0.3.tgz#e3dacab123a311f2f0d4a0369793fe90fdab7569" + integrity sha512-auO6trCo7ivLJYuLjxrnK4xuUTangVPTq8UuOMTlGbJFjmb8PLEkaXuRoVGSzv9jsT2FeS7KsP7Fs+yvv0WPdg== "@agile-ts/proxytree@file:.yalc/@agile-ts/proxytree": - version "0.0.1" + version "0.0.3" "@agile-ts/react@file:.yalc/@agile-ts/react": - version "0.0.16" + version "0.0.18" dependencies: - "@agile-ts/proxytree" "^0.0.2" + "@agile-ts/proxytree" "^0.0.3" -"@agile-ts/utils@^0.0.2": - version "0.0.2" - resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.2.tgz#5f03761ace569b6c9ddd28c22f7b0fbec8b006b1" - integrity sha512-LqgQyMdK+zDuTCmOX6FOxTH4JNXhEvGFqIyNqRDoP99BK6MHGrK+n7nOW+1b4x6ZCYe0+VmwtG5CeOPOm3Siow== +"@agile-ts/utils@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.4.tgz#66e9536e561796489a37155da6b74ce2dc482697" + integrity sha512-GiZyTYmCm4j2N57oDjeMuPpfQdgn9clb0Cxpfuwi2Bq5T/KPXlaROLsVGwHLjwwT+NX7xxr5qNJH8pZTnHnYRQ== "@babel/code-frame@7.8.3": version "7.8.3" diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index e4d982bb..91846493 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -281,21 +281,22 @@ export class Runtime { subscriptionContainer: SubscriptionContainer, job: RuntimeJob ): boolean { - const selectors = subscriptionContainer.selectorsWeakMap.get(job.observer) - ?.selectors; + const selectorMethods = subscriptionContainer.selectorsWeakMap.get( + job.observer + )?.methods; // If no selector functions found, return true // because no specific part of the Observer was selected // -> The Subscription Container should update // no matter what was updated in the Observer - if (selectors == null) return true; + if (selectorMethods == null) return true; // Check if a selected part of Observer value has changed const previousValue = job.observer.previousValue; const newValue = job.observer.value; - for (const selector of selectors) { + for (const selectorMethod of selectorMethods) { if ( - notEqual(selector(newValue), selector(previousValue)) + notEqual(selectorMethod(newValue), selectorMethod(previousValue)) // || newValueDeepness !== previousValueDeepness // Not possible to check the object deepness ) return true; diff --git a/packages/core/src/runtime/observer.ts b/packages/core/src/runtime/observer.ts index cfe7e7ce..6c7bc051 100644 --- a/packages/core/src/runtime/observer.ts +++ b/packages/core/src/runtime/observer.ts @@ -7,6 +7,7 @@ import { IngestConfigInterface, CreateRuntimeJobConfigInterface, LogCodeManager, + AddSubscriptionMethodConfigInterface, } from '../internal'; export type ObserverKey = string | number; @@ -140,8 +141,12 @@ export class Observer { * * @public * @param subscriptionContainer - Subscription Container to which the Observer should subscribe. + * @param config - Configuration object */ - public subscribe(subscriptionContainer: SubscriptionContainer): void { + public subscribe( + subscriptionContainer: SubscriptionContainer, + config: AddSubscriptionMethodConfigInterface = {} + ): void { if (!this.subscribedTo.has(subscriptionContainer)) { this.subscribedTo.add(subscriptionContainer); @@ -149,7 +154,7 @@ export class Observer { // to keep track of the Observers that have subscribed the Subscription Container. // For example to unsubscribe the subscribed Observers // when the Subscription Container (Component) unmounts. - subscriptionContainer.subscribers.add(this); + subscriptionContainer.addSubscription(this, config); } } @@ -162,7 +167,7 @@ export class Observer { public unsubscribe(subscriptionContainer: SubscriptionContainer): void { if (this.subscribedTo.has(subscriptionContainer)) { this.subscribedTo.delete(subscriptionContainer); - subscriptionContainer.subscribers.delete(this); + subscriptionContainer.removeSubscription(this); } } } diff --git a/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts index aad3736b..9ba41abe 100644 --- a/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts @@ -28,7 +28,7 @@ export class CallbackSubscriptionContainer extends SubscriptionContainer { */ constructor( callback: Function, - subs: Array = [], + subs: Array | { [key: string]: Observer }, config: SubscriptionContainerConfigInterface = {} ) { super(subs, config); diff --git a/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts index 9e1daeef..4da1277f 100644 --- a/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts @@ -57,7 +57,7 @@ export class ComponentSubscriptionContainer< */ constructor( component: C, - subs: Array = [], + subs: Array | { [key: string]: Observer }, config: SubscriptionContainerConfigInterface = {} ) { super(subs, config); diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index e5791cf9..10ae46bd 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -90,7 +90,7 @@ export class SubscriptionContainer { * @param config - Configuration object */ constructor( - subs: Array = [], + subs: Array | { [key: string]: Observer }, config: SubscriptionContainerConfigInterface = {} ) { config = defineConfig(config, { @@ -99,52 +99,78 @@ export class SubscriptionContainer { key: generateId(), }); - this.subscribers = new Set(subs); + this.subscribers = new Set(); this.key = config.key; this.componentId = config?.componentId; this.subscriberKeysWeakMap = new WeakMap(); + this.selectorsWeakMap = new WeakMap(); + this.isObjectBased = !Array.isArray(subs); - // Create a selector function for each specified proxy path - // that selects the property at the path end - const selectorWeakMap: SelectorWeakMapType = config.selectorWeakMap as any; - this.assignProxySelectors( - selectorWeakMap, - config.proxyWeakMap as any, - subs - ); - this.selectorsWeakMap = selectorWeakMap; + for (const key in subs) { + const sub = subs[key]; + this.addSubscription(sub, { + proxyPaths: config.proxyWeakMap?.get(sub)?.paths, + selectorMethods: config.selectorWeakMap?.get(sub)?.methods, + key: !Array.isArray(subs) ? key : undefined, + }); + } } /** - * Assigns to the specified `selectorWeakMap` selector functions - * created based on the paths of the specified Proxy WeakMap. + * Adds specified Observer to the `subscription` array + * and its selectors to the `selectorsWeakMap`. * - * @param selectorWeakMap - Selector WeakMap the created proxy selector functions are added to. - * @param proxyWeakMap - Proxy Weak Map containing proxy paths for specified Observers in `subs`. - * @param subs - Observers whose values are to be selected based on the specified `proxyWeakMap`. + * @internal + * @param sub - Observer to be subscribed to the Subscription Container + * @param config - Configuration object */ - public assignProxySelectors( - selectorWeakMap: SelectorWeakMapType, - proxyWeakMap: ProxyWeakMapType, - subs: Array + public addSubscription( + sub: Observer, + config: AddSubscriptionMethodConfigInterface = {} ): void { - for (const observer of subs) { - const paths = proxyWeakMap.get(observer)?.paths; - if (paths != null) { - const selectors: SelectorMethodType[] = []; - for (const path of paths) { - selectors.push((value) => { - let _value = value; - for (const branch of path) { - if (!isValidObject(_value, true)) break; - _value = _value[branch]; - } - return _value; - }); + const toAddSelectorMethods: SelectorMethodType[] = + config.selectorMethods ?? []; + const paths = config.proxyPaths ?? []; + + // Create selector methods based on the specified proxy paths + for (const path of paths) { + toAddSelectorMethods.push((value) => { + let _value = value; + for (const branch of path) { + if (!isValidObject(_value, true)) break; + _value = _value[branch]; } - selectorWeakMap.set(observer, { selectors }); - } + return _value; + }); } + + // Add defined/created selector methods to the 'selectorsWeakMap' + const existingSelectorMethods = this.selectorsWeakMap.get(sub)?.methods; + const newSelectorMethods = existingSelectorMethods + ? existingSelectorMethods.concat(toAddSelectorMethods) + : toAddSelectorMethods; + if (newSelectorMethods.length > 0) + this.selectorsWeakMap.set(sub, { methods: newSelectorMethods }); + + // Assign specified key to the 'subscriberKeysWeakMap' + // (Not to the Observer, since the here specified key only counts for this Subscription Container) + if (config.key != null) this.subscriberKeysWeakMap.set(sub, config.key); + + // Add Observer to subscribers + this.subscribers.add(sub); + } + + /** + * Removes the Observer from the Subscription Container + * and from all WeakMaps it might be in. + * + * @internal + * @param sub - Observer to be removed from the Subscription Container + */ + public removeSubscription(sub: Observer) { + this.selectorsWeakMap.delete(sub); + this.subscriberKeysWeakMap.delete(sub); + this.subscribers.delete(sub); } } @@ -200,12 +226,19 @@ export interface SubscriptionContainerConfigInterface { selectorWeakMap?: SelectorWeakMapType; } -export type ProxyWeakMapType = WeakMap; +export interface AddSubscriptionMethodConfigInterface { + proxyPaths?: ProxyPathType[]; + selectorMethods?: SelectorMethodType[]; + key?: string; +} + +export type ProxyPathType = string[]; +export type ProxyWeakMapType = WeakMap; +export type SelectorMethodType = (value: T) => any; export type SelectorWeakMapType = WeakMap< Observer, - { selectors: SelectorMethodType[] } + { methods: SelectorMethodType[] } >; -export type SelectorMethodType = (value: T) => any; export type ComponentIdType = string | number; diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index 37b56192..bfd4ee2c 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -37,7 +37,7 @@ export class SubController { } /** - * Creates a so called Subscription Container which represents an UI-Component in AgileTs. + * Creates a so called Subscription Container that represents an UI-Component in AgileTs. * Such Subscription Container know how to trigger a rerender on the UI-Component it represents * through the provided `integrationInstance`. * @@ -48,60 +48,21 @@ export class SubController { * For example by mutating a local State Management property. (Component based Subscription) * [Learn more..](https://agile-ts.org/docs/core/integration/#component-based) * - * The in an object specified Observers are then automatically subscribed + * The in an array specified Observers are then automatically subscribed * to the created Subscription Container and thus to the Component it represents. * - * The advantage of subscribing the Observer via a object keymap, - * is that each Observer has its own unique key identifier. - * Such key identifier is for example required when merging the Observer value into - * a local Component State Management property. - * ``` - * this.state = {...this.state, {state1: Observer1.value, state2: Observer2.value}} - * ``` - * * @public * @param integrationInstance - Callback function or Component Instance to trigger a rerender on a UI-Component. * @param subs - Observers to be subscribed to the to create Subscription Container. * @param config - Configuration object */ - public subscribeWithSubsObject( + public subscribe( integrationInstance: any, - subs: { [key: string]: Observer } = {}, - config: RegisterSubscriptionConfigInterface = {} - ): { - subscriptionContainer: SubscriptionContainer; - props: { [key: string]: Observer['value'] }; - } { - const props: { [key: string]: Observer['value'] } = {}; - - // Create Subscription Container - const subscriptionContainer = this.createSubscriptionContainer( - integrationInstance, - config - ); - - // Set SubscriptionContainer to object based - // and assign property keys to the 'subscriberKeysWeakMap' - subscriptionContainer.isObjectBased = true; - for (const key in subs) - subscriptionContainer.subscriberKeysWeakMap.set(subs[key], key); - - // Subscribe Observers to the created Subscription Container - // and build a Observer value keymap called props - for (const key in subs) { - const observer = subs[key]; - observer.subscribe(subscriptionContainer); - if (observer.value) props[key] = observer.value; - } - - return { - subscriptionContainer: subscriptionContainer, - props: props, - }; - } - + subs: Array, + config: RegisterSubscriptionConfigInterface + ): SubscriptionContainer; /** - * Creates a so called Subscription Container which represents an UI-Component in AgileTs. + * Creates a so called Subscription Container that represents an UI-Component in AgileTs. * Such Subscription Container know how to trigger a rerender on the UI-Component it represents * through the provided `integrationInstance`. * @@ -112,29 +73,73 @@ export class SubController { * For example by mutating a local State Management property. (Component based Subscription) * [Learn more..](https://agile-ts.org/docs/core/integration/#component-based) * - * The in an array specified Observers are then automatically subscribed + * The in an object specified Observers are then automatically subscribed * to the created Subscription Container and thus to the Component it represents. * + * The advantage of subscribing the Observer via a object keymap, + * is that each Observer has its own unique key identifier. + * Such key identifier is for example required when merging the Observer value into + * a local Component State Management property. + * ``` + * this.state = {...this.state, {state1: Observer1.value, state2: Observer2.value}} + * ``` + * * @public * @param integrationInstance - Callback function or Component Instance to trigger a rerender on a UI-Component. * @param subs - Observers to be subscribed to the to create Subscription Container. * @param config - Configuration object */ - public subscribeWithSubsArray( + public subscribe( integrationInstance: any, - subs: Array = [], + subs: { [key: string]: Observer }, + config: RegisterSubscriptionConfigInterface + ): { + subscriptionContainer: SubscriptionContainer; + props: { [key: string]: Observer['value'] }; + }; + public subscribe( + integrationInstance: any, + subs: { [key: string]: Observer } | Array, config: RegisterSubscriptionConfigInterface = {} - ): SubscriptionContainer { + ): + | SubscriptionContainer + | { + subscriptionContainer: SubscriptionContainer; + props: { [key: string]: Observer['value'] }; + } { + config = defineConfig(config, { + waitForMount: this.agileInstance().config.waitForMount, + }); + // Create Subscription Container - const subscriptionContainer = this.createSubscriptionContainer( - integrationInstance, - config - ); + const subscriptionContainer = isFunction(integrationInstance) + ? this.createCallbackSubscriptionContainer( + integrationInstance, + subs, + config + ) + : this.createComponentSubscriptionContainer( + integrationInstance, + subs, + config + ); + + const props: { [key: string]: Observer['value'] } = {}; // Subscribe Observers to the created Subscription Container - subs.forEach((observer) => observer.subscribe(subscriptionContainer)); + // and build an Observer value keymap called props + for (const key in subs) { + const observer = subs[key]; + observer.subscribedTo.add(subscriptionContainer); + if (observer.value) props[key] = observer.value; + } - return subscriptionContainer; + return Array.isArray(subs) + ? subscriptionContainer + : { + subscriptionContainer: subscriptionContainer, + props: props, + }; } /** @@ -198,40 +203,22 @@ export class SubController { } } - /** - * Returns a newly created Component or Callback based Subscription Container - * based on the specified `integrationInstance`. - * - * @internal - * @param integrationInstance - Callback function or Component Instance to trigger a rerender on a UI-Component. - * @param config - Configuration object - */ - public createSubscriptionContainer( - integrationInstance: any, - config: RegisterSubscriptionConfigInterface = {} - ): SubscriptionContainer { - config = defineConfig(config, { - waitForMount: this.agileInstance().config.waitForMount, - }); - return isFunction(integrationInstance) - ? this.createCallbackSubscriptionContainer(integrationInstance, config) - : this.createComponentSubscriptionContainer(integrationInstance, config); - } - /** * Returns a newly created Component based Subscription Container. * * @internal * @param componentInstance - Component Instance to trigger a rerender on a UI-Component. + * @param subs - Observers to be subscribed to the to create Subscription Container. * @param config - Configuration object. */ public createComponentSubscriptionContainer( componentInstance: any, + subs: { [key: string]: Observer } | Array, config: RegisterSubscriptionConfigInterface = {} ): ComponentSubscriptionContainer { const componentSubscriptionContainer = new ComponentSubscriptionContainer( componentInstance, - [], + subs, removeProperties(config, ['waitForMount']) ); this.componentSubs.add(componentSubscriptionContainer); @@ -268,15 +255,17 @@ export class SubController { * * @internal * @param callbackFunction - Callback function to trigger a rerender on a UI-Component. + * @param subs - Observers to be subscribed to the to create Subscription Container. * @param config - Configuration object */ public createCallbackSubscriptionContainer( callbackFunction: () => void, + subs: { [key: string]: Observer } | Array, config: RegisterSubscriptionConfigInterface = {} ): CallbackSubscriptionContainer { const callbackSubscriptionContainer = new CallbackSubscriptionContainer( callbackFunction, - [], + subs, removeProperties(config, ['waitForMount']) ); this.callbackSubs.add(callbackSubscriptionContainer); diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts index f129fed4..a4fa90ec 100644 --- a/packages/core/tests/unit/runtime/runtime.test.ts +++ b/packages/core/tests/unit/runtime/runtime.test.ts @@ -519,60 +519,7 @@ describe('Runtime Tests', () => { ); }); - describe('handleObjectBasedSubscription function tests', () => { - let arraySubscriptionContainer: SubscriptionContainer; - const dummyComponent = { - my: 'cool component', - }; - let objectSubscriptionContainer: SubscriptionContainer; - const dummyComponent2 = { - my: 'second cool component', - }; - let arrayJob: RuntimeJob; - let objectJob1: RuntimeJob; - let objectJob2: RuntimeJob; - - beforeEach(() => { - arraySubscriptionContainer = dummyAgile.subController.subscribeWithSubsArray( - dummyComponent, - [dummyObserver1, dummyObserver2, dummyObserver3] - ); - arrayJob = new RuntimeJob(dummyObserver1, { key: 'dummyArrayJob' }); - - objectSubscriptionContainer = dummyAgile.subController.subscribeWithSubsObject( - dummyComponent2, - { - observer1: dummyObserver1, - observer2: dummyObserver2, - observer3: dummyObserver3, - } - ).subscriptionContainer; - objectJob1 = new RuntimeJob(dummyObserver1, { key: 'dummyObjectJob1' }); - objectJob2 = new RuntimeJob(dummyObserver3, { key: 'dummyObjectJob2' }); - }); - - it('should ignore not object based SubscriptionContainer', () => { - runtime.handleObjectBasedSubscription( - arraySubscriptionContainer, - arrayJob - ); - - expect(arraySubscriptionContainer.updatedSubscribers).toStrictEqual([]); - }); - - it('should add Job Observer to changedObjectKeys in SubscriptionContainer', () => { - runtime.handleObjectBasedSubscription( - objectSubscriptionContainer, - objectJob1 - ); - - expect(objectSubscriptionContainer.updatedSubscribers).toStrictEqual([ - 'observer1', - ]); - }); - }); - - describe('getObjectBasedProps function tests', () => { + describe('getUpdatedObserverValues function tests', () => { let subscriptionContainer: SubscriptionContainer; const dummyFunction = () => { /* empty function */ @@ -607,7 +554,7 @@ describe('Runtime Tests', () => { }); }); - describe('handleProxyBasedSubscription function tests', () => { + describe('handleSelector function tests', () => { let objectSubscriptionContainer: SubscriptionContainer; const dummyFunction = () => { /* empty function */ diff --git a/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts index 80688764..ac6fcd3d 100644 --- a/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts @@ -2,6 +2,8 @@ import { Agile, CallbackSubscriptionContainer, Observer, + ProxyWeakMapType, + SelectorWeakMapType, } from '../../../../../src'; import { LogMock } from '../../../../helper/logMock'; @@ -9,6 +11,8 @@ describe('CallbackSubscriptionContainer Tests', () => { let dummyAgile: Agile; let dummyObserver1: Observer; let dummyObserver2: Observer; + let dummySelectorWeakMap: SelectorWeakMapType; + let dummyProxyWeakMap: ProxyWeakMapType; beforeEach(() => { jest.clearAllMocks(); @@ -17,6 +21,8 @@ describe('CallbackSubscriptionContainer Tests', () => { dummyAgile = new Agile(); dummyObserver1 = new Observer(dummyAgile, { key: 'dummyObserver1' }); dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' }); + dummySelectorWeakMap = new WeakMap(); + dummyProxyWeakMap = new WeakMap(); }); it('should create CallbackSubscriptionContainer', () => { @@ -27,22 +33,27 @@ describe('CallbackSubscriptionContainer Tests', () => { const subscriptionContainer = new CallbackSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2], - { key: 'dummyKey', proxyKeyMap: { myState: { paths: [['hi']] } } } + { + key: 'dummyKey', + proxyWeakMap: dummyProxyWeakMap, + selectorWeakMap: dummySelectorWeakMap, + componentId: 'testID', + } ); expect(subscriptionContainer.callback).toBe(dummyIntegration); expect(subscriptionContainer.key).toBe('dummyKey'); expect(subscriptionContainer.ready).toBeFalsy(); + expect(subscriptionContainer.componentId).toBe('testID'); expect(subscriptionContainer.subscribers.size).toBe(2); expect(subscriptionContainer.subscribers.has(dummyObserver1)).toBeTruthy(); expect(subscriptionContainer.subscribers.has(dummyObserver2)).toBeTruthy(); - expect(subscriptionContainer.isObjectBased).toBeFalsy(); expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); - expect(subscriptionContainer.subscriberKeysWeakMap).toBeUndefined(); - expect(subscriptionContainer.proxyKeyMap).toStrictEqual({ - myState: { paths: [['hi']] }, - }); - expect(subscriptionContainer.isProxyBased).toBeTruthy(); + expect(subscriptionContainer.isObjectBased).toBeFalsy(); + expect(subscriptionContainer.subscriberKeysWeakMap).toStrictEqual( + expect.any(WeakMap) + ); + expect(subscriptionContainer.selectorsWeakMap).toBe(dummySelectorWeakMap); }); }); diff --git a/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts index 786c115a..7da24042 100644 --- a/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts @@ -2,6 +2,8 @@ import { Agile, ComponentSubscriptionContainer, Observer, + ProxyWeakMapType, + SelectorWeakMapType, } from '../../../../../src'; import { LogMock } from '../../../../helper/logMock'; @@ -9,6 +11,8 @@ describe('ComponentSubscriptionContainer Tests', () => { let dummyAgile: Agile; let dummyObserver1: Observer; let dummyObserver2: Observer; + let dummySelectorWeakMap: SelectorWeakMapType; + let dummyProxyWeakMap: ProxyWeakMapType; beforeEach(() => { jest.clearAllMocks(); @@ -17,6 +21,8 @@ describe('ComponentSubscriptionContainer Tests', () => { dummyAgile = new Agile(); dummyObserver1 = new Observer(dummyAgile, { key: 'dummyObserver1' }); dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' }); + dummySelectorWeakMap = new WeakMap(); + dummyProxyWeakMap = new WeakMap(); }); it('should create ComponentSubscriptionContainer', () => { @@ -25,22 +31,27 @@ describe('ComponentSubscriptionContainer Tests', () => { const subscriptionContainer = new ComponentSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2], - { key: 'dummyKey', proxyKeyMap: { myState: { paths: [['hi']] } } } + { + key: 'dummyKey', + proxyWeakMap: dummyProxyWeakMap, + selectorWeakMap: dummySelectorWeakMap, + componentId: 'testID', + } ); expect(subscriptionContainer.component).toStrictEqual(dummyIntegration); expect(subscriptionContainer.key).toBe('dummyKey'); expect(subscriptionContainer.ready).toBeFalsy(); + expect(subscriptionContainer.componentId).toBe('testID'); expect(subscriptionContainer.subscribers.size).toBe(2); expect(subscriptionContainer.subscribers.has(dummyObserver1)).toBeTruthy(); expect(subscriptionContainer.subscribers.has(dummyObserver2)).toBeTruthy(); - expect(subscriptionContainer.isObjectBased).toBeFalsy(); expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); - expect(subscriptionContainer.subscriberKeysWeakMap).toBeUndefined(); - expect(subscriptionContainer.proxyKeyMap).toStrictEqual({ - myState: { paths: [['hi']] }, - }); - expect(subscriptionContainer.isProxyBased).toBeTruthy(); + expect(subscriptionContainer.isObjectBased).toBeFalsy(); + expect(subscriptionContainer.subscriberKeysWeakMap).toStrictEqual( + expect.any(WeakMap) + ); + expect(subscriptionContainer.selectorsWeakMap).toBe(dummySelectorWeakMap); }); }); diff --git a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts index ebc5fa91..61046089 100644 --- a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts @@ -1,4 +1,10 @@ -import { Agile, Observer, SubscriptionContainer } from '../../../../../src'; +import { + Agile, + Observer, + ProxyWeakMapType, + SelectorWeakMapType, + SubscriptionContainer, +} from '../../../../../src'; import * as Utils from '@agile-ts/utils'; import { LogMock } from '../../../../helper/logMock'; @@ -6,6 +12,8 @@ describe('SubscriptionContainer Tests', () => { let dummyAgile: Agile; let dummyObserver1: Observer; let dummyObserver2: Observer; + let dummySelectorWeakMap: SelectorWeakMapType; + let dummyProxyWeakMap: ProxyWeakMapType; beforeEach(() => { jest.clearAllMocks(); @@ -14,6 +22,10 @@ describe('SubscriptionContainer Tests', () => { dummyAgile = new Agile(); dummyObserver1 = new Observer(dummyAgile, { key: 'dummyObserver1' }); dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' }); + dummySelectorWeakMap = new WeakMap(); + dummyProxyWeakMap = new WeakMap(); + + jest.spyOn(SubscriptionContainer.prototype, 'assignProxySelectors'); }); it('should create SubscriptionContainer (default config)', () => { @@ -21,33 +33,74 @@ describe('SubscriptionContainer Tests', () => { const subscriptionContainer = new SubscriptionContainer(); + expect(subscriptionContainer.assignProxySelectors).toHaveBeenCalledWith( + expect.any(WeakMap), + expect.any(WeakMap), + [] + ); + expect(subscriptionContainer.key).toBe('generatedId'); expect(subscriptionContainer.ready).toBeFalsy(); + expect(subscriptionContainer.componentId).toBeUndefined(); expect(subscriptionContainer.subscribers.size).toBe(0); - expect(subscriptionContainer.isObjectBased).toBeFalsy(); expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); - expect(subscriptionContainer.subscriberKeysWeakMap).toBeUndefined(); - expect(subscriptionContainer.proxyKeyMap).toStrictEqual({}); - expect(subscriptionContainer.isProxyBased).toBeFalsy(); + expect(subscriptionContainer.isObjectBased).toBeFalsy(); + expect(subscriptionContainer.subscriberKeysWeakMap).toStrictEqual( + expect.any(WeakMap) + ); + expect(subscriptionContainer.selectorsWeakMap).not.toBe( + dummySelectorWeakMap + ); + expect(subscriptionContainer.selectorsWeakMap).toStrictEqual( + expect.any(WeakMap) + ); }); it('should create SubscriptionContainer (specific config)', () => { const subscriptionContainer = new SubscriptionContainer( [dummyObserver1, dummyObserver2], - { key: 'dummyKey', proxyKeyMap: { myState: { paths: [['a', 'b']] } } } + { + key: 'dummyKey', + proxyWeakMap: dummyProxyWeakMap, + selectorWeakMap: dummySelectorWeakMap, + componentId: 'testID', + } ); + expect( + subscriptionContainer.assignProxySelectors + ).toHaveBeenCalledWith(dummySelectorWeakMap, dummyProxyWeakMap, [ + dummyObserver1, + dummyObserver2, + ]); + expect(subscriptionContainer.key).toBe('dummyKey'); expect(subscriptionContainer.ready).toBeFalsy(); + expect(subscriptionContainer.componentId).toBe('testID'); expect(subscriptionContainer.subscribers.size).toBe(2); expect(subscriptionContainer.subscribers.has(dummyObserver1)).toBeTruthy(); expect(subscriptionContainer.subscribers.has(dummyObserver2)).toBeTruthy(); - expect(subscriptionContainer.isObjectBased).toBeFalsy(); expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); - expect(subscriptionContainer.subscriberKeysWeakMap).toBeUndefined(); - expect(subscriptionContainer.proxyKeyMap).toStrictEqual({ - myState: { paths: [['a', 'b']] }, + expect(subscriptionContainer.isObjectBased).toBeFalsy(); + expect(subscriptionContainer.subscriberKeysWeakMap).toStrictEqual( + expect.any(WeakMap) + ); + expect(subscriptionContainer.selectorsWeakMap).toBe(dummySelectorWeakMap); + }); + + describe('Subscription Container Function Tests', () => { + let observer: SubscriptionContainer; + + beforeEach(() => { + observer = new SubscriptionContainer(); + }); + + describe('assignProxySelectors function tests', () => { + beforeEach(() => {}); + + it('todo', () => { + // TODO + }); }); - expect(subscriptionContainer.isProxyBased).toBeTruthy(); }); }); diff --git a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts index 77634bd9..4ce359d4 100644 --- a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts +++ b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts @@ -45,9 +45,7 @@ describe('SubController Tests', () => { dummySubscriptionContainer = new SubscriptionContainer(); dummyObserver1.value = 'myCoolValue'; - subController.createSubscriptionContainer = jest.fn( - () => dummySubscriptionContainer - ); + subController.subscribe = jest.fn(() => dummySubscriptionContainer); jest.spyOn(dummyObserver1, 'subscribe'); jest.spyOn(dummyObserver2, 'subscribe'); }); @@ -73,7 +71,7 @@ describe('SubController Tests', () => { subscriptionContainer: dummySubscriptionContainer, }); - expect(subController.createSubscriptionContainer).toHaveBeenCalledWith( + expect(subController.subscribe).toHaveBeenCalledWith( dummyIntegration, [dummyObserver1, dummyObserver2], { @@ -113,9 +111,7 @@ describe('SubController Tests', () => { beforeEach(() => { dummySubscriptionContainer = new SubscriptionContainer(); - subController.createSubscriptionContainer = jest.fn( - () => dummySubscriptionContainer - ); + subController.subscribe = jest.fn(() => dummySubscriptionContainer); jest.spyOn(dummyObserver1, 'subscribe'); jest.spyOn(dummyObserver2, 'subscribe'); }); @@ -133,7 +129,7 @@ describe('SubController Tests', () => { expect(subscribeWithSubsArrayResponse).toBe(dummySubscriptionContainer); - expect(subController.createSubscriptionContainer).toHaveBeenCalledWith( + expect(subController.subscribe).toHaveBeenCalledWith( dummyIntegration, [dummyObserver1, dummyObserver2], { @@ -290,7 +286,7 @@ describe('SubController Tests', () => { /* empty function */ }; - const subscriptionContainer = subController.createSubscriptionContainer( + const subscriptionContainer = subController.subscribe( dummyIntegration, [dummyObserver1, dummyObserver2] ); @@ -313,7 +309,7 @@ describe('SubController Tests', () => { /* empty function */ }; - const subscriptionContainer = subController.createSubscriptionContainer( + const subscriptionContainer = subController.subscribe( dummyIntegration, [dummyObserver1, dummyObserver2], { key: 'niceKey', proxyKeyMap: {}, waitForMount: false } @@ -335,7 +331,7 @@ describe('SubController Tests', () => { it('should call registerComponentSubscription if passed integrationInstance is not a Function (default config)', () => { const dummyIntegration = { dummy: 'integration' }; - const subscriptionContainer = subController.createSubscriptionContainer( + const subscriptionContainer = subController.subscribe( dummyIntegration, [dummyObserver1, dummyObserver2] ); @@ -356,7 +352,7 @@ describe('SubController Tests', () => { it('should call registerComponentSubscription if passed integrationInstance is not a Function (specific config)', () => { const dummyIntegration = { dummy: 'integration' }; - const subscriptionContainer = subController.createSubscriptionContainer( + const subscriptionContainer = subController.subscribe( dummyIntegration, [dummyObserver1, dummyObserver2], { key: 'niceKey', proxyKeyMap: {}, waitForMount: false } diff --git a/packages/event/src/hooks/useEvent.ts b/packages/event/src/hooks/useEvent.ts index 1bb4e371..905d6413 100644 --- a/packages/event/src/hooks/useEvent.ts +++ b/packages/event/src/hooks/useEvent.ts @@ -27,7 +27,7 @@ export function useEvent>( } // Create Callback based Subscription - const subscriptionContainer = agileInstance.subController.subscribeWithSubsArray( + const subscriptionContainer = agileInstance.subController.subscribe( () => { forceRender(); }, diff --git a/packages/react/src/hocs/AgileHOC.ts b/packages/react/src/hocs/AgileHOC.ts index 8249b15c..189271d9 100644 --- a/packages/react/src/hocs/AgileHOC.ts +++ b/packages/react/src/hocs/AgileHOC.ts @@ -109,16 +109,14 @@ const createHOC = ( UNSAFE_componentWillMount() { // Create Subscription with Observer that have no Indicator and can't be merged into 'this.state' (Rerender will be caused via force Update) if (depsWithoutIndicator.length > 0) { - this.agileInstance.subController.subscribeWithSubsArray( - this, - depsWithoutIndicator, - { waitForMount: this.waitForMount } - ); + this.agileInstance.subController.subscribe(this, depsWithoutIndicator, { + waitForMount: this.waitForMount, + }); } // Create Subscription with Observer that have an Indicator (Rerender will be cause via mutating 'this.state') if (depsWithIndicator) { - const response = this.agileInstance.subController.subscribeWithSubsObject( + const response = this.agileInstance.subController.subscribe( this, depsWithIndicator, { waitForMount: this.waitForMount } diff --git a/packages/react/src/hooks/useAgile.ts b/packages/react/src/hooks/useAgile.ts index 5ddb00f1..bafcce1d 100644 --- a/packages/react/src/hooks/useAgile.ts +++ b/packages/react/src/hooks/useAgile.ts @@ -124,7 +124,7 @@ export function useAgile< } // Create Callback based Subscription - const subscriptionContainer = agileInstance.subController.subscribeWithSubsArray( + const subscriptionContainer = agileInstance.subController.subscribe( () => { forceRender(); }, diff --git a/packages/vue/src/bindAgileInstances.ts b/packages/vue/src/bindAgileInstances.ts index 0e89bdc9..451c759d 100644 --- a/packages/vue/src/bindAgileInstances.ts +++ b/packages/vue/src/bindAgileInstances.ts @@ -27,20 +27,16 @@ export function bindAgileInstances( // Create Subscription with Observer that have no Indicator and can't be merged into the 'sharedState' (Rerender will be caused via force Update) if (depsWithoutIndicator.length > 0) { - agile.subController.subscribeWithSubsArray( - vueComponent, - depsWithoutIndicator, - { waitForMount: false } - ); + agile.subController.subscribe(vueComponent, depsWithoutIndicator, { + waitForMount: false, + }); } // Create Subscription with Observer that have an Indicator (Rerender will be cause via mutating 'this.$data.sharedState') if (depsWithIndicator) { - return agile.subController.subscribeWithSubsObject( - vueComponent, - depsWithIndicator, - { waitForMount: false } - ).props; + return agile.subController.subscribe(vueComponent, depsWithIndicator, { + waitForMount: false, + }).props; } return {}; From f1f0806aeb0f8f64398ffd96f1dea8e56305abf5 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Tue, 8 Jun 2021 20:30:10 +0200 Subject: [PATCH 055/117] fixed typos --- .../functional-component-ts/src/App.tsx | 18 +++++--- .../functional-component-ts/src/core/index.ts | 27 ++++++----- examples/vue/develop/my-project/src/core.js | 10 +++-- examples/vue/develop/my-project/yarn.lock | 26 +++++------ packages/core/src/collection/index.ts | 6 ++- packages/core/src/computed/index.ts | 2 +- packages/core/src/runtime/observer.ts | 45 ++----------------- packages/core/src/runtime/runtime.job.ts | 7 ++- .../container/SubscriptionContainer.ts | 17 ++++--- .../runtime/subscription/sub.controller.ts | 2 +- .../core/tests/unit/computed/computed.test.ts | 24 ++++++---- .../core/tests/unit/runtime/observer.test.ts | 6 +-- 12 files changed, 94 insertions(+), 96 deletions(-) diff --git a/examples/react/develop/functional-component-ts/src/App.tsx b/examples/react/develop/functional-component-ts/src/App.tsx index ad7478e5..0574cf89 100644 --- a/examples/react/develop/functional-component-ts/src/App.tsx +++ b/examples/react/develop/functional-component-ts/src/App.tsx @@ -4,6 +4,7 @@ import { useAgile, useWatcher, useProxy } from '@agile-ts/react'; import { useEvent } from '@agile-ts/event'; import { COUNTUP, + externalCreatedItem, MY_COLLECTION, MY_COMPUTED, MY_EVENT, @@ -12,7 +13,7 @@ import { MY_STATE_3, STATE_OBJECT, } from './core'; -import { generateId, globalBind } from '@agile-ts/core'; +import { generateId, globalBind, Item } from '@agile-ts/core'; let rerenderCount = 0; let rerenderCountInCountupView = 0; @@ -42,11 +43,10 @@ const App = (props: any) => { ]); const [myGroup] = useAgile([MY_COLLECTION.getGroupWithReference('myGroup')]); - const [stateObject, item2, collection2] = useProxy([ - STATE_OBJECT, - MY_COLLECTION.getItem('id2'), - MY_COLLECTION, - ]); + const [stateObject, item2, collection2] = useProxy( + [STATE_OBJECT, MY_COLLECTION.getItem('id2'), MY_COLLECTION], + { key: 'useProxy' } + ); console.log('Item1: ', item2?.name); console.log('Collection: ', collection2.slice(0, 2)); @@ -142,6 +142,12 @@ const App = (props: any) => { }> Collect + diff --git a/examples/react/develop/functional-component-ts/src/core/index.ts b/examples/react/develop/functional-component-ts/src/core/index.ts index c55426d9..7c45b97a 100644 --- a/examples/react/develop/functional-component-ts/src/core/index.ts +++ b/examples/react/develop/functional-component-ts/src/core/index.ts @@ -1,10 +1,10 @@ -import { Agile, clone, Collection, Logger } from '@agile-ts/core'; +import { Agile, clone, Item, Logger } from '@agile-ts/core'; import Event from '@agile-ts/event'; export const myStorage: any = {}; export const App = new Agile({ - logConfig: { level: Logger.level.DEBUG }, + logConfig: { level: Logger.level.DEBUG, allowedTags: ['storage'] }, localStorage: true, }); @@ -84,15 +84,20 @@ export const MY_COLLECTION = App.createCollection( ], }) ).persist(); -MY_COLLECTION.collect({ key: 'id1', name: 'test' }); -MY_COLLECTION.collect({ key: 'id2', name: 'test2' }, 'myGroup'); -MY_COLLECTION.update('id1', { id: 'id1Updated', name: 'testUpdated' }); -MY_COLLECTION.getGroup('myGroup')?.persist({ - followCollectionPersistKeyPattern: true, -}); -MY_COLLECTION.onLoad(() => { - console.log('On Load MY_COLLECTION'); -}); +// MY_COLLECTION.collect({ key: 'id1', name: 'test' }); +// MY_COLLECTION.collect({ key: 'id2', name: 'test2' }, 'myGroup'); +// MY_COLLECTION.update('id1', { key: 'id1Updated', name: 'testUpdated' }); +// MY_COLLECTION.getGroup('myGroup')?.persist({ +// followCollectionPersistKeyPattern: true, +// }); +// MY_COLLECTION.onLoad(() => { +// console.log('On Load MY_COLLECTION'); +// }); + +export const externalCreatedItem = new Item(MY_COLLECTION, { + key: 'id10', + name: 'test', +}).persist({ followCollectionPersistKeyPattern: true }); console.log('Initial: myCollection ', clone(MY_COLLECTION)); diff --git a/examples/vue/develop/my-project/src/core.js b/examples/vue/develop/my-project/src/core.js index fe4efb2f..40527a9d 100644 --- a/examples/vue/develop/my-project/src/core.js +++ b/examples/vue/develop/my-project/src/core.js @@ -1,4 +1,4 @@ -import { Agile, Logger } from '@agile-ts/core'; +import { Agile, Logger, globalBind } from '@agile-ts/core'; import vueIntegration from '@agile-ts/vue'; // Create Agile Instance @@ -7,9 +7,13 @@ export const App = new Agile({ }).integrate(vueIntegration); // Create State -export const MY_STATE = App.createState('Hello World'); +export const MY_STATE = App.createState('Hello World', { key: 'my-state' }); // Create Collection export const TODOS = App.createCollection({ initialData: [{ id: 1, name: 'Clean Bathroom' }], -}).persist('todos'); +}); // .persist('todos'); + +// TODOS.collect({ id: 2, name: 'jeff' }); + +globalBind('__core__', { App, MY_STATE, TODOS }); diff --git a/examples/vue/develop/my-project/yarn.lock b/examples/vue/develop/my-project/yarn.lock index 9bd6b58a..04c336c0 100644 --- a/examples/vue/develop/my-project/yarn.lock +++ b/examples/vue/develop/my-project/yarn.lock @@ -3,25 +3,25 @@ "@agile-ts/core@file:.yalc/@agile-ts/core": - version "0.0.16" + version "0.0.17" dependencies: - "@agile-ts/logger" "^0.0.3" - "@agile-ts/utils" "^0.0.3" + "@agile-ts/logger" "^0.0.4" + "@agile-ts/utils" "^0.0.4" -"@agile-ts/logger@^0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@agile-ts/logger/-/logger-0.0.3.tgz#21f460bab99b5a1f50fbe6be95e1e9ed471ef456" - integrity sha512-8yejNCB7LXJ334smxovGaBWoqyXIUTHHO0/l2jPJt7WiMag0337KWbo1jyx6D8IkDioI9lunsN2U4CIBsRRhYA== +"@agile-ts/logger@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@agile-ts/logger/-/logger-0.0.4.tgz#7f4d82ef8f03b13089af0878c360575c43f0962d" + integrity sha512-qm0obAKqJMaPKM+c76gktRXyw3OL1v39AnhMZ0FBGwJqHWU+fLRkCzlQwjaROCr3F1XP01Lc/Ls3efF0WzyEPw== dependencies: - "@agile-ts/utils" "^0.0.3" + "@agile-ts/utils" "^0.0.4" -"@agile-ts/utils@^0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.3.tgz#f0e99d9ed9b21744f31effd99f7f7f32d26e3aec" - integrity sha512-h/gbPRRnFYxpIH4D0F/+6gVcZoZ2YPreT+cl8TCysjkjR6XnZ4YgC7patHIopX7ZvR97IMiu+BtpmS1UDbOftg== +"@agile-ts/utils@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.4.tgz#66e9536e561796489a37155da6b74ce2dc482697" + integrity sha512-GiZyTYmCm4j2N57oDjeMuPpfQdgn9clb0Cxpfuwi2Bq5T/KPXlaROLsVGwHLjwwT+NX7xxr5qNJH8pZTnHnYRQ== "@agile-ts/vue@file:.yalc/@agile-ts/vue": - version "0.0.4" + version "0.0.5" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13": version "7.12.13" diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index e2d38d20..3fe709c5 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -97,7 +97,11 @@ export class Collection { // Rebuild of Groups // Not necessary because if Items are added to the Collection, // the Groups which contain these added Items are rebuilt. - // for (const key in this.groups) this.groups[key].rebuild(); + for (const key in this.groups) this.groups[key].rebuild(); + + // TODO ISSUE with collecting the 'initialData' before 'isInstantiated = true' + // if (_config.initialData) this.collect(_config.initialData); // TODO REMOVE + Agile.logger.debug('END of COLLECTION INSTANTIATION'); // TODO REMOVE } /** diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index 983fc42c..cea3cdc7 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -153,7 +153,7 @@ export class Computed extends State< newDeps.push(observer); // Make this Observer depend on the found dep Observers - observer.depend(this.observer); + observer.addDependent(this.observer); }); this.deps = newDeps; diff --git a/packages/core/src/runtime/observer.ts b/packages/core/src/runtime/observer.ts index 6c7bc051..34e8cf7d 100644 --- a/packages/core/src/runtime/observer.ts +++ b/packages/core/src/runtime/observer.ts @@ -7,7 +7,6 @@ import { IngestConfigInterface, CreateRuntimeJobConfigInterface, LogCodeManager, - AddSubscriptionMethodConfigInterface, } from '../internal'; export type ObserverKey = string | number; @@ -52,9 +51,9 @@ export class Observer { this._key = config.key; this.value = config.value; this.previousValue = config.value; - config.dependents?.forEach((observer) => this.depend(observer)); + config.dependents?.forEach((observer) => this.addDependent(observer)); config.subs?.forEach((subscriptionContainer) => - this.subscribe(subscriptionContainer) + subscriptionContainer.addSubscription(this) ); } @@ -129,47 +128,9 @@ export class Observer { * @public * @param observer - Observer to depends on this Observer. */ - public depend(observer: Observer): void { + public addDependent(observer: Observer): void { if (!this.dependents.has(observer)) this.dependents.add(observer); } - - /** - * Subscribes Observer to the specified Subscription Container (Component). - * - * Every time this Observer is ingested into the Runtime, - * a rerender might be triggered on the Component the Subscription Container represents. - * - * @public - * @param subscriptionContainer - Subscription Container to which the Observer should subscribe. - * @param config - Configuration object - */ - public subscribe( - subscriptionContainer: SubscriptionContainer, - config: AddSubscriptionMethodConfigInterface = {} - ): void { - if (!this.subscribedTo.has(subscriptionContainer)) { - this.subscribedTo.add(subscriptionContainer); - - // Add Observer to Subscription Container - // to keep track of the Observers that have subscribed the Subscription Container. - // For example to unsubscribe the subscribed Observers - // when the Subscription Container (Component) unmounts. - subscriptionContainer.addSubscription(this, config); - } - } - - /** - * Unsubscribes Observer from specified Subscription Container (Component). - * - * @public - * @param subscriptionContainer - Subscription Container that the Observer should unsubscribe. - */ - public unsubscribe(subscriptionContainer: SubscriptionContainer): void { - if (this.subscribedTo.has(subscriptionContainer)) { - this.subscribedTo.delete(subscriptionContainer); - subscriptionContainer.removeSubscription(this); - } - } } /** diff --git a/packages/core/src/runtime/runtime.job.ts b/packages/core/src/runtime/runtime.job.ts index 25420d8d..9a459acb 100644 --- a/packages/core/src/runtime/runtime.job.ts +++ b/packages/core/src/runtime/runtime.job.ts @@ -1,4 +1,9 @@ -import { Observer, defineConfig, SubscriptionContainer } from '../internal'; +import { + Observer, + defineConfig, + SubscriptionContainer, + Agile, +} from '../internal'; export class RuntimeJob { public config: RuntimeJobConfigInterface; diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index 10ae46bd..74cfd1e7 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -144,7 +144,7 @@ export class SubscriptionContainer { }); } - // Add defined/created selector methods to the 'selectorsWeakMap' + // Assign defined/created selector methods to the 'selectorsWeakMap' const existingSelectorMethods = this.selectorsWeakMap.get(sub)?.methods; const newSelectorMethods = existingSelectorMethods ? existingSelectorMethods.concat(toAddSelectorMethods) @@ -153,11 +153,15 @@ export class SubscriptionContainer { this.selectorsWeakMap.set(sub, { methods: newSelectorMethods }); // Assign specified key to the 'subscriberKeysWeakMap' - // (Not to the Observer, since the here specified key only counts for this Subscription Container) + // (Not to the Observer itself, since the key specified here only counts for this Subscription Container) if (config.key != null) this.subscriberKeysWeakMap.set(sub, config.key); // Add Observer to subscribers this.subscribers.add(sub); + + // Add Subscription Container to Observer + // so that it can be updated when to Observer changes + sub.subscribedTo.add(this); } /** @@ -168,9 +172,12 @@ export class SubscriptionContainer { * @param sub - Observer to be removed from the Subscription Container */ public removeSubscription(sub: Observer) { - this.selectorsWeakMap.delete(sub); - this.subscriberKeysWeakMap.delete(sub); - this.subscribers.delete(sub); + if (this.subscribers.has(sub)) { + this.selectorsWeakMap.delete(sub); + this.subscriberKeysWeakMap.delete(sub); + this.subscribers.delete(sub); + sub.subscribedTo.delete(this); + } } } diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index bfd4ee2c..78ad537d 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -158,7 +158,7 @@ export class SubController { const unsub = (subscriptionContainer: SubscriptionContainer) => { subscriptionContainer.ready = false; subscriptionContainer.subscribers.forEach((observer) => { - observer.unsubscribe(subscriptionContainer); + subscriptionContainer.removeSubscription(observer); }); }; diff --git a/packages/core/tests/unit/computed/computed.test.ts b/packages/core/tests/unit/computed/computed.test.ts index 9f645220..2dd8a4d3 100644 --- a/packages/core/tests/unit/computed/computed.test.ts +++ b/packages/core/tests/unit/computed/computed.test.ts @@ -232,9 +232,9 @@ describe('Computed Tests', () => { computed.hardCodedDeps = [dummyObserver3]; computed.deps = [dummyObserver3]; // normally the hardCodedDeps get automatically added to the deps.. but this time we set the hardCodedProperty after the instantiation - dummyObserver1.depend = jest.fn(); - dummyObserver2.depend = jest.fn(); - dummyObserver3.depend = jest.fn(); + dummyObserver1.addDependent = jest.fn(); + dummyObserver2.addDependent = jest.fn(); + dummyObserver3.addDependent = jest.fn(); jest.spyOn(ComputedTracker, 'track').mockClear(); // mockClear because otherwise the static mock doesn't get reset after each 'it' test jest.spyOn(ComputedTracker, 'getTrackedObservers').mockClear(); }); @@ -257,9 +257,15 @@ describe('Computed Tests', () => { dummyObserver1, dummyObserver2, ]); - expect(dummyObserver1.depend).toHaveBeenCalledWith(computed.observer); - expect(dummyObserver2.depend).toHaveBeenCalledWith(computed.observer); - expect(dummyObserver3.depend).toHaveBeenCalledWith(computed.observer); + expect(dummyObserver1.addDependent).toHaveBeenCalledWith( + computed.observer + ); + expect(dummyObserver2.addDependent).toHaveBeenCalledWith( + computed.observer + ); + expect(dummyObserver3.addDependent).toHaveBeenCalledWith( + computed.observer + ); }); it("should call computeFunction and shouldn't track dependencies the computeFunction depends on (autodetect false)", () => { @@ -273,9 +279,9 @@ describe('Computed Tests', () => { expect(ComputedTracker.getTrackedObservers).not.toHaveBeenCalled(); expect(computed.hardCodedDeps).toStrictEqual([dummyObserver3]); expect(computed.deps).toStrictEqual([dummyObserver3]); - expect(dummyObserver1.depend).not.toHaveBeenCalled(); - expect(dummyObserver2.depend).not.toHaveBeenCalled(); - expect(dummyObserver3.depend).not.toHaveBeenCalled(); + expect(dummyObserver1.addDependent).not.toHaveBeenCalled(); + expect(dummyObserver2.addDependent).not.toHaveBeenCalled(); + expect(dummyObserver3.addDependent).not.toHaveBeenCalled(); }); }); diff --git a/packages/core/tests/unit/runtime/observer.test.ts b/packages/core/tests/unit/runtime/observer.test.ts index b833ac97..11c9ff5b 100644 --- a/packages/core/tests/unit/runtime/observer.test.ts +++ b/packages/core/tests/unit/runtime/observer.test.ts @@ -158,16 +158,16 @@ describe('Observer Tests', () => { }); it('should add passed Observer to deps', () => { - observer.depend(dummyObserver1); + observer.addDependent(dummyObserver1); expect(observer.dependents.size).toBe(1); expect(observer.dependents.has(dummyObserver2)); }); it("shouldn't add the same Observer twice to deps", () => { - observer.depend(dummyObserver1); + observer.addDependent(dummyObserver1); - observer.depend(dummyObserver1); + observer.addDependent(dummyObserver1); expect(observer.dependents.size).toBe(1); expect(observer.dependents.has(dummyObserver1)); From 9c3a9ac81dc2f92f383a1619a7c6baa1ae23bad1 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 9 Jun 2021 06:35:16 +0200 Subject: [PATCH 056/117] fixed adding Item before 'isInstantiated = true' issue --- examples/vue/develop/my-project/src/core.js | 1 + packages/core/src/collection/index.ts | 15 +++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/vue/develop/my-project/src/core.js b/examples/vue/develop/my-project/src/core.js index 40527a9d..8dafcb95 100644 --- a/examples/vue/develop/my-project/src/core.js +++ b/examples/vue/develop/my-project/src/core.js @@ -12,6 +12,7 @@ export const MY_STATE = App.createState('Hello World', { key: 'my-state' }); // Create Collection export const TODOS = App.createCollection({ initialData: [{ id: 1, name: 'Clean Bathroom' }], + selectors: [1], }); // .persist('todos'); // TODOS.collect({ id: 2, name: 'jeff' }); diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 3fe709c5..8ac6a0e0 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -85,23 +85,22 @@ export class Collection { this.initGroups(_config.groups as any); this.initSelectors(_config.selectors as any); - if (_config.initialData) this.collect(_config.initialData); - this.isInstantiated = true; + // Add 'initialData' to Collection + // (after 'isInstantiated' to add them properly to the Collection) + if (_config.initialData) this.collect(_config.initialData); + // Reselect Selector Items // Necessary because the selection of an Item - // hasn't worked with a not 'instantiated' Collection before + // hasn't worked with a not correctly 'instantiated' Collection before for (const key in this.selectors) this.selectors[key].reselect(); // Rebuild of Groups // Not necessary because if Items are added to the Collection, + // (after 'isInstantiated = true') // the Groups which contain these added Items are rebuilt. - for (const key in this.groups) this.groups[key].rebuild(); - - // TODO ISSUE with collecting the 'initialData' before 'isInstantiated = true' - // if (_config.initialData) this.collect(_config.initialData); // TODO REMOVE - Agile.logger.debug('END of COLLECTION INSTANTIATION'); // TODO REMOVE + // for (const key in this.groups) this.groups[key].rebuild(); } /** From 66aa53a1720ed4b65aa8824d8f7903b3ad4baaee Mon Sep 17 00:00:00 2001 From: BennoDev Date: Wed, 9 Jun 2021 13:50:08 +0200 Subject: [PATCH 057/117] documented loading collection value prints many warnings issue in code --- examples/vue/develop/my-project/src/core.js | 2 +- .../core/src/collection/collection.persistent.ts | 12 ++++++++++-- packages/core/src/state/state.persistent.ts | 5 ++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/examples/vue/develop/my-project/src/core.js b/examples/vue/develop/my-project/src/core.js index 8dafcb95..ecb19e8b 100644 --- a/examples/vue/develop/my-project/src/core.js +++ b/examples/vue/develop/my-project/src/core.js @@ -13,7 +13,7 @@ export const MY_STATE = App.createState('Hello World', { key: 'my-state' }); export const TODOS = App.createCollection({ initialData: [{ id: 1, name: 'Clean Bathroom' }], selectors: [1], -}); // .persist('todos'); +}).persist('todos'); // TODOS.collect({ id: 2, name: 'jeff' }); diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index d7e099a8..380451b8 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -138,6 +138,14 @@ export class CollectionPersistent< if (defaultGroup.persistent?.ready) await defaultGroup.persistent.initialLoading(); + // TODO rebuild the default Group once at the end when all Items were loaded into the Collection + // because otherwise it rebuilds the Group for each loaded Item + // (-> warnings are printed for all not yet loaded Items when rebuilding the Group) + // or rethink the whole Group rebuild process by adding a 'addItem()', 'removeItem()' and 'updateItem()' function + // so that there is no need for rebuilding the whole Group when for example only Item B changed or Item C was added + // + // See Issue by starting the vue develop example app and adding some todos to the _todo_ list + // Persist Items found in the default Group's value for (const itemKey of defaultGroup._value) { const item = this.collection().getItem(itemKey); @@ -170,11 +178,11 @@ export class CollectionPersistent< if (dummyItem?.persistent?.ready) { const loadedPersistedValueIntoItem = await dummyItem.persistent.loadPersistedValue( itemStorageKey - ); + ); // TODO FIRST GROUP REBUILD (by assigning loaded value to Item) // If successfully loaded Item value, assign Item to Collection if (loadedPersistedValueIntoItem) - this.collection().assignItem(dummyItem); + this.collection().assignItem(dummyItem); // TODO SECOND GROUP REBUILD (by calling rebuildGroupThatInclude() in the assignItem() method) } } } diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index da15578c..89a7063a 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -107,7 +107,10 @@ export class StatePersistent extends Persistent { if (loadedValue == null) return false; // Assign loaded Value to State - this.state().set(loadedValue, { storage: false, overwrite: true }); + this.state().set(loadedValue, { + storage: false, + overwrite: true, + }); // Setup Side Effects to keep the Storage value in sync with the State value this.setupSideEffects(storageItemKey); From 326e6ff4cfc5b27e08f57f9c34da7b85d5f6c119 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 9 Jun 2021 16:28:51 +0200 Subject: [PATCH 058/117] added basic tests for SubscriptionContainer --- .../functional-component-ts/src/core/index.ts | 20 +- .../container/SubscriptionContainer.ts | 12 +- .../CallbackSubscriptionContainer.test.ts | 7 +- .../ComponentSubscriptionContainer.test.ts | 7 +- .../container/SubscriptionContainer.test.ts | 277 ++++++++++++++++-- 5 files changed, 279 insertions(+), 44 deletions(-) diff --git a/examples/react/develop/functional-component-ts/src/core/index.ts b/examples/react/develop/functional-component-ts/src/core/index.ts index 7c45b97a..cf448f46 100644 --- a/examples/react/develop/functional-component-ts/src/core/index.ts +++ b/examples/react/develop/functional-component-ts/src/core/index.ts @@ -4,7 +4,7 @@ import Event from '@agile-ts/event'; export const myStorage: any = {}; export const App = new Agile({ - logConfig: { level: Logger.level.DEBUG, allowedTags: ['storage'] }, + logConfig: { level: Logger.level.DEBUG }, localStorage: true, }); @@ -84,15 +84,15 @@ export const MY_COLLECTION = App.createCollection( ], }) ).persist(); -// MY_COLLECTION.collect({ key: 'id1', name: 'test' }); -// MY_COLLECTION.collect({ key: 'id2', name: 'test2' }, 'myGroup'); -// MY_COLLECTION.update('id1', { key: 'id1Updated', name: 'testUpdated' }); -// MY_COLLECTION.getGroup('myGroup')?.persist({ -// followCollectionPersistKeyPattern: true, -// }); -// MY_COLLECTION.onLoad(() => { -// console.log('On Load MY_COLLECTION'); -// }); +MY_COLLECTION.collect({ key: 'id1', name: 'test' }); +MY_COLLECTION.collect({ key: 'id2', name: 'test2' }, 'myGroup'); +MY_COLLECTION.update('id1', { key: 'id1Updated', name: 'testUpdated' }); +MY_COLLECTION.getGroup('myGroup')?.persist({ + followCollectionPersistKeyPattern: true, +}); +MY_COLLECTION.onLoad(() => { + console.log('On Load MY_COLLECTION'); +}); export const externalCreatedItem = new Item(MY_COLLECTION, { key: 'id10', diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index 74cfd1e7..e9f345bc 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -145,10 +145,11 @@ export class SubscriptionContainer { } // Assign defined/created selector methods to the 'selectorsWeakMap' - const existingSelectorMethods = this.selectorsWeakMap.get(sub)?.methods; - const newSelectorMethods = existingSelectorMethods - ? existingSelectorMethods.concat(toAddSelectorMethods) - : toAddSelectorMethods; + const existingSelectorMethods = + this.selectorsWeakMap.get(sub)?.methods ?? []; + const newSelectorMethods = existingSelectorMethods.concat( + toAddSelectorMethods + ); if (newSelectorMethods.length > 0) this.selectorsWeakMap.set(sub, { methods: newSelectorMethods }); @@ -160,7 +161,8 @@ export class SubscriptionContainer { this.subscribers.add(sub); // Add Subscription Container to Observer - // so that it can be updated when to Observer changes + // so that it can be updated (cause rerender on the Component it represents) + // when for example the Observer value changes sub.subscribedTo.add(this); } diff --git a/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts index ac6fcd3d..2edeff7c 100644 --- a/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts @@ -54,6 +54,11 @@ describe('CallbackSubscriptionContainer Tests', () => { expect(subscriptionContainer.subscriberKeysWeakMap).toStrictEqual( expect.any(WeakMap) ); - expect(subscriptionContainer.selectorsWeakMap).toBe(dummySelectorWeakMap); + expect(subscriptionContainer.selectorsWeakMap).toStrictEqual( + expect.any(WeakMap) + ); + expect(subscriptionContainer.selectorsWeakMap).not.toBe( + dummySelectorWeakMap + ); }); }); diff --git a/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts index 7da24042..6ebb14fc 100644 --- a/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts @@ -52,6 +52,11 @@ describe('ComponentSubscriptionContainer Tests', () => { expect(subscriptionContainer.subscriberKeysWeakMap).toStrictEqual( expect.any(WeakMap) ); - expect(subscriptionContainer.selectorsWeakMap).toBe(dummySelectorWeakMap); + expect(subscriptionContainer.selectorsWeakMap).toStrictEqual( + expect.any(WeakMap) + ); + expect(subscriptionContainer.selectorsWeakMap).not.toBe( + dummySelectorWeakMap + ); }); }); diff --git a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts index 61046089..8a7cb06a 100644 --- a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts @@ -24,39 +24,112 @@ describe('SubscriptionContainer Tests', () => { dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' }); dummySelectorWeakMap = new WeakMap(); dummyProxyWeakMap = new WeakMap(); - - jest.spyOn(SubscriptionContainer.prototype, 'assignProxySelectors'); }); - it('should create SubscriptionContainer (default config)', () => { - jest.spyOn(Utils, 'generateId').mockReturnValue('generatedId'); + it('should create SubscriptionContainer with passed subs array (default config)', () => { + jest.spyOn(Utils, 'generateId').mockReturnValueOnce('generatedId'); + jest + .spyOn(SubscriptionContainer.prototype, 'addSubscription') + .mockReturnValueOnce() + .mockReturnValueOnce(); - const subscriptionContainer = new SubscriptionContainer(); + const subscriptionContainer = new SubscriptionContainer([ + dummyObserver1, + dummyObserver2, + ]); - expect(subscriptionContainer.assignProxySelectors).toHaveBeenCalledWith( - expect.any(WeakMap), - expect.any(WeakMap), - [] + expect(subscriptionContainer.addSubscription).toHaveBeenCalledTimes(2); + expect(subscriptionContainer.addSubscription).toHaveBeenCalledWith( + dummyObserver1, + { + proxyPaths: undefined, + selectorMethods: undefined, + key: undefined, + } + ); + expect(subscriptionContainer.addSubscription).toHaveBeenCalledWith( + dummyObserver2, + { + proxyPaths: undefined, + selectorMethods: undefined, + key: undefined, + } ); expect(subscriptionContainer.key).toBe('generatedId'); expect(subscriptionContainer.ready).toBeFalsy(); expect(subscriptionContainer.componentId).toBeUndefined(); - expect(subscriptionContainer.subscribers.size).toBe(0); + expect(subscriptionContainer.subscribers.size).toBe(0); // because of mocking addSubscription expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); expect(subscriptionContainer.isObjectBased).toBeFalsy(); expect(subscriptionContainer.subscriberKeysWeakMap).toStrictEqual( expect.any(WeakMap) ); - expect(subscriptionContainer.selectorsWeakMap).not.toBe( - dummySelectorWeakMap + expect(subscriptionContainer.selectorsWeakMap).toStrictEqual( + expect.any(WeakMap) + ); + }); + + it('should create SubscriptionContainer with passed subs object (default config)', () => { + jest.spyOn(Utils, 'generateId').mockReturnValueOnce('generatedId'); + jest + .spyOn(SubscriptionContainer.prototype, 'addSubscription') + .mockReturnValueOnce() + .mockReturnValueOnce(); + + const subscriptionContainer = new SubscriptionContainer({ + dummyObserver1: dummyObserver1, + dummyObserver2: dummyObserver2, + }); + + expect(subscriptionContainer.addSubscription).toHaveBeenCalledTimes(2); + expect(subscriptionContainer.addSubscription).toHaveBeenCalledWith( + dummyObserver1, + { + proxyPaths: undefined, + selectorMethods: undefined, + key: 'dummyObserver1', + } + ); + expect(subscriptionContainer.addSubscription).toHaveBeenCalledWith( + dummyObserver2, + { + proxyPaths: undefined, + selectorMethods: undefined, + key: 'dummyObserver2', + } + ); + + expect(subscriptionContainer.key).toBe('generatedId'); + expect(subscriptionContainer.ready).toBeFalsy(); + expect(subscriptionContainer.componentId).toBeUndefined(); + expect(subscriptionContainer.subscribers.size).toBe(0); // because of mocking addSubscription + expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); + expect(subscriptionContainer.isObjectBased).toBeTruthy(); + expect(subscriptionContainer.subscriberKeysWeakMap).toStrictEqual( + expect.any(WeakMap) ); expect(subscriptionContainer.selectorsWeakMap).toStrictEqual( expect.any(WeakMap) ); }); - it('should create SubscriptionContainer (specific config)', () => { + it('should create SubscriptionContainer with passed subs array (specific config)', () => { + jest + .spyOn(SubscriptionContainer.prototype, 'addSubscription') + .mockReturnValueOnce() + .mockReturnValueOnce(); + + dummyProxyWeakMap.set(dummyObserver1, { + paths: 'dummyObserver1_paths' as any, + }); + dummyProxyWeakMap.set(dummyObserver2, { + paths: 'dummyObserver2_paths' as any, + }); + dummySelectorWeakMap.set(dummyObserver2, { + methods: 'dummyObserver2_selectors' as any, + }); + const subscriptionContainer = new SubscriptionContainer( [dummyObserver1, dummyObserver2], { @@ -67,39 +140,189 @@ describe('SubscriptionContainer Tests', () => { } ); - expect( - subscriptionContainer.assignProxySelectors - ).toHaveBeenCalledWith(dummySelectorWeakMap, dummyProxyWeakMap, [ + expect(subscriptionContainer.addSubscription).toHaveBeenCalledTimes(2); + expect(subscriptionContainer.addSubscription).toHaveBeenCalledWith( dummyObserver1, + { + proxyPaths: 'dummyObserver1_paths', + selectorMethods: undefined, + key: undefined, + } + ); + expect(subscriptionContainer.addSubscription).toHaveBeenCalledWith( dummyObserver2, - ]); + { + proxyPaths: 'dummyObserver2_paths', + selectorMethods: 'dummyObserver2_selectors', + key: undefined, + } + ); expect(subscriptionContainer.key).toBe('dummyKey'); expect(subscriptionContainer.ready).toBeFalsy(); expect(subscriptionContainer.componentId).toBe('testID'); - expect(subscriptionContainer.subscribers.size).toBe(2); - expect(subscriptionContainer.subscribers.has(dummyObserver1)).toBeTruthy(); - expect(subscriptionContainer.subscribers.has(dummyObserver2)).toBeTruthy(); + expect(subscriptionContainer.subscribers.size).toBe(0); // because of mocking addSubscription expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); expect(subscriptionContainer.isObjectBased).toBeFalsy(); expect(subscriptionContainer.subscriberKeysWeakMap).toStrictEqual( expect.any(WeakMap) ); - expect(subscriptionContainer.selectorsWeakMap).toBe(dummySelectorWeakMap); + expect(subscriptionContainer.selectorsWeakMap).toStrictEqual( + expect.any(WeakMap) + ); + expect(subscriptionContainer.selectorsWeakMap).not.toBe( + dummySelectorWeakMap + ); }); describe('Subscription Container Function Tests', () => { - let observer: SubscriptionContainer; + let subscriptionContainer: SubscriptionContainer; beforeEach(() => { - observer = new SubscriptionContainer(); + subscriptionContainer = new SubscriptionContainer([]); + }); + + describe('addSubscription function tests', () => { + it( + 'should create selectors based on the specified proxies, ' + + 'assigns newly created or provided selectors to the selectorsWeakMap ' + + 'and subscribe the specified Observer to the SubscriptionContainer', + () => { + dummyObserver1.value = { + das: { haus: { vom: 'nikolaus' } }, + alle: { meine: 'entchien' }, + test1: 'test1Value', + test2: 'test2Value', + test3: 'test3Value', + }; + subscriptionContainer.selectorsWeakMap.set(dummyObserver1, { + methods: [(value) => value.test3], + }); + subscriptionContainer.selectorsWeakMap.set(dummyObserver2, { + methods: [(value) => 'doesNotMatter'], + }); + subscriptionContainer.subscriberKeysWeakMap.set( + dummyObserver2, + 'dummyObserver2' + ); + + subscriptionContainer.addSubscription(dummyObserver1, { + key: 'dummyObserver1', + proxyPaths: [['das', 'haus', 'vom'], ['test1']], + selectorMethods: [ + (value) => value.alle.meine, + (value) => value.test2, + ], + }); + + expect(subscriptionContainer.subscribers.size).toBe(1); + expect( + subscriptionContainer.subscribers.has(dummyObserver1) + ).toBeTruthy(); + expect(dummyObserver1.subscribedTo.size).toBe(1); + expect( + dummyObserver1.subscribedTo.has(subscriptionContainer) + ).toBeTruthy(); + + // should assign specified selectors/(and selectors created from proxy paths) to the selectorsWeakMap + const observer1Selector = subscriptionContainer.selectorsWeakMap.get( + dummyObserver1 + ) as any; + expect(observer1Selector.methods.length).toBe(5); + expect(observer1Selector.methods[0](dummyObserver1.value)).toBe( + 'test3Value' + ); + expect(observer1Selector.methods[1](dummyObserver1.value)).toBe( + 'entchien' + ); + expect(observer1Selector.methods[2](dummyObserver1.value)).toBe( + 'test2Value' + ); + expect(observer1Selector.methods[3](dummyObserver1.value)).toBe( + 'nikolaus' + ); + expect(observer1Selector.methods[4](dummyObserver1.value)).toBe( + 'test1Value' + ); + + // shouldn't overwrite already set values in selectorsWeakMap + const observer2Selector = subscriptionContainer.selectorsWeakMap.get( + dummyObserver2 + ) as any; + expect(observer2Selector.methods.length).toBe(1); + expect(observer2Selector.methods[0](null)).toBe('doesNotMatter'); + + // should assign specified key to the subscriberKeysWeakMap + const observer1Key = subscriptionContainer.subscriberKeysWeakMap.get( + dummyObserver1 + ); + expect(observer1Key).toBe('dummyObserver1'); + + // shouldn't overwrite already set values in subscriberKeysWeakMap + const observer2Key = subscriptionContainer.subscriberKeysWeakMap.get( + dummyObserver2 + ); + expect(observer2Key).toBe('dummyObserver2'); + } + ); }); - describe('assignProxySelectors function tests', () => { - beforeEach(() => {}); + describe('removeSubscription function tests', () => { + let subscriptionContainer: SubscriptionContainer; + + beforeEach(() => { + subscriptionContainer = new SubscriptionContainer([]); + + subscriptionContainer.subscribers = new Set([ + dummyObserver1, + dummyObserver2, + ]); + dummyObserver1.subscribedTo = new Set([subscriptionContainer]); + dummyObserver2.subscribedTo = new Set([subscriptionContainer]); + + subscriptionContainer.selectorsWeakMap.set(dummyObserver1, { + methods: [], + }); + subscriptionContainer.selectorsWeakMap.set(dummyObserver2, { + methods: [], + }); + subscriptionContainer.subscriberKeysWeakMap.set( + dummyObserver1, + 'dummyObserver1' + ); + subscriptionContainer.subscriberKeysWeakMap.set( + dummyObserver2, + 'dummyObserver2' + ); + }); + + it('should remove subscribed Observer from Subscription Container', () => { + subscriptionContainer.removeSubscription(dummyObserver1); + + expect(subscriptionContainer.subscribers.size).toBe(1); + expect( + subscriptionContainer.subscribers.has(dummyObserver2) + ).toBeTruthy(); + + expect( + subscriptionContainer.selectorsWeakMap.get(dummyObserver1) + ).toBeUndefined(); + expect( + subscriptionContainer.selectorsWeakMap.get(dummyObserver2) + ).not.toBeUndefined(); + + expect( + subscriptionContainer.subscriberKeysWeakMap.get(dummyObserver1) + ).toBeUndefined(); + expect( + subscriptionContainer.subscriberKeysWeakMap.get(dummyObserver2) + ).toBe('dummyObserver2'); - it('todo', () => { - // TODO + expect(dummyObserver1.subscribedTo.size).toBe(0); + expect(dummyObserver2.subscribedTo.size).toBe(1); + expect( + dummyObserver2.subscribedTo.has(subscriptionContainer) + ).toBeTruthy(); }); }); }); From 5556af0b6a12417c0fa7e62d00da595cd63957f1 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 9 Jun 2021 18:25:32 +0200 Subject: [PATCH 059/117] added basic subController tests --- .../runtime/subscription/sub.controller.ts | 25 +- .../subscription/sub.controller.test.ts | 726 +++++++++--------- 2 files changed, 357 insertions(+), 394 deletions(-) diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index 78ad537d..72f979bc 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -59,7 +59,7 @@ export class SubController { public subscribe( integrationInstance: any, subs: Array, - config: RegisterSubscriptionConfigInterface + config?: RegisterSubscriptionConfigInterface ): SubscriptionContainer; /** * Creates a so called Subscription Container that represents an UI-Component in AgileTs. @@ -92,7 +92,7 @@ export class SubController { public subscribe( integrationInstance: any, subs: { [key: string]: Observer }, - config: RegisterSubscriptionConfigInterface + config?: RegisterSubscriptionConfigInterface ): { subscriptionContainer: SubscriptionContainer; props: { [key: string]: Observer['value'] }; @@ -124,22 +124,17 @@ export class SubController { config ); - const props: { [key: string]: Observer['value'] } = {}; + // Return object based Subscription Container + if (subscriptionContainer.isObjectBased && !Array.isArray(subs)) { + // Build an Observer value keymap + const props: { [key: string]: Observer['value'] } = {}; + for (const key in subs) if (subs[key].value) props[key] = subs[key].value; - // Subscribe Observers to the created Subscription Container - // and build an Observer value keymap called props - for (const key in subs) { - const observer = subs[key]; - observer.subscribedTo.add(subscriptionContainer); - if (observer.value) props[key] = observer.value; + return { subscriptionContainer, props }; } - return Array.isArray(subs) - ? subscriptionContainer - : { - subscriptionContainer: subscriptionContainer, - props: props, - }; + // Return array based Subscription Container + return subscriptionContainer; } /** diff --git a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts index 4ce359d4..c96710fe 100644 --- a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts +++ b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts @@ -4,7 +4,6 @@ import { ComponentSubscriptionContainer, Observer, SubController, - SubscriptionContainer, } from '../../../../src'; import * as Utils from '@agile-ts/utils'; import { LogMock } from '../../../helper/logMock'; @@ -32,141 +31,172 @@ describe('SubController Tests', () => { let dummyObserver2: Observer; beforeEach(() => { - dummyObserver1 = new Observer(dummyAgile, { key: 'dummyObserver1' }); - dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' }); - subController = new SubController(dummyAgile); - }); - - describe('subscribeWithSubsObject function tests', () => { - const dummyIntegration = 'myDummyIntegration'; - let dummySubscriptionContainer: SubscriptionContainer; - - beforeEach(() => { - dummySubscriptionContainer = new SubscriptionContainer(); - dummyObserver1.value = 'myCoolValue'; - - subController.subscribe = jest.fn(() => dummySubscriptionContainer); - jest.spyOn(dummyObserver1, 'subscribe'); - jest.spyOn(dummyObserver2, 'subscribe'); + dummyObserver1 = new Observer(dummyAgile, { + key: 'dummyObserver1', + value: 'dummyObserver1Value', }); - - it('should create subscriptionContainer and add in Object shape passed Observers to it', () => { - const subscribeWithSubsResponse = subController.subscribeWithSubsObject( - dummyIntegration, - { - dummyObserver1: dummyObserver1, - dummyObserver2: dummyObserver2, - }, - { - key: 'subscribeWithSubsObjectKey', - proxyKeyMap: {}, - waitForMount: false, - } - ); - - expect(subscribeWithSubsResponse).toStrictEqual({ - props: { - dummyObserver1: 'myCoolValue', - }, - subscriptionContainer: dummySubscriptionContainer, - }); - - expect(subController.subscribe).toHaveBeenCalledWith( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { - key: 'subscribeWithSubsObjectKey', - proxyKeyMap: {}, - waitForMount: false, - } - ); - - expect(dummySubscriptionContainer.isObjectBased).toBeTruthy(); - expect(dummySubscriptionContainer.subscriberKeysWeakMap).toStrictEqual({ - dummyObserver1: dummyObserver1, - dummyObserver2: dummyObserver2, - }); - - expect(dummySubscriptionContainer.subscribers.size).toBe(2); - expect( - dummySubscriptionContainer.subscribers.has(dummyObserver1) - ).toBeTruthy(); - expect( - dummySubscriptionContainer.subscribers.has(dummyObserver2) - ).toBeTruthy(); - - expect(dummyObserver1.subscribe).toHaveBeenCalledWith( - dummySubscriptionContainer - ); - expect(dummyObserver2.subscribe).toHaveBeenCalledWith( - dummySubscriptionContainer - ); + dummyObserver2 = new Observer(dummyAgile, { + key: 'dummyObserver2', + value: 'dummyObserver2Value', }); + subController = new SubController(dummyAgile); }); - describe('subscribeWithSubsArray function tests', () => { - const dummyIntegration = 'myDummyIntegration'; - let dummySubscriptionContainer: SubscriptionContainer; - + describe('subscribe function tests', () => { beforeEach(() => { - dummySubscriptionContainer = new SubscriptionContainer(); - - subController.subscribe = jest.fn(() => dummySubscriptionContainer); - jest.spyOn(dummyObserver1, 'subscribe'); - jest.spyOn(dummyObserver2, 'subscribe'); + jest.spyOn(subController, 'createCallbackSubscriptionContainer'); + jest.spyOn(subController, 'createComponentSubscriptionContainer'); }); - it('should create subscriptionContainer and add in Array Shape passed Observers to it', () => { - const subscribeWithSubsArrayResponse = subController.subscribeWithSubsArray( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { - key: 'subscribeWithSubsArrayKey', - proxyKeyMap: {}, - waitForMount: false, - } - ); - - expect(subscribeWithSubsArrayResponse).toBe(dummySubscriptionContainer); - - expect(subController.subscribe).toHaveBeenCalledWith( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { - key: 'subscribeWithSubsArrayKey', - proxyKeyMap: {}, - waitForMount: false, - } - ); - - expect(dummySubscriptionContainer.isObjectBased).toBeFalsy(); - expect( - dummySubscriptionContainer.subscriberKeysWeakMap - ).toBeUndefined(); - - expect(dummySubscriptionContainer.subscribers.size).toBe(2); - expect( - dummySubscriptionContainer.subscribers.has(dummyObserver1) - ).toBeTruthy(); - expect( - dummySubscriptionContainer.subscribers.has(dummyObserver2) - ).toBeTruthy(); - - expect(dummyObserver1.subscribe).toHaveBeenCalledWith( - dummySubscriptionContainer - ); - expect(dummyObserver2.subscribe).toHaveBeenCalledWith( - dummySubscriptionContainer - ); - }); + it( + 'should create a Component based Subscription Container with specified component' + + ' and add in object specified Observers to it', + () => { + dummyAgile.config.waitForMount = 'aFakeBoolean' as any; + const dummyIntegration: any = { + dummy: 'integration', + }; + + const returnValue = subController.subscribe( + dummyIntegration, + { observer1: dummyObserver1, observer2: dummyObserver2 }, + { + key: 'subscriptionContainerKey', + componentId: 'testID', + waitForMount: true, + } + ); + + expect(returnValue.subscriptionContainer).toBeInstanceOf( + ComponentSubscriptionContainer + ); + expect(returnValue.props).toStrictEqual({ + observer1: dummyObserver1.value, + observer2: dummyObserver2.value, + }); + + expect( + subController.createComponentSubscriptionContainer + ).toHaveBeenCalledWith( + dummyIntegration, + { observer1: dummyObserver1, observer2: dummyObserver2 }, + { + key: 'subscriptionContainerKey', + componentId: 'testID', + waitForMount: true, + } + ); + } + ); + + it( + 'should create a Component based Subscription Container with specified component' + + ' and add in array specified Observers to it', + () => { + dummyAgile.config.waitForMount = 'aFakeBoolean' as any; + const dummyIntegration: any = { + dummy: 'integration', + }; + + const returnValue = subController.subscribe( + dummyIntegration, + [dummyObserver1, dummyObserver2], + { key: 'subscriptionContainerKey', componentId: 'testID' } + ); + + expect(returnValue).toBeInstanceOf(ComponentSubscriptionContainer); + + expect( + subController.createComponentSubscriptionContainer + ).toHaveBeenCalledWith( + dummyIntegration, + [dummyObserver1, dummyObserver2], + { + key: 'subscriptionContainerKey', + componentId: 'testID', + waitForMount: dummyAgile.config.waitForMount, + } + ); + } + ); + + it( + 'should create a Callback based Subscription Container with specified callback function' + + ' and add in object specified Observers to it', + () => { + dummyAgile.config.waitForMount = 'aFakeBoolean' as any; + const dummyIntegration = () => { + /* empty function */ + }; + + const returnValue = subController.subscribe( + dummyIntegration, + { observer1: dummyObserver1, observer2: dummyObserver2 }, + { + key: 'subscriptionContainerKey', + componentId: 'testID', + } + ); + + expect(returnValue.subscriptionContainer).toBeInstanceOf( + CallbackSubscriptionContainer + ); + expect(returnValue.props).toStrictEqual({ + observer1: dummyObserver1.value, + observer2: dummyObserver2.value, + }); + + expect( + subController.createCallbackSubscriptionContainer + ).toHaveBeenCalledWith( + dummyIntegration, + { observer1: dummyObserver1, observer2: dummyObserver2 }, + { + key: 'subscriptionContainerKey', + componentId: 'testID', + waitForMount: dummyAgile.config.waitForMount, + } + ); + } + ); + + it( + 'should create a Callback based Subscription Container with specified callback function' + + ' and add in array specified Observers to it', + () => { + dummyAgile.config.waitForMount = 'aFakeBoolean' as any; + const dummyIntegration = () => { + /* empty function */ + }; + + const returnValue = subController.subscribe( + dummyIntegration, + [dummyObserver1, dummyObserver2], + { + key: 'subscriptionContainerKey', + componentId: 'testID', + waitForMount: false, + } + ); + + expect(returnValue).toBeInstanceOf(CallbackSubscriptionContainer); + + expect( + subController.createCallbackSubscriptionContainer + ).toHaveBeenCalledWith( + dummyIntegration, + [dummyObserver1, dummyObserver2], + { + key: 'subscriptionContainerKey', + componentId: 'testID', + waitForMount: false, + } + ); + } + ); }); describe('unsubscribe function tests', () => { - beforeEach(() => { - jest.spyOn(dummyObserver1, 'unsubscribe'); - jest.spyOn(dummyObserver2, 'unsubscribe'); - }); - it('should unsubscribe callbackSubscriptionContainer', () => { const dummyIntegration = () => { /* empty function */ @@ -175,17 +205,21 @@ describe('SubController Tests', () => { dummyIntegration, [dummyObserver1, dummyObserver2] ); + callbackSubscriptionContainer.removeSubscription = jest.fn(); subController.unsubscribe(callbackSubscriptionContainer); expect(subController.callbackSubs.size).toBe(0); expect(callbackSubscriptionContainer.ready).toBeFalsy(); - expect(dummyObserver1.unsubscribe).toHaveBeenCalledWith( - callbackSubscriptionContainer - ); - expect(dummyObserver2.unsubscribe).toHaveBeenCalledWith( - callbackSubscriptionContainer - ); + expect( + callbackSubscriptionContainer.removeSubscription + ).toHaveBeenCalledTimes(2); + expect( + callbackSubscriptionContainer.removeSubscription + ).toHaveBeenCalledWith(dummyObserver1); + expect( + callbackSubscriptionContainer.removeSubscription + ).toHaveBeenCalledWith(dummyObserver2); }); it('should unsubscribe componentSubscriptionContainer', () => { @@ -196,41 +230,24 @@ describe('SubController Tests', () => { dummyIntegration, [dummyObserver1, dummyObserver2] ); + componentSubscriptionContainer.removeSubscription = jest.fn(); subController.unsubscribe(componentSubscriptionContainer); expect(subController.componentSubs.size).toBe(0); expect(componentSubscriptionContainer.ready).toBeFalsy(); - expect(dummyObserver1.unsubscribe).toHaveBeenCalledWith( - componentSubscriptionContainer - ); - expect(dummyObserver2.unsubscribe).toHaveBeenCalledWith( - componentSubscriptionContainer - ); - }); - - it('should unsubscribe componentSubscriptionContainer from passed Object that hold an instance of componentSubscriptionContainer', () => { - const dummyIntegration: any = { - dummy: 'integration', - }; - const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2] - ); - - subController.unsubscribe(dummyIntegration); - - expect(subController.componentSubs.size).toBe(0); - expect(componentSubscriptionContainer.ready).toBeFalsy(); - expect(dummyObserver1.unsubscribe).toHaveBeenCalledWith( - componentSubscriptionContainer - ); - expect(dummyObserver2.unsubscribe).toHaveBeenCalledWith( - componentSubscriptionContainer - ); + expect( + componentSubscriptionContainer.removeSubscription + ).toHaveBeenCalledTimes(2); + expect( + componentSubscriptionContainer.removeSubscription + ).toHaveBeenCalledWith(dummyObserver1); + expect( + componentSubscriptionContainer.removeSubscription + ).toHaveBeenCalledWith(dummyObserver2); }); - it('should unsubscribe componentSubscriptionContainers from passed Object that hold an Array of componentSubscriptionContainers', () => { + it('should unsubscribe componentSubscriptionContainers from passed Object that holds an instance of componentSubscriptionContainers', () => { const dummyIntegration: any = { dummy: 'integration', componentSubscriptionContainers: [], @@ -239,185 +256,172 @@ describe('SubController Tests', () => { dummyIntegration, [dummyObserver1, dummyObserver2] ); + componentSubscriptionContainer.removeSubscription = jest.fn(); const componentSubscriptionContainer2 = subController.createComponentSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2] ); + componentSubscriptionContainer2.removeSubscription = jest.fn(); subController.unsubscribe(dummyIntegration); expect(subController.componentSubs.size).toBe(0); expect(componentSubscriptionContainer.ready).toBeFalsy(); - expect(dummyObserver1.unsubscribe).toHaveBeenCalledWith( - componentSubscriptionContainer - ); - expect(dummyObserver2.unsubscribe).toHaveBeenCalledWith( - componentSubscriptionContainer - ); - - expect(componentSubscriptionContainer2.ready).toBeFalsy(); - expect(dummyObserver1.unsubscribe).toHaveBeenCalledWith( - componentSubscriptionContainer2 - ); - expect(dummyObserver2.unsubscribe).toHaveBeenCalledWith( - componentSubscriptionContainer2 - ); - }); - }); - - describe('registerSubscription function tests', () => { - let dummySubscriptionContainer: SubscriptionContainer; - - beforeEach(() => { - dummySubscriptionContainer = new SubscriptionContainer(); - dummyAgile.config.waitForMount = 'dummyWaitForMount' as any; - - subController.createCallbackSubscriptionContainer = jest.fn( - () => dummySubscriptionContainer as CallbackSubscriptionContainer - ); - subController.createComponentSubscriptionContainer = jest.fn( - () => dummySubscriptionContainer as ComponentSubscriptionContainer - ); - }); - - it('should call registerCallbackSubscription if passed integrationInstance is a Function (default config)', () => { - const dummyIntegration = () => { - /* empty function */ - }; - - const subscriptionContainer = subController.subscribe( - dummyIntegration, - [dummyObserver1, dummyObserver2] - ); - - expect(subscriptionContainer).toBe(dummySubscriptionContainer); expect( - subController.createCallbackSubscriptionContainer - ).toHaveBeenCalledWith( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { waitForMount: dummyAgile.config.waitForMount } - ); + componentSubscriptionContainer.removeSubscription + ).toHaveBeenCalledTimes(2); expect( - subController.createComponentSubscriptionContainer - ).not.toHaveBeenCalled(); - }); - - it('should call registerCallbackSubscription if passed integrationInstance is a Function (specific config)', () => { - const dummyIntegration = () => { - /* empty function */ - }; - - const subscriptionContainer = subController.subscribe( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { key: 'niceKey', proxyKeyMap: {}, waitForMount: false } - ); - - expect(subscriptionContainer).toBe(dummySubscriptionContainer); + componentSubscriptionContainer.removeSubscription + ).toHaveBeenCalledWith(dummyObserver1); expect( - subController.createCallbackSubscriptionContainer - ).toHaveBeenCalledWith( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { key: 'niceKey', proxyKeyMap: {}, waitForMount: false } - ); - expect( - subController.createComponentSubscriptionContainer - ).not.toHaveBeenCalled(); - }); - - it('should call registerComponentSubscription if passed integrationInstance is not a Function (default config)', () => { - const dummyIntegration = { dummy: 'integration' }; - - const subscriptionContainer = subController.subscribe( - dummyIntegration, - [dummyObserver1, dummyObserver2] - ); + componentSubscriptionContainer.removeSubscription + ).toHaveBeenCalledWith(dummyObserver2); - expect(subscriptionContainer).toBe(dummySubscriptionContainer); + expect(componentSubscriptionContainer2.ready).toBeFalsy(); expect( - subController.createComponentSubscriptionContainer - ).toHaveBeenCalledWith( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { waitForMount: dummyAgile.config.waitForMount } - ); + componentSubscriptionContainer2.removeSubscription + ).toHaveBeenCalledTimes(2); expect( - subController.createCallbackSubscriptionContainer - ).not.toHaveBeenCalled(); - }); - - it('should call registerComponentSubscription if passed integrationInstance is not a Function (specific config)', () => { - const dummyIntegration = { dummy: 'integration' }; - - const subscriptionContainer = subController.subscribe( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { key: 'niceKey', proxyKeyMap: {}, waitForMount: false } - ); - - expect(subscriptionContainer).toBe(dummySubscriptionContainer); - expect( - subController.createComponentSubscriptionContainer - ).toHaveBeenCalledWith( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { key: 'niceKey', proxyKeyMap: {}, waitForMount: false } - ); + componentSubscriptionContainer2.removeSubscription + ).toHaveBeenCalledWith(dummyObserver1); expect( - subController.createCallbackSubscriptionContainer - ).not.toHaveBeenCalled(); + componentSubscriptionContainer2.removeSubscription + ).toHaveBeenCalledWith(dummyObserver2); }); }); - describe('registerComponentSubscription function tests', () => { - it('should return ready componentSubscriptionContainer and add it to dummyIntegration at componentSubscriptionContainer (config.waitForMount = false)', () => { - const dummyIntegration: any = { dummy: 'integration' }; - - const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { waitForMount: false } - ); - - expect(componentSubscriptionContainer).toBeInstanceOf( - ComponentSubscriptionContainer - ); - expect(componentSubscriptionContainer.component).toStrictEqual( - dummyIntegration - ); - expect(componentSubscriptionContainer.ready).toBeTruthy(); - - expect(componentSubscriptionContainer.subscribers.size).toBe(2); - expect( - componentSubscriptionContainer.subscribers.has(dummyObserver1) - ).toBeTruthy(); - expect( - componentSubscriptionContainer.subscribers.has(dummyObserver2) - ).toBeTruthy(); + describe('createComponentSubscriptionContainer function tests', () => { + it( + 'should return ready componentSubscriptionContainer ' + + 'and add an instance of it to the not existing componentSubscriptions property in the dummyIntegration (default config)', + () => { + jest.spyOn(Utils, 'generateId').mockReturnValueOnce('generatedKey'); + const dummyIntegration: any = { + dummy: 'integration', + }; + + const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( + dummyIntegration, + [dummyObserver1, dummyObserver2], + { waitForMount: false } + ); + + expect(componentSubscriptionContainer).toBeInstanceOf( + ComponentSubscriptionContainer + ); + expect(componentSubscriptionContainer.component).toStrictEqual( + dummyIntegration + ); + expect(componentSubscriptionContainer.ready).toBeTruthy(); + + expect(subController.componentSubs.size).toBe(1); + expect( + subController.componentSubs.has(componentSubscriptionContainer) + ).toBeTruthy(); + + expect(dummyIntegration.componentSubscriptionContainers.length).toBe( + 1 + ); + expect(dummyIntegration.componentSubscriptionContainers[0]).toBe( + componentSubscriptionContainer + ); + + // Check if ComponentSubscriptionContainer was called with correct parameters + expect(componentSubscriptionContainer.key).toBe('generatedKey'); + expect(componentSubscriptionContainer.componentId).toBeUndefined(); + expect(componentSubscriptionContainer.subscribers.size).toBe(2); + expect( + componentSubscriptionContainer.subscribers.has(dummyObserver1) + ).toBeTruthy(); + expect( + componentSubscriptionContainer.subscribers.has(dummyObserver2) + ).toBeTruthy(); + } + ); + + it( + 'should return ready componentSubscriptionContainer ' + + 'and add an instance of it to the existing componentSubscriptions property in the dummyIntegration (default config)', + () => { + jest.spyOn(Utils, 'generateId').mockReturnValueOnce('generatedKey'); + const dummyIntegration: any = { + dummy: 'integration', + componentSubscriptionContainers: [], + }; + + const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( + dummyIntegration, + [dummyObserver1, dummyObserver2], + { waitForMount: false } + ); + + expect(dummyIntegration.componentSubscriptionContainers.length).toBe( + 1 + ); + expect(dummyIntegration.componentSubscriptionContainers[0]).toBe( + componentSubscriptionContainer + ); + } + ); + + it( + 'should return ready componentSubscriptionContainer ' + + 'and add an instance of it to the not existing componentSubscriptions property in the dummyIntegration (specific config)', + () => { + const dummyIntegration: any = { + dummy: 'integration', + }; + + const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( + dummyIntegration, + [dummyObserver1, dummyObserver2], + { waitForMount: false, componentId: 'testID', key: 'dummyKey' } + ); + + expect(componentSubscriptionContainer).toBeInstanceOf( + ComponentSubscriptionContainer + ); + expect(componentSubscriptionContainer.component).toStrictEqual( + dummyIntegration + ); + expect(componentSubscriptionContainer.ready).toBeTruthy(); + + expect(subController.componentSubs.size).toBe(1); + expect( + subController.componentSubs.has(componentSubscriptionContainer) + ).toBeTruthy(); + + expect(dummyIntegration.componentSubscriptionContainers.length).toBe( + 1 + ); + expect(dummyIntegration.componentSubscriptionContainers[0]).toBe( + componentSubscriptionContainer + ); + + // Check if ComponentSubscriptionContainer was called with correct parameters + expect(componentSubscriptionContainer.key).toBe('dummyKey'); + expect(componentSubscriptionContainer.componentId).toBe('testID'); + expect(componentSubscriptionContainer.subscribers.size).toBe(2); + expect( + componentSubscriptionContainer.subscribers.has(dummyObserver1) + ).toBeTruthy(); + expect( + componentSubscriptionContainer.subscribers.has(dummyObserver2) + ).toBeTruthy(); + } + ); - expect(subController.componentSubs.size).toBe(1); - expect( - subController.componentSubs.has(componentSubscriptionContainer) - ).toBeTruthy(); - - expect(dummyIntegration.componentSubscriptionContainer).toBe( - componentSubscriptionContainer - ); - }); - - it('should return ready componentSubscriptionContainer and add it to componentSubscriptions in dummyIntegration (config.waitForMount = false)', () => { + it("should return not ready componentSubscriptionContainer if componentInstance isn't mounted (waitForMount = true)", () => { + jest.spyOn(Utils, 'generateId').mockReturnValueOnce('generatedKey'); const dummyIntegration: any = { dummy: 'integration', - componentSubscriptionContainers: [], }; const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( dummyIntegration, [dummyObserver1, dummyObserver2], - { waitForMount: false } + { waitForMount: true } ); expect(componentSubscriptionContainer).toBeInstanceOf( @@ -426,47 +430,16 @@ describe('SubController Tests', () => { expect(componentSubscriptionContainer.component).toStrictEqual( dummyIntegration ); - expect(componentSubscriptionContainer.ready).toBeTruthy(); - - expect(componentSubscriptionContainer.subscribers.size).toBe(2); - expect( - componentSubscriptionContainer.subscribers.has(dummyObserver1) - ).toBeTruthy(); - expect( - componentSubscriptionContainer.subscribers.has(dummyObserver2) - ).toBeTruthy(); + expect(componentSubscriptionContainer.ready).toBeFalsy(); expect(subController.componentSubs.size).toBe(1); expect( subController.componentSubs.has(componentSubscriptionContainer) ).toBeTruthy(); - expect(dummyIntegration.componentSubscriptionContainers.length).toBe(1); - expect(dummyIntegration.componentSubscriptionContainers[0]).toBe( - componentSubscriptionContainer - ); - expect(dummyIntegration.componentSubscriptionContainer).toBeUndefined(); - }); - - it("should return not ready componentSubscriptionContainer if componentInstance isn't mounted (waitForMount = true)", () => { - const dummyIntegration: any = { - dummy: 'integration', - }; - - const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { waitForMount: true } - ); - - expect(componentSubscriptionContainer).toBeInstanceOf( - ComponentSubscriptionContainer - ); - expect(componentSubscriptionContainer.component).toStrictEqual( - dummyIntegration - ); - expect(componentSubscriptionContainer.ready).toBeFalsy(); - + // Check if ComponentSubscriptionContainer was called with correct parameters + expect(componentSubscriptionContainer.key).toBe('generatedKey'); + expect(componentSubscriptionContainer.componentId).toBeUndefined(); expect(componentSubscriptionContainer.subscribers.size).toBe(2); expect( componentSubscriptionContainer.subscribers.has(dummyObserver1) @@ -474,14 +447,10 @@ describe('SubController Tests', () => { expect( componentSubscriptionContainer.subscribers.has(dummyObserver2) ).toBeTruthy(); - - expect(subController.componentSubs.size).toBe(1); - expect( - subController.componentSubs.has(componentSubscriptionContainer) - ).toBeTruthy(); }); it('should return ready componentSubscriptionContainer if componentInstance is mounted (config.waitForMount = true)', () => { + jest.spyOn(Utils, 'generateId').mockReturnValueOnce('generatedKey'); const dummyIntegration: any = { dummy: 'integration', }; @@ -501,6 +470,14 @@ describe('SubController Tests', () => { ); expect(componentSubscriptionContainer.ready).toBeTruthy(); + expect(subController.componentSubs.size).toBe(1); + expect( + subController.componentSubs.has(componentSubscriptionContainer) + ).toBeTruthy(); + + // Check if ComponentSubscriptionContainer was called with correct parameters + expect(componentSubscriptionContainer.key).toBe('generatedKey'); + expect(componentSubscriptionContainer.componentId).toBeUndefined(); expect(componentSubscriptionContainer.subscribers.size).toBe(2); expect( componentSubscriptionContainer.subscribers.has(dummyObserver1) @@ -508,17 +485,12 @@ describe('SubController Tests', () => { expect( componentSubscriptionContainer.subscribers.has(dummyObserver2) ).toBeTruthy(); - - expect(subController.componentSubs.size).toBe(1); - expect( - subController.componentSubs.has(componentSubscriptionContainer) - ).toBeTruthy(); }); }); describe('registerCallbackSubscription function tests', () => { it('should return callbackSubscriptionContainer (default config)', () => { - jest.spyOn(Utils, 'generateId').mockReturnValueOnce('randomKey'); + jest.spyOn(Utils, 'generateId').mockReturnValueOnce('generatedKey'); const dummyIntegration = () => { /* empty function */ }; @@ -534,16 +506,19 @@ describe('SubController Tests', () => { expect(callbackSubscriptionContainer.callback).toBe(dummyIntegration); expect(callbackSubscriptionContainer.ready).toBeTruthy(); + expect(subController.callbackSubs.size).toBe(1); + expect( + subController.callbackSubs.has(callbackSubscriptionContainer) + ).toBeTruthy(); + // TODO find a way to spy on a class constructor without overwriting it // https://stackoverflow.com/questions/48219267/how-to-spy-on-a-class-constructor-jest/48486214 // Because the below tests are not really related to this test, - // they are checking if the CallbackSubscriptionContainer got called with the right parameters + // they are checking if the CallbackSubscriptionContainer was called with the correct parameters // by checking if CallbackSubscriptionContainer has set its properties correctly // Note:This 'issue' happens in multiple parts of the AgileTs test - expect(callbackSubscriptionContainer.key).toBe('randomKey'); - expect(callbackSubscriptionContainer.proxyKeyMap).toStrictEqual({}); - expect(callbackSubscriptionContainer.isProxyBased).toBeFalsy(); - + expect(callbackSubscriptionContainer.key).toBe('generatedKey'); + expect(callbackSubscriptionContainer.componentId).toBeUndefined(); expect(callbackSubscriptionContainer.subscribers.size).toBe(2); expect( callbackSubscriptionContainer.subscribers.has(dummyObserver1) @@ -551,11 +526,6 @@ describe('SubController Tests', () => { expect( callbackSubscriptionContainer.subscribers.has(dummyObserver2) ).toBeTruthy(); - - expect(subController.callbackSubs.size).toBe(1); - expect( - subController.callbackSubs.has(callbackSubscriptionContainer) - ).toBeTruthy(); }); it('should return callbackSubscriptionContainer (specific config)', () => { @@ -568,8 +538,8 @@ describe('SubController Tests', () => { [dummyObserver1, dummyObserver2], { waitForMount: false, - proxyKeyMap: { jeff: { paths: [[]] } }, - key: 'jeff', + componentId: 'testID', + key: 'dummyKey', } ); @@ -578,12 +548,15 @@ describe('SubController Tests', () => { ); expect(callbackSubscriptionContainer.callback).toBe(dummyIntegration); expect(callbackSubscriptionContainer.ready).toBeTruthy(); - expect(callbackSubscriptionContainer.key).toBe('jeff'); - expect(callbackSubscriptionContainer.proxyKeyMap).toStrictEqual({ - jeff: { paths: [[]] }, - }); - expect(callbackSubscriptionContainer.isProxyBased).toBeTruthy(); + expect(subController.callbackSubs.size).toBe(1); + expect( + subController.callbackSubs.has(callbackSubscriptionContainer) + ).toBeTruthy(); + + // Check if CallbackSubscriptionContainer was called with correct parameters + expect(callbackSubscriptionContainer.key).toBe('dummyKey'); + expect(callbackSubscriptionContainer.componentId).toBe('testID'); expect(callbackSubscriptionContainer.subscribers.size).toBe(2); expect( callbackSubscriptionContainer.subscribers.has(dummyObserver1) @@ -591,11 +564,6 @@ describe('SubController Tests', () => { expect( callbackSubscriptionContainer.subscribers.has(dummyObserver2) ).toBeTruthy(); - - expect(subController.callbackSubs.size).toBe(1); - expect( - subController.callbackSubs.has(callbackSubscriptionContainer) - ).toBeTruthy(); }); }); From 6c698a9e608e8bc13a0c79769fb40780170e6781 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 9 Jun 2021 20:28:07 +0200 Subject: [PATCH 060/117] split the updateSubscription method monster into smaller peaces for better testability --- packages/core/src/runtime/index.ts | 61 ++- .../core/tests/unit/runtime/runtime.test.ts | 374 +----------------- 2 files changed, 57 insertions(+), 378 deletions(-) diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index 91846493..2fc93266 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -152,10 +152,6 @@ export class Runtime { ) return false; - // Subscription Containers that have to be updated. - // Using a 'Set()' to combine several equal SubscriptionContainers into one (rerender optimisation). - const subscriptionsToUpdate = new Set(); - // Build final 'jobsToRerender' array // based on the new 'jobsToRerender' array and the 'notReadyJobsToRerender' array. const jobsToRerender = this.jobsToRerender.concat( @@ -164,10 +160,39 @@ export class Runtime { this.notReadyJobsToRerender = new Set(); this.jobsToRerender = []; + // Extract Subscription Container from the Jobs to be rerendered + const subscriptionContainerToUpdate = this.extractToUpdateSubscriptionContainer( + jobsToRerender + ); + if (subscriptionContainerToUpdate.length <= 0) return false; + + // Update Subscription Container (trigger rerender on Components they represent) + this.updateSubscriptionContainer(subscriptionContainerToUpdate); + + return true; + } + + /** + * Extracts the Subscription Containers + * that should be updated from the provided Runtime Jobs. + * + * @internal + * @param jobs - Jobs from which to extract the Subscription Containers to be updated. + */ + public extractToUpdateSubscriptionContainer( + jobs: Array + ): Array { + // Subscription Containers that have to be updated. + // Using a 'Set()' to combine several equal SubscriptionContainers into one (rerender optimisation). + const subscriptionsToUpdate = new Set(); + // Check if Job Subscription Container of Jobs should be updated // and if so add it to the 'subscriptionsToUpdate' array - jobsToRerender.forEach((job) => { + jobs.forEach((job) => { job.subscriptionContainersToUpdate.forEach((subscriptionContainer) => { + let updateSubscriptionContainer = true; + + // Handle not ready Subscription Container if (!subscriptionContainer.ready) { if ( !job.config.numberOfTriesToUpdate || @@ -191,14 +216,11 @@ export class Runtime { return; } - let updateSubscriptionContainer; - // Handle Selectors of Subscription Container // (-> check if a selected part of the Observer value has changed) - updateSubscriptionContainer = this.handleSelectors( - subscriptionContainer, - job - ); + updateSubscriptionContainer = + updateSubscriptionContainer && + this.handleSelectors(subscriptionContainer, job); // Check if Subscription Container with same 'componentId' // is already in the 'subscriptionToUpdate' queue (rerender optimisation) @@ -218,8 +240,21 @@ export class Runtime { }); }); - if (subscriptionsToUpdate.size <= 0) return false; + return Array.from(subscriptionsToUpdate); + } + /** + * Updates the specified Subscription Container. + * + * By updating the SubscriptionContainer a rerender is triggered + * on the Component it represents. + * + * @internal + * @param subscriptionsToUpdate - Subscription Containers to be updated. + */ + public updateSubscriptionContainer( + subscriptionsToUpdate: Array + ): void { // Update Subscription Containers (trigger rerender on Components they represent) subscriptionsToUpdate.forEach((subscriptionContainer) => { // Call 'callback function' if Callback based Subscription @@ -239,8 +274,6 @@ export class Runtime { Agile.logger.if .tag(['runtime']) .info(LogCodeManager.getLog('16:01:02'), subscriptionsToUpdate); - - return true; } /** diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts index a4fa90ec..03b28ee5 100644 --- a/packages/core/tests/unit/runtime/runtime.test.ts +++ b/packages/core/tests/unit/runtime/runtime.test.ts @@ -153,370 +153,16 @@ describe('Runtime Tests', () => { }); }); - describe('updateSubscribers function tests', () => { - let dummyObserver4: Observer; - let rCallbackSubJob: RuntimeJob; - let nrArCallbackSubJob: RuntimeJob; - let rComponentSubJob: RuntimeJob; - let nrArComponentSubJob: RuntimeJob; - let rCallbackSubContainer: CallbackSubscriptionContainer; - const rCallbackSubContainerCallbackFunction = () => { - /* empty function */ - }; - let nrCallbackSubContainer: CallbackSubscriptionContainer; - const nrCallbackSubContainerCallbackFunction = () => { - /* empty function */ - }; - let rComponentSubContainer: ComponentSubscriptionContainer; - const rComponentSubContainerComponent = { - my: 'cool component', - }; - let nrComponentSubContainer: ComponentSubscriptionContainer; - const nrComponentSubContainerComponent = { - my: 'second cool component', - }; - const dummyProxyKeyMap = { myState: { paths: [['a', 'b']] } }; - - beforeEach(() => { - dummyAgile.integrate(testIntegration); - dummyObserver4 = new Observer(dummyAgile, { key: 'dummyObserver4' }); - - dummyObserver1.value = 'dummyObserverValue1'; - dummyObserver2.value = 'dummyObserverValue2'; - dummyObserver3.value = 'dummyObserverValue3'; - dummyObserver4.value = 'dummyObserverValue4'; - - // Create Ready Callback Subscription - rCallbackSubContainer = dummyAgile.subController.subscribeWithSubsArray( - rCallbackSubContainerCallbackFunction, - [dummyObserver1, dummyObserver2] - ) as CallbackSubscriptionContainer; - rCallbackSubContainer.callback = jest.fn(); - rCallbackSubContainer.ready = true; - rCallbackSubContainer.key = 'rCallbackSubContainerKey'; - - // Create Not Ready Callback Subscription - nrCallbackSubContainer = dummyAgile.subController.subscribeWithSubsArray( - nrCallbackSubContainerCallbackFunction, - [dummyObserver2] - ) as CallbackSubscriptionContainer; - nrCallbackSubContainer.callback = jest.fn(); - nrCallbackSubContainer.ready = false; - nrCallbackSubContainer.key = 'nrCallbackSubContainerKey'; - - // Create Ready Component Subscription - rComponentSubContainer = dummyAgile.subController.subscribeWithSubsObject( - rComponentSubContainerComponent, - { - observer3: dummyObserver3, - observer4: dummyObserver4, - } - ).subscriptionContainer as ComponentSubscriptionContainer; - rComponentSubContainer.ready = true; - rComponentSubContainer.key = 'rComponentSubContainerKey'; - - // Create Not Ready Component Subscription - nrComponentSubContainer = dummyAgile.subController.subscribeWithSubsObject( - nrComponentSubContainerComponent, - { - observer4: dummyObserver4, - } - ).subscriptionContainer as ComponentSubscriptionContainer; - nrComponentSubContainer.ready = false; - nrComponentSubContainer.key = 'nrComponentSubContainerKey'; - - rComponentSubJob = new RuntimeJob(dummyObserver3, { key: 'dummyJob3' }); // Job with ready Component Subscription - rCallbackSubJob = new RuntimeJob(dummyObserver1, { key: 'dummyJob1' }); // Job with ready CallbackSubscription - nrArComponentSubJob = new RuntimeJob(dummyObserver4, { - key: 'dummyJob4', - }); // Job with not ready and ready Component Subscription - nrArCallbackSubJob = new RuntimeJob(dummyObserver2, { - key: 'dummyJob2', - }); // Job with not ready and ready Callback Subscription - - jest.spyOn(dummyAgile.integrations, 'update'); - jest.spyOn(runtime, 'handleObjectBasedSubscription'); - jest.spyOn(runtime, 'handleSelectors'); - }); - - it('should return false if agile has no integration', () => { - dummyAgile.hasIntegration = jest.fn(() => false); - runtime.jobsToRerender.push(rCallbackSubJob); - runtime.jobsToRerender.push(nrArCallbackSubJob); - - const response = runtime.updateSubscribers(); - - expect(response).toBeFalsy(); - expect(runtime.jobsToRerender).toStrictEqual([]); - expect(runtime.notReadyJobsToRerender.size).toBe(0); - expect(dummyAgile.integrations.update).not.toHaveBeenCalled(); - expect(rCallbackSubContainer.callback).not.toHaveBeenCalled(); - expect(nrCallbackSubContainer.callback).not.toHaveBeenCalled(); - }); - - it('should return false if no Jobs in jobsToRerender and notReadyJobsToRerender left', () => { - dummyAgile.hasIntegration = jest.fn(() => true); - runtime.jobsToRerender = []; - runtime.notReadyJobsToRerender = new Set(); - - const response = runtime.updateSubscribers(); - - expect(response).toBeFalsy(); - }); - - it('should update ready component based SubscriptionContainer', () => { - dummyAgile.hasIntegration = jest.fn(() => true); - runtime.jobsToRerender.push(rComponentSubJob); - - const response = runtime.updateSubscribers(); - - expect(runtime.jobsToRerender).toStrictEqual([]); - expect(runtime.notReadyJobsToRerender.size).toBe(0); - expect(runtime.handleSelectors).not.toHaveBeenCalled(); - - expect(dummyAgile.integrations.update).toHaveBeenCalledTimes(1); - expect(dummyAgile.integrations.update).toHaveBeenCalledWith( - rComponentSubContainerComponent, - { - observer3: 'dummyObserverValue3', - } - ); - expect(runtime.handleObjectBasedSubscription).toHaveBeenCalledWith( - rComponentSubContainer, - rComponentSubJob - ); - expect(rComponentSubJob.subscriptionContainersToUpdate.size).toBe(0); - expect(dummyObserver3.subscribedTo.size).toBe(1); - - expect(response).toBeTruthy(); - }); - - it('should update ready callback based SubscriptionContainer', () => { - dummyAgile.hasIntegration = jest.fn(() => true); - runtime.jobsToRerender.push(rCallbackSubJob); - - const response = runtime.updateSubscribers(); - - expect(runtime.jobsToRerender).toStrictEqual([]); - expect(runtime.notReadyJobsToRerender.size).toBe(0); - expect(runtime.handleSelectors).not.toHaveBeenCalled(); - - expect(rCallbackSubContainer.callback).toHaveBeenCalledTimes(1); - expect(rCallbackSubJob.subscriptionContainersToUpdate.size).toBe(0); - expect(dummyObserver1.subscribedTo.size).toBe(1); - - expect(response).toBeTruthy(); - }); - - it('should update ready proxy, callback based SubscriptionContainer if handleProxyBasedSubscriptions() returns true', () => { - jest.spyOn(runtime, 'handleSelectors').mockReturnValueOnce(true); - dummyAgile.hasIntegration = jest.fn(() => true); - rCallbackSubContainer.isProxyBased = true; - rCallbackSubContainer.proxyKeyMap = dummyProxyKeyMap; - runtime.jobsToRerender.push(rCallbackSubJob); - - const response = runtime.updateSubscribers(); - - expect(runtime.jobsToRerender).toStrictEqual([]); - expect(runtime.notReadyJobsToRerender.size).toBe(0); - expect(runtime.handleSelectors).toHaveBeenCalledWith( - rCallbackSubContainer, - rCallbackSubJob - ); - - expect(rCallbackSubContainer.callback).toHaveBeenCalledTimes(1); - expect(rCallbackSubJob.subscriptionContainersToUpdate.size).toBe(0); - expect(dummyObserver1.subscribedTo.size).toBe(1); - - expect(response).toBeTruthy(); - }); - - it("shouldn't update ready proxy, callback based SubscriptionContainer if handleProxyBasedSubscriptions() returns false", () => { - jest.spyOn(runtime, 'handleSelectors').mockReturnValueOnce(false); - dummyAgile.hasIntegration = jest.fn(() => true); - rCallbackSubContainer.isProxyBased = true; - rCallbackSubContainer.proxyKeyMap = dummyProxyKeyMap; - runtime.jobsToRerender.push(rCallbackSubJob); - - const response = runtime.updateSubscribers(); - - expect(runtime.jobsToRerender).toStrictEqual([]); - expect(runtime.notReadyJobsToRerender.size).toBe(0); - expect(runtime.handleSelectors).toHaveBeenCalledWith( - rCallbackSubContainer, - rCallbackSubJob - ); - - expect(rCallbackSubContainer.callback).not.toHaveBeenCalled(); - expect(rCallbackSubJob.subscriptionContainersToUpdate.size).toBe(0); - expect(dummyObserver1.subscribedTo.size).toBe(1); - - expect(response).toBeFalsy(); - }); - - it("shouldn't update not ready SubscriptionContainers but it should update ready SubscriptionContainers", () => { - dummyAgile.hasIntegration = jest.fn(() => true); - runtime.jobsToRerender.push(nrArCallbackSubJob); - runtime.jobsToRerender.push(nrArComponentSubJob); - - const response = runtime.updateSubscribers(); - - expect(runtime.jobsToRerender).toStrictEqual([]); - expect(runtime.notReadyJobsToRerender.size).toBe(2); - expect( - runtime.notReadyJobsToRerender.has(nrArCallbackSubJob) - ).toBeTruthy(); - expect( - runtime.notReadyJobsToRerender.has(nrArComponentSubJob) - ).toBeTruthy(); - - expect(nrArCallbackSubJob.subscriptionContainersToUpdate.size).toBe(1); - expect( - nrArCallbackSubJob.subscriptionContainersToUpdate.has( - nrCallbackSubContainer - ) - ).toBeTruthy(); - expect(nrArComponentSubJob.subscriptionContainersToUpdate.size).toBe(1); - expect( - nrArComponentSubJob.subscriptionContainersToUpdate.has( - nrComponentSubContainer - ) - ).toBeTruthy(); - - expect(rCallbackSubContainer.callback).toHaveBeenCalledTimes(1); - expect(nrCallbackSubContainer.callback).not.toHaveBeenCalled(); - - expect(dummyAgile.integrations.update).toHaveBeenCalledTimes(1); - expect(dummyAgile.integrations.update).toHaveBeenCalledWith( - rComponentSubContainerComponent, - { - observer4: 'dummyObserverValue4', - } - ); - expect(dummyAgile.integrations.update).not.toHaveBeenCalledWith( - nrComponentSubContainerComponent, - { - observer4: 'dummyObserverValue4', - } - ); - - expect(dummyObserver2.subscribedTo.size).toBe(2); - expect(dummyObserver4.subscribedTo.size).toBe(2); - - expect(runtime.handleObjectBasedSubscription).toHaveBeenCalledWith( - rComponentSubContainer, - nrArComponentSubJob - ); - expect(runtime.handleObjectBasedSubscription).not.toHaveBeenCalledWith( - nrComponentSubContainer, - nrArComponentSubJob - ); - - expect(nrArComponentSubJob.triesToUpdate).toBe(1); - expect(nrArCallbackSubJob.triesToUpdate).toBe(1); - - LogMock.hasLoggedCode( - '16:02:00', - [nrCallbackSubContainer.key], - nrCallbackSubContainer - ); - LogMock.hasLoggedCode( - '16:02:00', - [nrComponentSubContainer.key], - nrComponentSubContainer - ); - - expect(response).toBeTruthy(); // because 2 SubscriptionContainer were ready - }); - - it('should try to update in the past not ready SubscriptionContainers from the notReadyJobsToUpdate queue', () => { - dummyAgile.hasIntegration = jest.fn(() => true); - runtime.notReadyJobsToRerender.add(rCallbackSubJob); - - const response = runtime.updateSubscribers(); - - expect(runtime.jobsToRerender).toStrictEqual([]); - expect(runtime.notReadyJobsToRerender.size).toBe(0); - - expect(rCallbackSubContainer.callback).toHaveBeenCalled(); - expect(rCallbackSubJob.subscriptionContainersToUpdate.size).toBe(0); - expect(dummyObserver1.subscribedTo.size).toBe(1); + describe('updateSubscriptions function tests', () => { + // TODO + }); - expect(response).toBeTruthy(); - }); + describe('extractToUpdateSubscriptionContainer function tests', () => { + // TODO + }); - it( - "shouldn't update not ready SubscriptionContainers from the notReadyJobsToUpdate queue " + - 'and completely remove them from the runtime when it exceeded numberOfTriesToUpdate', - () => { - dummyAgile.hasIntegration = jest.fn(() => true); - rCallbackSubJob.config.numberOfTriesToUpdate = 2; - rCallbackSubJob.triesToUpdate = 2; - rCallbackSubContainer.ready = false; - runtime.notReadyJobsToRerender.add(rCallbackSubJob); - - const response = runtime.updateSubscribers(); - - expect(runtime.jobsToRerender).toStrictEqual([]); - expect(runtime.notReadyJobsToRerender.size).toBe(0); - - expect(rCallbackSubContainer.callback).not.toHaveBeenCalled(); - expect(rCallbackSubJob.subscriptionContainersToUpdate.size).toBe(1); - expect( - rCallbackSubJob.subscriptionContainersToUpdate.has( - rCallbackSubContainer - ) - ).toBeTruthy(); - expect(dummyObserver1.subscribedTo.size).toBe(1); - expect(rCallbackSubJob.triesToUpdate).toBe(2); - - LogMock.hasLoggedCode( - '16:02:01', - [rCallbackSubJob.config.numberOfTriesToUpdate], - rCallbackSubContainer - ); - - expect(response).toBeFalsy(); - } - ); - - it( - "shouldn't update not ready SubscriptionContainer from the notReadyJobsToUpdate queue " + - 'and add it again to the notReadyJobsToUpdate queue if numberOfTriesToUpdate is null', - () => { - dummyAgile.hasIntegration = jest.fn(() => true); - rCallbackSubJob.config.numberOfTriesToUpdate = null; - rCallbackSubJob.triesToUpdate = 2; - rCallbackSubContainer.ready = false; - runtime.notReadyJobsToRerender.add(rCallbackSubJob); - - const response = runtime.updateSubscribers(); - - expect(runtime.jobsToRerender).toStrictEqual([]); - expect(runtime.notReadyJobsToRerender.size).toBe(1); - expect( - runtime.notReadyJobsToRerender.has(rCallbackSubJob) - ).toBeTruthy(); - - expect(rCallbackSubContainer.callback).not.toHaveBeenCalled(); - expect(rCallbackSubJob.subscriptionContainersToUpdate.size).toBe(1); - expect( - rCallbackSubJob.subscriptionContainersToUpdate.has( - rCallbackSubContainer - ) - ).toBeTruthy(); - expect(dummyObserver1.subscribedTo.size).toBe(1); - expect(rCallbackSubJob.triesToUpdate).toBe(3); - - LogMock.hasLoggedCode( - '16:02:00', - [rCallbackSubContainer.key], - rCallbackSubContainer - ); - - expect(response).toBeFalsy(); - } - ); + describe('updateSubscriptionContainer function tests', () => { + // TODO }); describe('getUpdatedObserverValues function tests', () => { @@ -526,7 +172,7 @@ describe('Runtime Tests', () => { }; beforeEach(() => { - subscriptionContainer = dummyAgile.subController.subscribeWithSubsObject( + subscriptionContainer = dummyAgile.subController.subscribe( dummyFunction, { observer1: dummyObserver1, @@ -569,7 +215,7 @@ describe('Runtime Tests', () => { beforeEach(() => { // Create Job with Object value - objectSubscriptionContainer = dummyAgile.subController.subscribeWithSubsObject( + objectSubscriptionContainer = dummyAgile.subController.subscribe( dummyFunction, { observer1: dummyObserver1 } ).subscriptionContainer; From b456ea950eda4ec8430ce98fd19d96adc63d6a96 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 10 Jun 2021 06:51:53 +0200 Subject: [PATCH 061/117] optimized runtime tests --- packages/core/src/runtime/index.ts | 22 +- .../core/tests/unit/runtime/runtime.test.ts | 348 ++++++++++++------ 2 files changed, 235 insertions(+), 135 deletions(-) diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index 2fc93266..21fbf74f 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -136,22 +136,11 @@ export class Runtime { * the Subscription Container (subscribed Component) * of each Job Observer. * - * It returns a boolean indicating whether any Subscription Container was updated. + * It returns a boolean indicating whether any Subscription Container was updated or not. * * @internal */ public updateSubscribers(): boolean { - if (!this.agileInstance().hasIntegration()) { - this.jobsToRerender = []; - this.notReadyJobsToRerender = new Set(); - return false; - } - if ( - this.jobsToRerender.length <= 0 && - this.notReadyJobsToRerender.size <= 0 - ) - return false; - // Build final 'jobsToRerender' array // based on the new 'jobsToRerender' array and the 'notReadyJobsToRerender' array. const jobsToRerender = this.jobsToRerender.concat( @@ -160,6 +149,9 @@ export class Runtime { this.notReadyJobsToRerender = new Set(); this.jobsToRerender = []; + if (!this.agileInstance().hasIntegration() || jobsToRerender.length <= 0) + return false; + // Extract Subscription Container from the Jobs to be rerendered const subscriptionContainerToUpdate = this.extractToUpdateSubscriptionContainer( jobsToRerender @@ -277,7 +269,7 @@ export class Runtime { } /** - * Maps the values of updated Observers (`updatedSubscribers`) + * Maps the values of the updated Observers (`updatedSubscribers`) * of the specified Subscription Container into a key map. * * The key containing the Observer value is extracted from the Observer itself @@ -293,7 +285,7 @@ export class Runtime { for (const observer of subscriptionContainer.updatedSubscribers) { const key = subscriptionContainer.subscriberKeysWeakMap.get(observer) ?? - subscriptionContainer.key; + observer.key; if (key != null) props[key] = observer.value; } return props; @@ -324,7 +316,7 @@ export class Runtime { // no matter what was updated in the Observer if (selectorMethods == null) return true; - // Check if a selected part of Observer value has changed + // Check if a selected part of the Observer value has changed const previousValue = job.observer.previousValue; const newValue = job.observer.value; for (const selectorMethod of selectorMethods) { diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts index 03b28ee5..850ebc4a 100644 --- a/packages/core/tests/unit/runtime/runtime.test.ts +++ b/packages/core/tests/unit/runtime/runtime.test.ts @@ -52,14 +52,35 @@ describe('Runtime Tests', () => { runtime.perform = jest.fn(); }); - it('should perform passed Job (default config)', () => { + it("should perform specified Job immediately if jobQueue isn't currently being processed (default config)", () => { + runtime.isPerformingJobs = false; + + runtime.ingest(dummyJob); + + expect(runtime.jobQueue.length).toBe(0); + expect(runtime.perform).toHaveBeenCalledWith(dummyJob); + }); + + it("shouldn't perform specified Job immediately if jobQueue is currently being processed (default config)", () => { + runtime.isPerformingJobs = true; + runtime.ingest(dummyJob); + expect(runtime.jobQueue.length).toBe(1); + expect(runtime.jobQueue[0]).toBe(dummyJob); + expect(runtime.perform).not.toHaveBeenCalled(); + }); + + it('should perform specified Job immediately (config.perform = true)', () => { + runtime.isPerformingJobs = true; + runtime.ingest(dummyJob, { perform: true }); + expect(runtime.jobQueue.length).toBe(0); expect(runtime.perform).toHaveBeenCalledWith(dummyJob); }); - it("shouldn't perform passed Job (config.perform = false)", () => { + it("shouldn't perform specified Job immediately (config.perform = false)", () => { + runtime.isPerformingJobs = false; runtime.ingest(dummyJob, { perform: false }); expect(runtime.jobQueue.length).toBe(1); @@ -88,32 +109,36 @@ describe('Runtime Tests', () => { dummyObserver2.ingest = jest.fn(); }); - it('should perform passed and all in jobQueue remaining Jobs and call updateSubscribers', async () => { - runtime.jobQueue.push(dummyJob2); - runtime.jobQueue.push(dummyJob3); + it( + 'should perform specified Job and all remaining Jobs in the jobQueue,' + + ' and call updateSubscribers if at least one performed Job needs to rerender', + async () => { + runtime.jobQueue.push(dummyJob2); + runtime.jobQueue.push(dummyJob3); - runtime.perform(dummyJob1); + runtime.perform(dummyJob1); - expect(dummyObserver1.perform).toHaveBeenCalledWith(dummyJob1); - expect(dummyJob1.performed).toBeTruthy(); - expect(dummyObserver2.perform).toHaveBeenCalledWith(dummyJob2); - expect(dummyJob2.performed).toBeTruthy(); - expect(dummyObserver1.perform).toHaveBeenCalledWith(dummyJob3); - expect(dummyJob3.performed).toBeTruthy(); + expect(dummyObserver1.perform).toHaveBeenCalledWith(dummyJob1); + expect(dummyJob1.performed).toBeTruthy(); + expect(dummyObserver2.perform).toHaveBeenCalledWith(dummyJob2); + expect(dummyJob2.performed).toBeTruthy(); + expect(dummyObserver1.perform).toHaveBeenCalledWith(dummyJob3); + expect(dummyJob3.performed).toBeTruthy(); - expect(runtime.jobQueue.length).toBe(0); - expect(runtime.jobsToRerender.length).toBe(2); - expect(runtime.jobsToRerender.includes(dummyJob1)).toBeTruthy(); - expect(runtime.jobsToRerender.includes(dummyJob2)).toBeTruthy(); - expect(runtime.jobsToRerender.includes(dummyJob3)).toBeFalsy(); + expect(runtime.jobQueue.length).toBe(0); + expect(runtime.jobsToRerender.length).toBe(2); + expect(runtime.jobsToRerender.includes(dummyJob1)).toBeTruthy(); + expect(runtime.jobsToRerender.includes(dummyJob2)).toBeTruthy(); + expect(runtime.jobsToRerender.includes(dummyJob3)).toBeFalsy(); - // Sleep 5ms because updateSubscribers get called in Timeout - await new Promise((resolve) => setTimeout(resolve, 5)); + // Sleep 5ms because updateSubscribers is called in a timeout + await new Promise((resolve) => setTimeout(resolve, 5)); - expect(runtime.updateSubscribers).toHaveBeenCalledTimes(1); - }); + expect(runtime.updateSubscribers).toHaveBeenCalledTimes(1); + } + ); - it('should perform passed Job and update it dependents', async () => { + it('should perform specified Job and ingest its dependents into the runtime', async () => { dummyJob1.observer.dependents.add(dummyObserver2); dummyJob1.observer.dependents.add(dummyObserver1); @@ -132,29 +157,133 @@ describe('Runtime Tests', () => { expect(dummyObserver2.ingest).toHaveBeenCalledTimes(1); }); - it("should perform passed and all in jobQueue remaining Jobs and shouldn't call updateSubscribes if no job needs to rerender", async () => { - dummyJob1.rerender = false; - runtime.jobQueue.push(dummyJob3); + it( + 'should perform specified Job and all remaining Jobs in the jobQueue' + + " and shouldn't call updateSubscribes if no performed Job needs to rerender", + async () => { + dummyJob1.rerender = false; + runtime.jobQueue.push(dummyJob3); - runtime.perform(dummyJob1); + runtime.perform(dummyJob1); - expect(dummyObserver1.perform).toHaveBeenCalledWith(dummyJob1); - expect(dummyJob1.performed).toBeTruthy(); - expect(dummyObserver1.perform).toHaveBeenCalledWith(dummyJob3); - expect(dummyJob3.performed).toBeTruthy(); + expect(dummyObserver1.perform).toHaveBeenCalledWith(dummyJob1); + expect(dummyJob1.performed).toBeTruthy(); + expect(dummyObserver1.perform).toHaveBeenCalledWith(dummyJob3); + expect(dummyJob3.performed).toBeTruthy(); - expect(runtime.jobQueue.length).toBe(0); - expect(runtime.jobsToRerender.length).toBe(0); + expect(runtime.jobQueue.length).toBe(0); + expect(runtime.jobsToRerender.length).toBe(0); - // Sleep 5ms because updateSubscribers get called in Timeout - await new Promise((resolve) => setTimeout(resolve, 5)); + // Sleep 5ms because updateSubscribers is called in a timeout + await new Promise((resolve) => setTimeout(resolve, 5)); - expect(runtime.updateSubscribers).not.toHaveBeenCalled(); - }); + expect(runtime.updateSubscribers).not.toHaveBeenCalled(); + } + ); }); - describe('updateSubscriptions function tests', () => { - // TODO + describe('updateSubscribers function tests', () => { + let dummyJob1: RuntimeJob; + let dummyJob2: RuntimeJob; + let dummyJob3: RuntimeJob; + const dummySubscriptionContainer1IntegrationInstance = () => { + /* empty function */ + }; + let dummySubscriptionContainer1: SubscriptionContainer; + const dummySubscriptionContainer2IntegrationInstance = { + my: 'cool component', + }; + let dummySubscriptionContainer2: SubscriptionContainer; + + beforeEach(() => { + dummySubscriptionContainer1 = dummyAgile.subController.subscribe( + dummySubscriptionContainer1IntegrationInstance, + [dummyObserver1] + ); + dummySubscriptionContainer2 = dummyAgile.subController.subscribe( + dummySubscriptionContainer2IntegrationInstance, + [dummyObserver2, dummyObserver3] + ); + + dummyJob1 = new RuntimeJob(dummyObserver1); + dummyJob2 = new RuntimeJob(dummyObserver2); + dummyJob3 = new RuntimeJob(dummyObserver3); + + runtime.updateSubscriptionContainer = jest.fn(); + jest.spyOn(runtime, 'extractToUpdateSubscriptionContainer'); + }); + + it('should return false if Agile has no registered Integration', () => { + dummyAgile.hasIntegration = jest.fn(() => false); + runtime.jobsToRerender.push(dummyJob1); + runtime.jobsToRerender.push(dummyJob2); + + const response = runtime.updateSubscribers(); + + expect(response).toBeFalsy(); + expect(runtime.jobsToRerender).toStrictEqual([]); + expect(runtime.notReadyJobsToRerender.size).toBe(0); + expect( + runtime.extractToUpdateSubscriptionContainer + ).not.toHaveBeenCalled(); + expect(runtime.updateSubscriptionContainer).not.toHaveBeenCalled(); + }); + + it('should return false if jobsToRerender and notReadyJobsToRerender queue is empty', () => { + dummyAgile.hasIntegration = jest.fn(() => true); + runtime.jobsToRerender = []; + runtime.notReadyJobsToRerender = new Set(); + + const response = runtime.updateSubscribers(); + + expect(response).toBeFalsy(); + }); + + it('should return false if no Subscription Container of the Jobs to rerender needs to update', () => { + dummyAgile.hasIntegration = jest.fn(() => true); + jest + .spyOn(runtime, 'extractToUpdateSubscriptionContainer') + .mockReturnValueOnce([]); + runtime.jobsToRerender.push(dummyJob1); + runtime.jobsToRerender.push(dummyJob2); + runtime.notReadyJobsToRerender.add(dummyJob3); + + const response = runtime.updateSubscribers(); + + expect(response).toBeFalsy(); + expect(runtime.jobsToRerender).toStrictEqual([]); + expect(runtime.notReadyJobsToRerender.size).toBe(0); + expect( + runtime.extractToUpdateSubscriptionContainer + ).toHaveBeenCalledWith([dummyJob1, dummyJob2, dummyJob3]); + expect(runtime.updateSubscriptionContainer).not.toHaveBeenCalled(); + }); + + it('should return true if at least one Subscription Container of the Jobs to rerender needs to update', () => { + dummyAgile.hasIntegration = jest.fn(() => true); + jest + .spyOn(runtime, 'extractToUpdateSubscriptionContainer') + .mockReturnValueOnce([ + dummySubscriptionContainer1, + dummySubscriptionContainer2, + ]); + runtime.jobsToRerender.push(dummyJob1); + runtime.jobsToRerender.push(dummyJob2); + runtime.notReadyJobsToRerender.add(dummyJob3); + + const response = runtime.updateSubscribers(); + + expect(response).toBeTruthy(); + expect(runtime.jobsToRerender).toStrictEqual([]); + expect(runtime.notReadyJobsToRerender.size).toBe(0); + expect( + runtime.extractToUpdateSubscriptionContainer + ).toHaveBeenCalledWith([dummyJob1, dummyJob2, dummyJob3]); + expect(runtime.updateSubscriptionContainer).toHaveBeenCalledWith([ + dummySubscriptionContainer1, + dummySubscriptionContainer2, + ]); + }); }); describe('extractToUpdateSubscriptionContainer function tests', () => { @@ -174,29 +303,41 @@ describe('Runtime Tests', () => { beforeEach(() => { subscriptionContainer = dummyAgile.subController.subscribe( dummyFunction, - { - observer1: dummyObserver1, - observer2: dummyObserver2, - observer3: dummyObserver3, - } - ).subscriptionContainer; + [dummyObserver1, dummyObserver2, dummyObserver3] + ); dummyObserver1.value = 'dummyObserverValue1'; dummyObserver3.value = 'dummyObserverValue3'; + + dummyObserver1._key = 'dummyObserver1KeyInObserver'; + dummyObserver2._key = undefined; + subscriptionContainer.subscriberKeysWeakMap.set( + dummyObserver2, + 'dummyObserver2KeyInWeakMap' + ); + dummyObserver3._key = 'dummyObserver3KeyInObserver'; + subscriptionContainer.subscriberKeysWeakMap.set( + dummyObserver3, + 'dummyObserver3KeyInWeakMap' + ); }); - it('should build Observer Value Object out of observerKeysToUpdate and Value of Observer', () => { - subscriptionContainer.updatedSubscribers.push('observer1'); - subscriptionContainer.updatedSubscribers.push('observer2'); - subscriptionContainer.updatedSubscribers.push('observer3'); + it('should map the values of the updated Observers into an object and return it', () => { + subscriptionContainer.updatedSubscribers.push(dummyObserver1); + subscriptionContainer.updatedSubscribers.push(dummyObserver2); + subscriptionContainer.updatedSubscribers.push(dummyObserver3); const props = runtime.getUpdatedObserverValues(subscriptionContainer); expect(props).toStrictEqual({ - observer1: 'dummyObserverValue1', - observer2: undefined, - observer3: 'dummyObserverValue3', + dummyObserver1KeyInObserver: 'dummyObserverValue1', + dummyObserver2KeyInWeakMap: undefined, + dummyObserver3KeyInWeakMap: 'dummyObserverValue3', }); - expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); + expect(subscriptionContainer.updatedSubscribers).toStrictEqual([ + dummyObserver1, + dummyObserver2, + dummyObserver3, + ]); }); }); @@ -214,38 +355,33 @@ describe('Runtime Tests', () => { let arrayJob: RuntimeJob; beforeEach(() => { - // Create Job with Object value + // Create Job with object based value objectSubscriptionContainer = dummyAgile.subController.subscribe( dummyFunction, - { observer1: dummyObserver1 } - ).subscriptionContainer; + [dummyObserver1] + ); dummyObserver1.value = { - key: 'dummyObserverValue1', data: { name: 'jeff' }, }; dummyObserver1.previousValue = { - key: 'dummyObserverValue1', data: { name: 'jeff' }, }; - objectSubscriptionContainer.isProxyBased = true; - objectSubscriptionContainer.proxyKeyMap = { - [dummyObserver1._key || 'unknown']: { paths: [['data', 'name']] }, - }; + objectSubscriptionContainer.selectorsWeakMap.set(dummyObserver1, { + methods: [(value) => value?.data?.name], + }); objectJob = new RuntimeJob(dummyObserver1, { key: 'dummyObjectJob1' }); - // Create Job with Array value - arraySubscriptionContainer = dummyAgile.subController.subscribeWithSubsObject( + // Create Job with array based value + arraySubscriptionContainer = dummyAgile.subController.subscribe( dummyFunction2, - { observer2: dummyObserver2 } + { dummyObserver2: dummyObserver2 } ).subscriptionContainer; dummyObserver2.value = [ { - key: 'dummyObserver2Value1', data: { name: 'jeff' }, }, { - key: 'dummyObserver2Value2', data: { name: 'hans' }, }, ]; @@ -259,23 +395,20 @@ describe('Runtime Tests', () => { data: { name: 'hans' }, }, ]; - arraySubscriptionContainer.isProxyBased = true; - arraySubscriptionContainer.proxyKeyMap = { - [dummyObserver2._key || 'unknown']: { - paths: [['0', 'data', 'name']], - }, - }; + arraySubscriptionContainer.selectorsWeakMap.set(dummyObserver2, { + methods: [(value) => value[0]?.data?.name], + }); arrayJob = new RuntimeJob(dummyObserver2, { key: 'dummyObjectJob2' }); jest.spyOn(Utils, 'notEqual'); - // Because not equals is called once during the creation of the subscriptionContainer + // Because not equals is called once during the creation of the Subscription Containers jest.clearAllMocks(); }); - it("should return true if subscriptionContainer isn't proxy based", () => { - objectSubscriptionContainer.isProxyBased = false; + it('should return true if Subscritpion Container has no selector methods', () => { + objectSubscriptionContainer.selectorsWeakMap.delete(dummyObserver1); const response = runtime.handleSelectors( objectSubscriptionContainer, @@ -286,33 +419,7 @@ describe('Runtime Tests', () => { expect(Utils.notEqual).not.toHaveBeenCalled(); }); - it('should return true if observer the job represents has no key', () => { - objectJob.observer._key = undefined; - - const response = runtime.handleSelectors( - objectSubscriptionContainer, - objectJob - ); - - expect(response).toBeTruthy(); - expect(Utils.notEqual).not.toHaveBeenCalled(); - }); - - it("should return true if the observer key isn't represented in the proxyKeyMap", () => { - objectSubscriptionContainer.proxyKeyMap = { - unknownKey: { paths: [['a', 'b']] }, - }; - - const response = runtime.handleSelectors( - objectSubscriptionContainer, - objectJob - ); - - expect(response).toBeTruthy(); - expect(Utils.notEqual).not.toHaveBeenCalled(); - }); - - it('should return true if used property has changed (object value)', () => { + it('should return true if selected property has changed (object value)', () => { dummyObserver1.value = { key: 'dummyObserverValue1', data: { name: 'hans' }, @@ -330,7 +437,7 @@ describe('Runtime Tests', () => { ); }); - it("should return false if used property hasn't changed (object value)", () => { + it("should return false if selected property hasn't changed (object value)", () => { const response = runtime.handleSelectors( objectSubscriptionContainer, objectJob @@ -343,23 +450,24 @@ describe('Runtime Tests', () => { ); }); - it('should return true if used property has changed in the deepness (object value)', () => { - dummyObserver1.value = { - key: 'dummyObserverValue1', - }; - dummyObserver1.previousValue = { - key: 'dummyObserverValue1', - data: { name: undefined }, - }; - - const response = runtime.handleSelectors( - objectSubscriptionContainer, - objectJob - ); - - expect(response).toBeTruthy(); - expect(Utils.notEqual).toHaveBeenCalledWith(undefined, undefined); - }); + // TODO the deepness check isn't possible with the custom defined selector methods + // it('should return true if selected property has changed in the deepness (object value)', () => { + // dummyObserver1.value = { + // key: 'dummyObserverValue1', + // }; + // dummyObserver1.previousValue = { + // key: 'dummyObserverValue1', + // data: { name: undefined }, + // }; + // + // const response = runtime.handleSelectors( + // objectSubscriptionContainer, + // objectJob + // ); + // + // expect(response).toBeTruthy(); + // expect(Utils.notEqual).toHaveBeenCalledWith(undefined, undefined); + // }); it('should return true if used property has changed (array value)', () => { dummyObserver2.value = [ From 7cf329fd1c838dae343d2259ff9e551dc3bdc562 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 10 Jun 2021 09:20:57 +0200 Subject: [PATCH 062/117] added basic extractToUpdateSubscriptionContainer tests --- packages/core/src/runtime/index.ts | 18 +- .../container/SubscriptionContainer.ts | 2 +- .../core/tests/unit/runtime/runtime.test.ts | 257 +++++++++++++++++- 3 files changed, 260 insertions(+), 17 deletions(-) diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index 21fbf74f..516b9001 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -214,17 +214,21 @@ export class Runtime { updateSubscriptionContainer && this.handleSelectors(subscriptionContainer, job); + // TODO has to be overthought because if it is a Component based Subscription + // the rerender is triggered via merging the changed properties into the Component. + // Although the 'componentId' might be equal, it doesn't mean + // that the changed properties are the equal! (-> changed properties would get missing) // Check if Subscription Container with same 'componentId' // is already in the 'subscriptionToUpdate' queue (rerender optimisation) - updateSubscriptionContainer = - updateSubscriptionContainer && - Array.from(subscriptionsToUpdate).findIndex( - (sc) => sc.componentId === subscriptionContainer.componentId - ) === -1; + // updateSubscriptionContainer = + // updateSubscriptionContainer && + // Array.from(subscriptionsToUpdate).findIndex( + // (sc) => sc.componentId === subscriptionContainer.componentId + // ) === -1; // Add Subscription Container to the 'subscriptionsToUpdate' queue if (updateSubscriptionContainer) { - subscriptionContainer.updatedSubscribers.push(job.observer); + subscriptionContainer.updatedSubscribers.add(job.observer); subscriptionsToUpdate.add(subscriptionContainer); } @@ -260,7 +264,7 @@ export class Runtime { this.getUpdatedObserverValues(subscriptionContainer) ); - subscriptionContainer.updatedSubscribers = []; + subscriptionContainer.updatedSubscribers.clear(); }); Agile.logger.if diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index e9f345bc..3e2ca5f9 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -39,7 +39,7 @@ export class SubscriptionContainer { * that were performed by the runtime * and are currently running through the update Subscription Container (rerender) process. */ - public updatedSubscribers: Array = []; + public updatedSubscribers: Set = new Set(); /** * Whether the Subscription Container is object based. diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts index 850ebc4a..dda6d98d 100644 --- a/packages/core/tests/unit/runtime/runtime.test.ts +++ b/packages/core/tests/unit/runtime/runtime.test.ts @@ -287,7 +287,248 @@ describe('Runtime Tests', () => { }); describe('extractToUpdateSubscriptionContainer function tests', () => { - // TODO + let dummyJob1: RuntimeJob; + let dummyJob2: RuntimeJob; + const dummySubscriptionContainer1IntegrationInstance = () => { + /* empty function */ + }; + let dummySubscriptionContainer1: SubscriptionContainer; + const dummySubscriptionContainer2IntegrationInstance = { + my: 'cool component', + }; + let dummySubscriptionContainer2: SubscriptionContainer; + + beforeEach(() => { + dummySubscriptionContainer1 = dummyAgile.subController.subscribe( + dummySubscriptionContainer1IntegrationInstance, + [dummyObserver1] + ); + dummySubscriptionContainer2 = dummyAgile.subController.subscribe( + dummySubscriptionContainer2IntegrationInstance, + [dummyObserver2] + ); + + dummyJob1 = new RuntimeJob(dummyObserver1); + dummyJob2 = new RuntimeJob(dummyObserver2); + + jest.spyOn(runtime, 'handleSelectors'); + }); + + it( + "shouldn't extract not ready Subscription Container from the specified Jobs, " + + "add it to the 'notReadyJobsToRerender' queue and print warning", + () => { + jest + .spyOn(runtime, 'handleSelectors') + .mockReturnValueOnce(true) + .mockReturnValueOnce(true); + dummySubscriptionContainer1.ready = true; + dummySubscriptionContainer2.ready = false; + + const response = runtime.extractToUpdateSubscriptionContainer([ + dummyJob1, + dummyJob2, + ]); + + expect(response).toStrictEqual([dummySubscriptionContainer1]); + + // Called with Job that ran through + expect(runtime.handleSelectors).toHaveBeenCalledTimes(1); + expect(runtime.handleSelectors).toHaveBeenCalledWith( + dummySubscriptionContainer1, + dummyJob1 + ); + + expect(Array.from(runtime.notReadyJobsToRerender)).toStrictEqual([ + dummyJob2, + ]); + + // Job that ran through + expect( + Array.from(dummyJob1.subscriptionContainersToUpdate) + ).toStrictEqual([]); + expect(dummyJob1.triesToUpdate).toBe(0); + expect( + Array.from(dummySubscriptionContainer1.updatedSubscribers) + ).toStrictEqual([dummyObserver1]); + + // Job that didn't ran through + expect( + Array.from(dummyJob2.subscriptionContainersToUpdate) + ).toStrictEqual([dummySubscriptionContainer2]); + expect(dummyJob2.triesToUpdate).toBe(1); + expect( + Array.from(dummySubscriptionContainer2.updatedSubscribers) + ).toStrictEqual([]); + + // Called with Job that didn't ran through + expect(console.warn).toHaveBeenCalledTimes(1); + LogMock.hasLoggedCode( + '16:02:00', + [dummySubscriptionContainer2.key], + dummySubscriptionContainer2 + ); + } + ); + + it( + "shouldn't extract not ready Subscription Container from the specified Jobs, " + + "remove the Job when it exceeded the max 'numberOfTriesToUpdate' " + + 'and print warning', + () => { + jest + .spyOn(runtime, 'handleSelectors') + .mockReturnValueOnce(true) + .mockReturnValueOnce(true); + dummySubscriptionContainer1.ready = true; + dummySubscriptionContainer2.ready = false; + const numberOfTries = + (dummyJob2.config.numberOfTriesToUpdate ?? 0) + 1; + dummyJob2.triesToUpdate = numberOfTries; + + const response = runtime.extractToUpdateSubscriptionContainer([ + dummyJob1, + dummyJob2, + ]); + + expect(response).toStrictEqual([dummySubscriptionContainer1]); + + // Called with Job that ran through + expect(runtime.handleSelectors).toHaveBeenCalledTimes(1); + expect(runtime.handleSelectors).toHaveBeenCalledWith( + dummySubscriptionContainer1, + dummyJob1 + ); + + expect(Array.from(runtime.notReadyJobsToRerender)).toStrictEqual([]); // Because not ready Job was removed + + // Job that ran through + expect( + Array.from(dummyJob1.subscriptionContainersToUpdate) + ).toStrictEqual([]); + expect(dummyJob1.triesToUpdate).toBe(0); + expect( + Array.from(dummySubscriptionContainer1.updatedSubscribers) + ).toStrictEqual([dummyObserver1]); + + // Job that didn't ran through + expect( + Array.from(dummyJob2.subscriptionContainersToUpdate) + ).toStrictEqual([dummySubscriptionContainer2]); + expect(dummyJob2.triesToUpdate).toBe(numberOfTries); + expect( + Array.from(dummySubscriptionContainer2.updatedSubscribers) + ).toStrictEqual([]); + + // Called with Job that didn't ran through + expect(console.warn).toHaveBeenCalledTimes(1); + LogMock.hasLoggedCode( + '16:02:01', + [numberOfTries], + dummySubscriptionContainer2 + ); + } + ); + + it("shouldn't extract Subscription Container if the selected property hasn't changed", () => { + jest + .spyOn(runtime, 'handleSelectors') + .mockReturnValueOnce(false) + .mockReturnValueOnce(true); + dummySubscriptionContainer1.ready = true; + dummySubscriptionContainer2.ready = true; + + const response = runtime.extractToUpdateSubscriptionContainer([ + dummyJob1, + dummyJob2, + ]); + + expect(response).toStrictEqual([dummySubscriptionContainer2]); + + expect(runtime.handleSelectors).toHaveBeenCalledTimes(2); + expect(runtime.handleSelectors).toHaveBeenCalledWith( + dummySubscriptionContainer1, + dummyJob1 + ); + expect(runtime.handleSelectors).toHaveBeenCalledWith( + dummySubscriptionContainer2, + dummyJob2 + ); + + // Since the Job is ready but the Observer value simply hasn't changed + expect(Array.from(runtime.notReadyJobsToRerender)).toStrictEqual([]); + + // Job that didn't ran through + expect( + Array.from(dummyJob1.subscriptionContainersToUpdate) + ).toStrictEqual([]); + expect(dummyJob1.triesToUpdate).toBe(0); + expect( + Array.from(dummySubscriptionContainer1.updatedSubscribers) + ).toStrictEqual([]); + + // Job that ran through + expect( + Array.from(dummyJob2.subscriptionContainersToUpdate) + ).toStrictEqual([]); + expect(dummyJob2.triesToUpdate).toBe(0); + expect( + Array.from(dummySubscriptionContainer2.updatedSubscribers) + ).toStrictEqual([dummyObserver2]); + + expect(console.warn).toHaveBeenCalledTimes(0); + }); + + it('should extract ready and updated Subscription Containers', () => { + jest + .spyOn(runtime, 'handleSelectors') + .mockReturnValueOnce(true) + .mockReturnValueOnce(true); + dummySubscriptionContainer1.ready = true; + dummySubscriptionContainer2.ready = true; + + const response = runtime.extractToUpdateSubscriptionContainer([ + dummyJob1, + dummyJob2, + ]); + + expect(response).toStrictEqual([ + dummySubscriptionContainer1, + dummySubscriptionContainer2, + ]); + + expect(runtime.handleSelectors).toHaveBeenCalledTimes(2); + expect(runtime.handleSelectors).toHaveBeenCalledWith( + dummySubscriptionContainer1, + dummyJob1 + ); + expect(runtime.handleSelectors).toHaveBeenCalledWith( + dummySubscriptionContainer2, + dummyJob2 + ); + + expect(Array.from(runtime.notReadyJobsToRerender)).toStrictEqual([]); + + // Job that ran through + expect( + Array.from(dummyJob1.subscriptionContainersToUpdate) + ).toStrictEqual([]); + expect(dummyJob1.triesToUpdate).toBe(0); + expect( + Array.from(dummySubscriptionContainer1.updatedSubscribers) + ).toStrictEqual([dummyObserver1]); + + // Job that ran through + expect( + Array.from(dummyJob2.subscriptionContainersToUpdate) + ).toStrictEqual([]); + expect(dummyJob2.triesToUpdate).toBe(0); + expect( + Array.from(dummySubscriptionContainer2.updatedSubscribers) + ).toStrictEqual([dummyObserver2]); + + expect(console.warn).not.toHaveBeenCalled(); + }); }); describe('updateSubscriptionContainer function tests', () => { @@ -322,9 +563,9 @@ describe('Runtime Tests', () => { }); it('should map the values of the updated Observers into an object and return it', () => { - subscriptionContainer.updatedSubscribers.push(dummyObserver1); - subscriptionContainer.updatedSubscribers.push(dummyObserver2); - subscriptionContainer.updatedSubscribers.push(dummyObserver3); + subscriptionContainer.updatedSubscribers.add(dummyObserver1); + subscriptionContainer.updatedSubscribers.add(dummyObserver2); + subscriptionContainer.updatedSubscribers.add(dummyObserver3); const props = runtime.getUpdatedObserverValues(subscriptionContainer); @@ -333,11 +574,9 @@ describe('Runtime Tests', () => { dummyObserver2KeyInWeakMap: undefined, dummyObserver3KeyInWeakMap: 'dummyObserverValue3', }); - expect(subscriptionContainer.updatedSubscribers).toStrictEqual([ - dummyObserver1, - dummyObserver2, - dummyObserver3, - ]); + expect( + Array.from(subscriptionContainer.updatedSubscribers) + ).toStrictEqual([dummyObserver1, dummyObserver2, dummyObserver3]); }); }); From a640755e957511696685ee2fcb5a6abd8ffed674 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Thu, 10 Jun 2021 18:19:56 +0200 Subject: [PATCH 063/117] fixed runtime tests --- packages/core/src/runtime/index.ts | 9 +- packages/core/src/runtime/observer.ts | 65 ++++++++------ packages/core/src/runtime/runtime.job.ts | 16 +--- .../core/tests/unit/runtime/observer.test.ts | 4 +- .../tests/unit/runtime/runtime.job.test.ts | 12 +-- .../core/tests/unit/runtime/runtime.test.ts | 90 ++++++++++++++++--- 6 files changed, 131 insertions(+), 65 deletions(-) diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index 516b9001..9a3285e8 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -187,10 +187,10 @@ export class Runtime { // Handle not ready Subscription Container if (!subscriptionContainer.ready) { if ( - !job.config.numberOfTriesToUpdate || - job.triesToUpdate < job.config.numberOfTriesToUpdate + !job.config.maxOfTriesToUpdate || + job.triedToUpdateCount < job.config.maxOfTriesToUpdate ) { - job.triesToUpdate++; + job.triedToUpdateCount++; this.notReadyJobsToRerender.add(job); LogCodeManager.log( @@ -201,7 +201,7 @@ export class Runtime { } else { LogCodeManager.log( '16:02:01', - [job.config.numberOfTriesToUpdate], + [job.config.maxOfTriesToUpdate], subscriptionContainer ); } @@ -251,7 +251,6 @@ export class Runtime { public updateSubscriptionContainer( subscriptionsToUpdate: Array ): void { - // Update Subscription Containers (trigger rerender on Components they represent) subscriptionsToUpdate.forEach((subscriptionContainer) => { // Call 'callback function' if Callback based Subscription if (subscriptionContainer instanceof CallbackSubscriptionContainer) diff --git a/packages/core/src/runtime/observer.ts b/packages/core/src/runtime/observer.ts index 34e8cf7d..59d1d483 100644 --- a/packages/core/src/runtime/observer.ts +++ b/packages/core/src/runtime/observer.ts @@ -15,25 +15,39 @@ export class Observer { // Agile Instance the Observer belongs to public agileInstance: () => Agile; - // Key/Name identifier of the Subscription Container + // Key/Name identifier of the Observer public _key?: ObserverKey; // Observers that depend on this Observer public dependents: Set = new Set(); // Subscription Containers (Components) the Observer is subscribed to public subscribedTo: Set = new Set(); + // Current value of Observer public value?: ValueType; // Previous value of Observer public previousValue?: ValueType; /** - * Handles the subscriptions to Subscription Containers (Components) - * and keeps track of dependencies. + * An Observer manages the subscriptions to Subscription Containers (UI-Components) + * and dependencies to other Observers (Agile Classes) + * for an Agile Class like the `State Class`. + * + * An Agile Class can use an Observer as an interface to the Runtime. + * Thereby, it ingests its own Observer into the Runtime + * when the Agile Class has changed in such a way + * that these changes need to be applied to UI-Components or dependent Observers. + * + * After the Observer has been ingested into the Runtime + * wrapped into a Runtime-Job, it is first added to the Jobs queue. + * When it is executed, the Observer's `perform()` method is called, + * where the accordingly changes can be applied to the Agile Class. * - * All Agile Classes that can be bound a UI-Component have their own Observer - * which manages the above mentioned things for them. + * Now that the Job was performed, it is added to the rerender queue, + * where the subscribed Subscription Container (UI-Components) are updated (rerender) + * accordingly. * - * The Observer is no standalone class and should be extended from a 'real' Observer. + * Note that the Observer itself is no standalone class + * and should be inherited and adapted to fulfill the Agile Class functions. * * @internal * @param agileInstance - Instance of Agile the Observer belongs to. @@ -68,7 +82,7 @@ export class Observer { } /** - * Returns the key/name identifier of the State. + * Returns the key/name identifier of the Observer. * * @public */ @@ -77,9 +91,10 @@ export class Observer { } /** - * Ingests the Observer into the runtime, - * by creating a Runtime Job - * and adding the Observer to the created Job. + * Passes the Observer into the runtime wrapped into a Runtime-Job + * where it is executed accordingly + * by performing its `perform()` method, updating its dependents + * and the UI-Components it is subscribed to * * @public * @param config - Configuration object @@ -95,7 +110,7 @@ export class Observer { force: false, }); - // Create Job + // Create Runtime-Job const job = new RuntimeJob(this, { force: config.force, sideEffects: config.sideEffects, @@ -103,45 +118,43 @@ export class Observer { key: config.key || this._key, }); + // Pass created Job into the Runtime this.agileInstance().runtime.ingest(job, { perform: config.perform, }); } /** - * Method executed by the Runtime to perform the Runtime Job, - * previously ingested (`ingest()`) by the Observer. + * Method executed by the Runtime to perform the Runtime-Job, + * previously ingested via the `ingest()` method. + * + * Note that this method should be overwritten + * to correctly apply the changes to the Agile Class it belongs to. * * @public - * @param job - Runtime Job to be performed. + * @param job - Runtime-Job to be performed. */ public perform(job: RuntimeJob): void { LogCodeManager.log('17:03:00'); } /** - * Adds specified Observer to the dependents of this Observer. + * Makes specified Observer depend on the Observer. * - * Every time this Observer is ingested into the Runtime, - * the dependent Observers are ingested into the Runtime too. + * A dependent Observer is always ingested into the Runtime, + * when the Observer it depends on was ingested too. * * @public - * @param observer - Observer to depends on this Observer. + * @param observer - Observer to depend on the Observer. */ public addDependent(observer: Observer): void { if (!this.dependents.has(observer)) this.dependents.add(observer); } } -/** - * @param deps - Initial Dependents of Observer - * @param subs - Initial Subscriptions of Observer - * @param key - Key/Name of Observer - * @param value - Initial Value of Observer - */ export interface CreateObserverConfigInterface { /** - * Initial Observers that depend on this Observer. + * Initial Observers to depend on the Observer. * @default [] */ dependents?: Array; @@ -157,7 +170,7 @@ export interface CreateObserverConfigInterface { key?: ObserverKey; /** * Initial value of the Observer. - * @defualt undefined + * @default undefined */ value?: ValueType; } diff --git a/packages/core/src/runtime/runtime.job.ts b/packages/core/src/runtime/runtime.job.ts index 9a459acb..b3a1588b 100644 --- a/packages/core/src/runtime/runtime.job.ts +++ b/packages/core/src/runtime/runtime.job.ts @@ -19,7 +19,7 @@ export class RuntimeJob { // Subscription Container of the Observer that have to be updated/re-rendered public subscriptionContainersToUpdate = new Set(); // How often not ready Subscription Container of the Observer have been tried to update - public triesToUpdate = 0; + public triedToUpdateCount = 0; /** * A Job that contains an Observer to be executed by the runtime. @@ -39,13 +39,13 @@ export class RuntimeJob { exclude: [], }, force: false, - numberOfTriesToUpdate: 3, + maxOfTriesToUpdate: 3, }); this.config = { background: config.background, force: config.force, sideEffects: config.sideEffects, - numberOfTriesToUpdate: config.numberOfTriesToUpdate, + maxOfTriesToUpdate: config.maxOfTriesToUpdate, }; this.observer = observer; this.rerender = @@ -86,14 +86,6 @@ export interface CreateRuntimeJobConfigInterface key?: RuntimeJobKey; } -/** - * @param background - If Job gets executed in the background -> not causing any rerender - * @param sideEffects - If SideEffects get executed - * @param force - Force performing Job - * @param numberOfTriesToUpdate - How often the runtime should try to update not ready SubscriptionContainers of this Job - * If 'null' the runtime tries to update the not ready SubscriptionContainer until they are ready (infinite). - * But be aware that this can lead to an overflow of 'old' Jobs after some time. (affects performance) - */ export interface RuntimeJobConfigInterface { /** * Whether to perform the Runtime Job in background. @@ -117,7 +109,7 @@ export interface RuntimeJobConfigInterface { * If 'null' the runtime tries to update the not ready Subscription Container until they are ready (infinite). * @default 3 */ - numberOfTriesToUpdate?: number | null; + maxOfTriesToUpdate?: number | null; } export interface SideEffectConfigInterface { diff --git a/packages/core/tests/unit/runtime/observer.test.ts b/packages/core/tests/unit/runtime/observer.test.ts index 11c9ff5b..d44c4b3e 100644 --- a/packages/core/tests/unit/runtime/observer.test.ts +++ b/packages/core/tests/unit/runtime/observer.test.ts @@ -93,7 +93,7 @@ describe('Observer Tests', () => { exclude: [], }, force: false, - numberOfTriesToUpdate: 3, + maxOfTriesToUpdate: 3, }); }); @@ -118,7 +118,7 @@ describe('Observer Tests', () => { exclude: [], }, force: true, - numberOfTriesToUpdate: 3, + maxOfTriesToUpdate: 3, }); }); diff --git a/packages/core/tests/unit/runtime/runtime.job.test.ts b/packages/core/tests/unit/runtime/runtime.job.test.ts index e9fd39df..b0dbce53 100644 --- a/packages/core/tests/unit/runtime/runtime.job.test.ts +++ b/packages/core/tests/unit/runtime/runtime.job.test.ts @@ -31,12 +31,12 @@ describe('RuntimeJob Tests', () => { exclude: [], }, force: false, - numberOfTriesToUpdate: 3, + maxOfTriesToUpdate: 3, }); expect(job.rerender).toBeTruthy(); expect(job.performed).toBeFalsy(); expect(job.subscriptionContainersToUpdate.size).toBe(0); - expect(job.triesToUpdate).toBe(0); + expect(job.triedToUpdateCount).toBe(0); }); it('should create RuntimeJob with Agile that has integrations (specific config)', () => { @@ -48,7 +48,7 @@ describe('RuntimeJob Tests', () => { enabled: false, }, force: true, - numberOfTriesToUpdate: 10, + maxOfTriesToUpdate: 10, }); expect(job._key).toBe('dummyJob'); @@ -59,7 +59,7 @@ describe('RuntimeJob Tests', () => { enabled: false, }, force: true, - numberOfTriesToUpdate: 10, + maxOfTriesToUpdate: 10, }); expect(job.rerender).toBeTruthy(); expect(job.performed).toBeFalsy(); @@ -78,7 +78,7 @@ describe('RuntimeJob Tests', () => { exclude: [], }, force: false, - numberOfTriesToUpdate: 3, + maxOfTriesToUpdate: 3, }); expect(job.rerender).toBeFalsy(); expect(job.performed).toBeFalsy(); @@ -99,7 +99,7 @@ describe('RuntimeJob Tests', () => { exclude: [], }, force: false, - numberOfTriesToUpdate: 3, + maxOfTriesToUpdate: 3, }); expect(job.rerender).toBeFalsy(); expect(job.performed).toBeFalsy(); diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts index dda6d98d..b967c1cd 100644 --- a/packages/core/tests/unit/runtime/runtime.test.ts +++ b/packages/core/tests/unit/runtime/runtime.test.ts @@ -347,7 +347,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob1.subscriptionContainersToUpdate) ).toStrictEqual([]); - expect(dummyJob1.triesToUpdate).toBe(0); + expect(dummyJob1.triedToUpdateCount).toBe(0); expect( Array.from(dummySubscriptionContainer1.updatedSubscribers) ).toStrictEqual([dummyObserver1]); @@ -356,7 +356,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob2.subscriptionContainersToUpdate) ).toStrictEqual([dummySubscriptionContainer2]); - expect(dummyJob2.triesToUpdate).toBe(1); + expect(dummyJob2.triedToUpdateCount).toBe(1); expect( Array.from(dummySubscriptionContainer2.updatedSubscribers) ).toStrictEqual([]); @@ -373,7 +373,7 @@ describe('Runtime Tests', () => { it( "shouldn't extract not ready Subscription Container from the specified Jobs, " + - "remove the Job when it exceeded the max 'numberOfTriesToUpdate' " + + "remove the Job when it exceeded the max 'maxOfTriesToUpdate' " + 'and print warning', () => { jest @@ -382,9 +382,8 @@ describe('Runtime Tests', () => { .mockReturnValueOnce(true); dummySubscriptionContainer1.ready = true; dummySubscriptionContainer2.ready = false; - const numberOfTries = - (dummyJob2.config.numberOfTriesToUpdate ?? 0) + 1; - dummyJob2.triesToUpdate = numberOfTries; + const numberOfTries = (dummyJob2.config.maxOfTriesToUpdate ?? 0) + 1; + dummyJob2.triedToUpdateCount = numberOfTries; const response = runtime.extractToUpdateSubscriptionContainer([ dummyJob1, @@ -406,7 +405,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob1.subscriptionContainersToUpdate) ).toStrictEqual([]); - expect(dummyJob1.triesToUpdate).toBe(0); + expect(dummyJob1.triedToUpdateCount).toBe(0); expect( Array.from(dummySubscriptionContainer1.updatedSubscribers) ).toStrictEqual([dummyObserver1]); @@ -415,7 +414,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob2.subscriptionContainersToUpdate) ).toStrictEqual([dummySubscriptionContainer2]); - expect(dummyJob2.triesToUpdate).toBe(numberOfTries); + expect(dummyJob2.triedToUpdateCount).toBe(numberOfTries); expect( Array.from(dummySubscriptionContainer2.updatedSubscribers) ).toStrictEqual([]); @@ -424,7 +423,7 @@ describe('Runtime Tests', () => { expect(console.warn).toHaveBeenCalledTimes(1); LogMock.hasLoggedCode( '16:02:01', - [numberOfTries], + [dummyJob2.config.maxOfTriesToUpdate], dummySubscriptionContainer2 ); } @@ -462,7 +461,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob1.subscriptionContainersToUpdate) ).toStrictEqual([]); - expect(dummyJob1.triesToUpdate).toBe(0); + expect(dummyJob1.triedToUpdateCount).toBe(0); expect( Array.from(dummySubscriptionContainer1.updatedSubscribers) ).toStrictEqual([]); @@ -471,7 +470,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob2.subscriptionContainersToUpdate) ).toStrictEqual([]); - expect(dummyJob2.triesToUpdate).toBe(0); + expect(dummyJob2.triedToUpdateCount).toBe(0); expect( Array.from(dummySubscriptionContainer2.updatedSubscribers) ).toStrictEqual([dummyObserver2]); @@ -513,7 +512,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob1.subscriptionContainersToUpdate) ).toStrictEqual([]); - expect(dummyJob1.triesToUpdate).toBe(0); + expect(dummyJob1.triedToUpdateCount).toBe(0); expect( Array.from(dummySubscriptionContainer1.updatedSubscribers) ).toStrictEqual([dummyObserver1]); @@ -522,7 +521,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob2.subscriptionContainersToUpdate) ).toStrictEqual([]); - expect(dummyJob2.triesToUpdate).toBe(0); + expect(dummyJob2.triedToUpdateCount).toBe(0); expect( Array.from(dummySubscriptionContainer2.updatedSubscribers) ).toStrictEqual([dummyObserver2]); @@ -532,7 +531,70 @@ describe('Runtime Tests', () => { }); describe('updateSubscriptionContainer function tests', () => { - // TODO + const dummyIntegration1 = { dummy: 'component' }; + let componentSubscriptionContainer1: ComponentSubscriptionContainer; + const dummyIntegration2 = jest.fn(); + let callbackSubscriptionContainer2: CallbackSubscriptionContainer; + const dummyIntegration3 = jest.fn(); + let callbackSubscriptionContainer3: CallbackSubscriptionContainer; + + beforeEach(() => { + componentSubscriptionContainer1 = dummyAgile.subController.subscribe( + dummyIntegration1, + [dummyObserver1] + ) as ComponentSubscriptionContainer; + componentSubscriptionContainer1.updatedSubscribers = new Set([ + dummyObserver1, + ]); + callbackSubscriptionContainer2 = dummyAgile.subController.subscribe( + dummyIntegration2, + [dummyObserver2] + ) as CallbackSubscriptionContainer; + callbackSubscriptionContainer2.updatedSubscribers = new Set([ + dummyObserver2, + ]); + callbackSubscriptionContainer3 = dummyAgile.subController.subscribe( + dummyIntegration3, + [dummyObserver3] + ) as CallbackSubscriptionContainer; + callbackSubscriptionContainer3.updatedSubscribers = new Set([ + dummyObserver3, + ]); + + dummyAgile.integrations.update = jest.fn(); + }); + + it('should update the specified Subscription Container', () => { + jest + .spyOn(runtime, 'getUpdatedObserverValues') + .mockReturnValueOnce('propsBasedOnUpdatedObservers' as any); + + runtime.updateSubscriptionContainer([ + componentSubscriptionContainer1, + callbackSubscriptionContainer2, + callbackSubscriptionContainer3, + ]); + + // Component Subscription Container 1 + expect(dummyAgile.integrations.update).toHaveBeenCalledTimes(1); + expect(dummyAgile.integrations.update).toHaveBeenCalledWith( + dummyIntegration1, + 'propsBasedOnUpdatedObservers' + ); + expect( + Array.from(componentSubscriptionContainer1.updatedSubscribers) + ).toStrictEqual([]); + + // Callback Subscription Container 2 + expect(callbackSubscriptionContainer2.callback).toHaveBeenCalledTimes( + 1 + ); + + // Callback Subscription Container 3 + expect(callbackSubscriptionContainer3.callback).toHaveBeenCalledTimes( + 1 + ); + }); }); describe('getUpdatedObserverValues function tests', () => { From faf3fc7b0994cbd0d5b201f4b0ea5aedaafea807 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 10 Jun 2021 20:04:20 +0200 Subject: [PATCH 064/117] fixed typos --- packages/core/src/runtime/observer.ts | 30 +++--- .../core/tests/unit/runtime/observer.test.ts | 99 +++++-------------- .../CallbackSubscriptionContainer.test.ts | 4 +- .../ComponentSubscriptionContainer.test.ts | 4 +- .../container/SubscriptionContainer.test.ts | 48 ++++----- 5 files changed, 73 insertions(+), 112 deletions(-) diff --git a/packages/core/src/runtime/observer.ts b/packages/core/src/runtime/observer.ts index 59d1d483..0ec534e2 100644 --- a/packages/core/src/runtime/observer.ts +++ b/packages/core/src/runtime/observer.ts @@ -32,22 +32,23 @@ export class Observer { * and dependencies to other Observers (Agile Classes) * for an Agile Class like the `State Class`. * - * An Agile Class can use an Observer as an interface to the Runtime. - * Thereby, it ingests its own Observer into the Runtime + * Agile Classes often use an Observer as an interface to the Runtime. + * In doing so, they ingest their own Observer into the Runtime * when the Agile Class has changed in such a way * that these changes need to be applied to UI-Components or dependent Observers. * * After the Observer has been ingested into the Runtime - * wrapped into a Runtime-Job, it is first added to the Jobs queue. + * wrapped into a Runtime-Job, it is first added to the Jobs queue + * to prevent race conditions. * When it is executed, the Observer's `perform()` method is called, - * where the accordingly changes can be applied to the Agile Class. + * where the accordingly changes are applied to the Agile Class. * * Now that the Job was performed, it is added to the rerender queue, - * where the subscribed Subscription Container (UI-Components) are updated (rerender) - * accordingly. + * where the subscribed Subscription Container (UI-Components) + * of the Observer are updated (rerender). * * Note that the Observer itself is no standalone class - * and should be inherited and adapted to fulfill the Agile Class functions. + * and should be adapted to the Agile Class it belongs to. * * @internal * @param agileInstance - Instance of Agile the Observer belongs to. @@ -94,7 +95,7 @@ export class Observer { * Passes the Observer into the runtime wrapped into a Runtime-Job * where it is executed accordingly * by performing its `perform()` method, updating its dependents - * and the UI-Components it is subscribed to + * and the UI-Components it is subscribed to. * * @public * @param config - Configuration object @@ -129,7 +130,8 @@ export class Observer { * previously ingested via the `ingest()` method. * * Note that this method should be overwritten - * to correctly apply the changes to the Agile Class it belongs to. + * to correctly apply the changes to the Agile Class + * to which the Observer belongs. * * @public * @param job - Runtime-Job to be performed. @@ -139,10 +141,10 @@ export class Observer { } /** - * Makes specified Observer depend on the Observer. + * Makes the specified Observer depend on the Observer. * * A dependent Observer is always ingested into the Runtime, - * when the Observer it depends on was ingested too. + * when the Observer it depends on has also been ingested. * * @public * @param observer - Observer to depend on the Observer. @@ -159,7 +161,7 @@ export interface CreateObserverConfigInterface { */ dependents?: Array; /** - * Initial Subscription Container the Observer is subscribed to. + * Initial Subscription Containers the Observer is subscribed to. * @default [] */ subs?: Array; @@ -170,6 +172,10 @@ export interface CreateObserverConfigInterface { key?: ObserverKey; /** * Initial value of the Observer. + * + * The value of an Observer is merged into the Component (Component Subscription Container) + * to be represented there, for example, in a local State Management property. + * * @default undefined */ value?: ValueType; diff --git a/packages/core/tests/unit/runtime/observer.test.ts b/packages/core/tests/unit/runtime/observer.test.ts index d44c4b3e..d725221c 100644 --- a/packages/core/tests/unit/runtime/observer.test.ts +++ b/packages/core/tests/unit/runtime/observer.test.ts @@ -20,20 +20,22 @@ describe('Observer Tests', () => { dummyAgile = new Agile(); dummyObserver1 = new Observer(dummyAgile, { key: 'dummyObserver1' }); dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' }); - dummySubscription1 = new SubscriptionContainer(); - dummySubscription2 = new SubscriptionContainer(); + dummySubscription1 = new SubscriptionContainer([]); + dummySubscription2 = new SubscriptionContainer([]); - jest.spyOn(Observer.prototype, 'subscribe'); + jest.spyOn(dummySubscription1, 'addSubscription'); + jest.spyOn(dummySubscription2, 'addSubscription'); }); it('should create Observer (default config)', () => { const observer = new Observer(dummyAgile); + expect(observer.agileInstance()).toBe(dummyAgile); expect(observer._key).toBeUndefined(); + expect(Array.from(observer.dependents)).toStrictEqual([]); + expect(Array.from(observer.subscribedTo)).toStrictEqual([]); expect(observer.value).toBeUndefined(); expect(observer.previousValue).toBeUndefined(); - expect(observer.dependents.size).toBe(0); - expect(observer.subscribedTo.size).toBe(0); }); it('should create Observer (specific config)', () => { @@ -44,18 +46,21 @@ describe('Observer Tests', () => { value: 'coolValue', }); + expect(observer.agileInstance()).toBe(dummyAgile); expect(observer._key).toBe('testKey'); + expect(Array.from(observer.dependents)).toStrictEqual([ + dummyObserver1, + dummyObserver2, + ]); + expect(Array.from(observer.subscribedTo)).toStrictEqual([ + dummySubscription1, + dummySubscription2, + ]); expect(observer.value).toBe('coolValue'); expect(observer.previousValue).toBe('coolValue'); - expect(observer.dependents.size).toBe(2); - expect(observer.dependents.has(dummyObserver2)).toBeTruthy(); - expect(observer.dependents.has(dummyObserver1)).toBeTruthy(); - expect(observer.subscribedTo.size).toBe(2); - expect(observer.subscribedTo.has(dummySubscription1)).toBeTruthy(); - expect(observer.subscribedTo.has(dummySubscription2)).toBeTruthy(); - - expect(observer.subscribe).toHaveBeenCalledWith(dummySubscription1); - expect(observer.subscribe).toHaveBeenCalledWith(dummySubscription2); + + expect(dummySubscription1.addSubscription).toHaveBeenCalledWith(observer); + expect(dummySubscription2.addSubscription).toHaveBeenCalledWith(observer); }); describe('Observer Function Tests', () => { @@ -82,7 +87,7 @@ describe('Observer Tests', () => { }); describe('ingest function tests', () => { - it('should create RuntimeJob and ingest Observer into the Runtime (default config)', () => { + it('should create RuntimeJob containing the Observer and ingest it into the Runtime (default config)', () => { dummyAgile.runtime.ingest = jest.fn((job: RuntimeJob) => { expect(job._key).toBe(observer._key); expect(job.observer).toBe(observer); @@ -107,7 +112,7 @@ describe('Observer Tests', () => { ); }); - it('should create RuntimeJob and ingest Observer into the Runtime (specific config)', () => { + it('should create RuntimeJob containing the Observer and ingest it into the Runtime (specific config)', () => { dummyAgile.runtime.ingest = jest.fn((job: RuntimeJob) => { expect(job._key).toBe('coolKey'); expect(job.observer).toBe(observer); @@ -148,7 +153,7 @@ describe('Observer Tests', () => { }); }); - describe('depend function tests', () => { + describe('addDependent function tests', () => { let dummyObserver1: Observer; let dummyObserver2: Observer; @@ -157,14 +162,14 @@ describe('Observer Tests', () => { dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' }); }); - it('should add passed Observer to deps', () => { + it('should add specified Observer to the dependents array', () => { observer.addDependent(dummyObserver1); expect(observer.dependents.size).toBe(1); expect(observer.dependents.has(dummyObserver2)); }); - it("shouldn't add the same Observer twice to deps", () => { + it("shouldn't add specified Observer twice to the dependents array", () => { observer.addDependent(dummyObserver1); observer.addDependent(dummyObserver1); @@ -173,61 +178,5 @@ describe('Observer Tests', () => { expect(observer.dependents.has(dummyObserver1)); }); }); - - describe('subscribe function tests', () => { - let dummySubscriptionContainer1: SubscriptionContainer; - - beforeEach(() => { - dummySubscriptionContainer1 = new SubscriptionContainer(); - }); - - it('should add subscriptionContainer to subs and this(Observer) to SubscriptionContainer subs', () => { - observer.subscribe(dummySubscriptionContainer1); - - expect(observer.subscribedTo.size).toBe(1); - expect(observer.subscribedTo.has(dummySubscriptionContainer1)); - expect(dummySubscriptionContainer1.subscribers.size).toBe(1); - expect( - dummySubscriptionContainer1.subscribers.has(observer) - ).toBeTruthy(); - }); - - it("shouldn't add same subscriptionContainer twice to subs", () => { - observer.subscribe(dummySubscriptionContainer1); - - observer.subscribe(dummySubscriptionContainer1); - - expect(observer.subscribedTo.size).toBe(1); - expect(observer.subscribedTo.has(dummySubscriptionContainer1)); - expect(dummySubscriptionContainer1.subscribers.size).toBe(1); - expect( - dummySubscriptionContainer1.subscribers.has(observer) - ).toBeTruthy(); - }); - }); - - describe('unsubscribe function tests', () => { - let dummySubscriptionContainer1: SubscriptionContainer; - let dummySubscriptionContainer2: SubscriptionContainer; - - beforeEach(() => { - dummySubscriptionContainer1 = new SubscriptionContainer(); - dummySubscriptionContainer2 = new SubscriptionContainer(); - observer.subscribe(dummySubscriptionContainer1); - observer.subscribe(dummySubscriptionContainer2); - }); - - it('should remove subscriptionContainer from subs and this(Observer) from SubscriptionContainer subs', () => { - observer.unsubscribe(dummySubscriptionContainer1); - - expect(observer.subscribedTo.size).toBe(1); - expect(observer.subscribedTo.has(dummySubscriptionContainer1)); - expect(dummySubscriptionContainer1.subscribers.size).toBe(0); - expect(dummySubscriptionContainer2.subscribers.size).toBe(1); - expect( - dummySubscriptionContainer2.subscribers.has(observer) - ).toBeTruthy(); - }); - }); }); }); diff --git a/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts index 2edeff7c..75e4c841 100644 --- a/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts @@ -49,7 +49,9 @@ describe('CallbackSubscriptionContainer Tests', () => { expect(subscriptionContainer.subscribers.size).toBe(2); expect(subscriptionContainer.subscribers.has(dummyObserver1)).toBeTruthy(); expect(subscriptionContainer.subscribers.has(dummyObserver2)).toBeTruthy(); - expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); + expect(Array.from(subscriptionContainer.updatedSubscribers)).toStrictEqual( + [] + ); expect(subscriptionContainer.isObjectBased).toBeFalsy(); expect(subscriptionContainer.subscriberKeysWeakMap).toStrictEqual( expect.any(WeakMap) diff --git a/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts index 6ebb14fc..7f9da8c4 100644 --- a/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts @@ -47,7 +47,9 @@ describe('ComponentSubscriptionContainer Tests', () => { expect(subscriptionContainer.subscribers.size).toBe(2); expect(subscriptionContainer.subscribers.has(dummyObserver1)).toBeTruthy(); expect(subscriptionContainer.subscribers.has(dummyObserver2)).toBeTruthy(); - expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); + expect(Array.from(subscriptionContainer.updatedSubscribers)).toStrictEqual( + [] + ); expect(subscriptionContainer.isObjectBased).toBeFalsy(); expect(subscriptionContainer.subscriberKeysWeakMap).toStrictEqual( expect.any(WeakMap) diff --git a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts index 8a7cb06a..9729177a 100644 --- a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts @@ -59,8 +59,10 @@ describe('SubscriptionContainer Tests', () => { expect(subscriptionContainer.key).toBe('generatedId'); expect(subscriptionContainer.ready).toBeFalsy(); expect(subscriptionContainer.componentId).toBeUndefined(); - expect(subscriptionContainer.subscribers.size).toBe(0); // because of mocking addSubscription - expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); + expect(Array.from(subscriptionContainer.subscribers)).toStrictEqual([]); // because of mocking addSubscription + expect(Array.from(subscriptionContainer.updatedSubscribers)).toStrictEqual( + [] + ); expect(subscriptionContainer.isObjectBased).toBeFalsy(); expect(subscriptionContainer.subscriberKeysWeakMap).toStrictEqual( expect.any(WeakMap) @@ -103,8 +105,10 @@ describe('SubscriptionContainer Tests', () => { expect(subscriptionContainer.key).toBe('generatedId'); expect(subscriptionContainer.ready).toBeFalsy(); expect(subscriptionContainer.componentId).toBeUndefined(); - expect(subscriptionContainer.subscribers.size).toBe(0); // because of mocking addSubscription - expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); + expect(Array.from(subscriptionContainer.subscribers)).toStrictEqual([]); // because of mocking addSubscription + expect(Array.from(subscriptionContainer.updatedSubscribers)).toStrictEqual( + [] + ); expect(subscriptionContainer.isObjectBased).toBeTruthy(); expect(subscriptionContainer.subscriberKeysWeakMap).toStrictEqual( expect.any(WeakMap) @@ -161,8 +165,10 @@ describe('SubscriptionContainer Tests', () => { expect(subscriptionContainer.key).toBe('dummyKey'); expect(subscriptionContainer.ready).toBeFalsy(); expect(subscriptionContainer.componentId).toBe('testID'); - expect(subscriptionContainer.subscribers.size).toBe(0); // because of mocking addSubscription - expect(subscriptionContainer.updatedSubscribers).toStrictEqual([]); + expect(Array.from(subscriptionContainer.subscribers)).toStrictEqual([]); // because of mocking addSubscription + expect(Array.from(subscriptionContainer.updatedSubscribers)).toStrictEqual( + [] + ); expect(subscriptionContainer.isObjectBased).toBeFalsy(); expect(subscriptionContainer.subscriberKeysWeakMap).toStrictEqual( expect.any(WeakMap) @@ -215,14 +221,12 @@ describe('SubscriptionContainer Tests', () => { ], }); - expect(subscriptionContainer.subscribers.size).toBe(1); - expect( - subscriptionContainer.subscribers.has(dummyObserver1) - ).toBeTruthy(); - expect(dummyObserver1.subscribedTo.size).toBe(1); - expect( - dummyObserver1.subscribedTo.has(subscriptionContainer) - ).toBeTruthy(); + expect(Array.from(subscriptionContainer.subscribers)).toStrictEqual([ + dummyObserver1, + ]); + expect(Array.from(dummyObserver1.subscribedTo)).toStrictEqual([ + subscriptionContainer, + ]); // should assign specified selectors/(and selectors created from proxy paths) to the selectorsWeakMap const observer1Selector = subscriptionContainer.selectorsWeakMap.get( @@ -299,10 +303,9 @@ describe('SubscriptionContainer Tests', () => { it('should remove subscribed Observer from Subscription Container', () => { subscriptionContainer.removeSubscription(dummyObserver1); - expect(subscriptionContainer.subscribers.size).toBe(1); - expect( - subscriptionContainer.subscribers.has(dummyObserver2) - ).toBeTruthy(); + expect(Array.from(subscriptionContainer.subscribers)).toStrictEqual([ + dummyObserver2, + ]); expect( subscriptionContainer.selectorsWeakMap.get(dummyObserver1) @@ -318,11 +321,10 @@ describe('SubscriptionContainer Tests', () => { subscriptionContainer.subscriberKeysWeakMap.get(dummyObserver2) ).toBe('dummyObserver2'); - expect(dummyObserver1.subscribedTo.size).toBe(0); - expect(dummyObserver2.subscribedTo.size).toBe(1); - expect( - dummyObserver2.subscribedTo.has(subscriptionContainer) - ).toBeTruthy(); + expect(Array.from(dummyObserver1.subscribedTo)).toStrictEqual([]); + expect(Array.from(dummyObserver2.subscribedTo)).toStrictEqual([ + subscriptionContainer, + ]); }); }); }); From 646f076c0fcc54317939cab791303f30f533119c Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 11 Jun 2021 06:50:10 +0200 Subject: [PATCH 065/117] fixed typos --- packages/core/src/runtime/observer.ts | 34 +++++++++----- .../CallbackSubscriptionContainer.ts | 10 ++--- .../ComponentSubscriptionContainer.ts | 45 ++++++++++--------- .../container/SubscriptionContainer.ts | 4 +- .../CallbackSubscriptionContainer.test.ts | 8 ++-- .../ComponentSubscriptionContainer.test.ts | 8 ++-- 6 files changed, 63 insertions(+), 46 deletions(-) diff --git a/packages/core/src/runtime/observer.ts b/packages/core/src/runtime/observer.ts index 0ec534e2..eb76f80c 100644 --- a/packages/core/src/runtime/observer.ts +++ b/packages/core/src/runtime/observer.ts @@ -7,6 +7,7 @@ import { IngestConfigInterface, CreateRuntimeJobConfigInterface, LogCodeManager, + generateId, } from '../internal'; export type ObserverKey = string | number; @@ -19,12 +20,12 @@ export class Observer { public _key?: ObserverKey; // Observers that depend on this Observer public dependents: Set = new Set(); - // Subscription Containers (Components) the Observer is subscribed to + // Subscription Containers (UI-Components) the Observer is subscribed to public subscribedTo: Set = new Set(); - // Current value of Observer + // Current value of the Observer public value?: ValueType; - // Previous value of Observer + // Previous value of the Observer public previousValue?: ValueType; /** @@ -35,7 +36,8 @@ export class Observer { * Agile Classes often use an Observer as an interface to the Runtime. * In doing so, they ingest their own Observer into the Runtime * when the Agile Class has changed in such a way - * that these changes need to be applied to UI-Components or dependent Observers. + * that these changes need to be applied to UI-Components + * or dependent other Observers. * * After the Observer has been ingested into the Runtime * wrapped into a Runtime-Job, it is first added to the Jobs queue @@ -45,10 +47,10 @@ export class Observer { * * Now that the Job was performed, it is added to the rerender queue, * where the subscribed Subscription Container (UI-Components) - * of the Observer are updated (rerender). + * of the Observer are updated (re-rendered). * * Note that the Observer itself is no standalone class - * and should be adapted to the Agile Class it belongs to. + * and should be adapted to the Agile Class needs it belongs to. * * @internal * @param agileInstance - Instance of Agile the Observer belongs to. @@ -93,9 +95,10 @@ export class Observer { /** * Passes the Observer into the runtime wrapped into a Runtime-Job - * where it is executed accordingly - * by performing its `perform()` method, updating its dependents - * and the UI-Components it is subscribed to. + * where it is executed accordingly. + * + * During the execution the runtime performs the Observer's `perform()` method, + * updates its dependents and re-renders the UI-Components it is subscribed to. * * @public * @param config - Configuration object @@ -116,7 +119,9 @@ export class Observer { force: config.force, sideEffects: config.sideEffects, background: config.background, - key: config.key || this._key, + key: + config.key ?? + `${this._key != null ? this._key + '_' : ''}${generateId()}`, }); // Pass created Job into the Runtime @@ -173,8 +178,13 @@ export interface CreateObserverConfigInterface { /** * Initial value of the Observer. * - * The value of an Observer is merged into the Component (Component Subscription Container) - * to be represented there, for example, in a local State Management property. + * The value of an Observer is given to the Integration's `updateMethod()` method + * (Component Subscription Container) where it can be, + * for example, merged in a local State Management property of the UI-Component + * it is subscribed to. + * + * Also the selection of specific properties of an Agile Class value + * is based on the Observer `value` and `previousValue`. * * @default undefined */ diff --git a/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts index 9ba41abe..bf974bea 100644 --- a/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts @@ -6,24 +6,24 @@ import { export class CallbackSubscriptionContainer extends SubscriptionContainer { /** - * Callback function to trigger a rerender - * on the Component represented by the Subscription Container. + * Callback function to trigger a re-render + * on the UI-Component which is represented by the Subscription Container. */ public callback: Function; /** * A Callback Subscription Container represents a UI-Component in AgileTs - * and triggers a rerender on the UI-Component via a specified callback function. + * and triggers re-renders on the UI-Component via the specified callback function. * * The Callback Subscription Container doesn't keep track of the Component itself. - * It only knows how to trigger a rerender on it via the callback function. + * It only knows how to trigger re-renders on it by calling the callback function. * * [Learn more..](https://agile-ts.org/docs/core/integration#callback-based) * * @internal * @param callback - Callback function to cause a rerender on the Component * to be represented by the Subscription Container. - * @param subs - Observers to be subscribed to the Subscription Container. + * @param subs - Observers to be initial subscribed to the Subscription Container. * @param config - Configuration object */ constructor( diff --git a/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts index 4da1277f..ff80eded 100644 --- a/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts @@ -8,51 +8,54 @@ export class ComponentSubscriptionContainer< C = any > extends SubscriptionContainer { /** - * Component the Subscription Container represents - * and mutates to cause rerender on it. + * UI-Component which is represented by the Subscription Container + * and mutated via the Integration's `updateMethod()` method + * to cause re-renders on it. */ public component: C; /** * A Component Subscription Container represents a UI-Component in AgileTs - * and triggers a rerender on the UI-Component by muting the specified Component Instance. - * For example by updating a local State Management property of the Component. - * (like in a React Class Components the `this.state` property) + * and triggers re-renders on the UI-Component by muting the specified Component Instance + * via the Integration's `updateMethod()` method. + * For example by updating a local State Management property of the Component + * (like in React Class Components the `this.state` property). * * The Component Subscription Container keeps track of the Component itself, - * to mutate it accordingly so that a rerender is triggered. + * to mutate it appropriately so that re-renders can be triggered on it. * * For this to work well, a Component Subscription Container is often object based. - * Meaning that each Observer was provided in a object keymap with a unique key identifier. + * Meaning that each Observer was provided in an object keymap + * with a unique key identifier. * ``` - * // Object based (guaranteed unique key) + * // Object based (guaranteed unique key identifier) * { * state1: Observer, * state2: Observer * } * - * // Array based (no guaranteed unique key) + * // Array based (no guaranteed unique key identifier) * [Observer, Observer] * ``` - * Thus the Integrations 'updateMethod' method can be called - * with an complete object of changed Observer values. + * Thus the Integration's 'updateMethod()' method can be called + * with a complete object of updated Observer values. * ``` * updateMethod: (componentInstance, updatedData) => { - * console.log(componentInstance); // Returns [this.component] - * console.log(updatedData); // Returns changed Observer values (see below) - * // { - * // state1: Observer.value, - * // state2: Observer.value - * // } - * } + * console.log(componentInstance); // Returns 'this.component' + * console.log(updatedData); // Returns updated Observer values keymap (see below) + * // { + * // state1: Observer.value, + * // state2: Observer.value, + * // } + * } * ``` * * [Learn more..](https://agile-ts.org/docs/core/integration#component-based) * * @internal - * @param component - Component to be represented by the Subscription Container - * and mutated via the Integration method 'updateMethod()' to trigger rerender on it. - * @param subs - Observers to be subscribed to the Subscription Container. + * @param component - UI-Component to be represented by the Subscription Container + * and mutated via the Integration's 'updateMethod()' method to trigger re-renders on it. + * @param subs - Observers to be initial subscribed to the Subscription Container. * @param config - Configuration object */ constructor( diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index 3e2ca5f9..0af1ae84 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -19,7 +19,7 @@ export class SubscriptionContainer { */ public ready = false; /** - * Unique identifier of the Component the Subscription Container represents. + * Unique identifier of the UI-Component the Subscription Container represents in AgileTs. */ public componentId?: ComponentIdType; @@ -86,7 +86,7 @@ export class SubscriptionContainer { * for example, when their value has changed. * * @internal - * @param subs - Observers to be subscribed to the Subscription Container. + * @param subs - Observers to be initial subscribed to the Subscription Container. * @param config - Configuration object */ constructor( diff --git a/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts index 75e4c841..e8bea19e 100644 --- a/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts @@ -43,12 +43,14 @@ describe('CallbackSubscriptionContainer Tests', () => { expect(subscriptionContainer.callback).toBe(dummyIntegration); + // Check if SubscriptionContainer was called with correct parameters expect(subscriptionContainer.key).toBe('dummyKey'); expect(subscriptionContainer.ready).toBeFalsy(); expect(subscriptionContainer.componentId).toBe('testID'); - expect(subscriptionContainer.subscribers.size).toBe(2); - expect(subscriptionContainer.subscribers.has(dummyObserver1)).toBeTruthy(); - expect(subscriptionContainer.subscribers.has(dummyObserver2)).toBeTruthy(); + expect(Array.from(subscriptionContainer.subscribers)).toStrictEqual([ + dummyObserver1, + dummyObserver2, + ]); expect(Array.from(subscriptionContainer.updatedSubscribers)).toStrictEqual( [] ); diff --git a/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts index 7f9da8c4..4401cf88 100644 --- a/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts @@ -41,12 +41,14 @@ describe('ComponentSubscriptionContainer Tests', () => { expect(subscriptionContainer.component).toStrictEqual(dummyIntegration); + // Check if SubscriptionContainer was called with correct parameters expect(subscriptionContainer.key).toBe('dummyKey'); expect(subscriptionContainer.ready).toBeFalsy(); expect(subscriptionContainer.componentId).toBe('testID'); - expect(subscriptionContainer.subscribers.size).toBe(2); - expect(subscriptionContainer.subscribers.has(dummyObserver1)).toBeTruthy(); - expect(subscriptionContainer.subscribers.has(dummyObserver2)).toBeTruthy(); + expect(Array.from(subscriptionContainer.subscribers)).toStrictEqual([ + dummyObserver1, + dummyObserver2, + ]); expect(Array.from(subscriptionContainer.updatedSubscribers)).toStrictEqual( [] ); From b0bb0f19eeaead34eca3e3a21553aa8a0f9e8faf Mon Sep 17 00:00:00 2001 From: BennoDev Date: Fri, 11 Jun 2021 17:28:36 +0200 Subject: [PATCH 066/117] fixed SubscriptionContainer typos --- .../container/SubscriptionContainer.ts | 124 +++++++++++------- .../runtime/subscription/sub.controller.ts | 3 +- .../container/SubscriptionContainer.test.ts | 14 +- 3 files changed, 86 insertions(+), 55 deletions(-) diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index 0af1ae84..4c0dcb17 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -12,56 +12,67 @@ export class SubscriptionContainer { public key?: SubscriptionContainerKeyType; /** * Whether the Subscription Container - * and the Component the Subscription Container represents are ready. + * and the UI-Component it represents are ready. * - * When both are ready, the Subscription Container is allowed - * to trigger rerender on the Component. + * When both instances are ready, + * the Subscription Container is allowed + * to trigger re-renders on the UI-Component. */ public ready = false; /** - * Unique identifier of the UI-Component the Subscription Container represents in AgileTs. + * Unique identifier of the UI-Component + * the Subscription Container represents. */ public componentId?: ComponentIdType; /** - * Observers that have subscribed the Subscription Container. + * Observers that are subscribed to the Subscription Container. * * The subscribed Observers use the Subscription Container - * as an interface to the Component it represents. - * Through the Subscription Container, they can easily trigger rerender - * on the Component, for example, when their value changes. + * as an interface to the UI-Component it represents. + * + * Through the Subscription Container, the Observers can easily trigger re-renders + * on the UI-Component, for example, when their value updates. * * [Learn more..](https://agile-ts.org/docs/core/integration#-subscriptions) */ public subscribers: Set; /** * Temporary stores the subscribed Observers, - * that were performed by the runtime - * and are currently running through the update Subscription Container (rerender) process. + * that were updated by the runtime + * and are currently running through + * the update (rerender) Subscription Container (UI-Component) process. */ public updatedSubscribers: Set = new Set(); /** * Whether the Subscription Container is object based. * - * An Observer is object based when the subscribed Observers - * have been provided in an Observer key map. + * A Subscription Container is object based when the subscribed Observers + * have been provided in an Observer keymap object * ``` * { * state1: Observer, * state2: Observer * } * ``` - * Thus each Observer has its 'external' unique key stored in the 'subscribersWeakMap'. + * Thus each Observer has its 'external' unique key stored in the `subscribersWeakMap`. * * Often Component based Subscriptions are object based, - * because each Observer requires a unique identifier - * to be properly represented in the 'updatedData' object sent to the Integration 'updateMethod()'. + * because each Observer requires in such Subscription a unique identifier. + * Mainly to be properly represented in the `updatedData` object + * sent to the Integration's `updateMethod()` method + * when the Subscription Container updates (re-renders the UI-Component). */ public isObjectBased = false; /** * Weak map for storing 'external' key identifiers for subscribed Observers. * + * Why is the key not applied directly to the Observer? + * + * Because the key defined here should be only valid + * for the scope of the Subscription Container. + * * https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap */ public subscriberKeysWeakMap: WeakMap; @@ -69,9 +80,14 @@ export class SubscriptionContainer { /** * Weak Map for storing selector functions for subscribed Observers. * - * A selector function allows partial subscription to an Observer value. + * A selector function allows the partial subscription to an Observer value. * Only when the selected Observer value part changes, - * the Subscription Container rerender the Component. + * the Subscription Container is updated (-> re-renders the UI-Component). + * + * Why are the selector functions not applied directly to the Observer? + * + * Because the selector function defined here should be only valid + * for the scope of the Subscription Container. * * https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap */ @@ -81,9 +97,10 @@ export class SubscriptionContainer { * A Subscription Container represents a UI-Component in AgileTs * that can be subscribed by multiple Observer Instances. * - * These Observers use the Subscription Container as an interface - * to trigger a rerender on the UI-Component it represents, - * for example, when their value has changed. + * The subscribed Observers can use the Subscription Container as an interface + * to the UI-Component it represents. + * For example, to trigger re-renders on the UI-Component, + * when their value has changed. * * @internal * @param subs - Observers to be initial subscribed to the Subscription Container. @@ -106,19 +123,18 @@ export class SubscriptionContainer { this.selectorsWeakMap = new WeakMap(); this.isObjectBased = !Array.isArray(subs); + // Assign initial Observers to the Subscription Container for (const key in subs) { - const sub = subs[key]; - this.addSubscription(sub, { - proxyPaths: config.proxyWeakMap?.get(sub)?.paths, - selectorMethods: config.selectorWeakMap?.get(sub)?.methods, - key: !Array.isArray(subs) ? key : undefined, + this.addSubscription(subs[key], { + proxyPaths: config.proxyWeakMap?.get(subs[key])?.paths, + selectorMethods: config.selectorWeakMap?.get(subs[key])?.methods, + key: this.isObjectBased ? key : undefined, }); } } /** - * Adds specified Observer to the `subscription` array - * and its selectors to the `selectorsWeakMap`. + * Subscribes the specified Observer to the Subscription Container. * * @internal * @param sub - Observer to be subscribed to the Subscription Container @@ -130,10 +146,10 @@ export class SubscriptionContainer { ): void { const toAddSelectorMethods: SelectorMethodType[] = config.selectorMethods ?? []; - const paths = config.proxyPaths ?? []; + const proxyPaths = config.proxyPaths ?? []; - // Create selector methods based on the specified proxy paths - for (const path of paths) { + // Create additional selector methods based on the specified proxy paths + for (const path of proxyPaths) { toAddSelectorMethods.push((value) => { let _value = value; for (const branch of path) { @@ -145,6 +161,8 @@ export class SubscriptionContainer { } // Assign defined/created selector methods to the 'selectorsWeakMap' + // (Not to the Observer itself, since the selector methods specified here + // only count for the scope of the Subscription Container) const existingSelectorMethods = this.selectorsWeakMap.get(sub)?.methods ?? []; const newSelectorMethods = existingSelectorMethods.concat( @@ -154,24 +172,24 @@ export class SubscriptionContainer { this.selectorsWeakMap.set(sub, { methods: newSelectorMethods }); // Assign specified key to the 'subscriberKeysWeakMap' - // (Not to the Observer itself, since the key specified here only counts for this Subscription Container) + // (Not to the Observer itself, since the key specified here + // only counts for the scope of the Subscription Container) if (config.key != null) this.subscriberKeysWeakMap.set(sub, config.key); // Add Observer to subscribers this.subscribers.add(sub); // Add Subscription Container to Observer - // so that it can be updated (cause rerender on the Component it represents) - // when for example the Observer value changes + // so that the Observer can cause updates on it + // (trigger re-render on the UI-Component it represents). sub.subscribedTo.add(this); } /** - * Removes the Observer from the Subscription Container - * and from all WeakMaps it might be in. + * Unsubscribes the specified Observer from the Subscription Container. * * @internal - * @param sub - Observer to be removed from the Subscription Container + * @param sub - Observer to be unsubscribed from the Subscription Container. */ public removeSubscription(sub: Observer) { if (this.subscribers.has(sub)) { @@ -192,18 +210,18 @@ export interface SubscriptionContainerConfigInterface { */ key?: SubscriptionContainerKeyType; /** - * Key/Name identifier of the Component to be represented by the Subscription Container. + * Key/Name identifier of the UI-Component to be represented by the Subscription Container. * @default undefined */ componentId?: ComponentIdType; /** - * A Weak Map with a set of paths to certain properties - * in a Observer value for Observers. + * A Weak Map with a set of proxy paths to certain properties + * in an Observer value for subscribed Observers. * * These paths are then selected via selector functions * which allow the partly subscription to an Observer value. * Only if the selected Observer value part changes, - * the Subscription Container rerender the Component. + * the Subscription Container re-renders the UI-Component it represents. * * For example: * ``` @@ -212,12 +230,12 @@ export interface SubscriptionContainerConfigInterface { * Observer2: {paths: [['car', 'speed']]} * } * ``` - * Now the Subscription Container will only trigger a rerender on the Component - * if 'data.name' in Observer1 or 'car.speed' in Observer2 changes. - * If, for instance, 'data.age' in Observer1 mutates it won't trigger a rerender, - * since 'data.age' isn't represented in the Proxy Weak Map. + * Now the Subscription Container will only trigger a re-render on the UI-Component + * if 'data.name' in Observer1 or 'car.speed' in Observer2 updates. + * If, for instance, 'data.age' in Observer1 mutates it won't trigger a re-render, + * since 'data.age' isn't represented in the specified Proxy Weak Map. * - * These particular paths were tracked via the ProxyTree. + * These particular paths can, for example, be tracked via the ProxyTree. * https://github.com/agile-ts/agile/tree/master/packages/proxytree * * @default new WeakMap() @@ -226,9 +244,21 @@ export interface SubscriptionContainerConfigInterface { /** * A Weak Map with a set of selector functions for Observers. * - * A selector functions allows the partly subscription to an Observer value. + * Selector functions allow the partly subscription to Observer values. * Only if the selected Observer value part changes, - * the Subscription Container rerender the Component. + * the Subscription Container re-renders the UI-Component it represents. + * + * For example: + * ``` + * WeakMap: { + * Observer1: {methods: [(value) => value.data.name]}, + * Observer2: {methods: [(value) => value.car.speed]} + * } + * ``` + * Now the Subscription Container will only trigger a re-render on the UI-Component + * if 'data.name' in Observer1 or 'car.speed' in Observer2 updates. + * If, for instance, 'data.age' in Observer1 mutates it won't trigger a re-render, + * since 'data.age' isn't selected by any selector method in the specified Selector Weak Map. * * @default new WeakMap() */ diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index 72f979bc..eb131d36 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -27,7 +27,8 @@ export class SubController { * The Subscription Controller manages the subscription to UI-Components. * * Thus it creates Subscription Containers (Interfaces to UI-Components) - * and assigns them to Observers, so that the Observers can easily trigger rerender on Components. + * and assigns them to specified Observers, + * so that these Observers can easily trigger re-renders on UI-Components. * * @internal * @param agileInstance - Instance of Agile the Subscription Controller belongs to. diff --git a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts index 9729177a..c3aa012d 100644 --- a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts @@ -190,9 +190,9 @@ describe('SubscriptionContainer Tests', () => { describe('addSubscription function tests', () => { it( - 'should create selectors based on the specified proxies, ' + - 'assigns newly created or provided selectors to the selectorsWeakMap ' + - 'and subscribe the specified Observer to the SubscriptionContainer', + 'should create selector methods based on the specified proxy paths, ' + + "assign newly created and provided selector methods to the 'selectorsWeakMap' " + + 'and subscribe the specified Observer to the Subscription Container', () => { dummyObserver1.value = { das: { haus: { vom: 'nikolaus' } }, @@ -228,7 +228,7 @@ describe('SubscriptionContainer Tests', () => { subscriptionContainer, ]); - // should assign specified selectors/(and selectors created from proxy paths) to the selectorsWeakMap + // should assign specified selectors/(and selectors created from proxy paths) to the 'selectorsWeakMap' const observer1Selector = subscriptionContainer.selectorsWeakMap.get( dummyObserver1 ) as any; @@ -249,20 +249,20 @@ describe('SubscriptionContainer Tests', () => { 'test1Value' ); - // shouldn't overwrite already set values in selectorsWeakMap + // shouldn't overwrite already set values in 'selectorsWeakMap' (Observer2) const observer2Selector = subscriptionContainer.selectorsWeakMap.get( dummyObserver2 ) as any; expect(observer2Selector.methods.length).toBe(1); expect(observer2Selector.methods[0](null)).toBe('doesNotMatter'); - // should assign specified key to the subscriberKeysWeakMap + // should assign specified key to the 'subscriberKeysWeakMap' const observer1Key = subscriptionContainer.subscriberKeysWeakMap.get( dummyObserver1 ); expect(observer1Key).toBe('dummyObserver1'); - // shouldn't overwrite already set values in subscriberKeysWeakMap + // shouldn't overwrite already set values in 'subscriberKeysWeakMap' (Observer2) const observer2Key = subscriptionContainer.subscriberKeysWeakMap.get( dummyObserver2 ); From bfd78a86763297de6a2e6c46fccf26a1134333da Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 11 Jun 2021 20:25:02 +0200 Subject: [PATCH 067/117] fixed typos --- packages/core/src/runtime/index.ts | 6 +- packages/core/src/runtime/runtime.job.ts | 46 +-- .../runtime/subscription/sub.controller.ts | 109 +++--- .../core/tests/unit/runtime/observer.test.ts | 4 +- .../tests/unit/runtime/runtime.job.test.ts | 28 +- .../core/tests/unit/runtime/runtime.test.ts | 4 +- .../subscription/sub.controller.test.ts | 314 +++++++++--------- 7 files changed, 256 insertions(+), 255 deletions(-) diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index 9a3285e8..63dc7e46 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -187,8 +187,8 @@ export class Runtime { // Handle not ready Subscription Container if (!subscriptionContainer.ready) { if ( - !job.config.maxOfTriesToUpdate || - job.triedToUpdateCount < job.config.maxOfTriesToUpdate + !job.config.maxTriesToUpdate || + job.triedToUpdateCount < job.config.maxTriesToUpdate ) { job.triedToUpdateCount++; this.notReadyJobsToRerender.add(job); @@ -201,7 +201,7 @@ export class Runtime { } else { LogCodeManager.log( '16:02:01', - [job.config.maxOfTriesToUpdate], + [job.config.maxTriesToUpdate], subscriptionContainer ); } diff --git a/packages/core/src/runtime/runtime.job.ts b/packages/core/src/runtime/runtime.job.ts index b3a1588b..c61b7c02 100644 --- a/packages/core/src/runtime/runtime.job.ts +++ b/packages/core/src/runtime/runtime.job.ts @@ -1,28 +1,28 @@ -import { - Observer, - defineConfig, - SubscriptionContainer, - Agile, -} from '../internal'; +import { Observer, defineConfig, SubscriptionContainer } from '../internal'; export class RuntimeJob { public config: RuntimeJobConfigInterface; - // Key/Name identifier of the Subscription Container + // Key/Name identifier of the Runtime Job public _key?: RuntimeJobKey; // Observer the Job represents public observer: ObserverType; - // Whether the Subscription Containers (Components) of the Observer can be re-rendered + // Whether the Subscription Containers (UI-Components) of the Observer should be updated (re-rendered) public rerender: boolean; - // Whether the Job has been performed by the runtime - public performed = false; - // Subscription Container of the Observer that have to be updated/re-rendered + // Subscription Containers (UI-Components) of the Observer that have to be updated (re-rendered) public subscriptionContainersToUpdate = new Set(); - // How often not ready Subscription Container of the Observer have been tried to update + // How often not ready Subscription Containers of the Observer have been tried to update public triedToUpdateCount = 0; + // Whether the Job has been performed by the runtime + public performed = false; + /** - * A Job that contains an Observer to be executed by the runtime. + * A Runtime Job is sent to the Runtime on behalf of the Observer it represents. + * + * In the Runtime, the Observer is performed via its `perform()` method + * and the Subscription Containers (UI-Components) + * to which it is subscribed are updated (re-rendered) accordingly. * * @internal * @param observer - Observer to be represented by the Runtime Job. @@ -39,13 +39,13 @@ export class RuntimeJob { exclude: [], }, force: false, - maxOfTriesToUpdate: 3, + maxTriesToUpdate: 3, }); this.config = { background: config.background, force: config.force, sideEffects: config.sideEffects, - maxOfTriesToUpdate: config.maxOfTriesToUpdate, + maxTriesToUpdate: config.maxTriesToUpdate, }; this.observer = observer; this.rerender = @@ -89,7 +89,8 @@ export interface CreateRuntimeJobConfigInterface export interface RuntimeJobConfigInterface { /** * Whether to perform the Runtime Job in background. - * So that the UI isn't notified of these changes and thus doesn't rerender. + * So that the Subscription Containers (UI-Components) aren't notified + * of these changes and thus doesn't update (re-render). * @default false */ background?: boolean; @@ -100,21 +101,24 @@ export interface RuntimeJobConfigInterface { sideEffects?: SideEffectConfigInterface; /** * Whether the Runtime Job should be forced through the runtime - * although it might be useless from the viewpoint of the runtime. + * although it might be useless from the current viewpoint of the runtime. * @default false */ force?: boolean; /** - * How often the runtime should try to update not ready Subscription Containers of the Observer the Job represents. - * If 'null' the runtime tries to update the not ready Subscription Container until they are ready (infinite). + * How often the Runtime should try to update not ready Subscription Containers + * subscribed by the Observer which the Job represents. + * + * When `null` the Runtime tries to update the not ready Subscription Containers + * until they are ready (infinite). * @default 3 */ - maxOfTriesToUpdate?: number | null; + maxTriesToUpdate?: number | null; } export interface SideEffectConfigInterface { /** - * Whether to execute the defined side effects + * Whether to execute the defined side effects. * @default true */ enabled?: boolean; diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index eb131d36..d1ac3641 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -24,11 +24,12 @@ export class SubController { public mountedComponents: Set = new Set(); /** - * The Subscription Controller manages the subscription to UI-Components. + * The Subscription Controller manages and simplifies the subscription to UI-Components. * * Thus it creates Subscription Containers (Interfaces to UI-Components) - * and assigns them to specified Observers, - * so that these Observers can easily trigger re-renders on UI-Components. + * and assigns them to the specified Observers. + * These Observers can then easily trigger re-renders on UI-Components + * via the created Subscription Containers. * * @internal * @param agileInstance - Instance of Agile the Subscription Controller belongs to. @@ -39,18 +40,20 @@ export class SubController { /** * Creates a so called Subscription Container that represents an UI-Component in AgileTs. - * Such Subscription Container know how to trigger a rerender on the UI-Component it represents + * Such Subscription Container know how to trigger a re-render on the UI-Component it represents * through the provided `integrationInstance`. * - * There exist two different ways the Subscription Container can cause a rerender on the Component. - * - 1. Via a callback function that directly triggers a rerender on the Component. (Callback based Subscription) - * [Learn more..](https://agile-ts.org/docs/core/integration/#callback-based) + * Currently, there are two different ways the Subscription Container can trigger a re-render on the UI-Component. + * - 1. Via a callback function that directly triggers a rerender on the UI-Component. + * (= Callback based Subscription) + * [Learn more..](https://agile-ts.org/docs/core/integration/#callback-based) * - 2. Via the Component instance itself. - * For example by mutating a local State Management property. (Component based Subscription) - * [Learn more..](https://agile-ts.org/docs/core/integration/#component-based) + * For example by mutating a local State Management property. + * (= Component based Subscription) + * [Learn more..](https://agile-ts.org/docs/core/integration/#component-based) * * The in an array specified Observers are then automatically subscribed - * to the created Subscription Container and thus to the Component it represents. + * to the created Subscription Container and thus to the UI-Component it represents. * * @public * @param integrationInstance - Callback function or Component Instance to trigger a rerender on a UI-Component. @@ -64,23 +67,25 @@ export class SubController { ): SubscriptionContainer; /** * Creates a so called Subscription Container that represents an UI-Component in AgileTs. - * Such Subscription Container know how to trigger a rerender on the UI-Component it represents + * Such Subscription Container know how to trigger a re-render on the UI-Component it represents * through the provided `integrationInstance`. * - * There exist two different ways the Subscription Container can cause a rerender on the Component. - * - 1. Via a callback function that directly triggers a rerender on the Component. (Callback based Subscription) - * [Learn more..](https://agile-ts.org/docs/core/integration/#callback-based) + * Currently, there are two different ways the Subscription Container can trigger a re-render on the UI-Component. + * - 1. Via a callback function that directly triggers a rerender on the UI-Component. + * (= Callback based Subscription) + * [Learn more..](https://agile-ts.org/docs/core/integration/#callback-based) * - 2. Via the Component instance itself. - * For example by mutating a local State Management property. (Component based Subscription) - * [Learn more..](https://agile-ts.org/docs/core/integration/#component-based) + * For example by mutating a local State Management property. + * (= Component based Subscription) + * [Learn more..](https://agile-ts.org/docs/core/integration/#component-based) * - * The in an object specified Observers are then automatically subscribed - * to the created Subscription Container and thus to the Component it represents. + * The in an object keymap specified Observers are then automatically subscribed + * to the created Subscription Container and thus to the UI-Component it represents. * * The advantage of subscribing the Observer via a object keymap, - * is that each Observer has its own unique key identifier. - * Such key identifier is for example required when merging the Observer value into - * a local Component State Management property. + * is that each Observer has its own unique 'external' key identifier. + * Such key identifier is, for example, required when merging the Observer value into + * a local UI-Component State Management property. * ``` * this.state = {...this.state, {state1: Observer1.value, state2: Observer2.value}} * ``` @@ -112,7 +117,7 @@ export class SubController { waitForMount: this.agileInstance().config.waitForMount, }); - // Create Subscription Container + // Create Subscription Container based on specified 'integrationInstance' const subscriptionContainer = isFunction(integrationInstance) ? this.createCallbackSubscriptionContainer( integrationInstance, @@ -125,12 +130,10 @@ export class SubController { config ); - // Return object based Subscription Container + // Return object based Subscription Container and an Observer value keymap if (subscriptionContainer.isObjectBased && !Array.isArray(subs)) { - // Build an Observer value keymap const props: { [key: string]: Observer['value'] } = {}; for (const key in subs) if (subs[key].value) props[key] = subs[key].value; - return { subscriptionContainer, props }; } @@ -139,15 +142,15 @@ export class SubController { } /** - * Unsubscribe the Subscription Container extracted from the specified 'subscriptionInstance' + * Removes the Subscription Container extracted from the specified 'subscriptionInstance' * from all Observers that were subscribed to it. * - * We should always unregister a Subscription Container when it is no longer in use, - * for example when the Component it represents has been unmounted. + * We should always unregister a Subscription Container when it is no longer in use. + * For example, when the UI-Component it represents has been unmounted. * * @public - * @param subscriptionInstance - Subscription Container - * or a UI-Component that contains an instance of a Subscription Container to be unsubscribed. + * @param subscriptionInstance - UI-Component that contains an instance of a Subscription Container + * or a Subscription Container to be unsubscribed/unregistered. */ public unsubscribe(subscriptionInstance: any) { // Helper function to remove Subscription Container from Observer @@ -158,29 +161,28 @@ export class SubController { }); }; - // Unsubscribe callback based Subscription Container + // Unsubscribe Callback based Subscription Container if (subscriptionInstance instanceof CallbackSubscriptionContainer) { unsub(subscriptionInstance); this.callbackSubs.delete(subscriptionInstance); - Agile.logger.if .tag(['runtime', 'subscription']) .info(LogCodeManager.getLog('15:01:00'), subscriptionInstance); return; } - // Unsubscribe component based Subscription Container + // Unsubscribe Component based Subscription Container if (subscriptionInstance instanceof ComponentSubscriptionContainer) { unsub(subscriptionInstance); this.componentSubs.delete(subscriptionInstance); - Agile.logger.if .tag(['runtime', 'subscription']) .info(LogCodeManager.getLog('15:01:01'), subscriptionInstance); return; } - // Unsubscribe component based Subscription Container extracted from the 'componentSubscriptionContainers' property + // Unsubscribe Component based Subscription Container + // extracted from the 'componentSubscriptionContainers' property if ( subscriptionInstance['componentSubscriptionContainers'] !== null && Array.isArray(subscriptionInstance.componentSubscriptionContainers) @@ -189,7 +191,6 @@ export class SubController { (subContainer) => { unsub(subContainer as ComponentSubscriptionContainer); this.componentSubs.delete(subContainer); - Agile.logger.if .tag(['runtime', 'subscription']) .info(LogCodeManager.getLog('15:01:01'), subscriptionInstance); @@ -203,8 +204,9 @@ export class SubController { * Returns a newly created Component based Subscription Container. * * @internal - * @param componentInstance - Component Instance to trigger a rerender on a UI-Component. - * @param subs - Observers to be subscribed to the to create Subscription Container. + * @param componentInstance - UI-Component to be represented by the Subscription Container + * and mutated via the Integration's 'updateMethod()' method to trigger re-renders on it. + * @param subs - Observers to be initial subscribed to the Subscription Container. * @param config - Configuration object. */ public createComponentSubscriptionContainer( @@ -225,17 +227,17 @@ export class SubController { componentSubscriptionContainer.ready = true; } else componentSubscriptionContainer.ready = true; - // Add subscriptionContainer to Component, to have an instance of it there - // (For example, required to unsubscribe the Subscription Container via the Component Instance) + // Add Subscription Container to the UI-Component it represents. + // (For example, useful to unsubscribe the Subscription Container via the Component Instance) if ( - componentInstance.componentSubscriptionContainers && + componentInstance['componentSubscriptionContainers'] != null && Array.isArray(componentInstance.componentSubscriptionContainers) ) componentInstance.componentSubscriptionContainers.push( componentSubscriptionContainer ); else - componentInstance.componentSubscriptionContainers = [ + componentInstance['componentSubscriptionContainers'] = [ componentSubscriptionContainer, ]; @@ -250,8 +252,9 @@ export class SubController { * Returns a newly created Callback based Subscription Container. * * @internal - * @param callbackFunction - Callback function to trigger a rerender on a UI-Component. - * @param subs - Observers to be subscribed to the to create Subscription Container. + * @param callbackFunction - Callback function to cause a rerender on the Component + * to be represented by the Subscription Container. + * @param subs - Observers to be initial subscribed to the Subscription Container. * @param config - Configuration object */ public createCallbackSubscriptionContainer( @@ -275,14 +278,15 @@ export class SubController { } /** - * Mounts Component based Subscription Container. + * Notifies the Subscription Containers representing the specified UI-Component (`componentInstance`) + * that the UI-Component they represent has been mounted. * * @public - * @param componentInstance - Component Instance containing a Subscription Container to be mounted + * @param componentInstance - Component Instance containing Subscription Containers to be mounted. */ public mount(componentInstance: any) { if ( - componentInstance.componentSubscriptionContainers && + componentInstance['componentSubscriptionContainers'] != null && Array.isArray(componentInstance.componentSubscriptionContainers) ) componentInstance.componentSubscriptionContainers.map( @@ -293,14 +297,15 @@ export class SubController { } /** - * Unmounts Component based Subscription Containers. + * Notifies the Subscription Containers representing the specified UI-Component (`componentInstance`) + * that the UI-Component they represent has been unmounted. * * @public - * @param componentInstance - Component Instance containing a Subscription Container to be unmounted + * @param componentInstance - Component Instance containing Subscription Containers to be unmounted */ public unmount(componentInstance: any) { if ( - componentInstance.componentSubscriptionContainers && + componentInstance['componentSubscriptionContainers'] != null && Array.isArray(componentInstance.componentSubscriptionContainers) ) componentInstance.componentSubscriptionContainers.map( @@ -314,8 +319,8 @@ export class SubController { interface RegisterSubscriptionConfigInterface extends SubscriptionContainerConfigInterface { /** - * Whether the Subscription Container should be ready only - * when the Component it represents has been mounted. + * Whether the Subscription Container should not be ready + * until the UI-Component it represents has been mounted. * @default agileInstance.config.waitForMount */ waitForMount?: boolean; diff --git a/packages/core/tests/unit/runtime/observer.test.ts b/packages/core/tests/unit/runtime/observer.test.ts index d725221c..525dcca7 100644 --- a/packages/core/tests/unit/runtime/observer.test.ts +++ b/packages/core/tests/unit/runtime/observer.test.ts @@ -98,7 +98,7 @@ describe('Observer Tests', () => { exclude: [], }, force: false, - maxOfTriesToUpdate: 3, + maxTriesToUpdate: 3, }); }); @@ -123,7 +123,7 @@ describe('Observer Tests', () => { exclude: [], }, force: true, - maxOfTriesToUpdate: 3, + maxTriesToUpdate: 3, }); }); diff --git a/packages/core/tests/unit/runtime/runtime.job.test.ts b/packages/core/tests/unit/runtime/runtime.job.test.ts index b0dbce53..e8989d14 100644 --- a/packages/core/tests/unit/runtime/runtime.job.test.ts +++ b/packages/core/tests/unit/runtime/runtime.job.test.ts @@ -17,7 +17,7 @@ describe('RuntimeJob Tests', () => { dummyObserver = new Observer(dummyAgile); }); - it('should create RuntimeJob with Agile that has integrations (default config)', () => { + it('should create RuntimeJob with a specified Agile Instance that has a registered Integration (default config)', () => { dummyAgile.integrate(dummyIntegration); const job = new RuntimeJob(dummyObserver); @@ -31,24 +31,25 @@ describe('RuntimeJob Tests', () => { exclude: [], }, force: false, - maxOfTriesToUpdate: 3, + maxTriesToUpdate: 3, }); expect(job.rerender).toBeTruthy(); expect(job.performed).toBeFalsy(); - expect(job.subscriptionContainersToUpdate.size).toBe(0); + expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); expect(job.triedToUpdateCount).toBe(0); }); - it('should create RuntimeJob with Agile that has integrations (specific config)', () => { + it('should create RuntimeJob with a specified Agile Instance that has a registered Integration (specific config)', () => { dummyAgile.integrate(dummyIntegration); const job = new RuntimeJob(dummyObserver, { key: 'dummyJob', sideEffects: { enabled: false, + exclude: ['jeff'], }, force: true, - maxOfTriesToUpdate: 10, + maxTriesToUpdate: 10, }); expect(job._key).toBe('dummyJob'); @@ -57,16 +58,17 @@ describe('RuntimeJob Tests', () => { background: false, sideEffects: { enabled: false, + exclude: ['jeff'], }, force: true, - maxOfTriesToUpdate: 10, + maxTriesToUpdate: 10, }); expect(job.rerender).toBeTruthy(); expect(job.performed).toBeFalsy(); - expect(job.subscriptionContainersToUpdate.size).toBe(0); + expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); }); - it('should create RuntimeJob with Agile that has no integrations (default config)', () => { + it('should create RuntimeJob with a specified Agile Instance that has no registered Integration (default config)', () => { const job = new RuntimeJob(dummyObserver); expect(job._key).toBeUndefined(); @@ -78,14 +80,14 @@ describe('RuntimeJob Tests', () => { exclude: [], }, force: false, - maxOfTriesToUpdate: 3, + maxTriesToUpdate: 3, }); expect(job.rerender).toBeFalsy(); expect(job.performed).toBeFalsy(); - expect(job.subscriptionContainersToUpdate.size).toBe(0); + expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); }); - it('should create RuntimeJob and Agile that has integrations (config.background = true)', () => { + it('should create RuntimeJob with a specified Agile Instance that has a registered Integrations (config.background = true)', () => { dummyAgile.integrate(dummyIntegration); const job = new RuntimeJob(dummyObserver, { background: true }); @@ -99,11 +101,11 @@ describe('RuntimeJob Tests', () => { exclude: [], }, force: false, - maxOfTriesToUpdate: 3, + maxTriesToUpdate: 3, }); expect(job.rerender).toBeFalsy(); expect(job.performed).toBeFalsy(); - expect(job.subscriptionContainersToUpdate.size).toBe(0); + expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); }); describe('RuntimeJob Function Tests', () => { diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts index b967c1cd..2182f056 100644 --- a/packages/core/tests/unit/runtime/runtime.test.ts +++ b/packages/core/tests/unit/runtime/runtime.test.ts @@ -382,7 +382,7 @@ describe('Runtime Tests', () => { .mockReturnValueOnce(true); dummySubscriptionContainer1.ready = true; dummySubscriptionContainer2.ready = false; - const numberOfTries = (dummyJob2.config.maxOfTriesToUpdate ?? 0) + 1; + const numberOfTries = (dummyJob2.config.maxTriesToUpdate ?? 0) + 1; dummyJob2.triedToUpdateCount = numberOfTries; const response = runtime.extractToUpdateSubscriptionContainer([ @@ -423,7 +423,7 @@ describe('Runtime Tests', () => { expect(console.warn).toHaveBeenCalledTimes(1); LogMock.hasLoggedCode( '16:02:01', - [dummyJob2.config.maxOfTriesToUpdate], + [dummyJob2.config.maxTriesToUpdate], dummySubscriptionContainer2 ); } diff --git a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts index c96710fe..1d60caf0 100644 --- a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts +++ b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts @@ -21,8 +21,9 @@ describe('SubController Tests', () => { it('should create SubController', () => { const subController = new SubController(dummyAgile); - expect(subController.callbackSubs.size).toBe(0); - expect(subController.callbackSubs.size).toBe(0); + expect(subController.agileInstance()).toBe(dummyAgile); + expect(Array.from(subController.callbackSubs)).toStrictEqual([]); + expect(Array.from(subController.componentSubs)).toStrictEqual([]); }); describe('SubController Function Tests', () => { @@ -49,8 +50,8 @@ describe('SubController Tests', () => { }); it( - 'should create a Component based Subscription Container with specified component' + - ' and add in object specified Observers to it', + 'should create a Component based Subscription Container with specified Component Instance ' + + 'and assign the in an object specified Observers to it', () => { dummyAgile.config.waitForMount = 'aFakeBoolean' as any; const dummyIntegration: any = { @@ -86,12 +87,15 @@ describe('SubController Tests', () => { waitForMount: true, } ); + expect( + subController.createCallbackSubscriptionContainer + ).not.toHaveBeenCalled(); } ); it( - 'should create a Component based Subscription Container with specified component' + - ' and add in array specified Observers to it', + 'should create a Component based Subscription Container with specified Component Instance ' + + 'and assign the in an array specified Observers to it', () => { dummyAgile.config.waitForMount = 'aFakeBoolean' as any; const dummyIntegration: any = { @@ -114,15 +118,18 @@ describe('SubController Tests', () => { { key: 'subscriptionContainerKey', componentId: 'testID', - waitForMount: dummyAgile.config.waitForMount, + waitForMount: 'aFakeBoolean', } ); + expect( + subController.createCallbackSubscriptionContainer + ).not.toHaveBeenCalled(); } ); it( - 'should create a Callback based Subscription Container with specified callback function' + - ' and add in object specified Observers to it', + 'should create a Callback based Subscription Container with specified callback function ' + + 'and assign the in an object specified Observers to it', () => { dummyAgile.config.waitForMount = 'aFakeBoolean' as any; const dummyIntegration = () => { @@ -154,15 +161,18 @@ describe('SubController Tests', () => { { key: 'subscriptionContainerKey', componentId: 'testID', - waitForMount: dummyAgile.config.waitForMount, + waitForMount: 'aFakeBoolean', } ); + expect( + subController.createComponentSubscriptionContainer + ).not.toHaveBeenCalled(); } ); it( - 'should create a Callback based Subscription Container with specified callback function' + - ' and add in array specified Observers to it', + 'should create a Callback based Subscription Container with specified callback function ' + + 'and assign the in an array specified Observers to it', () => { dummyAgile.config.waitForMount = 'aFakeBoolean' as any; const dummyIntegration = () => { @@ -192,12 +202,15 @@ describe('SubController Tests', () => { waitForMount: false, } ); + expect( + subController.createComponentSubscriptionContainer + ).not.toHaveBeenCalled(); } ); }); describe('unsubscribe function tests', () => { - it('should unsubscribe callbackSubscriptionContainer', () => { + it('should unsubscribe Callback based Subscription Container', () => { const dummyIntegration = () => { /* empty function */ }; @@ -209,7 +222,7 @@ describe('SubController Tests', () => { subController.unsubscribe(callbackSubscriptionContainer); - expect(subController.callbackSubs.size).toBe(0); + expect(Array.from(subController.callbackSubs)).toStrictEqual([]); expect(callbackSubscriptionContainer.ready).toBeFalsy(); expect( callbackSubscriptionContainer.removeSubscription @@ -222,7 +235,7 @@ describe('SubController Tests', () => { ).toHaveBeenCalledWith(dummyObserver2); }); - it('should unsubscribe componentSubscriptionContainer', () => { + it('should unsubscribe Component Subscription Container', () => { const dummyIntegration: any = { dummy: 'integration', }; @@ -234,7 +247,7 @@ describe('SubController Tests', () => { subController.unsubscribe(componentSubscriptionContainer); - expect(subController.componentSubs.size).toBe(0); + expect(Array.from(subController.componentSubs)).toStrictEqual([]); expect(componentSubscriptionContainer.ready).toBeFalsy(); expect( componentSubscriptionContainer.removeSubscription @@ -247,54 +260,59 @@ describe('SubController Tests', () => { ).toHaveBeenCalledWith(dummyObserver2); }); - it('should unsubscribe componentSubscriptionContainers from passed Object that holds an instance of componentSubscriptionContainers', () => { - const dummyIntegration: any = { - dummy: 'integration', - componentSubscriptionContainers: [], - }; - const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2] - ); - componentSubscriptionContainer.removeSubscription = jest.fn(); - const componentSubscriptionContainer2 = subController.createComponentSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2] - ); - componentSubscriptionContainer2.removeSubscription = jest.fn(); + it( + 'should unsubscribe Component based Subscription Container ' + + 'from specified object (UI-Component) that contains an instance of the Component Subscription Container', + () => { + const dummyIntegration: any = { + dummy: 'integration', + componentSubscriptionContainers: [], + }; + const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( + dummyIntegration, + [dummyObserver1, dummyObserver2] + ); + componentSubscriptionContainer.removeSubscription = jest.fn(); + const componentSubscriptionContainer2 = subController.createComponentSubscriptionContainer( + dummyIntegration, + [dummyObserver1, dummyObserver2] + ); + componentSubscriptionContainer2.removeSubscription = jest.fn(); - subController.unsubscribe(dummyIntegration); + subController.unsubscribe(dummyIntegration); - expect(subController.componentSubs.size).toBe(0); + expect(Array.from(subController.componentSubs)).toStrictEqual([]); - expect(componentSubscriptionContainer.ready).toBeFalsy(); - expect( - componentSubscriptionContainer.removeSubscription - ).toHaveBeenCalledTimes(2); - expect( - componentSubscriptionContainer.removeSubscription - ).toHaveBeenCalledWith(dummyObserver1); - expect( - componentSubscriptionContainer.removeSubscription - ).toHaveBeenCalledWith(dummyObserver2); + expect(componentSubscriptionContainer.ready).toBeFalsy(); + expect( + componentSubscriptionContainer.removeSubscription + ).toHaveBeenCalledTimes(2); + expect( + componentSubscriptionContainer.removeSubscription + ).toHaveBeenCalledWith(dummyObserver1); + expect( + componentSubscriptionContainer.removeSubscription + ).toHaveBeenCalledWith(dummyObserver2); - expect(componentSubscriptionContainer2.ready).toBeFalsy(); - expect( - componentSubscriptionContainer2.removeSubscription - ).toHaveBeenCalledTimes(2); - expect( - componentSubscriptionContainer2.removeSubscription - ).toHaveBeenCalledWith(dummyObserver1); - expect( - componentSubscriptionContainer2.removeSubscription - ).toHaveBeenCalledWith(dummyObserver2); - }); + expect(componentSubscriptionContainer2.ready).toBeFalsy(); + expect( + componentSubscriptionContainer2.removeSubscription + ).toHaveBeenCalledTimes(2); + expect( + componentSubscriptionContainer2.removeSubscription + ).toHaveBeenCalledWith(dummyObserver1); + expect( + componentSubscriptionContainer2.removeSubscription + ).toHaveBeenCalledWith(dummyObserver2); + } + ); }); describe('createComponentSubscriptionContainer function tests', () => { it( - 'should return ready componentSubscriptionContainer ' + - 'and add an instance of it to the not existing componentSubscriptions property in the dummyIntegration (default config)', + 'should return ready Component based Subscription Container ' + + "and add an instance of it to the not existing 'componentSubscriptions' property " + + 'in the dummyIntegration (default config)', () => { jest.spyOn(Utils, 'generateId').mockReturnValueOnce('generatedKey'); const dummyIntegration: any = { @@ -315,34 +333,27 @@ describe('SubController Tests', () => { ); expect(componentSubscriptionContainer.ready).toBeTruthy(); - expect(subController.componentSubs.size).toBe(1); - expect( - subController.componentSubs.has(componentSubscriptionContainer) - ).toBeTruthy(); + expect(Array.from(subController.componentSubs)).toStrictEqual([ + componentSubscriptionContainer, + ]); - expect(dummyIntegration.componentSubscriptionContainers.length).toBe( - 1 - ); - expect(dummyIntegration.componentSubscriptionContainers[0]).toBe( - componentSubscriptionContainer - ); + expect( + dummyIntegration.componentSubscriptionContainers + ).toStrictEqual([componentSubscriptionContainer]); // Check if ComponentSubscriptionContainer was called with correct parameters expect(componentSubscriptionContainer.key).toBe('generatedKey'); expect(componentSubscriptionContainer.componentId).toBeUndefined(); - expect(componentSubscriptionContainer.subscribers.size).toBe(2); expect( - componentSubscriptionContainer.subscribers.has(dummyObserver1) - ).toBeTruthy(); - expect( - componentSubscriptionContainer.subscribers.has(dummyObserver2) - ).toBeTruthy(); + Array.from(componentSubscriptionContainer.subscribers) + ).toStrictEqual([dummyObserver1, dummyObserver2]); } ); it( - 'should return ready componentSubscriptionContainer ' + - 'and add an instance of it to the existing componentSubscriptions property in the dummyIntegration (default config)', + 'should return ready Component based Subscription Container ' + + "and add an instance of it to the existing 'componentSubscriptions' property " + + 'in the dummyIntegration (default config)', () => { jest.spyOn(Utils, 'generateId').mockReturnValueOnce('generatedKey'); const dummyIntegration: any = { @@ -356,18 +367,16 @@ describe('SubController Tests', () => { { waitForMount: false } ); - expect(dummyIntegration.componentSubscriptionContainers.length).toBe( - 1 - ); - expect(dummyIntegration.componentSubscriptionContainers[0]).toBe( - componentSubscriptionContainer - ); + expect( + dummyIntegration.componentSubscriptionContainers + ).toStrictEqual([componentSubscriptionContainer]); } ); it( - 'should return ready componentSubscriptionContainer ' + - 'and add an instance of it to the not existing componentSubscriptions property in the dummyIntegration (specific config)', + 'should return ready Component based Subscription Container ' + + "and add an instance of it to the not existing 'componentSubscriptions' property " + + 'in the dummyIntegration (specific config)', () => { const dummyIntegration: any = { dummy: 'integration', @@ -387,32 +396,25 @@ describe('SubController Tests', () => { ); expect(componentSubscriptionContainer.ready).toBeTruthy(); - expect(subController.componentSubs.size).toBe(1); - expect( - subController.componentSubs.has(componentSubscriptionContainer) - ).toBeTruthy(); + expect(Array.from(subController.componentSubs)).toBe([ + componentSubscriptionContainer, + ]); - expect(dummyIntegration.componentSubscriptionContainers.length).toBe( - 1 - ); - expect(dummyIntegration.componentSubscriptionContainers[0]).toBe( - componentSubscriptionContainer - ); + expect( + dummyIntegration.componentSubscriptionContainers + ).toStrictEqual([componentSubscriptionContainer]); // Check if ComponentSubscriptionContainer was called with correct parameters expect(componentSubscriptionContainer.key).toBe('dummyKey'); expect(componentSubscriptionContainer.componentId).toBe('testID'); - expect(componentSubscriptionContainer.subscribers.size).toBe(2); - expect( - componentSubscriptionContainer.subscribers.has(dummyObserver1) - ).toBeTruthy(); - expect( - componentSubscriptionContainer.subscribers.has(dummyObserver2) - ).toBeTruthy(); + expect(Array.from(componentSubscriptionContainer.subscribers)).toBe([ + dummyObserver1, + dummyObserver2, + ]); } ); - it("should return not ready componentSubscriptionContainer if componentInstance isn't mounted (waitForMount = true)", () => { + it("should return not ready Component based Subscription Container if componentInstance isn't mounted (config.waitForMount = true)", () => { jest.spyOn(Utils, 'generateId').mockReturnValueOnce('generatedKey'); const dummyIntegration: any = { dummy: 'integration', @@ -432,24 +434,20 @@ describe('SubController Tests', () => { ); expect(componentSubscriptionContainer.ready).toBeFalsy(); - expect(subController.componentSubs.size).toBe(1); - expect( - subController.componentSubs.has(componentSubscriptionContainer) - ).toBeTruthy(); + expect(Array.from(subController.componentSubs)).toBe([ + componentSubscriptionContainer, + ]); // Check if ComponentSubscriptionContainer was called with correct parameters expect(componentSubscriptionContainer.key).toBe('generatedKey'); expect(componentSubscriptionContainer.componentId).toBeUndefined(); - expect(componentSubscriptionContainer.subscribers.size).toBe(2); - expect( - componentSubscriptionContainer.subscribers.has(dummyObserver1) - ).toBeTruthy(); - expect( - componentSubscriptionContainer.subscribers.has(dummyObserver2) - ).toBeTruthy(); + expect(Array.from(componentSubscriptionContainer.subscribers)).toBe([ + dummyObserver1, + dummyObserver2, + ]); }); - it('should return ready componentSubscriptionContainer if componentInstance is mounted (config.waitForMount = true)', () => { + it('should return ready Component based Subscription Container if componentInstance is mounted (config.waitForMount = true)', () => { jest.spyOn(Utils, 'generateId').mockReturnValueOnce('generatedKey'); const dummyIntegration: any = { dummy: 'integration', @@ -470,26 +468,21 @@ describe('SubController Tests', () => { ); expect(componentSubscriptionContainer.ready).toBeTruthy(); - expect(subController.componentSubs.size).toBe(1); - expect( - subController.componentSubs.has(componentSubscriptionContainer) - ).toBeTruthy(); + expect(Array.from(subController.componentSubs)).toBe([ + componentSubscriptionContainer, + ]); // Check if ComponentSubscriptionContainer was called with correct parameters expect(componentSubscriptionContainer.key).toBe('generatedKey'); expect(componentSubscriptionContainer.componentId).toBeUndefined(); - expect(componentSubscriptionContainer.subscribers.size).toBe(2); - expect( - componentSubscriptionContainer.subscribers.has(dummyObserver1) - ).toBeTruthy(); expect( - componentSubscriptionContainer.subscribers.has(dummyObserver2) - ).toBeTruthy(); + Array.from(componentSubscriptionContainer.subscribers) + ).toStrictEqual([dummyObserver1, dummyObserver2]); }); }); describe('registerCallbackSubscription function tests', () => { - it('should return callbackSubscriptionContainer (default config)', () => { + it('should return Callback based Subscription Container (default config)', () => { jest.spyOn(Utils, 'generateId').mockReturnValueOnce('generatedKey'); const dummyIntegration = () => { /* empty function */ @@ -506,29 +499,24 @@ describe('SubController Tests', () => { expect(callbackSubscriptionContainer.callback).toBe(dummyIntegration); expect(callbackSubscriptionContainer.ready).toBeTruthy(); - expect(subController.callbackSubs.size).toBe(1); - expect( - subController.callbackSubs.has(callbackSubscriptionContainer) - ).toBeTruthy(); + expect(Array.from(subController.callbackSubs)).toStrictEqual([ + callbackSubscriptionContainer, + ]); - // TODO find a way to spy on a class constructor without overwriting it + // TODO find a way to spy on a class constructor without overwriting it. // https://stackoverflow.com/questions/48219267/how-to-spy-on-a-class-constructor-jest/48486214 - // Because the below tests are not really related to this test, - // they are checking if the CallbackSubscriptionContainer was called with the correct parameters - // by checking if CallbackSubscriptionContainer has set its properties correctly - // Note:This 'issue' happens in multiple parts of the AgileTs test + // Because the below tests are not really related to this test. + // They are checking if the CallbackSubscriptionContainer was called with the correct parameters + // by checking if the CallbackSubscriptionContainer has correctly set properties. + // Note: This 'issue' happens in multiple parts of the AgileTs test! expect(callbackSubscriptionContainer.key).toBe('generatedKey'); expect(callbackSubscriptionContainer.componentId).toBeUndefined(); - expect(callbackSubscriptionContainer.subscribers.size).toBe(2); - expect( - callbackSubscriptionContainer.subscribers.has(dummyObserver1) - ).toBeTruthy(); expect( - callbackSubscriptionContainer.subscribers.has(dummyObserver2) - ).toBeTruthy(); + Array.from(callbackSubscriptionContainer.subscribers) + ).toStrictEqual([dummyObserver1, dummyObserver2]); }); - it('should return callbackSubscriptionContainer (specific config)', () => { + it('should return Callback based Subscription Container (specific config)', () => { const dummyIntegration = () => { /* empty function */ }; @@ -549,21 +537,16 @@ describe('SubController Tests', () => { expect(callbackSubscriptionContainer.callback).toBe(dummyIntegration); expect(callbackSubscriptionContainer.ready).toBeTruthy(); - expect(subController.callbackSubs.size).toBe(1); - expect( - subController.callbackSubs.has(callbackSubscriptionContainer) - ).toBeTruthy(); + expect(Array.from(subController.callbackSubs)).toStrictEqual([ + callbackSubscriptionContainer, + ]); // Check if CallbackSubscriptionContainer was called with correct parameters expect(callbackSubscriptionContainer.key).toBe('dummyKey'); expect(callbackSubscriptionContainer.componentId).toBe('testID'); - expect(callbackSubscriptionContainer.subscribers.size).toBe(2); expect( - callbackSubscriptionContainer.subscribers.has(dummyObserver1) - ).toBeTruthy(); - expect( - callbackSubscriptionContainer.subscribers.has(dummyObserver2) - ).toBeTruthy(); + Array.from(callbackSubscriptionContainer.subscribers) + ).toStrictEqual([dummyObserver1, dummyObserver2]); }); }); @@ -581,15 +564,18 @@ describe('SubController Tests', () => { ); }); - it('should add componentInstance to mountedComponents and set its subscriptionContainer to ready', () => { - subController.mount(dummyIntegration); + it( + "should add specified 'componentInstance' to the 'mountedComponents' " + + 'and set the Subscription Container representing the mounted Component to ready', + () => { + subController.mount(dummyIntegration); - expect(componentSubscriptionContainer.ready).toBeTruthy(); - expect(subController.mountedComponents.size).toBe(1); - expect( - subController.mountedComponents.has(dummyIntegration) - ).toBeTruthy(); - }); + expect(componentSubscriptionContainer.ready).toBeTruthy(); + expect(Array.from(subController.mountedComponents)).toStrictEqual([ + dummyIntegration, + ]); + } + ); }); describe('unmount function tests', () => { @@ -607,12 +593,16 @@ describe('SubController Tests', () => { subController.mount(dummyIntegration); }); - it('should remove componentInstance from mountedComponents and set its subscriptionContainer to not ready', () => { - subController.unmount(dummyIntegration); + it( + "should remove specified 'componentInstance' to the 'mountedComponents' " + + 'and set the Subscription Container representing the mounted Component to not ready', + () => { + subController.unmount(dummyIntegration); - expect(componentSubscriptionContainer.ready).toBeFalsy(); - expect(subController.mountedComponents.size).toBe(0); - }); + expect(componentSubscriptionContainer.ready).toBeFalsy(); + expect(Array.from(subController.mountedComponents)).toStrictEqual([]); + } + ); }); }); }); From 47144960e68a3ac4e48bf12c2538498792edc1f4 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 12 Jun 2021 11:06:15 +0200 Subject: [PATCH 068/117] fixed typos in runtime.ts --- packages/core/src/runtime/index.ts | 123 +++++++------- .../core/tests/unit/runtime/runtime.test.ts | 152 ++++++++++++------ 2 files changed, 160 insertions(+), 115 deletions(-) diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index 63dc7e46..ec0b841d 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -18,29 +18,31 @@ export class Runtime { // Jobs to be performed public jobQueue: Array = []; - // Jobs that were performed and are ready to rerender + // Jobs that were performed and are ready to re-render public jobsToRerender: Array = []; - // Jobs that were performed and should be rerendered. - // However their Subscription Container isn't ready to rerender yet. - // For example when the UI-Component isn't mounted yet. + // Jobs that were performed and couldn't be re-rendered yet. + // That is the case when at least one Subscription Container (UI-Component) in the Job + // wasn't ready to update (re-render). public notReadyJobsToRerender: Set = new Set(); - // Whether the job queue is currently being actively processed + // Whether the `jobQueue` is currently being actively processed public isPerformingJobs = false; /** - * The Runtime executes and queues ingested Observer based Jobs - * to prevent race conditions and optimized rerender of subscribed Components. + * The Runtime queues and executes incoming Observer-based Jobs + * to prevent [race conditions](https://en.wikipedia.org/wiki/Race_condition#:~:text=A%20race%20condition%20or%20race,the%20possible%20behaviors%20is%20undesirable.) + * and optimized the re-rendering of the Observer's subscribed UI-Components. * - * Each provided Job will be executed when it is its turn - * by calling the Job Observer's 'perform()' method. + * Each queued Job is executed when it is its turn + * by calling the Job Observer's `perform()` method. * - * After a successful execution the Job is added to a rerender queue, - * which is firstly put into the browser's 'Bucket' and executed when resources are left. + * After successful execution, the Job is added to a re-render queue, + * which is first put into the browser's 'Bucket' and started to work off + * when resources are left. * - * The rerender queue is designed for optimizing the render count - * by combining rerender Jobs of the same Component - * and ignoring rerender requests for unmounted Components. + * The re-render queue is designed for optimizing the render count + * by batching multiple re-render Jobs of the same UI-Component + * and ignoring re-render requests for unmounted UI-Components. * * @internal * @param agileInstance - Instance of Agile the Runtime belongs to. @@ -50,15 +52,15 @@ export class Runtime { } /** - * Adds the specified Observer based Job to the internal Job queue, - * where it will be performed when it is its turn. + * Adds the specified Observer-based Job to the internal Job queue, + * where it is executed when it is its turn. * - * After a successful execution it is added to the rerender queue, - * where all the Observer's subscribed Subscription Containers - * cause rerender on Components the Observer is represented in. + * After successful execution, the Job is assigned to the re-render queue, + * where all the Observer's subscribed Subscription Containers (UI-Components) + * are updated (re-rendered). * * @public - * @param job - Job to be performed. + * @param job - Job to be added to the Job queue. * @param config - Configuration object */ public ingest(job: RuntimeJob, config: IngestConfigInterface = {}): void { @@ -82,12 +84,12 @@ export class Runtime { /** * Performs the specified Job - * and adds it to the rerender queue if necessary. + * and assigns it to the re-render queue if necessary. * - * After the execution of the provided Job it is checked whether + * After the execution of the provided Job, it is checked whether * there are still Jobs left in the Job queue. - * - If so, the next Job in the queue is performed. - * - If not, the `jobsToRerender` queue will be started to work off. + * - If so, the next Job in the `jobQueue` is performed. + * - If not, the `jobsToRerender` queue is started to work off. * * @internal * @param job - Job to be performed. @@ -101,7 +103,7 @@ export class Runtime { job.performed = true; // Ingest dependents of the Observer into runtime, - // since they depend on the Observer and have properly changed too + // since they depend on the Observer and therefore have properly changed too job.observer.dependents.forEach((observer) => observer.ingest({ perform: false }) ); @@ -115,7 +117,7 @@ export class Runtime { .info(LogCodeManager.getLog('16:01:01', [job._key]), job); // Perform Jobs as long as Jobs are left in the queue. - // If no Job is left start updating/rerendering Subscribers + // If no Job is left start updating (re-rendering) Subscription Container (UI-Components) // of the Job based on the 'jobsToRerender' queue. if (this.jobQueue.length > 0) { const performJob = this.jobQueue.shift(); @@ -132,17 +134,17 @@ export class Runtime { } /** - * Works of the `jobsToRerender` queue by updating (causing rerender on) - * the Subscription Container (subscribed Component) - * of each Job Observer. + * Processes the `jobsToRerender` queue by updating (causing a re-render on) + * the subscribed Subscription Containers (UI-Components) of each Job Observer. * - * It returns a boolean indicating whether any Subscription Container was updated or not. + * It returns a boolean indicating whether + * any Subscription Container (UI-Component) was updated (re-rendered) or not. * * @internal */ public updateSubscribers(): boolean { // Build final 'jobsToRerender' array - // based on the new 'jobsToRerender' array and the 'notReadyJobsToRerender' array. + // based on the new 'jobsToRerender' array and the 'notReadyJobsToRerender' array const jobsToRerender = this.jobsToRerender.concat( Array.from(this.notReadyJobsToRerender) ); @@ -152,21 +154,21 @@ export class Runtime { if (!this.agileInstance().hasIntegration() || jobsToRerender.length <= 0) return false; - // Extract Subscription Container from the Jobs to be rerendered + // Extract the Subscription Container to be re-rendered from the Jobs const subscriptionContainerToUpdate = this.extractToUpdateSubscriptionContainer( jobsToRerender ); if (subscriptionContainerToUpdate.length <= 0) return false; - // Update Subscription Container (trigger rerender on Components they represent) + // Update Subscription Container (trigger re-render on the UI-Component they represent) this.updateSubscriptionContainer(subscriptionContainerToUpdate); return true; } /** - * Extracts the Subscription Containers - * that should be updated from the provided Runtime Jobs. + * Extracts the Subscription Containers (UI-Components) + * to be updated (re-rendered) from the specified Runtime Jobs. * * @internal * @param jobs - Jobs from which to extract the Subscription Containers to be updated. @@ -174,12 +176,8 @@ export class Runtime { public extractToUpdateSubscriptionContainer( jobs: Array ): Array { - // Subscription Containers that have to be updated. - // Using a 'Set()' to combine several equal SubscriptionContainers into one (rerender optimisation). const subscriptionsToUpdate = new Set(); - // Check if Job Subscription Container of Jobs should be updated - // and if so add it to the 'subscriptionsToUpdate' array jobs.forEach((job) => { job.subscriptionContainersToUpdate.forEach((subscriptionContainer) => { let updateSubscriptionContainer = true; @@ -192,7 +190,6 @@ export class Runtime { ) { job.triedToUpdateCount++; this.notReadyJobsToRerender.add(job); - LogCodeManager.log( '16:02:00', [subscriptionContainer.key], @@ -208,16 +205,10 @@ export class Runtime { return; } - // Handle Selectors of Subscription Container - // (-> check if a selected part of the Observer value has changed) - updateSubscriptionContainer = - updateSubscriptionContainer && - this.handleSelectors(subscriptionContainer, job); - - // TODO has to be overthought because if it is a Component based Subscription + // TODO has to be overthought because when it is a Component based Subscription // the rerender is triggered via merging the changed properties into the Component. // Although the 'componentId' might be equal, it doesn't mean - // that the changed properties are the equal! (-> changed properties would get missing) + // that the changed properties are equal! (-> changed properties might get missing) // Check if Subscription Container with same 'componentId' // is already in the 'subscriptionToUpdate' queue (rerender optimisation) // updateSubscriptionContainer = @@ -226,6 +217,11 @@ export class Runtime { // (sc) => sc.componentId === subscriptionContainer.componentId // ) === -1; + // Check whether a selected part of the Observer value has changed + updateSubscriptionContainer = + updateSubscriptionContainer && + this.handleSelectors(subscriptionContainer, job); + // Add Subscription Container to the 'subscriptionsToUpdate' queue if (updateSubscriptionContainer) { subscriptionContainer.updatedSubscribers.add(job.observer); @@ -240,10 +236,10 @@ export class Runtime { } /** - * Updates the specified Subscription Container. + * Updates the specified Subscription Containers. * - * By updating the SubscriptionContainer a rerender is triggered - * on the Component it represents. + * Updating a Subscription Container triggers a re-render + * on the Component it represents, based on the type of the Subscription Containers. * * @internal * @param subscriptionsToUpdate - Subscription Containers to be updated. @@ -273,13 +269,13 @@ export class Runtime { /** * Maps the values of the updated Observers (`updatedSubscribers`) - * of the specified Subscription Container into a key map. + * of the specified Subscription Container into a key map object. * * The key containing the Observer value is extracted from the Observer itself * or from the Subscription Container's `subscriberKeysWeakMap`. * * @internal - * @param subscriptionContainer - Subscription Container from which the `updatedSubscribers` are to be mapped to a key map. + * @param subscriptionContainer - Subscription Container from which the `updatedSubscribers` are to be mapped into a key map. */ public getUpdatedObserverValues( subscriptionContainer: SubscriptionContainer @@ -295,15 +291,18 @@ export class Runtime { } /** - * Returns a boolean indicating whether the specified Subscription Container can be updated or not - * based on the selector functions (`selectorsWeakMap`) of the Subscription Container. + * Returns a boolean indicating whether the specified Subscription Container can be updated or not, + * based on its selector functions (`selectorsWeakMap`). * * This is done by checking the '.value' and the '.previousValue' property of the Observer represented by the Job. - * If a selected property differs, the Subscription Container is allowed to update/rerender (returns true). + * If a selected property differs, the Subscription Container (UI-Component) is allowed to update (re-render) + * and `true` is returned. + * + * If the Subscription Container has no selector function at all, `true` is returned. * * @internal - * @param subscriptionContainer - Subscription Container to be checked if it can update. - * @param job - Job containing the Observer which has subscribed the Subscription Container. + * @param subscriptionContainer - Subscription Container to be checked if it can be updated. + * @param job - Job containing the Observer that is subscribed to the Subscription Container. */ public handleSelectors( subscriptionContainer: SubscriptionContainer, @@ -313,10 +312,10 @@ export class Runtime { job.observer )?.methods; - // If no selector functions found, return true - // because no specific part of the Observer was selected - // -> The Subscription Container should update - // no matter what was updated in the Observer + // If no selector functions found, return true. + // Because no specific part of the Observer was selected. + // -> The Subscription Container should be updated + // no matter what has updated in the Observer. if (selectorMethods == null) return true; // Check if a selected part of the Observer value has changed diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts index 2182f056..b0814322 100644 --- a/packages/core/tests/unit/runtime/runtime.test.ts +++ b/packages/core/tests/unit/runtime/runtime.test.ts @@ -8,7 +8,6 @@ import { SubscriptionContainer, } from '../../../src'; import * as Utils from '@agile-ts/utils'; -import testIntegration from '../../helper/test.integration'; import { LogMock } from '../../helper/logMock'; describe('Runtime Tests', () => { @@ -24,10 +23,12 @@ describe('Runtime Tests', () => { it('should create Runtime', () => { const runtime = new Runtime(dummyAgile); + expect(runtime.agileInstance()).toBe(dummyAgile); expect(runtime.currentJob).toBeNull(); expect(runtime.jobQueue).toStrictEqual([]); - expect(runtime.notReadyJobsToRerender.size).toBe(0); expect(runtime.jobsToRerender).toStrictEqual([]); + expect(Array.from(runtime.notReadyJobsToRerender)).toStrictEqual([]); + expect(runtime.isPerformingJobs).toBeFalsy(); }); describe('Runtime Function Tests', () => { @@ -52,22 +53,21 @@ describe('Runtime Tests', () => { runtime.perform = jest.fn(); }); - it("should perform specified Job immediately if jobQueue isn't currently being processed (default config)", () => { + it("should perform specified Job immediately if jobQueue isn't being processed (default config)", () => { runtime.isPerformingJobs = false; runtime.ingest(dummyJob); - expect(runtime.jobQueue.length).toBe(0); + expect(runtime.jobQueue).toStrictEqual([]); expect(runtime.perform).toHaveBeenCalledWith(dummyJob); }); - it("shouldn't perform specified Job immediately if jobQueue is currently being processed (default config)", () => { + it("shouldn't perform specified Job immediately if jobQueue is being processed (default config)", () => { runtime.isPerformingJobs = true; runtime.ingest(dummyJob); - expect(runtime.jobQueue.length).toBe(1); - expect(runtime.jobQueue[0]).toBe(dummyJob); + expect(runtime.jobQueue).toStrictEqual([dummyJob]); expect(runtime.perform).not.toHaveBeenCalled(); }); @@ -75,7 +75,7 @@ describe('Runtime Tests', () => { runtime.isPerformingJobs = true; runtime.ingest(dummyJob, { perform: true }); - expect(runtime.jobQueue.length).toBe(0); + expect(runtime.jobQueue).toStrictEqual([]); expect(runtime.perform).toHaveBeenCalledWith(dummyJob); }); @@ -83,8 +83,7 @@ describe('Runtime Tests', () => { runtime.isPerformingJobs = false; runtime.ingest(dummyJob, { perform: false }); - expect(runtime.jobQueue.length).toBe(1); - expect(runtime.jobQueue[0]).toBe(dummyJob); + expect(runtime.jobQueue).toStrictEqual([dummyJob]); expect(runtime.perform).not.toHaveBeenCalled(); }); }); @@ -110,8 +109,8 @@ describe('Runtime Tests', () => { }); it( - 'should perform specified Job and all remaining Jobs in the jobQueue,' + - ' and call updateSubscribers if at least one performed Job needs to rerender', + "should perform specified Job and all remaining Jobs in the 'jobQueue' " + + "and call 'updateSubscribers' if at least one performed Job needs to rerender", async () => { runtime.jobQueue.push(dummyJob2); runtime.jobQueue.push(dummyJob3); @@ -125,11 +124,9 @@ describe('Runtime Tests', () => { expect(dummyObserver1.perform).toHaveBeenCalledWith(dummyJob3); expect(dummyJob3.performed).toBeTruthy(); - expect(runtime.jobQueue.length).toBe(0); - expect(runtime.jobsToRerender.length).toBe(2); - expect(runtime.jobsToRerender.includes(dummyJob1)).toBeTruthy(); - expect(runtime.jobsToRerender.includes(dummyJob2)).toBeTruthy(); - expect(runtime.jobsToRerender.includes(dummyJob3)).toBeFalsy(); + expect(runtime.isPerformingJobs).toBeFalsy(); // because Jobs were performed + expect(runtime.jobQueue).toStrictEqual([]); + expect(runtime.jobsToRerender).toStrictEqual([dummyJob1, dummyJob2]); // Sleep 5ms because updateSubscribers is called in a timeout await new Promise((resolve) => setTimeout(resolve, 5)); @@ -147,19 +144,19 @@ describe('Runtime Tests', () => { expect(dummyObserver1.perform).toHaveBeenCalledWith(dummyJob1); expect(dummyJob1.performed).toBeTruthy(); + expect(dummyObserver1.ingest).toHaveBeenCalledTimes(1); expect(dummyObserver1.ingest).toHaveBeenCalledWith({ perform: false, }); - expect(dummyObserver1.ingest).toHaveBeenCalledTimes(1); + expect(dummyObserver2.ingest).toHaveBeenCalledTimes(1); expect(dummyObserver2.ingest).toHaveBeenCalledWith({ perform: false, }); - expect(dummyObserver2.ingest).toHaveBeenCalledTimes(1); }); it( - 'should perform specified Job and all remaining Jobs in the jobQueue' + - " and shouldn't call updateSubscribes if no performed Job needs to rerender", + "should perform specified Job and all remaining Jobs in the 'jobQueue' " + + "and shouldn't call 'updateSubscribes' if no performed Job needs to rerender", async () => { dummyJob1.rerender = false; runtime.jobQueue.push(dummyJob3); @@ -171,8 +168,9 @@ describe('Runtime Tests', () => { expect(dummyObserver1.perform).toHaveBeenCalledWith(dummyJob3); expect(dummyJob3.performed).toBeTruthy(); - expect(runtime.jobQueue.length).toBe(0); - expect(runtime.jobsToRerender.length).toBe(0); + expect(runtime.isPerformingJobs).toBeFalsy(); // because Jobs were performed + expect(runtime.jobQueue).toStrictEqual([]); + expect(runtime.jobsToRerender).toStrictEqual([]); // Sleep 5ms because updateSubscribers is called in a timeout await new Promise((resolve) => setTimeout(resolve, 5)); @@ -215,21 +213,21 @@ describe('Runtime Tests', () => { it('should return false if Agile has no registered Integration', () => { dummyAgile.hasIntegration = jest.fn(() => false); - runtime.jobsToRerender.push(dummyJob1); - runtime.jobsToRerender.push(dummyJob2); + runtime.jobsToRerender = [dummyJob1, dummyJob2]; const response = runtime.updateSubscribers(); expect(response).toBeFalsy(); + expect(runtime.jobsToRerender).toStrictEqual([]); - expect(runtime.notReadyJobsToRerender.size).toBe(0); + expect(Array.from(runtime.notReadyJobsToRerender)).toStrictEqual([]); expect( runtime.extractToUpdateSubscriptionContainer ).not.toHaveBeenCalled(); expect(runtime.updateSubscriptionContainer).not.toHaveBeenCalled(); }); - it('should return false if jobsToRerender and notReadyJobsToRerender queue is empty', () => { + it('should return false if jobsToRerender and notReadyJobsToRerender queue are both empty', () => { dummyAgile.hasIntegration = jest.fn(() => true); runtime.jobsToRerender = []; runtime.notReadyJobsToRerender = new Set(); @@ -237,29 +235,36 @@ describe('Runtime Tests', () => { const response = runtime.updateSubscribers(); expect(response).toBeFalsy(); + + expect(response).toBeFalsy(); + expect(runtime.jobsToRerender).toStrictEqual([]); + expect(Array.from(runtime.notReadyJobsToRerender)).toStrictEqual([]); + expect( + runtime.extractToUpdateSubscriptionContainer + ).not.toHaveBeenCalled(); + expect(runtime.updateSubscriptionContainer).not.toHaveBeenCalled(); }); - it('should return false if no Subscription Container of the Jobs to rerender needs to update', () => { + it('should return false if no Subscription Container of the Jobs to rerender queue needs to update', () => { dummyAgile.hasIntegration = jest.fn(() => true); jest .spyOn(runtime, 'extractToUpdateSubscriptionContainer') .mockReturnValueOnce([]); - runtime.jobsToRerender.push(dummyJob1); - runtime.jobsToRerender.push(dummyJob2); - runtime.notReadyJobsToRerender.add(dummyJob3); + runtime.jobsToRerender = [dummyJob1, dummyJob2]; + runtime.notReadyJobsToRerender = new Set([dummyJob3]); const response = runtime.updateSubscribers(); expect(response).toBeFalsy(); expect(runtime.jobsToRerender).toStrictEqual([]); - expect(runtime.notReadyJobsToRerender.size).toBe(0); + expect(Array.from(runtime.notReadyJobsToRerender)).toStrictEqual([]); expect( runtime.extractToUpdateSubscriptionContainer ).toHaveBeenCalledWith([dummyJob1, dummyJob2, dummyJob3]); expect(runtime.updateSubscriptionContainer).not.toHaveBeenCalled(); }); - it('should return true if at least one Subscription Container of the Jobs to rerender needs to update', () => { + it('should return true if at least one Subscription Container of the Jobs to rerender queue needs to update', () => { dummyAgile.hasIntegration = jest.fn(() => true); jest .spyOn(runtime, 'extractToUpdateSubscriptionContainer') @@ -267,15 +272,14 @@ describe('Runtime Tests', () => { dummySubscriptionContainer1, dummySubscriptionContainer2, ]); - runtime.jobsToRerender.push(dummyJob1); - runtime.jobsToRerender.push(dummyJob2); - runtime.notReadyJobsToRerender.add(dummyJob3); + runtime.jobsToRerender = [dummyJob1, dummyJob2]; + runtime.notReadyJobsToRerender = new Set([dummyJob3]); const response = runtime.updateSubscribers(); expect(response).toBeTruthy(); expect(runtime.jobsToRerender).toStrictEqual([]); - expect(runtime.notReadyJobsToRerender.size).toBe(0); + expect(Array.from(runtime.notReadyJobsToRerender)).toStrictEqual([]); expect( runtime.extractToUpdateSubscriptionContainer ).toHaveBeenCalledWith([dummyJob1, dummyJob2, dummyJob3]); @@ -316,7 +320,7 @@ describe('Runtime Tests', () => { it( "shouldn't extract not ready Subscription Container from the specified Jobs, " + - "add it to the 'notReadyJobsToRerender' queue and print warning", + "should add it to the 'notReadyJobsToRerender' queue and print a warning", () => { jest .spyOn(runtime, 'handleSelectors') @@ -373,8 +377,8 @@ describe('Runtime Tests', () => { it( "shouldn't extract not ready Subscription Container from the specified Jobs, " + - "remove the Job when it exceeded the max 'maxOfTriesToUpdate' " + - 'and print warning', + "should remove the Job when it exceeded the max 'maxTriesToUpdate' " + + 'and print a warning', () => { jest .spyOn(runtime, 'handleSelectors') @@ -399,7 +403,7 @@ describe('Runtime Tests', () => { dummyJob1 ); - expect(Array.from(runtime.notReadyJobsToRerender)).toStrictEqual([]); // Because not ready Job was removed + expect(Array.from(runtime.notReadyJobsToRerender)).toStrictEqual([]); // Because exceeded Job was removed // Job that ran through expect( @@ -455,6 +459,7 @@ describe('Runtime Tests', () => { ); // Since the Job is ready but the Observer value simply hasn't changed + // -> no point in trying to update it again expect(Array.from(runtime.notReadyJobsToRerender)).toStrictEqual([]); // Job that didn't ran through @@ -478,7 +483,7 @@ describe('Runtime Tests', () => { expect(console.warn).toHaveBeenCalledTimes(0); }); - it('should extract ready and updated Subscription Containers', () => { + it('should extract ready and to update Subscription Containers', () => { jest .spyOn(runtime, 'handleSelectors') .mockReturnValueOnce(true) @@ -589,11 +594,17 @@ describe('Runtime Tests', () => { expect(callbackSubscriptionContainer2.callback).toHaveBeenCalledTimes( 1 ); + expect( + Array.from(callbackSubscriptionContainer2.updatedSubscribers) + ).toStrictEqual([]); // Callback Subscription Container 3 expect(callbackSubscriptionContainer3.callback).toHaveBeenCalledTimes( 1 ); + expect( + Array.from(callbackSubscriptionContainer2.updatedSubscribers) + ).toStrictEqual([]); }); }); @@ -685,19 +696,26 @@ describe('Runtime Tests', () => { { data: { name: 'hans' }, }, + { + data: { name: 'frank' }, + }, ]; dummyObserver2.previousValue = [ { - key: 'dummyObserver2Value1', data: { name: 'jeff' }, }, { - key: 'dummyObserver2Value2', data: { name: 'hans' }, }, + { + data: { name: 'frank' }, + }, ]; arraySubscriptionContainer.selectorsWeakMap.set(dummyObserver2, { - methods: [(value) => value[0]?.data?.name], + methods: [ + (value) => value[0]?.data?.name, + (value) => value[2]?.data?.name, + ], }); arrayJob = new RuntimeJob(dummyObserver2, { key: 'dummyObjectJob2' }); @@ -708,7 +726,7 @@ describe('Runtime Tests', () => { jest.clearAllMocks(); }); - it('should return true if Subscritpion Container has no selector methods', () => { + it('should return true if Subscription Container has no selector methods', () => { objectSubscriptionContainer.selectorsWeakMap.delete(dummyObserver1); const response = runtime.handleSelectors( @@ -722,8 +740,7 @@ describe('Runtime Tests', () => { it('should return true if selected property has changed (object value)', () => { dummyObserver1.value = { - key: 'dummyObserverValue1', - data: { name: 'hans' }, + data: { name: 'changedName' }, }; const response = runtime.handleSelectors( @@ -732,6 +749,8 @@ describe('Runtime Tests', () => { ); expect(response).toBeTruthy(); + + expect(Utils.notEqual).toHaveBeenCalledTimes(1); expect(Utils.notEqual).toHaveBeenCalledWith( dummyObserver1.value.data.name, dummyObserver1.previousValue.data.name @@ -745,13 +764,15 @@ describe('Runtime Tests', () => { ); expect(response).toBeFalsy(); + + expect(Utils.notEqual).toHaveBeenCalledTimes(1); expect(Utils.notEqual).toHaveBeenCalledWith( dummyObserver1.value.data.name, dummyObserver1.previousValue.data.name ); }); - // TODO the deepness check isn't possible with the custom defined selector methods + // TODO the deepness check isn't possible with the current way of handling selector methods // it('should return true if selected property has changed in the deepness (object value)', () => { // dummyObserver1.value = { // key: 'dummyObserverValue1', @@ -770,16 +791,17 @@ describe('Runtime Tests', () => { // expect(Utils.notEqual).toHaveBeenCalledWith(undefined, undefined); // }); - it('should return true if used property has changed (array value)', () => { + it('should return true if a selected property has changed (array value)', () => { dummyObserver2.value = [ { - key: 'dummyObserver2Value1', - data: { name: 'frank' }, + data: { name: 'jeff' }, }, { - key: 'dummyObserver2Value2', data: { name: 'hans' }, }, + { + data: { name: 'changedName' }, + }, ]; const response = runtime.handleSelectors( @@ -788,23 +810,47 @@ describe('Runtime Tests', () => { ); expect(response).toBeTruthy(); + + expect(Utils.notEqual).toHaveBeenCalledTimes(2); expect(Utils.notEqual).toHaveBeenCalledWith( dummyObserver2.value['0'].data.name, dummyObserver2.previousValue['0'].data.name ); + expect(Utils.notEqual).toHaveBeenCalledWith( + dummyObserver2.value['2'].data.name, + dummyObserver2.previousValue['2'].data.name + ); }); it("should return false if used property hasn't changed (array value)", () => { + dummyObserver2.value = [ + { + data: { name: 'jeff' }, + }, + { + data: { name: 'changedName (but not selected)' }, + }, + { + data: { name: 'frank' }, + }, + ]; + const response = runtime.handleSelectors( arraySubscriptionContainer, arrayJob ); expect(response).toBeFalsy(); + + expect(Utils.notEqual).toHaveBeenCalledTimes(2); expect(Utils.notEqual).toHaveBeenCalledWith( dummyObserver2.value['0'].data.name, dummyObserver2.previousValue['0'].data.name ); + expect(Utils.notEqual).toHaveBeenCalledWith( + dummyObserver2.value['2'].data.name, + dummyObserver2.previousValue['2'].data.name + ); }); }); }); From 55e496a827a775fc9ec4d7add107f0fbb59d3b39 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 12 Jun 2021 11:35:49 +0200 Subject: [PATCH 069/117] fixed typos --- packages/core/src/state/state.observer.ts | 5 ++++- .../core/tests/unit/runtime/observer.test.ts | 5 ++++- .../subscription/sub.controller.test.ts | 20 +++++++++---------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/core/src/state/state.observer.ts b/packages/core/src/state/state.observer.ts index fd0182dc..a8c986dd 100644 --- a/packages/core/src/state/state.observer.ts +++ b/packages/core/src/state/state.observer.ts @@ -14,6 +14,7 @@ import { SideEffectInterface, createArrayFromObject, CreateStateRuntimeJobConfigInterface, + generateId, } from '../internal'; export class StateObserver extends Observer { @@ -100,7 +101,9 @@ export class StateObserver extends Observer { force: config.force, background: config.background, overwrite: config.overwrite, - key: config.key || this._key, + key: + config.key ?? + `${this._key != null ? this._key + '_' : ''}${generateId()}`, }); this.agileInstance().runtime.ingest(job, { diff --git a/packages/core/tests/unit/runtime/observer.test.ts b/packages/core/tests/unit/runtime/observer.test.ts index 525dcca7..7d246abd 100644 --- a/packages/core/tests/unit/runtime/observer.test.ts +++ b/packages/core/tests/unit/runtime/observer.test.ts @@ -4,6 +4,7 @@ import { SubscriptionContainer, RuntimeJob, } from '../../../src'; +import * as Utils from '@agile-ts/utils'; import { LogMock } from '../../helper/logMock'; describe('Observer Tests', () => { @@ -88,8 +89,10 @@ describe('Observer Tests', () => { describe('ingest function tests', () => { it('should create RuntimeJob containing the Observer and ingest it into the Runtime (default config)', () => { + jest.spyOn(Utils, 'generateId').mockReturnValueOnce('generatedKey'); + dummyAgile.runtime.ingest = jest.fn((job: RuntimeJob) => { - expect(job._key).toBe(observer._key); + expect(job._key).toBe(`${observer._key}_generatedKey`); expect(job.observer).toBe(observer); expect(job.config).toStrictEqual({ background: false, diff --git a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts index 1d60caf0..e52e81a1 100644 --- a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts +++ b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts @@ -396,7 +396,7 @@ describe('SubController Tests', () => { ); expect(componentSubscriptionContainer.ready).toBeTruthy(); - expect(Array.from(subController.componentSubs)).toBe([ + expect(Array.from(subController.componentSubs)).toStrictEqual([ componentSubscriptionContainer, ]); @@ -407,10 +407,9 @@ describe('SubController Tests', () => { // Check if ComponentSubscriptionContainer was called with correct parameters expect(componentSubscriptionContainer.key).toBe('dummyKey'); expect(componentSubscriptionContainer.componentId).toBe('testID'); - expect(Array.from(componentSubscriptionContainer.subscribers)).toBe([ - dummyObserver1, - dummyObserver2, - ]); + expect( + Array.from(componentSubscriptionContainer.subscribers) + ).toStrictEqual([dummyObserver1, dummyObserver2]); } ); @@ -434,17 +433,16 @@ describe('SubController Tests', () => { ); expect(componentSubscriptionContainer.ready).toBeFalsy(); - expect(Array.from(subController.componentSubs)).toBe([ + expect(Array.from(subController.componentSubs)).toStrictEqual([ componentSubscriptionContainer, ]); // Check if ComponentSubscriptionContainer was called with correct parameters expect(componentSubscriptionContainer.key).toBe('generatedKey'); expect(componentSubscriptionContainer.componentId).toBeUndefined(); - expect(Array.from(componentSubscriptionContainer.subscribers)).toBe([ - dummyObserver1, - dummyObserver2, - ]); + expect( + Array.from(componentSubscriptionContainer.subscribers) + ).toStrictEqual([dummyObserver1, dummyObserver2]); }); it('should return ready Component based Subscription Container if componentInstance is mounted (config.waitForMount = true)', () => { @@ -468,7 +466,7 @@ describe('SubController Tests', () => { ); expect(componentSubscriptionContainer.ready).toBeTruthy(); - expect(Array.from(subController.componentSubs)).toBe([ + expect(Array.from(subController.componentSubs)).toStrictEqual([ componentSubscriptionContainer, ]); From 2a2e75cfc29e17cb3fabc0ae23a0f35f57ac5724 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 12 Jun 2021 14:49:33 +0200 Subject: [PATCH 070/117] optimized state observer method descriptions --- examples/vue/develop/my-project/src/core.js | 6 +- packages/core/src/state/index.ts | 7 +- packages/core/src/state/state.observer.ts | 113 +++++++++++------- .../tests/unit/state/state.observer.test.ts | 30 +++-- 4 files changed, 97 insertions(+), 59 deletions(-) diff --git a/examples/vue/develop/my-project/src/core.js b/examples/vue/develop/my-project/src/core.js index ecb19e8b..2e3f7902 100644 --- a/examples/vue/develop/my-project/src/core.js +++ b/examples/vue/develop/my-project/src/core.js @@ -7,7 +7,11 @@ export const App = new Agile({ }).integrate(vueIntegration); // Create State -export const MY_STATE = App.createState('Hello World', { key: 'my-state' }); +export const MY_STATE = App.createState('World', { + key: 'my-state', +}).computeValue((v) => { + return `Hello ${v}`; +}); // Create Collection export const TODOS = App.createCollection({ diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index 1e70c201..3dc8a1fd 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -218,9 +218,9 @@ export class State { /** * Ingests the State without any specified new value into the runtime. * - * Since no new value was defined either the State value is computed + * Since no new value was defined either the new State value is computed * based on a compute method (Computed Class) - * or the `nextStateValue` is taken. + * or the `nextStateValue` is taken as the next State value. * * [Learn more..](https://agile-ts.org/docs/core/state/methods/#ingest) * @@ -610,7 +610,8 @@ export class State { this.computeValueMethod = method; // Initial compute - this.set(method(this.nextStateValue)); + // (not directly computing it here since it is computed once in the runtime!) + this.set(this.nextStateValue); return this; } diff --git a/packages/core/src/state/state.observer.ts b/packages/core/src/state/state.observer.ts index a8c986dd..7b058fb7 100644 --- a/packages/core/src/state/state.observer.ts +++ b/packages/core/src/state/state.observer.ts @@ -18,14 +18,20 @@ import { } from '../internal'; export class StateObserver extends Observer { + // State the Observer belongs to public state: () => State; - public nextStateValue: ValueType; // Next State value + + // Next value applied to the State + public nextStateValue: ValueType; /** + * A State Observer manages the subscriptions to Subscription Containers (UI-Components) + * and dependencies to other Observers (Agile Classes) + * for a State Class. + * * @internal - * State Observer - Handles State changes, dependencies (-> Interface to Runtime) - * @param state - State - * @param config - Config + * @param state - Instance of State the Observer belongs to. + * @param config - Configuration object */ constructor( state: State, @@ -36,13 +42,16 @@ export class StateObserver extends Observer { this.nextStateValue = copy(state._value); } - //========================================================================================================= - // Ingest - //========================================================================================================= /** + * Passes the State Observer into the runtime wrapped into a Runtime-Job + * where it is executed accordingly. + * + * During the execution the runtime applies the `nextStateValue` + * or the `computedValue` (Computed Class) to the State, + * updates its dependents and re-renders the UI-Components it is subscribed to. + * * @internal - * Ingests nextStateValue or computedValue into Runtime and applies it to the State - * @param config - Config + * @param config - Configuration object */ public ingest(config: StateIngestConfigInterface = {}): void { const state = this.state(); @@ -54,14 +63,16 @@ export class StateObserver extends Observer { this.ingestValue(newStateValue, config); } - //========================================================================================================= - // Ingest Value - //========================================================================================================= /** + * Passes the State Observer into the runtime wrapped into a Runtime-Job + * where it is executed accordingly. + * + * During the execution the runtime applies the specified `newStateValue` to the State, + * updates its dependents and re-renders the UI-Components it is subscribed to. + * * @internal - * Ingests new State Value into Runtime and applies it to the State - * @param newStateValue - New Value of the State - * @param config - Config + * @param newStateValue - New value to be applied to the State. + * @param config - Configuration object. */ public ingestValue( newStateValue: ValueType, @@ -80,21 +91,22 @@ export class StateObserver extends Observer { overwrite: false, }); - // Force overwriting State because if assigning Value to State, the State shouldn't be a placeholder anymore + // Force overwriting the State value if it was a placeholder. + // Because after assigning a value to the State it shouldn't be a placeholder anymore. if (state.isPlaceholder) { config.force = true; config.overwrite = true; } - // Assign next State Value and compute it if necessary + // Assign next State value and compute it if necessary this.nextStateValue = state.computeValueMethod ? copy(state.computeValueMethod(newStateValue)) : copy(newStateValue); - // Check if State Value and new/next Value are equals + // Check if current State value and to assign State value are equals if (equal(state._value, this.nextStateValue) && !config.force) return; - // Create Job + // Create Runtime-Job const job = new StateRuntimeJob(this, { storage: config.storage, sideEffects: config.sideEffects, @@ -106,23 +118,26 @@ export class StateObserver extends Observer { `${this._key != null ? this._key + '_' : ''}${generateId()}`, }); + // Pass created Job into the Runtime this.agileInstance().runtime.ingest(job, { perform: config.perform, }); } - //========================================================================================================= - // Perform - //========================================================================================================= /** + * Method executed by the Runtime to perform the Runtime-Job, + * previously ingested via the `ingest()` or `ingestValue()` method. + * + * Thereby the previously defined `nextStateValue` is assigned to the State + * and the side effects (`sideEffects`) are executed. + * * @internal - * Performs Job that holds this Observer - * @param job - Job + * @param job - Runtime-Job to be performed. */ public perform(job: StateRuntimeJob) { const state = job.observer.state(); - // Assign new State Values + // Assign new State values state.previousStateValue = copy(state._value); state._value = copy(job.observer.nextStateValue); state.nextStateValue = copy(job.observer.nextStateValue); @@ -130,7 +145,7 @@ export class StateObserver extends Observer { // https://www.geeksforgeeks.org/object-freeze-javascript/#:~:text=Object.freeze()%20Method&text=freeze()%20which%20is%20used,the%20prototype%20of%20the%20object. // if (typeof state._value === 'object') Object.freeze(state._value); - // Overwrite old State Values + // Overwrite entire State with the newly assigned value if (job.config.overwrite) { state.initialStateValue = copy(state._value); state.previousStateValue = copy(state._value); @@ -138,35 +153,37 @@ export class StateObserver extends Observer { } state.isSet = notEqual(state._value, state.initialStateValue); - - // Execute sideEffects like 'rebuildGroup' or 'rebuildStateStorageValue' this.sideEffects(job); - // Assign Public Value to Observer after sideEffects like 'rebuildGroup', - // because sometimes (for instance in a Group State) the publicValue() is not the .value (nextStateValue) property. - // The Observer value is at some point the public Value because Integrations like React are using it as return value. - // For example 'useAgile()' returns the Observer.value and not the State.value. + // Assign public value to the Observer after sideEffects like 'rebuildGroup' were executed. + // Because sometimes (for instance in a Group State) the 'publicValue()' + // is not the '.value' ('nextStateValue') property. + // The Observer value is at some point the public value + // since Integrations like React are using it as the return value. + // (For example 'useAgile()' returns 'Observer.value' and not 'State.value'.) job.observer.previousValue = copy(job.observer.value); job.observer.value = copy(state.getPublicValue()); } - //========================================================================================================= - // Side Effect - //========================================================================================================= /** + * Performs the side effects of applying the next State value to the State. + * + * Side effects are, for example, calling the watcher functions + * or executing the side effects defined in the State Class + * like 'rebuildGroup' or 'rebuildStateStorageValue'. + * * @internal - * SideEffects of Job - * @param job - Job + * @param job - Job that is currently performed. */ public sideEffects(job: StateRuntimeJob) { const state = job.observer.state(); - // Call Watchers Functions + // Call watcher functions for (const watcherKey in state.watchers) if (isFunction(state.watchers[watcherKey])) state.watchers[watcherKey](state.getPublicValue(), watcherKey); - // Call SideEffect Functions + // Call side effect functions if (job.config?.sideEffects?.enabled) { const sideEffectArray = createArrayFromObject< SideEffectInterface> @@ -174,6 +191,7 @@ export class StateObserver extends Observer { sideEffectArray.sort(function (a, b) { return b.instance.weight - a.instance.weight; }); + for (const sideEffect of sideEffectArray) { if (isFunction(sideEffect.instance.callback)) { if (!job.config.sideEffects.exclude?.includes(sideEffect.key)) @@ -184,14 +202,21 @@ export class StateObserver extends Observer { } } -/** - * @param dependents - Initial Dependents of State Observer - * @param subs - Initial Subscriptions of State Observer - * @param key - Key/Name of State Observer - */ export interface CreateStateObserverConfigInterface { + /** + * Initial Observers to depend on the State Observer. + * @default [] + */ dependents?: Array; + /** + * Initial Subscription Containers the State Observer is subscribed to. + * @default [] + */ subs?: Array; + /** + * Key/Name identifier of the State Observer. + * @default undefined + */ key?: ObserverKey; } diff --git a/packages/core/tests/unit/state/state.observer.test.ts b/packages/core/tests/unit/state/state.observer.test.ts index e187a34b..c9d837e8 100644 --- a/packages/core/tests/unit/state/state.observer.test.ts +++ b/packages/core/tests/unit/state/state.observer.test.ts @@ -8,6 +8,7 @@ import { StatePersistent, SubscriptionContainer, } from '../../../src'; +import * as Utils from '@agile-ts/utils'; import { LogMock } from '../../helper/logMock'; describe('StateObserver Tests', () => { @@ -38,8 +39,8 @@ describe('StateObserver Tests', () => { it('should create StateObserver (specific config)', () => { const dummyObserver1 = new Observer(dummyAgile, { key: 'dummyObserver1' }); const dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' }); - const dummySubscription1 = new SubscriptionContainer(); - const dummySubscription2 = new SubscriptionContainer(); + const dummySubscription1 = new SubscriptionContainer([]); + const dummySubscription2 = new SubscriptionContainer([]); const stateObserver = new StateObserver(dummyState, { key: 'testKey', @@ -145,8 +146,10 @@ describe('StateObserver Tests', () => { }); it("should ingest State into Runtime if newValue isn't equal to currentValue (default config)", () => { + jest.spyOn(Utils, 'generateId').mockReturnValue('randomKey'); + dummyAgile.runtime.ingest = jest.fn((job: StateRuntimeJob) => { - expect(job._key).toBe(stateObserver._key); + expect(job._key).toBe(`${stateObserver._key}_randomKey`); expect(job.observer).toBe(stateObserver); expect(job.config).toStrictEqual({ background: false, @@ -215,9 +218,10 @@ describe('StateObserver Tests', () => { }); it('should ingest State into Runtime if newValue is equal to currentValue (config.force = true)', () => { + jest.spyOn(Utils, 'generateId').mockReturnValue('randomKey'); dummyState._value = 'updatedDummyValue'; dummyAgile.runtime.ingest = jest.fn((job: StateRuntimeJob) => { - expect(job._key).toBe(stateObserver._key); + expect(job._key).toBe(`${stateObserver._key}_randomKey`); expect(job.observer).toBe(stateObserver); expect(job.config).toStrictEqual({ background: false, @@ -243,8 +247,9 @@ describe('StateObserver Tests', () => { }); it('should ingest placeholder State into Runtime (default config)', () => { + jest.spyOn(Utils, 'generateId').mockReturnValue('randomKey'); dummyAgile.runtime.ingest = jest.fn((job: StateRuntimeJob) => { - expect(job._key).toBe(stateObserver._key); + expect(job._key).toBe(`${stateObserver._key}_randomKey`); expect(job.observer).toBe(stateObserver); expect(job.config).toStrictEqual({ background: false, @@ -302,11 +307,11 @@ describe('StateObserver Tests', () => { it('should perform Job', () => { dummyJob.observer.nextStateValue = 'newValue'; + dummyJob.observer.value = 'dummyValue'; dummyState.initialStateValue = 'initialValue'; dummyState._value = 'dummyValue'; dummyState.getPublicValue = jest .fn() - .mockReturnValueOnce('previousPublicValue') .mockReturnValueOnce('newPublicValue'); stateObserver.perform(dummyJob); @@ -316,20 +321,21 @@ describe('StateObserver Tests', () => { expect(dummyState._value).toBe('newValue'); expect(dummyState.nextStateValue).toBe('newValue'); expect(dummyState.isSet).toBeTruthy(); + expect(stateObserver.value).toBe('newPublicValue'); - expect(stateObserver.previousValue).toBe('previousPublicValue'); + expect(stateObserver.previousValue).toBe('dummyValue'); expect(stateObserver.sideEffects).toHaveBeenCalledWith(dummyJob); }); it('should perform Job and overwrite State (job.config.overwrite = true)', () => { dummyJob.observer.nextStateValue = 'newValue'; + dummyJob.observer.value = 'dummyValue'; dummyJob.config.overwrite = true; dummyState.isPlaceholder = true; dummyState.initialStateValue = 'overwriteValue'; dummyState._value = 'dummyValue'; dummyState.getPublicValue = jest .fn() - .mockReturnValueOnce('previousPublicValue') .mockReturnValueOnce('newPublicValue'); stateObserver.perform(dummyJob); @@ -340,18 +346,19 @@ describe('StateObserver Tests', () => { expect(dummyState.nextStateValue).toBe('newValue'); expect(dummyState.isSet).toBeFalsy(); expect(dummyState.isPlaceholder).toBeFalsy(); + expect(stateObserver.value).toBe('newPublicValue'); - expect(stateObserver.previousValue).toBe('previousPublicValue'); + expect(stateObserver.previousValue).toBe('dummyValue'); expect(stateObserver.sideEffects).toHaveBeenCalledWith(dummyJob); }); it('should perform Job and set isSet to false if initialStateValue equals to newStateValue', () => { dummyJob.observer.nextStateValue = 'newValue'; + dummyJob.observer.value = 'dummyValue'; dummyState.initialStateValue = 'newValue'; dummyState._value = 'dummyValue'; dummyState.getPublicValue = jest .fn() - .mockReturnValueOnce('previousPublicValue') .mockReturnValueOnce('newPublicValue'); stateObserver.perform(dummyJob); @@ -361,8 +368,9 @@ describe('StateObserver Tests', () => { expect(dummyState._value).toBe('newValue'); expect(dummyState.nextStateValue).toBe('newValue'); expect(dummyState.isSet).toBeFalsy(); + expect(stateObserver.value).toBe('newPublicValue'); - expect(stateObserver.previousValue).toBe('previousPublicValue'); + expect(stateObserver.previousValue).toBe('dummyValue'); expect(stateObserver.sideEffects).toHaveBeenCalledWith(dummyJob); }); }); From 0c2cd5ee9e40d57c740e63eba20bea3a27aeede7 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 12 Jun 2021 17:45:08 +0200 Subject: [PATCH 071/117] fixed typos --- .../src/collection/collection.persistent.ts | 78 ++--- packages/core/src/state/state.persistent.ts | 159 ++++------ packages/core/src/state/state.runtime.job.ts | 20 +- packages/core/src/storages/persistent.ts | 58 +++- .../collection/collection.persistent.test.ts | 111 +------ .../tests/unit/state/state.persistent.test.ts | 295 +++++++++--------- .../unit/state/state.runtime.job.test.ts | 6 +- .../tests/unit/storages/persistent.test.ts | 68 ++++ 8 files changed, 373 insertions(+), 422 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 380451b8..ee835da6 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -53,35 +53,6 @@ export class CollectionPersistent< if (this.ready && config.instantiate) this.initialLoading(); } - /** - * Updates key/name identifier of Persistent. - * - * @internal - * @param value - New key/name identifier. - */ - public async setKey(value?: StorageKey): Promise { - const oldKey = this._key; - const wasReady = this.ready; - - // Assign new key to Persistent - if (value === this._key) return; - this._key = value ?? Persistent.placeHolderKey; - - const isValid = this.validatePersistent(); - - // Try to initial load value if persistent wasn't ready before - if (!wasReady) { - if (isValid) await this.initialLoading(); - return; - } - - // Remove persisted values at old key - await this.removePersistedValue(oldKey); - - // Persist values at the new key - if (isValid) await this.persistValue(value); - } - /** * Loads the persisted value into the Collection * or persists the Collection value in the corresponding Storage. @@ -97,10 +68,12 @@ export class CollectionPersistent< /** * Loads Collection Instances (like Items or Groups) from the corresponding Storage - * and sets up side effects that dynamically update the Storage value when the Collection (Instances) changes. + * and sets up side effects that dynamically update + * the Storage value when the Collection (Instances) changes. * * @internal - * @param storageItemKey - Prefix key of persisted Collection Instances. | default = Persistent.key | + * @param storageItemKey - Prefix Storage key of the persisted Collection Instances. + * | default = Persistent.key | * @return Whether the loading was successful. */ public async loadPersistedValue( @@ -109,13 +82,14 @@ export class CollectionPersistent< if (!this.ready) return false; const _storageItemKey = storageItemKey ?? this._key; - // Check if Collection is already persisted (indicated by the persistence of true at _storageItemKey) + // Check if Collection is already persisted + // (indicated by the persistence of 'true' at '_storageItemKey') const isPersisted = await this.agileInstance().storages.get( _storageItemKey, this.config.defaultStorageKey as any ); - // Return false if Collection isn't persisted yet + // Return 'false' if Collection isn't persisted yet if (!isPersisted) return false; // Helper function to load persisted values into the Collection @@ -191,7 +165,8 @@ export class CollectionPersistent< }; const success = await loadValuesIntoCollection(); - // Setup Side Effects to keep the Storage value in sync with the Collection (Instances) value + // Setup Side Effects to keep the Storage value in sync + // with the Collection (Instances) value if (success) this.setupSideEffects(_storageItemKey); return success; @@ -199,10 +174,12 @@ export class CollectionPersistent< /** * Persists Collection Instances (like Items or Groups) in the corresponding Storage - * and sets up side effects that dynamically update the Storage value when the Collection (Instances) changes. + * and sets up side effects that dynamically update + * the Storage value when the Collection (Instances) changes. * * @internal - * @param storageItemKey - Prefix key of persisted Collection Instances. | default = Persistent.key | + * @param storageItemKey - Prefix Storage key of the persisted Collection Instances. + * | default = Persistent.key | * @return Whether the persisting and the setting up of the side effects was successful. */ public async persistValue(storageItemKey?: PersistentKey): Promise { @@ -239,7 +216,8 @@ export class CollectionPersistent< }); } - // Setup Side Effects to keep the Storage value in sync with the Collection (Instances) value + // Setup Side Effects to keep the Storage value in sync + // with the Collection (Instances) value this.setupSideEffects(_storageItemKey); this.isPersisted = true; @@ -247,17 +225,19 @@ export class CollectionPersistent< } /** - * Sets up side effects to keep the Storage value in sync with the Collection (Instances) value. + * Sets up side effects to keep the Storage value in sync + * with the Collection (Instances) value. * * @internal - * @param storageItemKey - Prefix key of persisted Collection Instances. | default = Persistent.key | + * @param storageItemKey - Prefix Storage key of the persisted Collection Instances. + * | default = Persistent.key | */ public setupSideEffects(storageItemKey?: PersistentKey): void { const _storageItemKey = storageItemKey ?? this._key; const defaultGroup = this.collection().getDefaultGroup(); if (defaultGroup == null) return; - // Add side effect to default Group + // Add side effect to the default Group // that adds and removes Items from the Storage based on the Group value defaultGroup.addSideEffect( CollectionPersistent.defaultGroupSideEffectKey, @@ -267,12 +247,13 @@ export class CollectionPersistent< } /** - * Removes Collection from the corresponding Storage. + * Removes the Collection from the corresponding Storage. * -> Collection is no longer persisted * * @internal - * @param storageItemKey - Prefix key of persisted Collection Instances. | default = Persistent.key | - * @return Whether the removing was successful. + * @param storageItemKey - Prefix Storage key of the persisted Collection Instances. + * | default = Persistent.key | + * @return Whether the removal of the persisted value was successful. */ public async removePersistedValue( storageItemKey?: PersistentKey @@ -310,12 +291,12 @@ export class CollectionPersistent< } /** - * Formats given key so that it can be used as a valid Storage key and returns it. - * If no formatable key (undefined/null) is given, - * an attempt is made to use the Collection key. + * Formats specified key so that it can be used as a valid Storage key and returns it. + * If no formatable key (undefined/null) was provided, + * an attempt is made to use the Collection identifier key. * * @internal - * @param key - Key to be formatted + * @param key - Key to be formatted. */ public formatKey(key: StorageKey | undefined | null): StorageKey | undefined { if (key == null && this.collection()._key) return this.collection()._key; @@ -329,7 +310,8 @@ export class CollectionPersistent< * * @internal * @param group - Group whose Items are to be dynamically added or removed from the Storage. - * @param storageItemKey - Prefix key of persisted Collection Instances. | default = Persistent.key | + * @param storageItemKey - Prefix Storage key of the persisted Collection Instances. + * | default = Persistent.key | */ public rebuildStorageSideEffect( group: Group, diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index 89a7063a..ef8f1b16 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -4,7 +4,6 @@ import { Persistent, PersistentKey, State, - StorageKey, } from '../internal'; export class StatePersistent extends Persistent { @@ -12,10 +11,11 @@ export class StatePersistent extends Persistent { public state: () => State; /** + * Internal Class for managing the permanent persistence of a State. + * * @internal - * State Persist Manager - Handles permanent storing of State Value - * @param state - State that gets stored - * @param config - Config + * @param state - State to be persisted. + * @param config - Configuration object */ constructor( state: State, @@ -36,47 +36,16 @@ export class StatePersistent extends Persistent { defaultStorageKey: config.defaultStorageKey, }); - // Load/Store persisted Value for the first Time + // Load/Store persisted value/s for the first time if (this.ready && config.instantiate) this.initialLoading(); } - //========================================================================================================= - // Set Key - //========================================================================================================= - /** - * @internal - * Updates Key/Name of Persistent - * @param value - New Key/Name of Persistent - */ - public async setKey(value?: StorageKey): Promise { - const oldKey = this._key; - const wasReady = this.ready; - - // Assign Key - if (value === this._key) return; - this._key = value || Persistent.placeHolderKey; - - const isValid = this.validatePersistent(); - - // Try to Initial Load Value if persistent wasn't ready and return - if (!wasReady) { - if (isValid) await this.initialLoading(); - return; - } - - // Remove value at old Key - await this.removePersistedValue(oldKey); - - // Assign Value to new Key - if (isValid) await this.persistValue(value); - } - - //========================================================================================================= - // Initial Loading - //========================================================================================================= /** + * Loads the persisted value into the State + * or persists the State value in the corresponding Storage. + * This behaviour depends on whether the State has been persisted before. + * * @internal - * Loads/Saves Storage Value for the first Time */ public async initialLoading() { super.initialLoading().then(() => { @@ -84,14 +53,15 @@ export class StatePersistent extends Persistent { }); } - //========================================================================================================= - // Load Persisted Value - //========================================================================================================= /** + * Loads the State from the corresponding Storage + * and sets up side effects that dynamically update + * the Storage value when the State changes. + * * @internal - * Loads State Value from the Storage - * @param storageItemKey - Prefix Key of Persisted Instances (default PersistentKey) - * @return Success? + * @param storageItemKey - Storage key of the persisted State Instance. + * | default = Persistent.key | + * @return Whether the loading was successful. */ public async loadPersistedValue( storageItemKey?: PersistentKey @@ -99,40 +69,43 @@ export class StatePersistent extends Persistent { if (!this.ready) return false; const _storageItemKey = storageItemKey ?? this._key; - // Load Value from default Storage + // Load value from default Storage const loadedValue = await this.agileInstance().storages.get( _storageItemKey, this.config.defaultStorageKey as any ); if (loadedValue == null) return false; - // Assign loaded Value to State + // Assign loaded value to the State this.state().set(loadedValue, { storage: false, overwrite: true, }); - // Setup Side Effects to keep the Storage value in sync with the State value - this.setupSideEffects(storageItemKey); + // Setup Side Effects to keep the Storage value in sync + // with the State value + this.setupSideEffects(_storageItemKey); return true; } - //========================================================================================================= - // Persist Value - //========================================================================================================= /** + * Persists the State in the corresponding Storage + * and sets up side effects that dynamically update + * the Storage value when the State changes. + * * @internal - * Sets everything up so that the State is saved in the Storage on every Value change - * @param storageItemKey - Prefix Key of Persisted Instances (default PersistentKey) - * @return Success? + * @param storageItemKey - Storage key of the persisted State Instance. + * | default = Persistent.key | + * @return Whether the persisting and the setting up of the side effects was successful. */ public async persistValue(storageItemKey?: PersistentKey): Promise { if (!this.ready) return false; const _storageItemKey = storageItemKey ?? this._key; - // Setup side effects to keep the Storage value in sync with the State value - this.setupSideEffects(storageItemKey); + // Setup Side Effects to keep the Storage value in sync + // with the State value + this.setupSideEffects(_storageItemKey); // Initial rebuild Storage for persisting State value in the corresponding Storage this.rebuildStorageSideEffect(this.state(), _storageItemKey); @@ -142,16 +115,18 @@ export class StatePersistent extends Persistent { } /** - * Sets up side effects to keep the Storage value in sync with the State value. + * Sets up side effects to keep the Storage value in sync + * with the State value. * * @internal - * @param storageItemKey - Prefix key of persisted Collection Instances | default = Persistent.key | + * @param storageItemKey - Storage key of the persisted State Instance. + * | default = Persistent.key | */ public setupSideEffects(storageItemKey?: PersistentKey) { const _storageItemKey = storageItemKey ?? this._key; - // Add side effect to State - // that updates the Storage value based on the State value + // Add side effect to the State + // that updates the Storage value based on the current State value this.state().addSideEffect( StatePersistent.storeValueSideEffectKey, (instance, config) => { @@ -161,76 +136,64 @@ export class StatePersistent extends Persistent { ); } - //========================================================================================================= - // Remove Persisted Value - //========================================================================================================= /** + * Removes the State from the corresponding Storage. + * -> State is no longer persisted + * * @internal - * Removes State Value form the Storage - * @param storageItemKey - Prefix Key of Persisted Instances (default PersistentKey) - * @return Success? + * @param storageItemKey - Storage key of the persisted State Instance. + * | default = Persistent.key | + * @return Whether the removal of the persisted value was successful. */ public async removePersistedValue( storageItemKey?: PersistentKey ): Promise { if (!this.ready) return false; const _storageItemKey = storageItemKey || this._key; - - // Remove SideEffect this.state().removeSideEffect(StatePersistent.storeValueSideEffectKey); - - // Remove Value from Storage this.agileInstance().storages.remove(_storageItemKey, this.storageKeys); - this.isPersisted = false; return true; } - //========================================================================================================= - // Format Key - //========================================================================================================= /** + * Formats specified key so that it can be used as a valid Storage key and returns it. + * If no formatable key (undefined/null) was provided, + * an attempt is made to use the State identifier key. + * * @internal - * Formats Storage Key - * @param key - Key that gets formatted + * @param key - Key to be formatted. */ public formatKey( key: PersistentKey | undefined | null ): PersistentKey | undefined { const state = this.state(); - - // Get key from State if (!key && state._key) return state._key; - if (!key) return; - - // Set State Key to Storage Key if State has no key if (!state._key) state._key = key; - return key; } - //========================================================================================================= - // Rebuild Storage SideEffect - //========================================================================================================= /** + * Rebuilds Storage value based on the current State value + * * @internal - * Rebuilds Storage depending on the State Value (Saves current State Value into the Storage) - * @param state - State that holds the new Value - * @param storageItemKey - StorageKey where value should be persisted - * @param config - Config + * @param state - State whose value to be in sync with the Storage value. + * @param storageItemKey - Storage key of the persisted State. + * | default = Persistent.key | + * @param config - Configuration object */ public rebuildStorageSideEffect( state: State, storageItemKey: PersistentKey, config: { [key: string]: any } = {} ) { - if (config.storage !== undefined && !config.storage) return; - - this.agileInstance().storages.set( - storageItemKey, - this.state().getPersistableValue(), - this.storageKeys - ); + if (config['storage'] == null || config.storage) { + this.agileInstance().storages.set( + storageItemKey, + this.state().getPersistableValue(), + this.storageKeys + ); + } } } diff --git a/packages/core/src/state/state.runtime.job.ts b/packages/core/src/state/state.runtime.job.ts index 5bd298f6..af57a67d 100644 --- a/packages/core/src/state/state.runtime.job.ts +++ b/packages/core/src/state/state.runtime.job.ts @@ -35,20 +35,26 @@ export class StateRuntimeJob extends RuntimeJob { } } -/** - * @param key - Key/Name of Job - */ export interface CreateStateRuntimeJobConfigInterface extends StateRuntimeJobConfigInterface { + /** + * Key/Name identifier of the Runtime Job. + * @default undefined + */ key?: RuntimeJobKey; } -/** - * @param overwrite - If whole State Value gets overwritten with Job Value - * @param storage - If Job Value can be saved in Storage - */ export interface StateRuntimeJobConfigInterface extends RuntimeJobConfigInterface { + /** + * Whether to overwrite the whole State with the new State value. + * @default false + */ overwrite?: boolean; + /** + * If the State is persisted, + * whether to store the new State value in an external Storage + * @default true + */ storage?: boolean; } diff --git a/packages/core/src/storages/persistent.ts b/packages/core/src/storages/persistent.ts index 2fa36634..6c521778 100644 --- a/packages/core/src/storages/persistent.ts +++ b/packages/core/src/storages/persistent.ts @@ -52,31 +52,51 @@ export class Persistent { } /** - * @internal - * Set Key/Name of Persistent + * Updates the key/name identifier of the Persistent. + * + * @public + * @param value - New key/name identifier. */ public set key(value: StorageKey) { this.setKey(value); } /** - * @internal - * Get Key/Name of Persistent + * Returns the key/name identifier of the Persistent. + * + * @public */ public get key(): StorageKey { return this._key; } - //========================================================================================================= - // Set Key - //========================================================================================================= /** + * Updates key/name identifier of Persistent. + * * @public - * Sets Key/Name of Persistent - * @param value - New Key/Name of Persistent + * @param value - New key/name identifier. */ - public setKey(value: StorageKey): void { - this._key = value; + public async setKey(value?: StorageKey): Promise { + const oldKey = this._key; + const wasReady = this.ready; + + // Assign new key to Persistent + if (value === this._key) return; + this._key = value ?? Persistent.placeHolderKey; + + const isValid = this.validatePersistent(); + + // Try to initial load value if persistent wasn't ready before + if (!wasReady) { + if (isValid) await this.initialLoading(); + return; + } + + // Remove persisted values with the old key + await this.removePersistedValue(oldKey); + + // Persist Collection values with the new key + if (isValid) await this.persistValue(value); } //========================================================================================================= @@ -181,9 +201,13 @@ export class Persistent { /** * @internal * Loads Value from Storage + * @param storageItemKey - Storage key of the persisted Instance. + * | default = Persistent.key | * @return Success? */ - public async loadPersistedValue(): Promise { + public async loadPersistedValue( + storageItemKey?: PersistentKey + ): Promise { LogCodeManager.log('00:03:00', ['loadPersistedValue', 'Persistent']); return false; } @@ -194,9 +218,11 @@ export class Persistent { /** * @internal * Saves/Updates Value in Storage + * @param storageItemKey - Storage key of the persisted Instance. + * | default = Persistent.key | * @return Success? */ - public async persistValue(): Promise { + public async persistValue(storageItemKey?: PersistentKey): Promise { LogCodeManager.log('00:03:00', ['persistValue', 'Persistent']); return false; } @@ -207,9 +233,13 @@ export class Persistent { /** * @internal * Removes Value form Storage + * @param storageItemKey - Storage key of the persisted Instance. + * | default = Persistent.key | * @return Success? */ - public async removePersistedValue(): Promise { + public async removePersistedValue( + storageItemKey?: PersistentKey + ): Promise { LogCodeManager.log('00:03:00', ['removePersistedValue', 'Persistent']); return false; } diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index 0a1044c5..731da595 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -205,88 +205,6 @@ describe('CollectionPersistent Tests', () => { }); }); - describe('setKey function tests', () => { - beforeEach(() => { - collectionPersistent.removePersistedValue = jest.fn(); - collectionPersistent.persistValue = jest.fn(); - collectionPersistent.initialLoading = jest.fn(); - }); - - it('should update key with valid key in ready Persistent', async () => { - collectionPersistent.ready = true; - collectionPersistent._key = 'dummyKey'; - jest - .spyOn(collectionPersistent, 'validatePersistent') - .mockReturnValueOnce(true); - - await collectionPersistent.setKey('newKey'); - - expect(collectionPersistent._key).toBe('newKey'); - expect(collectionPersistent.validatePersistent).toHaveBeenCalled(); - expect(collectionPersistent.initialLoading).not.toHaveBeenCalled(); - expect(collectionPersistent.persistValue).toHaveBeenCalledWith( - 'newKey' - ); - expect(collectionPersistent.removePersistedValue).toHaveBeenCalledWith( - 'dummyKey' - ); - }); - - it('should update key with not valid key in ready Persistent', async () => { - collectionPersistent.ready = true; - collectionPersistent._key = 'dummyKey'; - jest - .spyOn(collectionPersistent, 'validatePersistent') - .mockReturnValueOnce(false); - - await collectionPersistent.setKey(); - - expect(collectionPersistent._key).toBe(Persistent.placeHolderKey); - expect(collectionPersistent.validatePersistent).toHaveBeenCalled(); - expect(collectionPersistent.initialLoading).not.toHaveBeenCalled(); - expect(collectionPersistent.persistValue).not.toHaveBeenCalled(); - expect(collectionPersistent.removePersistedValue).toHaveBeenCalledWith( - 'dummyKey' - ); - }); - - it('should update key with valid key in not ready Persistent', async () => { - collectionPersistent.ready = false; - collectionPersistent._key = 'dummyKey'; - jest - .spyOn(collectionPersistent, 'validatePersistent') - .mockReturnValueOnce(true); - - await collectionPersistent.setKey('newKey'); - - expect(collectionPersistent._key).toBe('newKey'); - expect(collectionPersistent.validatePersistent).toHaveBeenCalled(); - expect(collectionPersistent.initialLoading).toHaveBeenCalled(); - expect(collectionPersistent.persistValue).not.toHaveBeenCalled(); - expect( - collectionPersistent.removePersistedValue - ).not.toHaveBeenCalled(); - }); - - it('should update key with not valid key in not ready Persistent', async () => { - collectionPersistent.ready = false; - collectionPersistent._key = 'dummyKey'; - jest - .spyOn(collectionPersistent, 'validatePersistent') - .mockReturnValueOnce(false); - - await collectionPersistent.setKey(); - - expect(collectionPersistent._key).toBe(Persistent.placeHolderKey); - expect(collectionPersistent.validatePersistent).toHaveBeenCalled(); - expect(collectionPersistent.initialLoading).not.toHaveBeenCalled(); - expect(collectionPersistent.persistValue).not.toHaveBeenCalled(); - expect( - collectionPersistent.removePersistedValue - ).not.toHaveBeenCalled(); - }); - }); - describe('initialLoading function tests', () => { beforeEach(() => { jest.spyOn(Persistent.prototype, 'initialLoading'); @@ -336,7 +254,7 @@ describe('CollectionPersistent Tests', () => { dummyAgile.storages.get = jest.fn(); }); - it('should load default Group and apply persisted value to Items that are already present in Collection (persistentKey)', async () => { + it('should load default Group and apply persisted value to Items that are already present in the Collection (persistentKey)', async () => { collectionPersistent.ready = true; dummyCollection.data = { ['3']: dummyItem3, @@ -387,7 +305,8 @@ describe('CollectionPersistent Tests', () => { ); }); - it("should load default Group and create/add persisted Items that aren't present in Collection yet (persistentKey)", async () => { + it("should load default Group " + + "and create/add persisted Items that aren't present in the Collection yet (persistentKey)", async () => { collectionPersistent.ready = true; dummyCollection.data = {}; dummyAgile.storages.get = jest @@ -502,8 +421,8 @@ describe('CollectionPersistent Tests', () => { it( 'should load default Group, ' + - "create/add persisted Items that aren't present in Collection yet " + - 'and apply persisted value to Items that are already present in Collection (specific key)', + "create/add persisted Items that aren't present in the Collection yet " + + 'and apply persisted value to Items that are already present in the Collection (specific key)', async () => { collectionPersistent.ready = true; dummyCollection.data = { @@ -564,7 +483,7 @@ describe('CollectionPersistent Tests', () => { expect( dummyCollection.createPlaceholderItem - ).not.toHaveBeenCalledWith('3'); // Because Item 3 is already present in Collection + ).not.toHaveBeenCalledWith('3'); // Because Item 3 is already present in the Collection expect(dummyCollection.createPlaceholderItem).toHaveBeenCalledWith( '1' ); @@ -582,7 +501,7 @@ describe('CollectionPersistent Tests', () => { ); expect(dummyCollection.assignItem).not.toHaveBeenCalledWith( placeholderItem3 - ); // Because Item 3 is already present in Collection + ); // Because Item 3 is already present in the Collection expect(collectionPersistent.setupSideEffects).toHaveBeenCalledWith( 'dummyKey' @@ -832,7 +751,7 @@ describe('CollectionPersistent Tests', () => { }); }); - describe('setupSideEffect function tests', () => { + describe('setupSideEffects function tests', () => { let dummyDefaultGroup: Group; beforeEach(() => { @@ -848,7 +767,7 @@ describe('CollectionPersistent Tests', () => { ); }); - it("shouldn't add rebuild Storage side effect to default Group", () => { + it("shouldn't add rebuild Storage side effect to the default Group", () => { collectionPersistent.setupSideEffects(); expect( @@ -860,7 +779,7 @@ describe('CollectionPersistent Tests', () => { ); }); - it("shouldn't add rebuild Storage side effect to default Group if Collection has no default Group", () => { + it("shouldn't add rebuild Storage side effect to the default Group if the Collection has no default Group", () => { dummyCollection.getDefaultGroup = jest.fn(() => undefined as any); collectionPersistent.setupSideEffects(); @@ -868,10 +787,8 @@ describe('CollectionPersistent Tests', () => { expect(dummyDefaultGroup.addSideEffect).not.toHaveBeenCalled(); }); - describe('test added sideEffect called CollectionPersistent.defaultGroupSideEffectKey', () => { + describe("test added sideEffect called 'CollectionPersistent.defaultGroupSideEffectKey'", () => { beforeEach(() => { - collectionPersistent.ready = true; - collectionPersistent.rebuildStorageSideEffect = jest.fn(); dummyCollection.getDefaultGroup = jest.fn( () => dummyDefaultGroup as any @@ -879,7 +796,7 @@ describe('CollectionPersistent Tests', () => { }); it('should call rebuildStorageSideEffect (persistentKey)', async () => { - await collectionPersistent.persistValue(); + await collectionPersistent.setupSideEffects(); dummyDefaultGroup.sideEffects[ CollectionPersistent.defaultGroupSideEffectKey @@ -890,8 +807,8 @@ describe('CollectionPersistent Tests', () => { ).toHaveBeenCalledWith(dummyDefaultGroup, collectionPersistent._key); }); - it('should call rebuildStorageSideEffect (specific key)', async () => { - await collectionPersistent.persistValue('dummyKey'); + it('should call rebuildStorageSideEffect (specified key)', async () => { + await collectionPersistent.setupSideEffects('dummyKey'); dummyDefaultGroup.sideEffects[ CollectionPersistent.defaultGroupSideEffectKey diff --git a/packages/core/tests/unit/state/state.persistent.test.ts b/packages/core/tests/unit/state/state.persistent.test.ts index ded895be..ed87d03a 100644 --- a/packages/core/tests/unit/state/state.persistent.test.ts +++ b/packages/core/tests/unit/state/state.persistent.test.ts @@ -133,73 +133,6 @@ describe('StatePersistent Tests', () => { ); }); - describe('setKey function tests', () => { - beforeEach(() => { - statePersistent.removePersistedValue = jest.fn(); - statePersistent.persistValue = jest.fn(); - statePersistent.initialLoading = jest.fn(); - jest.spyOn(statePersistent, 'validatePersistent'); - }); - - it('should update key with valid key in ready Persistent', async () => { - statePersistent.ready = true; - statePersistent._key = 'dummyKey'; - - await statePersistent.setKey('newKey'); - - expect(statePersistent._key).toBe('newKey'); - expect(statePersistent.ready).toBeTruthy(); - expect(statePersistent.validatePersistent).toHaveBeenCalled(); - expect(statePersistent.initialLoading).not.toHaveBeenCalled(); - expect(statePersistent.persistValue).toHaveBeenCalledWith('newKey'); - expect(statePersistent.removePersistedValue).toHaveBeenCalledWith( - 'dummyKey' - ); - }); - - it('should update key with not valid key in ready Persistent', async () => { - statePersistent.ready = true; - statePersistent._key = 'dummyKey'; - - await statePersistent.setKey(); - - expect(statePersistent._key).toBe(StatePersistent.placeHolderKey); - expect(statePersistent.ready).toBeFalsy(); - expect(statePersistent.validatePersistent).toHaveBeenCalled(); - expect(statePersistent.initialLoading).not.toHaveBeenCalled(); - expect(statePersistent.persistValue).not.toHaveBeenCalled(); - expect(statePersistent.removePersistedValue).toHaveBeenCalledWith( - 'dummyKey' - ); - }); - - it('should update key with valid key in not ready Persistent', async () => { - statePersistent.ready = false; - - await statePersistent.setKey('newKey'); - - expect(statePersistent._key).toBe('newKey'); - expect(statePersistent.ready).toBeTruthy(); - expect(statePersistent.validatePersistent).toHaveBeenCalled(); - expect(statePersistent.initialLoading).toHaveBeenCalled(); - expect(statePersistent.persistValue).not.toHaveBeenCalled(); - expect(statePersistent.removePersistedValue).not.toHaveBeenCalled(); - }); - - it('should update key with not valid key in not ready Persistent', async () => { - statePersistent.ready = false; - - await statePersistent.setKey(); - - expect(statePersistent._key).toBe(StatePersistent.placeHolderKey); - expect(statePersistent.ready).toBeFalsy(); - expect(statePersistent.validatePersistent).toHaveBeenCalled(); - expect(statePersistent.initialLoading).not.toHaveBeenCalled(); - expect(statePersistent.persistValue).not.toHaveBeenCalled(); - expect(statePersistent.removePersistedValue).not.toHaveBeenCalled(); - }); - }); - describe('initialLoading function tests', () => { beforeEach(() => { jest.spyOn(Persistent.prototype, 'initialLoading'); @@ -216,101 +149,117 @@ describe('StatePersistent Tests', () => { describe('loadPersistedValue function tests', () => { beforeEach(() => { dummyState.set = jest.fn(); - statePersistent.persistValue = jest.fn(); + statePersistent.setupSideEffects = jest.fn(); }); - it('should load State Value with persistentKey and apply it to the State if loading was successful', async () => { - statePersistent.ready = true; - dummyAgile.storages.get = jest.fn(() => - Promise.resolve('dummyValue' as any) - ); + it( + 'should load State value with Persistent key from the corresponding Storage ' + + 'and apply it to the State if the loading was successful', + async () => { + statePersistent.ready = true; + dummyAgile.storages.get = jest.fn(() => + Promise.resolve('dummyValue' as any) + ); - const response = await statePersistent.loadPersistedValue(); + const response = await statePersistent.loadPersistedValue(); - expect(response).toBeTruthy(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( - statePersistent._key, - statePersistent.config.defaultStorageKey - ); - expect(dummyState.set).toHaveBeenCalledWith('dummyValue', { - storage: false, - }); - expect(statePersistent.persistValue).toHaveBeenCalledWith( - statePersistent._key - ); - }); + expect(response).toBeTruthy(); + expect(dummyAgile.storages.get).toHaveBeenCalledWith( + statePersistent._key, + statePersistent.config.defaultStorageKey + ); + expect(dummyState.set).toHaveBeenCalledWith('dummyValue', { + storage: false, + overwrite: true, + }); + expect(statePersistent.setupSideEffects).toHaveBeenCalledWith( + statePersistent._key + ); + } + ); - it("should load State Value with persistentKey and shouldn't apply it to the State if loading wasn't successful", async () => { - statePersistent.ready = true; - dummyAgile.storages.get = jest.fn(() => - Promise.resolve(undefined as any) - ); + it( + "shouldn't load State value with Persistent key from the corresponding Storage " + + "and apply it to the State if the loading wasn't successful", + async () => { + statePersistent.ready = true; + dummyAgile.storages.get = jest.fn(() => + Promise.resolve(undefined as any) + ); - const response = await statePersistent.loadPersistedValue(); + const response = await statePersistent.loadPersistedValue(); - expect(response).toBeFalsy(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( - statePersistent._key, - statePersistent.config.defaultStorageKey - ); - expect(dummyState.set).not.toHaveBeenCalled(); - expect(statePersistent.persistValue).not.toHaveBeenCalled(); - }); + expect(response).toBeFalsy(); + expect(dummyAgile.storages.get).toHaveBeenCalledWith( + statePersistent._key, + statePersistent.config.defaultStorageKey + ); + expect(dummyState.set).not.toHaveBeenCalled(); + expect(statePersistent.setupSideEffects).not.toHaveBeenCalled(); + } + ); - it('should load State Value with specific Key and apply it to the State if loading was successful', async () => { - statePersistent.ready = true; - dummyAgile.storages.get = jest.fn(() => - Promise.resolve('dummyValue' as any) - ); + it( + 'should load State value with specified key from the corresponding Storage ' + + 'and apply it to the State if the loading was successful', + async () => { + statePersistent.ready = true; + dummyAgile.storages.get = jest.fn(() => + Promise.resolve('dummyValue' as any) + ); - const response = await statePersistent.loadPersistedValue('coolKey'); + const response = await statePersistent.loadPersistedValue('coolKey'); - expect(response).toBeTruthy(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( - 'coolKey', - statePersistent.config.defaultStorageKey - ); - expect(dummyState.set).toHaveBeenCalledWith('dummyValue', { - storage: false, - }); - expect(statePersistent.persistValue).toHaveBeenCalledWith('coolKey'); - }); + expect(response).toBeTruthy(); + expect(dummyAgile.storages.get).toHaveBeenCalledWith( + 'coolKey', + statePersistent.config.defaultStorageKey + ); + expect(dummyState.set).toHaveBeenCalledWith('dummyValue', { + storage: false, + overwrite: true, + }); + expect(statePersistent.setupSideEffects).toHaveBeenCalledWith( + 'coolKey' + ); + } + ); - it("shouldn't load State Value if Persistent isn't ready", async () => { - statePersistent.ready = false; - dummyAgile.storages.get = jest.fn(() => - Promise.resolve(undefined as any) - ); + it( + "shouldn't load State value from the corresponding Storage " + + "if Persistent isn't ready yet", + async () => { + statePersistent.ready = false; + dummyAgile.storages.get = jest.fn(() => + Promise.resolve(undefined as any) + ); - const response = await statePersistent.loadPersistedValue(); + const response = await statePersistent.loadPersistedValue(); - expect(response).toBeFalsy(); - expect(dummyAgile.storages.get).not.toHaveBeenCalled(); - expect(dummyState.set).not.toHaveBeenCalled(); - expect(statePersistent.persistValue).not.toHaveBeenCalled(); - }); + expect(response).toBeFalsy(); + expect(dummyAgile.storages.get).not.toHaveBeenCalled(); + expect(dummyState.set).not.toHaveBeenCalled(); + expect(statePersistent.setupSideEffects).not.toHaveBeenCalled(); + } + ); }); describe('persistValue function tests', () => { beforeEach(() => { - dummyState.addSideEffect = jest.fn(); + statePersistent.setupSideEffects = jest.fn(); statePersistent.rebuildStorageSideEffect = jest.fn(); statePersistent.isPersisted = false; }); - it('should persist State with persistentKey', async () => { + it('should persist State value with Persistent key', async () => { statePersistent.ready = true; const response = await statePersistent.persistValue(); expect(response).toBeTruthy(); - expect( - dummyState.addSideEffect - ).toHaveBeenCalledWith( - StatePersistent.storeValueSideEffectKey, - expect.any(Function), - { weight: 0 } + expect(statePersistent.setupSideEffects).toHaveBeenCalledWith( + statePersistent._key ); expect(statePersistent.rebuildStorageSideEffect).toHaveBeenCalledWith( dummyState, @@ -319,18 +268,14 @@ describe('StatePersistent Tests', () => { expect(statePersistent.isPersisted).toBeTruthy(); }); - it('should persist State with specific Key', async () => { + it('should persist State value with specified key', async () => { statePersistent.ready = true; const response = await statePersistent.persistValue('coolKey'); expect(response).toBeTruthy(); - expect( - dummyState.addSideEffect - ).toHaveBeenCalledWith( - StatePersistent.storeValueSideEffectKey, - expect.any(Function), - { weight: 0 } + expect(statePersistent.setupSideEffects).toHaveBeenCalledWith( + 'coolKey' ); expect(statePersistent.rebuildStorageSideEffect).toHaveBeenCalledWith( dummyState, @@ -339,24 +284,46 @@ describe('StatePersistent Tests', () => { expect(statePersistent.isPersisted).toBeTruthy(); }); - it("shouldn't persist State if Persistent isn't ready", async () => { + it("shouldn't persist State if Persistent isn't ready yet", async () => { statePersistent.ready = false; const response = await statePersistent.persistValue(); expect(response).toBeFalsy(); - expect(dummyState.addSideEffect).not.toHaveBeenCalled(); + expect(statePersistent.setupSideEffects).not.toHaveBeenCalled(); expect(statePersistent.rebuildStorageSideEffect).not.toHaveBeenCalled(); expect(statePersistent.isPersisted).toBeFalsy(); }); + }); + + describe('setupSideEffects function tests', () => { + beforeEach(() => { + jest.spyOn(dummyState, 'addSideEffect'); + }); + + it( + 'should add side effects for keeping the State value in sync ' + + 'with the Storage value to the State', + () => { + statePersistent.setupSideEffects(); - describe('test added sideEffect called StatePersistent.storeValueSideEffectKey', () => { + expect( + dummyState.addSideEffect + ).toHaveBeenCalledWith( + StatePersistent.storeValueSideEffectKey, + expect.any(Function), + { weight: 0 } + ); + } + ); + + describe("test added sideEffect called 'StatePersistent.storeValueSideEffectKey'", () => { beforeEach(() => { statePersistent.rebuildStorageSideEffect = jest.fn(); }); - it('should call rebuildStorageSideEffect', async () => { - await statePersistent.persistValue(); + it('should call rebuildStorageSideEffect (persistentKey)', async () => { + await statePersistent.setupSideEffects(); dummyState.sideEffects[ StatePersistent.storeValueSideEffectKey @@ -372,6 +339,24 @@ describe('StatePersistent Tests', () => { } ); }); + + it('should call rebuildStorageSideEffect (specified key)', async () => { + await statePersistent.setupSideEffects('dummyKey'); + + dummyState.sideEffects[ + StatePersistent.storeValueSideEffectKey + ].callback(dummyState, { + dummy: 'property', + }); + + expect(statePersistent.rebuildStorageSideEffect).toHaveBeenCalledWith( + dummyState, + 'dummyKey', + { + dummy: 'property', + } + ); + }); }); }); @@ -383,7 +368,7 @@ describe('StatePersistent Tests', () => { statePersistent.isPersisted = true; }); - it('should remove persisted State from Storage with persistentKey', async () => { + it('should remove persisted State value from the corresponding Storage with Persistent key', async () => { statePersistent.ready = true; const response = await statePersistent.removePersistedValue(); @@ -399,7 +384,7 @@ describe('StatePersistent Tests', () => { expect(statePersistent.isPersisted).toBeFalsy(); }); - it('should remove persisted State from Storage with specific Key', async () => { + it('should remove persisted State from the corresponding Storage with specified Key', async () => { statePersistent.ready = true; const response = await statePersistent.removePersistedValue('coolKey'); @@ -415,7 +400,7 @@ describe('StatePersistent Tests', () => { expect(statePersistent.isPersisted).toBeFalsy(); }); - it("shouldn't remove State from Storage if Persistent isn't ready", async () => { + it("shouldn't remove State from the corresponding Storage if Persistent isn't ready yet", async () => { statePersistent.ready = false; const response = await statePersistent.removePersistedValue('coolKey'); @@ -428,10 +413,10 @@ describe('StatePersistent Tests', () => { }); describe('formatKey function tests', () => { - it('should return key of State if no key got passed', () => { + it('should return key of the State if no valid key was specified', () => { dummyState._key = 'coolKey'; - const response = statePersistent.formatKey(); + const response = statePersistent.formatKey(undefined); expect(response).toBe('coolKey'); }); @@ -444,7 +429,7 @@ describe('StatePersistent Tests', () => { expect(response).toBe('awesomeKey'); }); - it('should return and apply passed key to State if State had no own key before', () => { + it('should return and apply specified key to State if State has no own valid key before', () => { dummyState._key = undefined; const response = statePersistent.formatKey('awesomeKey'); @@ -453,10 +438,10 @@ describe('StatePersistent Tests', () => { expect(dummyState._key).toBe('awesomeKey'); }); - it('should return undefined if no key got passed and State has no key', () => { + it('should return undefined if no valid key was specified and State has no valid key either', () => { dummyState._key = undefined; - const response = statePersistent.formatKey(); + const response = statePersistent.formatKey(undefined); expect(response).toBeUndefined(); }); diff --git a/packages/core/tests/unit/state/state.runtime.job.test.ts b/packages/core/tests/unit/state/state.runtime.job.test.ts index 24ded7f3..b1f6c04f 100644 --- a/packages/core/tests/unit/state/state.runtime.job.test.ts +++ b/packages/core/tests/unit/state/state.runtime.job.test.ts @@ -27,7 +27,7 @@ describe('RuntimeJob Tests', () => { dummyObserver = new StateObserver(dummyState); }); - it('should create RuntimeJob with Agile that has integrations (default config)', () => { + it('should create RuntimeJob with a specified Agile Instance that has a registered Integration (default config)', () => { dummyAgile.integrate(dummyIntegration); const job = new StateRuntimeJob(dummyObserver); @@ -49,7 +49,7 @@ describe('RuntimeJob Tests', () => { expect(job.subscriptionContainersToUpdate.size).toBe(0); }); - it('should create RuntimeJob with Agile that has integrations (specific config)', () => { + it('should create RuntimeJob with a specified Agile Instance that has a registered Integration (specific config)', () => { dummyAgile.integrate(dummyIntegration); const job = new StateRuntimeJob(dummyObserver, { @@ -76,7 +76,7 @@ describe('RuntimeJob Tests', () => { expect(job.subscriptionContainersToUpdate.size).toBe(0); }); - it('should create RuntimeJob with Agile that has no integrations (default config)', () => { + it('should create RuntimeJob with a specified Agile Instance that has no registered Integration (default config)', () => { const job = new StateRuntimeJob(dummyObserver); expect(job._key).toBeUndefined(); diff --git a/packages/core/tests/unit/storages/persistent.test.ts b/packages/core/tests/unit/storages/persistent.test.ts index 5377a12e..79ca7ca2 100644 --- a/packages/core/tests/unit/storages/persistent.test.ts +++ b/packages/core/tests/unit/storages/persistent.test.ts @@ -118,6 +118,74 @@ describe('Persistent Tests', () => { }); }); + describe('setKey function tests', () => { + beforeEach(() => { + persistent.removePersistedValue = jest.fn(); + persistent.persistValue = jest.fn(); + persistent.initialLoading = jest.fn(); + }); + + it('should update key with valid key in ready Persistent', async () => { + persistent.ready = true; + persistent._key = 'dummyKey'; + jest.spyOn(persistent, 'validatePersistent').mockReturnValueOnce(true); + + await persistent.setKey('newKey'); + + expect(persistent._key).toBe('newKey'); + expect(persistent.validatePersistent).toHaveBeenCalled(); + expect(persistent.initialLoading).not.toHaveBeenCalled(); + expect(persistent.persistValue).toHaveBeenCalledWith('newKey'); + expect(persistent.removePersistedValue).toHaveBeenCalledWith( + 'dummyKey' + ); + }); + + it('should update key with not valid key in ready Persistent', async () => { + persistent.ready = true; + persistent._key = 'dummyKey'; + jest.spyOn(persistent, 'validatePersistent').mockReturnValueOnce(false); + + await persistent.setKey(); + + expect(persistent._key).toBe(Persistent.placeHolderKey); + expect(persistent.validatePersistent).toHaveBeenCalled(); + expect(persistent.initialLoading).not.toHaveBeenCalled(); + expect(persistent.persistValue).not.toHaveBeenCalled(); + expect(persistent.removePersistedValue).toHaveBeenCalledWith( + 'dummyKey' + ); + }); + + it('should update key with valid key in not ready Persistent', async () => { + persistent.ready = false; + persistent._key = 'dummyKey'; + jest.spyOn(persistent, 'validatePersistent').mockReturnValueOnce(true); + + await persistent.setKey('newKey'); + + expect(persistent._key).toBe('newKey'); + expect(persistent.validatePersistent).toHaveBeenCalled(); + expect(persistent.initialLoading).toHaveBeenCalled(); + expect(persistent.persistValue).not.toHaveBeenCalled(); + expect(persistent.removePersistedValue).not.toHaveBeenCalled(); + }); + + it('should update key with not valid key in not ready Persistent', async () => { + persistent.ready = false; + persistent._key = 'dummyKey'; + jest.spyOn(persistent, 'validatePersistent').mockReturnValueOnce(false); + + await persistent.setKey(); + + expect(persistent._key).toBe(Persistent.placeHolderKey); + expect(persistent.validatePersistent).toHaveBeenCalled(); + expect(persistent.initialLoading).not.toHaveBeenCalled(); + expect(persistent.persistValue).not.toHaveBeenCalled(); + expect(persistent.removePersistedValue).not.toHaveBeenCalled(); + }); + }); + describe('instantiatePersistent function tests', () => { it('should call assign key to formatKey and call assignStorageKeys, validatePersistent', () => { jest.spyOn(persistent, 'formatKey'); From 97e03ce4b2e6c92a9a7821cc3a4c8402135a1e0f Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 12 Jun 2021 19:00:21 +0200 Subject: [PATCH 072/117] fixed typos --- .../src/collection/collection.persistent.ts | 12 +- packages/core/src/runtime/index.ts | 4 +- packages/core/src/runtime/runtime.job.ts | 2 +- packages/core/src/state/state.observer.ts | 15 +- packages/core/src/state/state.persistent.ts | 33 +- packages/core/src/state/state.runtime.job.ts | 15 +- .../collection/collection.persistent.test.ts | 235 +++++----- .../tests/unit/runtime/runtime.job.test.ts | 202 +++++---- .../core/tests/unit/runtime/runtime.test.ts | 18 +- .../tests/unit/state/state.observer.test.ts | 412 ++++++++++-------- .../tests/unit/state/state.persistent.test.ts | 14 +- .../unit/state/state.runtime.job.test.ts | 184 ++++---- 12 files changed, 645 insertions(+), 501 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index ee835da6..6454ea63 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -74,7 +74,7 @@ export class CollectionPersistent< * @internal * @param storageItemKey - Prefix Storage key of the persisted Collection Instances. * | default = Persistent.key | - * @return Whether the loading was successful. + * @return Whether the loading and the setting up of the side effects was successful. */ public async loadPersistedValue( storageItemKey?: PersistentKey @@ -165,7 +165,7 @@ export class CollectionPersistent< }; const success = await loadValuesIntoCollection(); - // Setup Side Effects to keep the Storage value in sync + // Setup side effects to keep the Storage value in sync // with the Collection (Instances) value if (success) this.setupSideEffects(_storageItemKey); @@ -216,7 +216,7 @@ export class CollectionPersistent< }); } - // Setup Side Effects to keep the Storage value in sync + // Setup side effects to keep the Storage value in sync // with the Collection (Instances) value this.setupSideEffects(_storageItemKey); @@ -253,7 +253,7 @@ export class CollectionPersistent< * @internal * @param storageItemKey - Prefix Storage key of the persisted Collection Instances. * | default = Persistent.key | - * @return Whether the removal of the persisted value was successful. + * @return Whether the removal of the persisted values was successful. */ public async removePersistedValue( storageItemKey?: PersistentKey @@ -291,7 +291,9 @@ export class CollectionPersistent< } /** - * Formats specified key so that it can be used as a valid Storage key and returns it. + * Formats the specified key so that it can be used as a valid Storage key + * and returns the formatted variant of it. + * * If no formatable key (undefined/null) was provided, * an attempt is made to use the Collection identifier key. * diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index ec0b841d..9a93ce62 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -186,9 +186,9 @@ export class Runtime { if (!subscriptionContainer.ready) { if ( !job.config.maxTriesToUpdate || - job.triedToUpdateCount < job.config.maxTriesToUpdate + job.timesTriedToUpdateCount < job.config.maxTriesToUpdate ) { - job.triedToUpdateCount++; + job.timesTriedToUpdateCount++; this.notReadyJobsToRerender.add(job); LogCodeManager.log( '16:02:00', diff --git a/packages/core/src/runtime/runtime.job.ts b/packages/core/src/runtime/runtime.job.ts index c61b7c02..d3eea3f2 100644 --- a/packages/core/src/runtime/runtime.job.ts +++ b/packages/core/src/runtime/runtime.job.ts @@ -12,7 +12,7 @@ export class RuntimeJob { // Subscription Containers (UI-Components) of the Observer that have to be updated (re-rendered) public subscriptionContainersToUpdate = new Set(); // How often not ready Subscription Containers of the Observer have been tried to update - public triedToUpdateCount = 0; + public timesTriedToUpdateCount = 0; // Whether the Job has been performed by the runtime public performed = false; diff --git a/packages/core/src/state/state.observer.ts b/packages/core/src/state/state.observer.ts index 7b058fb7..b5a4ee2e 100644 --- a/packages/core/src/state/state.observer.ts +++ b/packages/core/src/state/state.observer.ts @@ -91,19 +91,19 @@ export class StateObserver extends Observer { overwrite: false, }); - // Force overwriting the State value if it was a placeholder. - // Because after assigning a value to the State it shouldn't be a placeholder anymore. + // Force overwriting the State value if it is a placeholder. + // After assigning a value to the State it shouldn't be a placeholder anymore. if (state.isPlaceholder) { config.force = true; config.overwrite = true; } - // Assign next State value and compute it if necessary + // Assign next State value to Observer and compute it if necessary this.nextStateValue = state.computeValueMethod ? copy(state.computeValueMethod(newStateValue)) : copy(newStateValue); - // Check if current State value and to assign State value are equals + // Check if current State value and to assign State value are equal if (equal(state._value, this.nextStateValue) && !config.force) return; // Create Runtime-Job @@ -128,8 +128,8 @@ export class StateObserver extends Observer { * Method executed by the Runtime to perform the Runtime-Job, * previously ingested via the `ingest()` or `ingestValue()` method. * - * Thereby the previously defined `nextStateValue` is assigned to the State - * and the side effects (`sideEffects`) are executed. + * Thereby the previously defined `nextStateValue` is assigned to the State. + * Also side effects (like calling watcher callbacks) of a State change are executed. * * @internal * @param job - Runtime-Job to be performed. @@ -142,6 +142,7 @@ export class StateObserver extends Observer { state._value = copy(job.observer.nextStateValue); state.nextStateValue = copy(job.observer.nextStateValue); + // TODO think about freezing the State value.. // https://www.geeksforgeeks.org/object-freeze-javascript/#:~:text=Object.freeze()%20Method&text=freeze()%20which%20is%20used,the%20prototype%20of%20the%20object. // if (typeof state._value === 'object') Object.freeze(state._value); @@ -168,7 +169,7 @@ export class StateObserver extends Observer { /** * Performs the side effects of applying the next State value to the State. * - * Side effects are, for example, calling the watcher functions + * Side effects are, for example, calling the watcher callbacks * or executing the side effects defined in the State Class * like 'rebuildGroup' or 'rebuildStateStorageValue'. * diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index ef8f1b16..d1a8243f 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -7,9 +7,11 @@ import { } from '../internal'; export class StatePersistent extends Persistent { - static storeValueSideEffectKey = 'rebuildStateStorageValue'; + // State the Persistent belongs to public state: () => State; + static storeValueSideEffectKey = 'rebuildStateStorageValue'; + /** * Internal Class for managing the permanent persistence of a State. * @@ -61,7 +63,7 @@ export class StatePersistent extends Persistent { * @internal * @param storageItemKey - Storage key of the persisted State Instance. * | default = Persistent.key | - * @return Whether the loading was successful. + * @return Whether the loading and the setting up of the side effects was successful. */ public async loadPersistedValue( storageItemKey?: PersistentKey @@ -69,7 +71,7 @@ export class StatePersistent extends Persistent { if (!this.ready) return false; const _storageItemKey = storageItemKey ?? this._key; - // Load value from default Storage + // Load State value from the default Storage const loadedValue = await this.agileInstance().storages.get( _storageItemKey, this.config.defaultStorageKey as any @@ -82,8 +84,8 @@ export class StatePersistent extends Persistent { overwrite: true, }); - // Setup Side Effects to keep the Storage value in sync - // with the State value + // Setup side effects to keep the Storage value in sync + // with the current State value this.setupSideEffects(_storageItemKey); return true; @@ -103,7 +105,7 @@ export class StatePersistent extends Persistent { if (!this.ready) return false; const _storageItemKey = storageItemKey ?? this._key; - // Setup Side Effects to keep the Storage value in sync + // Setup side effects to keep the Storage value in sync // with the State value this.setupSideEffects(_storageItemKey); @@ -116,7 +118,7 @@ export class StatePersistent extends Persistent { /** * Sets up side effects to keep the Storage value in sync - * with the State value. + * with the current State value. * * @internal * @param storageItemKey - Storage key of the persisted State Instance. @@ -157,7 +159,9 @@ export class StatePersistent extends Persistent { } /** - * Formats specified key so that it can be used as a valid Storage key and returns it. + * Formats the specified key so that it can be used as a valid Storage key + * and returns the formatted variant of it. + * * If no formatable key (undefined/null) was provided, * an attempt is made to use the State identifier key. * @@ -167,19 +171,18 @@ export class StatePersistent extends Persistent { public formatKey( key: PersistentKey | undefined | null ): PersistentKey | undefined { - const state = this.state(); - if (!key && state._key) return state._key; - if (!key) return; - if (!state._key) state._key = key; + if (key == null && this.state()._key) return this.state()._key; + if (key == null) return; + if (this.state()._key == null) this.state()._key = key; return key; } /** - * Rebuilds Storage value based on the current State value + * Rebuilds Storage value based on the current State value. * * @internal - * @param state - State whose value to be in sync with the Storage value. - * @param storageItemKey - Storage key of the persisted State. + * @param state - State whose current value to be applied to the Storage value. + * @param storageItemKey - Storage key of the persisted State Instance. * | default = Persistent.key | * @param config - Configuration object */ diff --git a/packages/core/src/state/state.runtime.job.ts b/packages/core/src/state/state.runtime.job.ts index af57a67d..f42af5c3 100644 --- a/packages/core/src/state/state.runtime.job.ts +++ b/packages/core/src/state/state.runtime.job.ts @@ -9,6 +9,17 @@ import { export class StateRuntimeJob extends RuntimeJob { public config: StateRuntimeJobConfigInterface; + /** + * A State Runtime Job is sent to the Runtime on behalf of the State Observer it represents. + * + * In the Runtime, the State Observer is performed via its `perform()` method + * and the Subscription Containers (UI-Components) + * to which it is subscribed are updated (re-rendered) accordingly. + * + * @internal + * @param observer - State Observer to be represented by the State Runtime Job. + * @param config - Configuration object + */ constructor( observer: StateObserver, config: CreateStateRuntimeJobConfigInterface = {} @@ -38,7 +49,7 @@ export class StateRuntimeJob extends RuntimeJob { export interface CreateStateRuntimeJobConfigInterface extends StateRuntimeJobConfigInterface { /** - * Key/Name identifier of the Runtime Job. + * Key/Name identifier of the State Runtime Job. * @default undefined */ key?: RuntimeJobKey; @@ -53,7 +64,7 @@ export interface StateRuntimeJobConfigInterface overwrite?: boolean; /** * If the State is persisted, - * whether to store the new State value in an external Storage + * whether to apply the new State value to the external Storages. * @default true */ storage?: boolean; diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index 731da595..764b482c 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -305,119 +305,130 @@ describe('CollectionPersistent Tests', () => { ); }); - it("should load default Group " + - "and create/add persisted Items that aren't present in the Collection yet (persistentKey)", async () => { - collectionPersistent.ready = true; - dummyCollection.data = {}; - dummyAgile.storages.get = jest - .fn() - .mockReturnValueOnce(Promise.resolve(true)); - placeholderItem1.persist = jest.fn(function () { - placeholderItem1.persistent = new StatePersistent(placeholderItem1); - placeholderItem1.persistent.ready = true; - placeholderItem1.persistent.loadPersistedValue = jest + it( + 'should load default Group ' + + "and create/add persisted Items that aren't present in the Collection yet (persistentKey)", + async () => { + collectionPersistent.ready = true; + dummyCollection.data = {}; + dummyAgile.storages.get = jest .fn() - .mockReturnValueOnce(true); - return null as any; - }); - placeholderItem2.persist = jest.fn(function () { - placeholderItem2.persistent = new StatePersistent(placeholderItem2); - placeholderItem2.persistent.ready = false; - placeholderItem2.persistent.loadPersistedValue = jest.fn(); - return null as any; - }); - placeholderItem3.persist = jest.fn(function () { - placeholderItem3.persistent = new StatePersistent(placeholderItem3); - placeholderItem3.persistent.ready = true; - placeholderItem3.persistent.loadPersistedValue = jest + .mockReturnValueOnce(Promise.resolve(true)); + placeholderItem1.persist = jest.fn(function () { + placeholderItem1.persistent = new StatePersistent(placeholderItem1); + placeholderItem1.persistent.ready = true; + placeholderItem1.persistent.loadPersistedValue = jest + .fn() + .mockReturnValueOnce(true); + return null as any; + }); + placeholderItem2.persist = jest.fn(function () { + placeholderItem2.persistent = new StatePersistent(placeholderItem2); + placeholderItem2.persistent.ready = false; + placeholderItem2.persistent.loadPersistedValue = jest.fn(); + return null as any; + }); + placeholderItem3.persist = jest.fn(function () { + placeholderItem3.persistent = new StatePersistent(placeholderItem3); + placeholderItem3.persistent.ready = true; + placeholderItem3.persistent.loadPersistedValue = jest + .fn() + .mockReturnValueOnce(false); + return null as any; + }); + dummyCollection.createPlaceholderItem = jest .fn() - .mockReturnValueOnce(false); - return null as any; - }); - dummyCollection.createPlaceholderItem = jest - .fn() - .mockReturnValueOnce(placeholderItem1) - .mockReturnValueOnce(placeholderItem2) - .mockReturnValueOnce(placeholderItem3); - dummyDefaultGroup._value = ['1', '2', '3']; + .mockReturnValueOnce(placeholderItem1) + .mockReturnValueOnce(placeholderItem2) + .mockReturnValueOnce(placeholderItem3); + dummyDefaultGroup._value = ['1', '2', '3']; - const response = await collectionPersistent.loadPersistedValue(); + const response = await collectionPersistent.loadPersistedValue(); - expect(response).toBeTruthy(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( - collectionPersistent._key, - collectionPersistent.config.defaultStorageKey - ); + expect(response).toBeTruthy(); + expect(dummyAgile.storages.get).toHaveBeenCalledWith( + collectionPersistent._key, + collectionPersistent.config.defaultStorageKey + ); - expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); - expect(dummyDefaultGroup.persist).toHaveBeenCalledWith( - CollectionPersistent.getGroupStorageKey( - dummyDefaultGroup._key, - collectionPersistent._key - ), - { - loadValue: false, - defaultStorageKey: collectionPersistent.config.defaultStorageKey, - storageKeys: collectionPersistent.storageKeys, - followCollectionPersistKeyPattern: false, - } - ); - expect(dummyDefaultGroup.persistent?.initialLoading).toHaveBeenCalled(); - expect(dummyDefaultGroup.isPersisted).toBeTruthy(); + expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); + expect(dummyDefaultGroup.persist).toHaveBeenCalledWith( + CollectionPersistent.getGroupStorageKey( + dummyDefaultGroup._key, + collectionPersistent._key + ), + { + loadValue: false, + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, + } + ); + expect( + dummyDefaultGroup.persistent?.initialLoading + ).toHaveBeenCalled(); + expect(dummyDefaultGroup.isPersisted).toBeTruthy(); - expect(dummyCollection.createPlaceholderItem).toHaveBeenCalledWith('1'); - expect(dummyCollection.createPlaceholderItem).toHaveBeenCalledWith('2'); - expect(dummyCollection.createPlaceholderItem).toHaveBeenCalledWith('3'); - expect(placeholderItem1.persist).toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey( - '1', - collectionPersistent._key - ), - { - loadValue: false, - defaultStorageKey: collectionPersistent.config.defaultStorageKey, - storageKeys: collectionPersistent.storageKeys, - followCollectionPersistKeyPattern: false, - } - ); - expect(placeholderItem2.persist).toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey( - '2', - collectionPersistent._key - ), - { - loadValue: false, - defaultStorageKey: collectionPersistent.config.defaultStorageKey, - storageKeys: collectionPersistent.storageKeys, - followCollectionPersistKeyPattern: false, - } - ); - expect(placeholderItem3.persist).toHaveBeenCalledWith( - CollectionPersistent.getItemStorageKey( - '3', - collectionPersistent._key - ), - { - loadValue: false, - defaultStorageKey: collectionPersistent.config.defaultStorageKey, - storageKeys: collectionPersistent.storageKeys, - followCollectionPersistKeyPattern: false, - } - ); - expect(dummyCollection.assignItem).toHaveBeenCalledWith( - placeholderItem1 - ); - expect(dummyCollection.assignItem).not.toHaveBeenCalledWith( - placeholderItem2 - ); // Because Item persistent isn't ready - expect(dummyCollection.assignItem).not.toHaveBeenCalledWith( - placeholderItem3 - ); // Because Item persistent 'leadPersistedValue()' returned false -> Item properly doesn't exist in Storage + expect(dummyCollection.createPlaceholderItem).toHaveBeenCalledWith( + '1' + ); + expect(dummyCollection.createPlaceholderItem).toHaveBeenCalledWith( + '2' + ); + expect(dummyCollection.createPlaceholderItem).toHaveBeenCalledWith( + '3' + ); + expect(placeholderItem1.persist).toHaveBeenCalledWith( + CollectionPersistent.getItemStorageKey( + '1', + collectionPersistent._key + ), + { + loadValue: false, + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, + } + ); + expect(placeholderItem2.persist).toHaveBeenCalledWith( + CollectionPersistent.getItemStorageKey( + '2', + collectionPersistent._key + ), + { + loadValue: false, + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, + } + ); + expect(placeholderItem3.persist).toHaveBeenCalledWith( + CollectionPersistent.getItemStorageKey( + '3', + collectionPersistent._key + ), + { + loadValue: false, + defaultStorageKey: collectionPersistent.config.defaultStorageKey, + storageKeys: collectionPersistent.storageKeys, + followCollectionPersistKeyPattern: false, + } + ); + expect(dummyCollection.assignItem).toHaveBeenCalledWith( + placeholderItem1 + ); + expect(dummyCollection.assignItem).not.toHaveBeenCalledWith( + placeholderItem2 + ); // Because Item persistent isn't ready + expect(dummyCollection.assignItem).not.toHaveBeenCalledWith( + placeholderItem3 + ); // Because Item persistent 'leadPersistedValue()' returned false -> Item properly doesn't exist in Storage - expect(collectionPersistent.setupSideEffects).toHaveBeenCalledWith( - collectionPersistent._key - ); - }); + expect(collectionPersistent.setupSideEffects).toHaveBeenCalledWith( + collectionPersistent._key + ); + } + ); it( 'should load default Group, ' + @@ -987,15 +998,15 @@ describe('CollectionPersistent Tests', () => { }); describe('formatKey function tests', () => { - it('should return key of Collection if no valid key got provided', () => { + it('should return key of the Collection if no valid key was specified', () => { dummyCollection._key = 'coolKey'; - const response = collectionPersistent.formatKey(null); + const response = collectionPersistent.formatKey(undefined); expect(response).toBe('coolKey'); }); - it('should return provided key if key is valid', () => { + it('should return specified key', () => { dummyCollection._key = 'coolKey'; const response = collectionPersistent.formatKey('awesomeKey'); @@ -1003,7 +1014,7 @@ describe('CollectionPersistent Tests', () => { expect(response).toBe('awesomeKey'); }); - it('should return and apply valid provided key to Collection if Collection has no own key', () => { + it('should return and apply specified key to Collection if Collection had no own valid key before', () => { dummyCollection._key = undefined; const response = collectionPersistent.formatKey('awesomeKey'); @@ -1012,10 +1023,10 @@ describe('CollectionPersistent Tests', () => { expect(dummyCollection._key).toBe('awesomeKey'); }); - it('should return undefined if no valid key got provided and Collection has no key', () => { + it('should return undefined if no valid key was specified and Collection has no valid key either', () => { dummyCollection._key = undefined; - const response = collectionPersistent.formatKey(null); + const response = collectionPersistent.formatKey(undefined); expect(response).toBeUndefined(); }); diff --git a/packages/core/tests/unit/runtime/runtime.job.test.ts b/packages/core/tests/unit/runtime/runtime.job.test.ts index e8989d14..4793df21 100644 --- a/packages/core/tests/unit/runtime/runtime.job.test.ts +++ b/packages/core/tests/unit/runtime/runtime.job.test.ts @@ -17,96 +17,120 @@ describe('RuntimeJob Tests', () => { dummyObserver = new Observer(dummyAgile); }); - it('should create RuntimeJob with a specified Agile Instance that has a registered Integration (default config)', () => { - dummyAgile.integrate(dummyIntegration); - - const job = new RuntimeJob(dummyObserver); - - expect(job._key).toBeUndefined(); - expect(job.observer).toBe(dummyObserver); - expect(job.config).toStrictEqual({ - background: false, - sideEffects: { - enabled: true, - exclude: [], - }, - force: false, - maxTriesToUpdate: 3, - }); - expect(job.rerender).toBeTruthy(); - expect(job.performed).toBeFalsy(); - expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); - expect(job.triedToUpdateCount).toBe(0); - }); - - it('should create RuntimeJob with a specified Agile Instance that has a registered Integration (specific config)', () => { - dummyAgile.integrate(dummyIntegration); - - const job = new RuntimeJob(dummyObserver, { - key: 'dummyJob', - sideEffects: { - enabled: false, - exclude: ['jeff'], - }, - force: true, - maxTriesToUpdate: 10, - }); - - expect(job._key).toBe('dummyJob'); - expect(job.observer).toBe(dummyObserver); - expect(job.config).toStrictEqual({ - background: false, - sideEffects: { - enabled: false, - exclude: ['jeff'], - }, - force: true, - maxTriesToUpdate: 10, - }); - expect(job.rerender).toBeTruthy(); - expect(job.performed).toBeFalsy(); - expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); - }); - - it('should create RuntimeJob with a specified Agile Instance that has no registered Integration (default config)', () => { - const job = new RuntimeJob(dummyObserver); - - expect(job._key).toBeUndefined(); - expect(job.observer).toBe(dummyObserver); - expect(job.config).toStrictEqual({ - background: false, - sideEffects: { - enabled: true, - exclude: [], - }, - force: false, - maxTriesToUpdate: 3, - }); - expect(job.rerender).toBeFalsy(); - expect(job.performed).toBeFalsy(); - expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); - }); + it( + 'should create RuntimeJob ' + + 'with a specified Agile Instance that has a registered Integration (default config)', + () => { + dummyAgile.integrate(dummyIntegration); + + const job = new RuntimeJob(dummyObserver); + + expect(job._key).toBeUndefined(); + expect(job.observer).toBe(dummyObserver); + expect(job.config).toStrictEqual({ + background: false, + sideEffects: { + enabled: true, + exclude: [], + }, + force: false, + maxTriesToUpdate: 3, + }); + expect(job.rerender).toBeTruthy(); + expect(job.performed).toBeFalsy(); + expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); + expect(job.timesTriedToUpdateCount).toBe(0); + expect(job.timesTriedToUpdateCount).toBe(0); + expect(job.performed).toBeFalsy(); + } + ); + + it( + 'should create RuntimeJob ' + + 'with a specified Agile Instance that has a registered Integration (specific config)', + () => { + dummyAgile.integrate(dummyIntegration); + + const job = new RuntimeJob(dummyObserver, { + key: 'dummyJob', + sideEffects: { + enabled: false, + exclude: ['jeff'], + }, + force: true, + maxTriesToUpdate: 10, + }); - it('should create RuntimeJob with a specified Agile Instance that has a registered Integrations (config.background = true)', () => { - dummyAgile.integrate(dummyIntegration); - - const job = new RuntimeJob(dummyObserver, { background: true }); - - expect(job._key).toBeUndefined(); - expect(job.observer).toBe(dummyObserver); - expect(job.config).toStrictEqual({ - background: true, - sideEffects: { - enabled: true, - exclude: [], - }, - force: false, - maxTriesToUpdate: 3, - }); - expect(job.rerender).toBeFalsy(); - expect(job.performed).toBeFalsy(); - expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); - }); + expect(job._key).toBe('dummyJob'); + expect(job.observer).toBe(dummyObserver); + expect(job.config).toStrictEqual({ + background: false, + sideEffects: { + enabled: false, + exclude: ['jeff'], + }, + force: true, + maxTriesToUpdate: 10, + }); + expect(job.rerender).toBeTruthy(); + expect(job.performed).toBeFalsy(); + expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); + expect(job.timesTriedToUpdateCount).toBe(0); + expect(job.performed).toBeFalsy(); + } + ); + + it( + 'should create RuntimeJob ' + + 'with a specified Agile Instance that has no registered Integration (default config)', + () => { + const job = new RuntimeJob(dummyObserver); + + expect(job._key).toBeUndefined(); + expect(job.observer).toBe(dummyObserver); + expect(job.config).toStrictEqual({ + background: false, + sideEffects: { + enabled: true, + exclude: [], + }, + force: false, + maxTriesToUpdate: 3, + }); + expect(job.rerender).toBeFalsy(); + expect(job.performed).toBeFalsy(); + expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); + expect(job.timesTriedToUpdateCount).toBe(0); + expect(job.performed).toBeFalsy(); + } + ); + + it( + 'should create RuntimeJob ' + + 'with a specified Agile Instance that has a registered Integrations (config.background = true)', + () => { + dummyAgile.integrate(dummyIntegration); + + const job = new RuntimeJob(dummyObserver, { background: true }); + + expect(job._key).toBeUndefined(); + expect(job.observer).toBe(dummyObserver); + expect(job.config).toStrictEqual({ + background: true, + sideEffects: { + enabled: true, + exclude: [], + }, + force: false, + maxTriesToUpdate: 3, + }); + expect(job.rerender).toBeFalsy(); + expect(job.performed).toBeFalsy(); + expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); + expect(job.timesTriedToUpdateCount).toBe(0); + expect(job.performed).toBeFalsy(); + } + ); describe('RuntimeJob Function Tests', () => { let job: RuntimeJob; diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts index b0814322..a3609ef8 100644 --- a/packages/core/tests/unit/runtime/runtime.test.ts +++ b/packages/core/tests/unit/runtime/runtime.test.ts @@ -351,7 +351,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob1.subscriptionContainersToUpdate) ).toStrictEqual([]); - expect(dummyJob1.triedToUpdateCount).toBe(0); + expect(dummyJob1.timesTriedToUpdateCount).toBe(0); expect( Array.from(dummySubscriptionContainer1.updatedSubscribers) ).toStrictEqual([dummyObserver1]); @@ -360,7 +360,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob2.subscriptionContainersToUpdate) ).toStrictEqual([dummySubscriptionContainer2]); - expect(dummyJob2.triedToUpdateCount).toBe(1); + expect(dummyJob2.timesTriedToUpdateCount).toBe(1); expect( Array.from(dummySubscriptionContainer2.updatedSubscribers) ).toStrictEqual([]); @@ -387,7 +387,7 @@ describe('Runtime Tests', () => { dummySubscriptionContainer1.ready = true; dummySubscriptionContainer2.ready = false; const numberOfTries = (dummyJob2.config.maxTriesToUpdate ?? 0) + 1; - dummyJob2.triedToUpdateCount = numberOfTries; + dummyJob2.timesTriedToUpdateCount = numberOfTries; const response = runtime.extractToUpdateSubscriptionContainer([ dummyJob1, @@ -409,7 +409,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob1.subscriptionContainersToUpdate) ).toStrictEqual([]); - expect(dummyJob1.triedToUpdateCount).toBe(0); + expect(dummyJob1.timesTriedToUpdateCount).toBe(0); expect( Array.from(dummySubscriptionContainer1.updatedSubscribers) ).toStrictEqual([dummyObserver1]); @@ -418,7 +418,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob2.subscriptionContainersToUpdate) ).toStrictEqual([dummySubscriptionContainer2]); - expect(dummyJob2.triedToUpdateCount).toBe(numberOfTries); + expect(dummyJob2.timesTriedToUpdateCount).toBe(numberOfTries); expect( Array.from(dummySubscriptionContainer2.updatedSubscribers) ).toStrictEqual([]); @@ -466,7 +466,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob1.subscriptionContainersToUpdate) ).toStrictEqual([]); - expect(dummyJob1.triedToUpdateCount).toBe(0); + expect(dummyJob1.timesTriedToUpdateCount).toBe(0); expect( Array.from(dummySubscriptionContainer1.updatedSubscribers) ).toStrictEqual([]); @@ -475,7 +475,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob2.subscriptionContainersToUpdate) ).toStrictEqual([]); - expect(dummyJob2.triedToUpdateCount).toBe(0); + expect(dummyJob2.timesTriedToUpdateCount).toBe(0); expect( Array.from(dummySubscriptionContainer2.updatedSubscribers) ).toStrictEqual([dummyObserver2]); @@ -517,7 +517,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob1.subscriptionContainersToUpdate) ).toStrictEqual([]); - expect(dummyJob1.triedToUpdateCount).toBe(0); + expect(dummyJob1.timesTriedToUpdateCount).toBe(0); expect( Array.from(dummySubscriptionContainer1.updatedSubscribers) ).toStrictEqual([dummyObserver1]); @@ -526,7 +526,7 @@ describe('Runtime Tests', () => { expect( Array.from(dummyJob2.subscriptionContainersToUpdate) ).toStrictEqual([]); - expect(dummyJob2.triedToUpdateCount).toBe(0); + expect(dummyJob2.timesTriedToUpdateCount).toBe(0); expect( Array.from(dummySubscriptionContainer2.updatedSubscribers) ).toStrictEqual([dummyObserver2]); diff --git a/packages/core/tests/unit/state/state.observer.test.ts b/packages/core/tests/unit/state/state.observer.test.ts index c9d837e8..a8a102d3 100644 --- a/packages/core/tests/unit/state/state.observer.test.ts +++ b/packages/core/tests/unit/state/state.observer.test.ts @@ -23,7 +23,7 @@ describe('StateObserver Tests', () => { dummyState = new State(dummyAgile, 'dummyValue', { key: 'dummyState' }); }); - it('should create StateObserver (default config)', () => { + it('should create State Observer (default config)', () => { const stateObserver = new StateObserver(dummyState); expect(stateObserver).toBeInstanceOf(StateObserver); @@ -32,11 +32,11 @@ describe('StateObserver Tests', () => { expect(stateObserver.value).toBe('dummyValue'); expect(stateObserver.previousValue).toBe('dummyValue'); expect(stateObserver._key).toBeUndefined(); - expect(stateObserver.dependents.size).toBe(0); - expect(stateObserver.subscribedTo.size).toBe(0); + expect(Array.from(stateObserver.dependents)).toStrictEqual([]); + expect(Array.from(stateObserver.subscribedTo)).toStrictEqual([]); }); - it('should create StateObserver (specific config)', () => { + it('should create State Observer (specific config)', () => { const dummyObserver1 = new Observer(dummyAgile, { key: 'dummyObserver1' }); const dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' }); const dummySubscription1 = new SubscriptionContainer([]); @@ -54,15 +54,17 @@ describe('StateObserver Tests', () => { expect(stateObserver.value).toBe('dummyValue'); expect(stateObserver.previousValue).toBe('dummyValue'); expect(stateObserver._key).toBe('testKey'); - expect(stateObserver.dependents.size).toBe(2); - expect(stateObserver.dependents.has(dummyObserver2)).toBeTruthy(); - expect(stateObserver.dependents.has(dummyObserver1)).toBeTruthy(); - expect(stateObserver.subscribedTo.size).toBe(2); - expect(stateObserver.subscribedTo.has(dummySubscription1)).toBeTruthy(); - expect(stateObserver.subscribedTo.has(dummySubscription2)).toBeTruthy(); + expect(Array.from(stateObserver.dependents)).toStrictEqual([ + dummyObserver1, + dummyObserver2, + ]); + expect(Array.from(stateObserver.subscribedTo)).toStrictEqual([ + dummySubscription1, + dummySubscription2, + ]); }); - describe('StateObserver Function Tests', () => { + describe('State Observer Function Tests', () => { let stateObserver: StateObserver; beforeEach(() => { @@ -101,7 +103,7 @@ describe('StateObserver Tests', () => { expect(stateObserver.ingestValue).toHaveBeenCalledWith('nextValue', {}); }); - it('should call ingestValue with nextStateValue (specific config)', () => { + it("should call 'ingestValue' with the 'nextStateValue' (specific config)", () => { dummyState.nextStateValue = 'nextValue'; stateObserver.ingest({ @@ -127,17 +129,21 @@ describe('StateObserver Tests', () => { }); }); - it('should call ingestValue with computedValue if Observer belongs to a ComputedState (default config)', () => { - dummyComputed.compute = jest.fn(() => 'computedValue'); - - computedObserver.ingest(); - - expect(computedObserver.ingestValue).toHaveBeenCalledWith( - 'computedValue', - {} - ); - expect(dummyComputed.compute).toHaveBeenCalled(); - }); + it( + "should call 'ingestValue' with computed value " + + 'if Observer belongs to a Computed State (default config)', + () => { + dummyComputed.compute = jest.fn(() => 'computedValue'); + + computedObserver.ingest(); + + expect(computedObserver.ingestValue).toHaveBeenCalledWith( + 'computedValue', + {} + ); + expect(dummyComputed.compute).toHaveBeenCalled(); + } + ); }); describe('ingestValue function tests', () => { @@ -145,108 +151,124 @@ describe('StateObserver Tests', () => { dummyAgile.runtime.ingest = jest.fn(); }); - it("should ingest State into Runtime if newValue isn't equal to currentValue (default config)", () => { - jest.spyOn(Utils, 'generateId').mockReturnValue('randomKey'); - - dummyAgile.runtime.ingest = jest.fn((job: StateRuntimeJob) => { - expect(job._key).toBe(`${stateObserver._key}_randomKey`); - expect(job.observer).toBe(stateObserver); - expect(job.config).toStrictEqual({ - background: false, - sideEffects: { - enabled: true, - exclude: [], - }, - force: false, - storage: true, - overwrite: false, + it( + 'should ingest the State into the Runtime ' + + "if the new value isn't equal to the current value (default config)", + () => { + jest.spyOn(Utils, 'generateId').mockReturnValue('randomKey'); + + dummyAgile.runtime.ingest = jest.fn((job: StateRuntimeJob) => { + expect(job._key).toBe(`${stateObserver._key}_randomKey`); + expect(job.observer).toBe(stateObserver); + expect(job.config).toStrictEqual({ + background: false, + sideEffects: { + enabled: true, + exclude: [], + }, + force: false, + storage: true, + overwrite: false, + }); }); - }); - - stateObserver.ingestValue('updatedDummyValue'); - expect(stateObserver.nextStateValue).toBe('updatedDummyValue'); - expect(dummyAgile.runtime.ingest).toHaveBeenCalledWith( - expect.any(StateRuntimeJob), - { - perform: true, - } - ); - }); + stateObserver.ingestValue('updatedDummyValue'); + + expect(stateObserver.nextStateValue).toBe('updatedDummyValue'); + expect(dummyAgile.runtime.ingest).toHaveBeenCalledWith( + expect.any(StateRuntimeJob), + { + perform: true, + } + ); + } + ); + + it( + 'should ingest the State into the Runtime ' + + "if the new value isn't equal to the current value (specific config)", + () => { + dummyAgile.runtime.ingest = jest.fn((job: StateRuntimeJob) => { + expect(job._key).toBe('dummyJob'); + expect(job.observer).toBe(stateObserver); + expect(job.config).toStrictEqual({ + background: false, + sideEffects: { + enabled: false, + }, + force: true, + storage: true, + overwrite: true, + }); + }); - it("should ingest State into Runtime if newValue isn't equal to currentValue (specific config)", () => { - dummyAgile.runtime.ingest = jest.fn((job: StateRuntimeJob) => { - expect(job._key).toBe('dummyJob'); - expect(job.observer).toBe(stateObserver); - expect(job.config).toStrictEqual({ - background: false, + stateObserver.ingestValue('updatedDummyValue', { + perform: false, + force: true, sideEffects: { enabled: false, }, - force: true, - storage: true, overwrite: true, + key: 'dummyJob', }); - }); - - stateObserver.ingestValue('updatedDummyValue', { - perform: false, - force: true, - sideEffects: { - enabled: false, - }, - overwrite: true, - key: 'dummyJob', - }); - - expect(stateObserver.nextStateValue).toBe('updatedDummyValue'); - expect(dummyAgile.runtime.ingest).toHaveBeenCalledWith( - expect.any(StateRuntimeJob), - { - perform: false, - } - ); - }); - - it("shouldn't ingest State into Runtime if newValue is equal to currentValue (default config)", () => { - dummyState._value = 'updatedDummyValue'; - - stateObserver.ingestValue('updatedDummyValue'); - - expect(stateObserver.nextStateValue).toBe('updatedDummyValue'); - expect(dummyAgile.runtime.ingest).not.toHaveBeenCalled(); - }); - it('should ingest State into Runtime if newValue is equal to currentValue (config.force = true)', () => { - jest.spyOn(Utils, 'generateId').mockReturnValue('randomKey'); - dummyState._value = 'updatedDummyValue'; - dummyAgile.runtime.ingest = jest.fn((job: StateRuntimeJob) => { - expect(job._key).toBe(`${stateObserver._key}_randomKey`); - expect(job.observer).toBe(stateObserver); - expect(job.config).toStrictEqual({ - background: false, - sideEffects: { - enabled: true, - exclude: [], - }, - force: true, - storage: true, - overwrite: false, + expect(stateObserver.nextStateValue).toBe('updatedDummyValue'); + expect(dummyAgile.runtime.ingest).toHaveBeenCalledWith( + expect.any(StateRuntimeJob), + { + perform: false, + } + ); + } + ); + + it( + "shouldn't ingest the State into the Runtime " + + 'if the new value is equal to the current value (default config)', + () => { + dummyState._value = 'updatedDummyValue'; + + stateObserver.ingestValue('updatedDummyValue'); + + expect(stateObserver.nextStateValue).toBe('updatedDummyValue'); + expect(dummyAgile.runtime.ingest).not.toHaveBeenCalled(); + } + ); + + it( + 'should ingest the State into the Runtime ' + + 'if the new value is equal to the current value (config.force = true)', + () => { + jest.spyOn(Utils, 'generateId').mockReturnValue('randomKey'); + dummyState._value = 'updatedDummyValue'; + dummyAgile.runtime.ingest = jest.fn((job: StateRuntimeJob) => { + expect(job._key).toBe(`${stateObserver._key}_randomKey`); + expect(job.observer).toBe(stateObserver); + expect(job.config).toStrictEqual({ + background: false, + sideEffects: { + enabled: true, + exclude: [], + }, + force: true, + storage: true, + overwrite: false, + }); }); - }); - stateObserver.ingestValue('updatedDummyValue', { force: true }); + stateObserver.ingestValue('updatedDummyValue', { force: true }); - expect(stateObserver.nextStateValue).toBe('updatedDummyValue'); - expect(dummyAgile.runtime.ingest).toHaveBeenCalledWith( - expect.any(StateRuntimeJob), - { - perform: true, - } - ); - }); + expect(stateObserver.nextStateValue).toBe('updatedDummyValue'); + expect(dummyAgile.runtime.ingest).toHaveBeenCalledWith( + expect.any(StateRuntimeJob), + { + perform: true, + } + ); + } + ); - it('should ingest placeholder State into Runtime (default config)', () => { + it('should ingest placeholder State into the Runtime (default config)', () => { jest.spyOn(Utils, 'generateId').mockReturnValue('randomKey'); dummyAgile.runtime.ingest = jest.fn((job: StateRuntimeJob) => { expect(job._key).toBe(`${stateObserver._key}_randomKey`); @@ -275,21 +297,25 @@ describe('StateObserver Tests', () => { ); }); - it('should ingest State into Runtime and compute newStateValue if State compute Function is set (default config)', () => { - dummyState.computeValueMethod = (value) => `cool value '${value}'`; - - stateObserver.ingestValue('updatedDummyValue'); - - expect(stateObserver.nextStateValue).toBe( - "cool value 'updatedDummyValue'" - ); - expect(dummyAgile.runtime.ingest).toHaveBeenCalledWith( - expect.any(StateRuntimeJob), - { - perform: true, - } - ); - }); + it( + 'should ingest the State into the Runtime and compute the new value ' + + 'if the State compute function is set (default config)', + () => { + dummyState.computeValueMethod = (value) => `cool value '${value}'`; + + stateObserver.ingestValue('updatedDummyValue'); + + expect(stateObserver.nextStateValue).toBe( + "cool value 'updatedDummyValue'" + ); + expect(dummyAgile.runtime.ingest).toHaveBeenCalledWith( + expect.any(StateRuntimeJob), + { + perform: true, + } + ); + } + ); }); describe('perform function tests', () => { @@ -305,7 +331,7 @@ describe('StateObserver Tests', () => { stateObserver.sideEffects = jest.fn(); }); - it('should perform Job', () => { + it('should perform the specified Job', () => { dummyJob.observer.nextStateValue = 'newValue'; dummyJob.observer.value = 'dummyValue'; dummyState.initialStateValue = 'initialValue'; @@ -327,7 +353,7 @@ describe('StateObserver Tests', () => { expect(stateObserver.sideEffects).toHaveBeenCalledWith(dummyJob); }); - it('should perform Job and overwrite State (job.config.overwrite = true)', () => { + it('should perform the specified Job and overwrite the State it represents (job.config.overwrite = true)', () => { dummyJob.observer.nextStateValue = 'newValue'; dummyJob.observer.value = 'dummyValue'; dummyJob.config.overwrite = true; @@ -352,27 +378,31 @@ describe('StateObserver Tests', () => { expect(stateObserver.sideEffects).toHaveBeenCalledWith(dummyJob); }); - it('should perform Job and set isSet to false if initialStateValue equals to newStateValue', () => { - dummyJob.observer.nextStateValue = 'newValue'; - dummyJob.observer.value = 'dummyValue'; - dummyState.initialStateValue = 'newValue'; - dummyState._value = 'dummyValue'; - dummyState.getPublicValue = jest - .fn() - .mockReturnValueOnce('newPublicValue'); - - stateObserver.perform(dummyJob); - - expect(dummyState.previousStateValue).toBe('dummyValue'); - expect(dummyState.initialStateValue).toBe('newValue'); - expect(dummyState._value).toBe('newValue'); - expect(dummyState.nextStateValue).toBe('newValue'); - expect(dummyState.isSet).toBeFalsy(); - - expect(stateObserver.value).toBe('newPublicValue'); - expect(stateObserver.previousValue).toBe('dummyValue'); - expect(stateObserver.sideEffects).toHaveBeenCalledWith(dummyJob); - }); + it( + "should perform the specified Job and set 'isSet' to false " + + 'if the initial State value is equal to the new State value', + () => { + dummyJob.observer.nextStateValue = 'newValue'; + dummyJob.observer.value = 'dummyValue'; + dummyState.initialStateValue = 'newValue'; + dummyState._value = 'dummyValue'; + dummyState.getPublicValue = jest + .fn() + .mockReturnValueOnce('newPublicValue'); + + stateObserver.perform(dummyJob); + + expect(dummyState.previousStateValue).toBe('dummyValue'); + expect(dummyState.initialStateValue).toBe('newValue'); + expect(dummyState._value).toBe('newValue'); + expect(dummyState.nextStateValue).toBe('newValue'); + expect(dummyState.isSet).toBeFalsy(); + + expect(stateObserver.value).toBe('newPublicValue'); + expect(stateObserver.previousValue).toBe('dummyValue'); + expect(stateObserver.sideEffects).toHaveBeenCalledWith(dummyJob); + } + ); }); describe('sideEffects function tests', () => { @@ -406,8 +436,9 @@ describe('StateObserver Tests', () => { }; }); - it('should call watchers, sideEffects and ingest dependencies of State', () => { + it('should call watcher callbacks and State side effect', () => { dummyState._value = 'dummyValue'; + stateObserver.sideEffects(dummyJob); expect(dummyState.watchers['dummyWatcher']).toHaveBeenCalledWith( @@ -430,27 +461,64 @@ describe('StateObserver Tests', () => { ]); }); - it("should call watchers, ingest dependencies of State and shouldn't call sideEffects (job.config.sideEffects = false)", () => { - dummyState._value = 'dummyValue'; - dummyJob.config.sideEffects = { - enabled: false, - }; - stateObserver.sideEffects(dummyJob); - - expect(dummyState.watchers['dummyWatcher']).toHaveBeenCalledWith( - 'dummyValue', - 'dummyWatcher' - ); - expect( - dummyState.sideEffects['dummySideEffect'].callback - ).not.toHaveBeenCalled(); - expect( - dummyState.sideEffects['dummySideEffect2'].callback - ).not.toHaveBeenCalled(); - expect( - dummyState.sideEffects['dummySideEffect3'].callback - ).not.toHaveBeenCalled(); - }); + it( + 'should call watcher callbacks ' + + "and shouldn't call State side effects (job.config.sideEffects.enabled = false)", + () => { + dummyState._value = 'dummyValue'; + dummyJob.config.sideEffects = { + enabled: false, + }; + + stateObserver.sideEffects(dummyJob); + + expect(dummyState.watchers['dummyWatcher']).toHaveBeenCalledWith( + 'dummyValue', + 'dummyWatcher' + ); + expect( + dummyState.sideEffects['dummySideEffect'].callback + ).not.toHaveBeenCalled(); + expect( + dummyState.sideEffects['dummySideEffect2'].callback + ).not.toHaveBeenCalled(); + expect( + dummyState.sideEffects['dummySideEffect3'].callback + ).not.toHaveBeenCalled(); + } + ); + + it( + 'should call watcher callbacks ' + + "and shouldn't call all State side effects (job.config.sideEffects.exclude = ['dummySideEffect2'])", + () => { + dummyState._value = 'dummyValue'; + dummyJob.config.sideEffects = { + enabled: true, + exclude: ['dummySideEffect2'], + }; + + stateObserver.sideEffects(dummyJob); + + expect(dummyState.watchers['dummyWatcher']).toHaveBeenCalledWith( + 'dummyValue', + 'dummyWatcher' + ); + expect( + dummyState.sideEffects['dummySideEffect'].callback + ).toHaveBeenCalledWith(dummyState, dummyJob.config); + expect( + dummyState.sideEffects['dummySideEffect2'].callback + ).not.toHaveBeenCalled(); + expect( + dummyState.sideEffects['dummySideEffect3'].callback + ).toHaveBeenCalledWith(dummyState, dummyJob.config); + expect(sideEffectCallOrder).toStrictEqual([ + 'dummySideEffect3', + 'dummySideEffect', + ]); + } + ); }); }); }); diff --git a/packages/core/tests/unit/state/state.persistent.test.ts b/packages/core/tests/unit/state/state.persistent.test.ts index ed87d03a..1fc7d061 100644 --- a/packages/core/tests/unit/state/state.persistent.test.ts +++ b/packages/core/tests/unit/state/state.persistent.test.ts @@ -322,7 +322,7 @@ describe('StatePersistent Tests', () => { statePersistent.rebuildStorageSideEffect = jest.fn(); }); - it('should call rebuildStorageSideEffect (persistentKey)', async () => { + it("should call 'rebuildStorageSideEffect' (persistentKey)", async () => { await statePersistent.setupSideEffects(); dummyState.sideEffects[ @@ -340,7 +340,7 @@ describe('StatePersistent Tests', () => { ); }); - it('should call rebuildStorageSideEffect (specified key)', async () => { + it("should call 'rebuildStorageSideEffect' (specified key)", async () => { await statePersistent.setupSideEffects('dummyKey'); dummyState.sideEffects[ @@ -384,7 +384,7 @@ describe('StatePersistent Tests', () => { expect(statePersistent.isPersisted).toBeFalsy(); }); - it('should remove persisted State from the corresponding Storage with specified Key', async () => { + it('should remove persisted State from the corresponding Storage with specified key', async () => { statePersistent.ready = true; const response = await statePersistent.removePersistedValue('coolKey'); @@ -421,7 +421,7 @@ describe('StatePersistent Tests', () => { expect(response).toBe('coolKey'); }); - it('should return passed key', () => { + it('should return specified key', () => { dummyState._key = 'coolKey'; const response = statePersistent.formatKey('awesomeKey'); @@ -429,7 +429,7 @@ describe('StatePersistent Tests', () => { expect(response).toBe('awesomeKey'); }); - it('should return and apply specified key to State if State has no own valid key before', () => { + it('should return and apply specified key to State if State had no own valid key before', () => { dummyState._key = undefined; const response = statePersistent.formatKey('awesomeKey'); @@ -452,7 +452,7 @@ describe('StatePersistent Tests', () => { dummyAgile.storages.set = jest.fn(); }); - it('should save State Value in Storage (default config)', () => { + it('should store current State value in the corresponding Storage (default config)', () => { statePersistent.rebuildStorageSideEffect(dummyState, 'coolKey'); expect(dummyAgile.storages.set).toHaveBeenCalledWith( @@ -462,7 +462,7 @@ describe('StatePersistent Tests', () => { ); }); - it("shouldn't save State Value in Storage (config.storage = false)", () => { + it("shouldn't store State value in the corresponding Storage (config.storage = false)", () => { statePersistent.rebuildStorageSideEffect(dummyState, 'coolKey', { storage: false, }); diff --git a/packages/core/tests/unit/state/state.runtime.job.test.ts b/packages/core/tests/unit/state/state.runtime.job.test.ts index b1f6c04f..c5465182 100644 --- a/packages/core/tests/unit/state/state.runtime.job.test.ts +++ b/packages/core/tests/unit/state/state.runtime.job.test.ts @@ -27,94 +27,118 @@ describe('RuntimeJob Tests', () => { dummyObserver = new StateObserver(dummyState); }); - it('should create RuntimeJob with a specified Agile Instance that has a registered Integration (default config)', () => { - dummyAgile.integrate(dummyIntegration); + it( + 'should create StateRuntimeJob ' + + 'with a specified Agile Instance that has a registered Integration (default config)', + () => { + dummyAgile.integrate(dummyIntegration); - const job = new StateRuntimeJob(dummyObserver); + const job = new StateRuntimeJob(dummyObserver); - expect(job._key).toBeUndefined(); - expect(job.observer).toBe(dummyObserver); - expect(job.config).toStrictEqual({ - background: false, - sideEffects: { - enabled: true, - exclude: [], - }, - force: false, - storage: true, - overwrite: false, - }); - expect(job.rerender).toBeTruthy(); - expect(job.performed).toBeFalsy(); - expect(job.subscriptionContainersToUpdate.size).toBe(0); - }); + expect(job._key).toBeUndefined(); + expect(job.observer).toBe(dummyObserver); + expect(job.config).toStrictEqual({ + background: false, + sideEffects: { + enabled: true, + exclude: [], + }, + force: false, + storage: true, + overwrite: false, + }); + expect(job.rerender).toBeTruthy(); + expect(job.performed).toBeFalsy(); + expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); + expect(job.timesTriedToUpdateCount).toBe(0); + expect(job.performed).toBeFalsy(); + } + ); - it('should create RuntimeJob with a specified Agile Instance that has a registered Integration (specific config)', () => { - dummyAgile.integrate(dummyIntegration); + it( + 'should create StateRuntimeJob ' + + 'with a specified Agile Instance that has a registered Integration (specific config)', + () => { + dummyAgile.integrate(dummyIntegration); - const job = new StateRuntimeJob(dummyObserver, { - key: 'dummyJob', - sideEffects: { - enabled: false, - }, - force: true, - }); + const job = new StateRuntimeJob(dummyObserver, { + key: 'dummyJob', + sideEffects: { + enabled: false, + }, + force: true, + }); - expect(job._key).toBe('dummyJob'); - expect(job.observer).toBe(dummyObserver); - expect(job.config).toStrictEqual({ - background: false, - sideEffects: { - enabled: false, - }, - force: true, - storage: true, - overwrite: false, - }); - expect(job.rerender).toBeTruthy(); - expect(job.performed).toBeFalsy(); - expect(job.subscriptionContainersToUpdate.size).toBe(0); - }); + expect(job._key).toBe('dummyJob'); + expect(job.observer).toBe(dummyObserver); + expect(job.config).toStrictEqual({ + background: false, + sideEffects: { + enabled: false, + }, + force: true, + storage: true, + overwrite: false, + }); + expect(job.rerender).toBeTruthy(); + expect(job.performed).toBeFalsy(); + expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); + expect(job.timesTriedToUpdateCount).toBe(0); + expect(job.performed).toBeFalsy(); + } + ); - it('should create RuntimeJob with a specified Agile Instance that has no registered Integration (default config)', () => { - const job = new StateRuntimeJob(dummyObserver); + it( + 'should create StateRuntimeJob ' + + 'with a specified Agile Instance that has no registered Integration (default config)', + () => { + const job = new StateRuntimeJob(dummyObserver); - expect(job._key).toBeUndefined(); - expect(job.observer).toBe(dummyObserver); - expect(job.config).toStrictEqual({ - background: false, - sideEffects: { - enabled: true, - exclude: [], - }, - force: false, - storage: true, - overwrite: false, - }); - expect(job.rerender).toBeFalsy(); - expect(job.performed).toBeFalsy(); - expect(job.subscriptionContainersToUpdate.size).toBe(0); - }); + expect(job._key).toBeUndefined(); + expect(job.observer).toBe(dummyObserver); + expect(job.config).toStrictEqual({ + background: false, + sideEffects: { + enabled: true, + exclude: [], + }, + force: false, + storage: true, + overwrite: false, + }); + expect(job.rerender).toBeFalsy(); + expect(job.performed).toBeFalsy(); + expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); + expect(job.timesTriedToUpdateCount).toBe(0); + expect(job.performed).toBeFalsy(); + } + ); - it('should create RuntimeJob and Agile that has integrations (config.background = true)', () => { - dummyAgile.integrate(dummyIntegration); + it( + 'should create StateRuntimeJob ' + + 'with a specified Agile Instance that has a registered Integrations (config.background = true)', + () => { + dummyAgile.integrate(dummyIntegration); - const job = new StateRuntimeJob(dummyObserver, { background: true }); + const job = new StateRuntimeJob(dummyObserver, { background: true }); - expect(job._key).toBeUndefined(); - expect(job.observer).toBe(dummyObserver); - expect(job.config).toStrictEqual({ - background: true, - sideEffects: { - enabled: true, - exclude: [], - }, - force: false, - storage: true, - overwrite: false, - }); - expect(job.rerender).toBeFalsy(); - expect(job.performed).toBeFalsy(); - expect(job.subscriptionContainersToUpdate.size).toBe(0); - }); + expect(job._key).toBeUndefined(); + expect(job.observer).toBe(dummyObserver); + expect(job.config).toStrictEqual({ + background: true, + sideEffects: { + enabled: true, + exclude: [], + }, + force: false, + storage: true, + overwrite: false, + }); + expect(job.rerender).toBeFalsy(); + expect(job.performed).toBeFalsy(); + expect(Array.from(job.subscriptionContainersToUpdate)).toStrictEqual([]); + expect(job.timesTriedToUpdateCount).toBe(0); + expect(job.performed).toBeFalsy(); + } + ); }); From 4824a138f4b7bc9e06cd9f9be5f5e09296b310cc Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 12 Jun 2021 19:41:05 +0200 Subject: [PATCH 073/117] fixed tests --- packages/event/tests/unit/event.observer.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/event/tests/unit/event.observer.test.ts b/packages/event/tests/unit/event.observer.test.ts index be77973c..221bd2b9 100644 --- a/packages/event/tests/unit/event.observer.test.ts +++ b/packages/event/tests/unit/event.observer.test.ts @@ -28,8 +28,8 @@ describe('EventObserver Tests', () => { it('should create EventObserver (specific config)', () => { const dummyObserver1 = new Observer(dummyAgile, { key: 'dummyObserver1' }); const dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' }); - const dummySubscription1 = new SubscriptionContainer(); - const dummySubscription2 = new SubscriptionContainer(); + const dummySubscription1 = new SubscriptionContainer([]); + const dummySubscription2 = new SubscriptionContainer([]); const eventObserver = new EventObserver(dummyEvent, { key: 'testKey', From 4534b7387acc0e9092ac71d850118be8d58a4bf3 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 13 Jun 2021 16:16:29 +0200 Subject: [PATCH 074/117] fixed descritpion for agile class --- packages/core/src/agile.ts | 266 ++++++++++++++++--------- packages/core/src/computed/index.ts | 5 +- packages/core/src/state/index.ts | 2 + packages/core/tests/unit/agile.test.ts | 18 +- 4 files changed, 192 insertions(+), 99 deletions(-) diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index f4c765f2..03d795c1 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -8,7 +8,6 @@ import { DefaultItem, Computed, Integrations, - Observer, SubController, globalBind, Storages, @@ -19,35 +18,62 @@ import { CreateLoggerConfigInterface, StateConfigInterface, flatMerge, - Group, LogCodeManager, + ComputedConfigInterface, + SubscribableAgileInstancesType, } from './internal'; export class Agile { public config: AgileConfigInterface; - public runtime: Runtime; // Handles assigning Values to Agile Instances - public subController: SubController; // Handles subscriptions to Components - public storages: Storages; // Handles permanent saving + // Queues and executes incoming Observer-based Jobs + public runtime: Runtime; + // Manages and simplifies the subscription to UI-Components + public subController: SubController; + // Handles the permanent persistence of Agile Classes + public storages: Storages; - // Integrations - public integrations: Integrations; // Integrated frameworks - static initialIntegrations: Integration[] = []; // External added initial Integrations + // Frameworks that are integrated into AgileTs + public integrations: Integrations; + // External added Integrations that are integrated into AgileTs when it is instantiated + static initialIntegrations: Integration[] = []; - // Static Logger with default config -> will be overwritten by config of last created Agile Instance + // Static AgileTs Logger with default config + // (-> is overwritten by the last created Agile Instance) static logger = new Logger({ prefix: 'Agile', active: true, level: Logger.level.WARN, }); - // Key used to bind AgileTs globally + // Identifier used to bind an Agile Instance globally static globalKey = '__agile__'; /** + * The Agile Class is the main Instance of AgileTs + * and should be unique to your application. + * + * Simply put, the Agile Instance is the brain of AgileTs + * and manages all [`Agile Sub Instance`](../main/Introduction.md#agile-sub-instance) + * like States. + * + * It should be noted that it doesn't store the States; + * It only manages them. Each State has an Instance of the Agile Class, + * for example, to ingest its changes into the runtime. + * In summary, the main tasks of the Agile Class are to: + * - queuing [`Agile Sub Instance`](../main/Introduction.md#agile-sub-instance) + * changes in the `runtime` and preventing race conditions + * - update/rerender subscribed Components + * through Integrations like the [React Integration](../packages/react/Introduction.md) + * - Integrating with persistent [Storage](../packages/core/features/storage/Introduction.md) + * - provide configuration object + * + * Each Agile Sub Instance requires an Agile Instance to be instantiated and function properly. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/) + * * @public - * Agile - Global state and logic framework for reactive Typescript & Javascript applications - * @param config - Config + * @param config - Configuration object */ constructor(config: CreateAgileConfigInterface = {}) { config = defineConfig(config, { @@ -73,39 +99,48 @@ export class Agile { localStorage: config.localStorage, }); - // Assign customized config to Logger + // Assign customized config to static Logger Agile.logger = new Logger(config.logConfig); // Logging LogCodeManager.log('10:00:00', [], this, Agile.logger); - // Create global instance of Agile - // Why? getAgileInstance() returns the global AgileInstance if it couldn't find the Agile Instance in the passed Instance - if (config.bindGlobal) { + // Create global instance of Agile. + // Why? 'getAgileInstance()' returns the global Agile Instance + // if it couldn't find any Agile Instance in the specified Instance. + if (config.bindGlobal) if (!globalBind(Agile.globalKey, this)) LogCodeManager.log('10:02:00'); - } } - //========================================================================================================= - // Storage - //========================================================================================================= /** + * Returns a newly created Storage. + * + * A Storage represents an external storage + * such as the Local Storage and is an interface for AgileTs to it. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstorage) + * * @public - * Storage - Handy Interface for storing Items permanently - * @param config - Config + * @param config - Configuration object */ public createStorage(config: CreateStorageConfigInterface): Storage { return new Storage(config); } - //========================================================================================================= - // State - //========================================================================================================= /** + * Returns a newly created State. + * + * A State manages a piece of Information + * that we need to remember globally at a later point in time. + * While providing a toolkit to use and mutate this set of Information. + * + * You can create as many global States as you need. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstate) + * * @public - * State - Class that holds one Value and causes rerender on subscribed Components - * @param initialValue - Initial Value of the State - * @param config - Config + * @param initialValue - Initial value of the State. + * @param config - Configuration object */ public createState( initialValue: ValueType, @@ -114,13 +149,23 @@ export class Agile { return new State(this, initialValue, config); } - //========================================================================================================= - // Collection - //========================================================================================================= /** + * Returns a newly created Collection. + * + * A Collection manages a reactive set of Information + * that we need to remember globally at a later point in time. + * While providing a toolkit to use and mutate this set of Information. + * + * It is designed for arrays of data objects following the same pattern. + * + * Each of these data object must have a unique `primaryKey` to be correctly identified later. + * + * You can create as many global Collections as you need. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createcollection) + * * @public - * Collection - Class that holds a List of Objects with key and causes rerender on subscribed Components - * @param config - Config + * @param config - Configuration object */ public createCollection( config?: CollectionConfig @@ -128,77 +173,98 @@ export class Agile { return new Collection(this, config); } - //========================================================================================================= - // Computed - //========================================================================================================= /** + * Returns a newly created Computed. + * + * A Computed is an extension of the State Class + * that computes its value based on a compute function. + * + * The computed value will be cached to avoid unnecessary recomputes + * and is only recomputed when one of its direct dependencies changes. + * + * Direct dependencies can be States and Collections. + * So when for example a dependent State value changes, the computed value will be recomputed. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstate) + * * @public - * Computed - Function that recomputes its value if a dependency changes - * @param computeFunction - Function for computing value - * @param config - Config - * @param deps - Hard coded dependencies of Computed Function + * @param computeFunction - Function to compute the computed value. + * @param config - Configuration object */ public createComputed( computeFunction: () => ComputedValueType, - config?: StateConfigInterface, - deps?: Array + config?: ComputedConfigInterface ): Computed; /** + * Returns a newly created Computed. + * + * A Computed is an extension of the State Class + * that computes its value based on a compute function. + * + * The computed value will be cached to avoid unnecessary recomputes + * and is only recomputed when one of its direct dependencies changes. + * + * Direct dependencies can be States and Collections. + * So when for example a dependent State value changes, the computed value will be recomputed. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createcomputed) + * * @public - * Computed - Function that recomputes its value if a dependency changes - * @param computeFunction - Function for computing value - * @param deps - Hard coded dependencies of Computed Function + * @param computeFunction - Function to compute the computed value. + * @param deps - Hard-coded dependencies on which the Computed Class should depend. */ public createComputed( computeFunction: () => ComputedValueType, - deps?: Array + deps?: Array ): Computed; public createComputed( computeFunction: () => ComputedValueType, - configOrDeps?: StateConfigInterface | Array, - deps?: Array + configOrDeps?: + | ComputedConfigInterface + | Array ): Computed { - let _deps: Array; - let _config: StateConfigInterface; + let _config: ComputedConfigInterface = {}; if (Array.isArray(configOrDeps)) { - _deps = configOrDeps; - _config = {}; + _config = flatMerge(_config, { + computedDeps: configOrDeps, + }); } else { - _config = configOrDeps || {}; - _deps = deps || []; + if (configOrDeps) _config = configOrDeps; } - return new Computed( - this, - computeFunction, - flatMerge(_config, { - computedDeps: _deps, - }) - ); + return new Computed(this, computeFunction, _config); } - //========================================================================================================= - // Integrate - //========================================================================================================= /** + * Registers the specified Integration with AgileTs. + * + * After a successful registration, + * Agile Sub Instances such as States + * can be bound to the Integration's UI-Components for reactivity. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#integrate) + * * @public - * Integrates framework into Agile - * @param integration - Integration that gets registered/integrated + * @param integration - Integration to be integrated/registered. */ public integrate(integration: Integration) { this.integrations.integrate(integration); return this; } - //========================================================================================================= - // Register Storage - //========================================================================================================= /** + * Registers the specified Storage with AgileTs. + * + * After a successful registration, + * Agile Sub Instances such as States + * can be persisted in the external Storage. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#registerstorage) + * * @public - * Registers new Storage as Agile Storage - * @param storage - new Storage - * @param config - Config + * @param storage - Storage to be registered. + * @param config - Configuration object */ public registerStorage( storage: Storage, @@ -208,45 +274,67 @@ export class Agile { return this; } - //========================================================================================================= - // Has Integration - //========================================================================================================= /** + * Returns a boolean indicating whether any Integration + * has been registered with AgileTs or not. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#hasintegration) + * * @public - * Checks if Agile has any registered Integration */ public hasIntegration(): boolean { return this.integrations.hasIntegration(); } - //========================================================================================================= - // Has Storage - //========================================================================================================= /** + * Returns a boolean indicating whether any Storage + * has been registered with AgileTs or not. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#hasstorage) + * * @public - * Checks if Agile has any registered Storage */ public hasStorage(): boolean { return this.storages.hasStorage(); } } -/** - * @param logJobs - Allow Agile Logs - * @param waitForMount - If Agile should wait until the component mounts - * @param storageConfig - To configure Agile Storage - * @param bindGlobal - Binds Agile Instance Global - */ export interface CreateAgileConfigInterface { + /** + * Configures the logging behaviour of AgileTs. + * @default { + prefix: 'Agile', + active: true, + level: Logger.level.WARN, + canUseCustomStyles: true, + allowedTags: ['runtime', 'storage', 'subscription', 'multieditor'], + } + */ logConfig?: CreateLoggerConfigInterface; + /** + * Whether the Subscription Container should not be ready + * until the UI-Component it represents has been mounted. + * @default true + */ waitForMount?: boolean; + /** + * Whether the Local Storage should be registered as Storage by default. + * @default true + */ localStorage?: boolean; + /** + * Whether the Agile instance should be globally bound (globalThis) + * and thus be globally available. + * @default false + */ bindGlobal?: boolean; } -/** - * @param waitForMount - If Agile should wait until the component mounts - */ export interface AgileConfigInterface { + /** + * Whether the Subscription Container should not be ready + * until the UI-Component it represents has been mounted. + * @default true + */ waitForMount: boolean; } diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index cea3cdc7..0a5c5cdb 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -26,7 +26,8 @@ export class Computed extends State< public hardCodedDeps: Array = []; /** - * An extension of the State Class that computes its value based on a compute function. + * A Computed is an extension of the State Class + * that computes its value based on a compute function. * * The computed value will be cached to avoid unnecessary recomputes * and is only recomputed when one of its direct dependencies changes. @@ -217,4 +218,4 @@ export interface RecomputeConfigInterface extends StateIngestConfigInterface, ComputeConfigInterface {} -type SubscribableAgileInstancesType = State | Collection | Observer; +export type SubscribableAgileInstancesType = State | Collection | Observer; diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index 3dc8a1fd..2d949993 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -72,6 +72,8 @@ export class State { * * You can create as many global States as you need. * + * [Learn more..](https://agile-ts.org/docs/core/state/) + * * @public * @param agileInstance - Instance of Agile the State belongs to. * @param initialValue - Initial value of the State. diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts index e231a090..11b9e863 100644 --- a/packages/core/tests/unit/agile.test.ts +++ b/packages/core/tests/unit/agile.test.ts @@ -240,26 +240,28 @@ describe('Agile Tests', () => { }); it('should create Computed', () => { - const computed = agile.createComputed(computedFunction, []); + const computed = agile.createComputed(computedFunction, [ + 'dummyDep' as any, + ]); expect(computed).toBeInstanceOf(Computed); expect(ComputedMock).toHaveBeenCalledWith(agile, computedFunction, { - computedDeps: [], + computedDeps: ['dummyDep' as any], }); }); it('should create Computed with config', () => { - const computed = agile.createComputed( - computedFunction, - { key: 'jeff', isPlaceholder: false }, - [] - ); + const computed = agile.createComputed(computedFunction, { + key: 'jeff', + isPlaceholder: false, + computedDeps: ['dummyDep' as any], + }); expect(computed).toBeInstanceOf(Computed); expect(ComputedMock).toHaveBeenCalledWith(agile, computedFunction, { key: 'jeff', isPlaceholder: false, - computedDeps: [], + computedDeps: ['dummyDep' as any], }); }); }); From 5bcd122ca93612003c7fab871b49d413578ba8be Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 13 Jun 2021 18:33:02 +0200 Subject: [PATCH 075/117] fixed descriptions for Integration/s class --- packages/core/src/integrations/index.ts | 46 +++++++------- packages/core/src/integrations/integration.ts | 61 ++++++++++++++----- 2 files changed, 72 insertions(+), 35 deletions(-) diff --git a/packages/core/src/integrations/index.ts b/packages/core/src/integrations/index.ts index 09cfa12f..9b6c140c 100644 --- a/packages/core/src/integrations/index.ts +++ b/packages/core/src/integrations/index.ts @@ -1,31 +1,35 @@ import { Agile, Integration, LogCodeManager } from '../internal'; export class Integrations { + // Agile Instance the Integrations belongs to public agileInstance: () => Agile; - public integrations: Set = new Set(); // All registered Integrations + // Registered Integrations + public integrations: Set = new Set(); /** + * The Integrations Class manages all Integrations for an Agile Instance + * and provides an interface to easily update + * or invoke functions in all registered Integrations. + * * @internal - * Integrations - Manages Integrations of Agile - * @param agileInstance - An Instance of Agile + * @param agileInstance - Instance of Agile the Integrations belongs to. */ constructor(agileInstance: Agile) { this.agileInstance = () => agileInstance; - // Integrate initial Integrations which are static and got set external + // Integrate initial Integrations which were statically set from external Agile.initialIntegrations.forEach((integration) => this.integrate(integration) ); } - //========================================================================================================= - // Integrate - //========================================================================================================= /** + * Integrates the specified Integration into AgileTs + * and sets it to ready when the binding was successful. + * * @internal - * Integrates Framework(Integration) into Agile - * @param integration - Integration/Framework that gets integrated + * @param integration - Integration to be integrated into AgileTs. */ public async integrate(integration: Integration): Promise { // Check if Integration is valid @@ -34,7 +38,7 @@ export class Integrations { return false; } - // Bind Framework to Agile + // Bind to integrate Integration to AgileTs if (integration.methods.bind) integration.ready = await integration.methods.bind(this.agileInstance()); else integration.ready = true; @@ -48,15 +52,16 @@ export class Integrations { return true; } - //========================================================================================================= - // Update - //========================================================================================================= /** + * Updates the specified UI-Component Instance + * with the updated data object in all registered Integrations that are ready. + * + * In doing so, it calls the `updateMethod()` method + * in all registered Integrations with the specified parameters. + * * @internal - * Updates registered and ready Integrations - * -> calls 'updateMethod' in all registered and ready Integrations - * @param componentInstance - Component that gets updated - * @param updatedData - Properties that differ from the last Value + * @param componentInstance - Component Instance to be updated. + * @param updatedData - Data object with updated data. */ public update(componentInstance: any, updatedData: Object): void { this.integrations.forEach((integration) => { @@ -69,12 +74,11 @@ export class Integrations { }); } - //========================================================================================================= - // Has Integration - //========================================================================================================= /** + * Returns a boolean indicating whether any Integration + * has been registered or not. + * * @internal - * Check if at least one Integration got registered */ public hasIntegration(): boolean { return this.integrations.size > 0; diff --git a/packages/core/src/integrations/integration.ts b/packages/core/src/integrations/integration.ts index 94c011db..41a4d75d 100644 --- a/packages/core/src/integrations/integration.ts +++ b/packages/core/src/integrations/integration.ts @@ -1,16 +1,23 @@ import { Agile } from '../internal'; export class Integration { + // Key/Name identifier of the Integration public _key: IntegrationKey; + // Instance of the Framework the Integration represents public frameworkInstance?: F; + // Whether the Integration is ready public ready = false; + // Whether the Integration was integrated into AgileTs public integrated = false; + // Methods to interact with the Framework represented by the Integration public methods: IntegrationMethods; /** + * An Integrations is an direct interface to an UI-Framework, + * and allows easy interaction with that Framework. + * * @public - * Integration - Represents a Framework/Integration of Agile - * @param config - Config + * @param config - Configuration object */ constructor(config: CreateIntegrationConfig) { this._key = config.key; @@ -22,38 +29,64 @@ export class Integration { } /** + * Updates the key/name identifier of the Integration. + * * @public - * Set Value of Integration + * @param value - New key/name identifier. */ - public set key(key: IntegrationKey) { - this._key = key; + public set key(value: IntegrationKey) { + this._key = value; } /** + * Returns the key/name identifier of the State. + * * @public - * Get Value of Integration */ public get key(): IntegrationKey { return this._key; } } -/** - * @param key - Key/Name of Integration - * @param frameworkInstance - An Instance of the Framework that this Integration represents (for instance React) - */ export interface CreateIntegrationConfig extends IntegrationMethods { + /** + * Key/Name identifier of the Integratioon. + * @default undefined + */ key: string; + /** + * An Instance of the Framework to be represented by the Integration. + * For example, in the case of React, the React Instance. + * @default undefined + */ frameworkInstance?: F; } -/** - * @param bind - Binds the Framework/Integration to Agile | Will be called after a successful integration - * @param updateMethod - Will be called if a Observer updates his subs (Only in Component based Subscriptions!) - */ export interface IntegrationMethods { + /** + * Binds the Framework/Integration to an Agile Instance. + * + * This method is called shortly after the Integration was registered with a Agile Instance. + * It is intended to set up things on the Framework side + * that are important for a integration into AgileTs. + * + * @param agileInstance - Agile Instance into which the Integration is to be integrated. + * @return Indicating whether the to integrate Integration is ready on the Framework side. + */ bind?: (agileInstance: Agile) => Promise; + /** + * Method to apply the specified updated data to the specified UI-Component + * in order to trigger a re-render on it. + * + * This method is called when the value of an Agile Sub Instance + * bound to the UI-Component changes + * in a Component based Subscription. + * The updated Agile Sub Instance values are represented in the `updatedData` object. + * + * @param componentInstance - Component Instance of the to update UI-Component. + * @param updatedData - Data object containing the updated data. + */ updateMethod?: (componentInstance: C, updatedData: Object) => void; } From 56d42f3dda828386b3bf851f0a1945b2c8d4c697 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 13 Jun 2021 20:23:19 +0200 Subject: [PATCH 076/117] fixed typos --- packages/core/src/agile.ts | 57 ++++++++++--------- packages/core/src/computed/index.ts | 4 +- packages/core/src/integrations/index.ts | 4 +- packages/core/src/integrations/integration.ts | 32 ++++++----- .../runtime/subscription/sub.controller.ts | 2 +- packages/core/src/state/index.ts | 2 +- 6 files changed, 53 insertions(+), 48 deletions(-) diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index 03d795c1..d47775ae 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -33,12 +33,12 @@ export class Agile { // Handles the permanent persistence of Agile Classes public storages: Storages; - // Frameworks that are integrated into AgileTs + // Integrations (UI-Frameworks) that are integrated into AgileTs public integrations: Integrations; - // External added Integrations that are integrated into AgileTs when it is instantiated + // External added Integrations that are to integrate into AgileTs when it is instantiated static initialIntegrations: Integration[] = []; - // Static AgileTs Logger with default config + // Static AgileTs Logger with the default config // (-> is overwritten by the last created Agile Instance) static logger = new Logger({ prefix: 'Agile', @@ -54,18 +54,18 @@ export class Agile { * and should be unique to your application. * * Simply put, the Agile Instance is the brain of AgileTs - * and manages all [`Agile Sub Instance`](../main/Introduction.md#agile-sub-instance) - * like States. + * and manages all [Agile Sub Instance](https://agile-ts.org/docs/introduction/#agile-sub-instance) + * such as States. * * It should be noted that it doesn't store the States; * It only manages them. Each State has an Instance of the Agile Class, - * for example, to ingest its changes into the runtime. + * for example, to ingest its changes into the Runtime. * In summary, the main tasks of the Agile Class are to: - * - queuing [`Agile Sub Instance`](../main/Introduction.md#agile-sub-instance) - * changes in the `runtime` and preventing race conditions - * - update/rerender subscribed Components - * through Integrations like the [React Integration](../packages/react/Introduction.md) - * - Integrating with persistent [Storage](../packages/core/features/storage/Introduction.md) + * - queue [Agile Sub Instance](https://agile-ts.org/docs/introduction/#agile-sub-instance) + * changes in the Runtime to prevent race conditions + * - update/rerender subscribed UI-Components through the provided Integrations + * such as the [React Integration](https://agile-ts.org/docs/react) + * - integrate with the persistent [Storage](https://agile-ts.org/docs/core/storage) * - provide configuration object * * Each Agile Sub Instance requires an Agile Instance to be instantiated and function properly. @@ -99,13 +99,12 @@ export class Agile { localStorage: config.localStorage, }); - // Assign customized config to static Logger + // Assign customized Logger config to the static Logger Agile.logger = new Logger(config.logConfig); - // Logging LogCodeManager.log('10:00:00', [], this, Agile.logger); - // Create global instance of Agile. + // Create a global instance of the Agile Instance. // Why? 'getAgileInstance()' returns the global Agile Instance // if it couldn't find any Agile Instance in the specified Instance. if (config.bindGlobal) @@ -115,8 +114,12 @@ export class Agile { /** * Returns a newly created Storage. * - * A Storage represents an external storage - * such as the Local Storage and is an interface for AgileTs to it. + * A Storage Class serves as an interface to external storages, + * such as the [Async Storage](https://github.com/react-native-async-storage/async-storage) or + * [Local Storage](https://www.w3schools.com/html/html5_webstorage.asp). + * + * It creates the foundation to easily [`persist()`](https://agile-ts.org/docs/core/state/methods#persist) [Agile Sub Instances](https://agile-ts.org/docs/introduction/#agile-sub-instance) + * (like States or Collections) in nearly any external storage. * * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstorage) * @@ -132,7 +135,7 @@ export class Agile { * * A State manages a piece of Information * that we need to remember globally at a later point in time. - * While providing a toolkit to use and mutate this set of Information. + * While providing a toolkit to use and mutate this piece of Information. * * You can create as many global States as you need. * @@ -177,13 +180,13 @@ export class Agile { * Returns a newly created Computed. * * A Computed is an extension of the State Class - * that computes its value based on a compute function. + * that computes its value based on a specified compute function. * * The computed value will be cached to avoid unnecessary recomputes * and is only recomputed when one of its direct dependencies changes. * * Direct dependencies can be States and Collections. - * So when for example a dependent State value changes, the computed value will be recomputed. + * So when, for example, a dependent State value changes, the computed value is recomputed. * * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstate) * @@ -199,13 +202,13 @@ export class Agile { * Returns a newly created Computed. * * A Computed is an extension of the State Class - * that computes its value based on a compute function. + * that computes its value based on a specified compute function. * * The computed value will be cached to avoid unnecessary recomputes * and is only recomputed when one of its direct dependencies changes. * * Direct dependencies can be States and Collections. - * So when for example a dependent State value changes, the computed value will be recomputed. + * So when, for example, a dependent State value changes, the computed value is recomputed. * * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createcomputed) * @@ -240,7 +243,7 @@ export class Agile { * Registers the specified Integration with AgileTs. * * After a successful registration, - * Agile Sub Instances such as States + * [Agile Sub Instances](https://agile-ts.org/docs/introduction/#agile-sub-instance) such as States * can be bound to the Integration's UI-Components for reactivity. * * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#integrate) @@ -257,7 +260,7 @@ export class Agile { * Registers the specified Storage with AgileTs. * * After a successful registration, - * Agile Sub Instances such as States + * [Agile Sub Instances](https://agile-ts.org/docs/introduction/#agile-sub-instance) such as States * can be persisted in the external Storage. * * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#registerstorage) @@ -312,18 +315,18 @@ export interface CreateAgileConfigInterface { */ logConfig?: CreateLoggerConfigInterface; /** - * Whether the Subscription Container should not be ready + * Whether the Subscription Container shouldn't be ready * until the UI-Component it represents has been mounted. * @default true */ waitForMount?: boolean; /** - * Whether the Local Storage should be registered as Storage by default. + * Whether the Local Storage should be registered as a Agile Storage by default. * @default true */ localStorage?: boolean; /** - * Whether the Agile instance should be globally bound (globalThis) + * Whether the Agile Instance should be globally bound (globalThis) * and thus be globally available. * @default false */ @@ -332,7 +335,7 @@ export interface CreateAgileConfigInterface { export interface AgileConfigInterface { /** - * Whether the Subscription Container should not be ready + * Whether the Subscription Container shouldn't be ready * until the UI-Component it represents has been mounted. * @default true */ diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index 0a5c5cdb..f0d95f6b 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -27,13 +27,13 @@ export class Computed extends State< /** * A Computed is an extension of the State Class - * that computes its value based on a compute function. + * that computes its value based on a specified compute function. * * The computed value will be cached to avoid unnecessary recomputes * and is only recomputed when one of its direct dependencies changes. * * Direct dependencies can be States and Collections. - * So when for example a dependent State value changes, the computed value will be recomputed. + * So when, for example, a dependent State value changes, the computed value is recomputed. * * [Learn more..](https://agile-ts.org/docs/core/computed/) * diff --git a/packages/core/src/integrations/index.ts b/packages/core/src/integrations/index.ts index 9b6c140c..53c41b6b 100644 --- a/packages/core/src/integrations/index.ts +++ b/packages/core/src/integrations/index.ts @@ -18,7 +18,7 @@ export class Integrations { constructor(agileInstance: Agile) { this.agileInstance = () => agileInstance; - // Integrate initial Integrations which were statically set from external + // Integrate initial Integrations which were statically set externally Agile.initialIntegrations.forEach((integration) => this.integrate(integration) ); @@ -43,7 +43,7 @@ export class Integrations { integration.ready = await integration.methods.bind(this.agileInstance()); else integration.ready = true; - // Integrate Framework + // Integrate Integration this.integrations.add(integration); integration.integrated = true; diff --git a/packages/core/src/integrations/integration.ts b/packages/core/src/integrations/integration.ts index 41a4d75d..f8f0b69b 100644 --- a/packages/core/src/integrations/integration.ts +++ b/packages/core/src/integrations/integration.ts @@ -5,7 +5,7 @@ export class Integration { public _key: IntegrationKey; // Instance of the Framework the Integration represents public frameworkInstance?: F; - // Whether the Integration is ready + // Whether the Integration is ready and the binding to AgileTs was successful public ready = false; // Whether the Integration was integrated into AgileTs public integrated = false; @@ -13,8 +13,11 @@ export class Integration { public methods: IntegrationMethods; /** - * An Integrations is an direct interface to an UI-Framework, - * and allows easy interaction with that Framework. + * An Integrations is an interface to a UI-Framework, + * and allows the easy interaction with that Framework. + * + * Due to the Integration, AgileTs can be integrated into almost any UI-Framework + * without a huge overhead. * * @public * @param config - Configuration object @@ -39,7 +42,7 @@ export class Integration { } /** - * Returns the key/name identifier of the State. + * Returns the key/name identifier of the Integration. * * @public */ @@ -51,12 +54,12 @@ export class Integration { export interface CreateIntegrationConfig extends IntegrationMethods { /** - * Key/Name identifier of the Integratioon. + * Key/Name identifier of the Integration. * @default undefined */ key: string; /** - * An Instance of the Framework to be represented by the Integration. + * An Instance of the UI-Framework to be represented by the Integration. * For example, in the case of React, the React Instance. * @default undefined */ @@ -65,24 +68,23 @@ export interface CreateIntegrationConfig export interface IntegrationMethods { /** - * Binds the Framework/Integration to an Agile Instance. + * Binds the Integration to an Agile Instance. * - * This method is called shortly after the Integration was registered with a Agile Instance. - * It is intended to set up things on the Framework side - * that are important for a integration into AgileTs. + * This method is called shortly after the Integration was registered with an Agile Instance. + * It is intended to set up things that are important + * for an seamless integration into AgileTs on the UI-Framework side. * * @param agileInstance - Agile Instance into which the Integration is to be integrated. * @return Indicating whether the to integrate Integration is ready on the Framework side. */ bind?: (agileInstance: Agile) => Promise; /** - * Method to apply the specified updated data to the specified UI-Component + * Method to apply the updated data to the provided UI-Component * in order to trigger a re-render on it. * - * This method is called when the value of an Agile Sub Instance - * bound to the UI-Component changes - * in a Component based Subscription. - * The updated Agile Sub Instance values are represented in the `updatedData` object. + * This method is called when the value of an [Agile Sub Instance](https://agile-ts.org/docs/introduction/#agile-sub-instance) + * bound to the specified UI-Component changes ([Component based Subscription](https://agile-ts.org/docs/core/integration/#component-based)). + * The updated Agile Sub Instance values were mapped in the provided `updatedData` object. * * @param componentInstance - Component Instance of the to update UI-Component. * @param updatedData - Data object containing the updated data. diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index d1ac3641..d738d5b5 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -319,7 +319,7 @@ export class SubController { interface RegisterSubscriptionConfigInterface extends SubscriptionContainerConfigInterface { /** - * Whether the Subscription Container should not be ready + * Whether the Subscription Container shouldn't be ready * until the UI-Component it represents has been mounted. * @default agileInstance.config.waitForMount */ diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index 2d949993..49829a47 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -68,7 +68,7 @@ export class State { /** * A State manages a piece of Information * that we need to remember globally at a later point in time. - * While providing a toolkit to use and mutate this set of Information. + * While providing a toolkit to use and mutate this piece of Information. * * You can create as many global States as you need. * From 598d607fcb6ef68151df6be7b3e6ef46c65740a3 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Tue, 15 Jun 2021 07:34:55 +0200 Subject: [PATCH 077/117] added size-limit --- package.json | 2 + packages/core/.size-limit.js | 6 +++ packages/core/package.json | 3 +- yarn.lock | 96 ++++++++++++++++++++++++++++++++---- 4 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 packages/core/.size-limit.js diff --git a/package.json b/package.json index 5ace251e..472b9722 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ }, "devDependencies": { "@changesets/cli": "^2.12.0", + "@size-limit/file": "^4.12.0", "@types/jest": "^26.0.15", "@types/node": "^14.14.7", "@typescript-eslint/eslint-plugin": "^4.12.0", @@ -62,6 +63,7 @@ "lerna-changelog": "^1.0.1", "nodemon": "^2.0.6", "prettier": "2.1.2", + "size-limit": "^4.12.0", "ts-jest": "^26.4.4", "ts-node": "^8.10.2", "tsc-watch": "^4.1.0", diff --git a/packages/core/.size-limit.js b/packages/core/.size-limit.js new file mode 100644 index 00000000..1b8247f6 --- /dev/null +++ b/packages/core/.size-limit.js @@ -0,0 +1,6 @@ +module.exports = [ + { + path: 'dist/*', + limit: '35 kB', + }, +]; diff --git a/packages/core/package.json b/packages/core/package.json index 334f739d..c1b896fb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -36,7 +36,8 @@ "preview": "npm pack", "test": "jest", "test:coverage": "jest --coverage", - "lint": "eslint src/**/*" + "lint": "eslint src/**/*", + "size": "yarn run build && size-limit" }, "devDependencies": { "@agile-ts/logger": "file:../logger", diff --git a/yarn.lock b/yarn.lock index de94593e..024bf2af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,10 +3,10 @@ "@agile-ts/core@file:packages/core": - version "0.0.16" + version "0.0.17" dependencies: - "@agile-ts/logger" "^0.0.3" - "@agile-ts/utils" "^0.0.3" + "@agile-ts/logger" "^0.0.4" + "@agile-ts/utils" "^0.0.4" "@akryum/winattr@^3.0.0": version "3.0.0" @@ -2788,6 +2788,13 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@size-limit/file@^4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@size-limit/file/-/file-4.12.0.tgz#50166eca7b9b5aa15f51a72b3d9d31e2d8530e6f" + integrity sha512-csgGSAG3s2y9eOl/taahojXY91AXpNgqLs9HJ5c/Qmrs+6UHgXbwJ4vo475NfZmt1Y9simircb1ygqupauNUyA== + dependencies: + semver "7.3.5" + "@surma/rollup-plugin-off-main-thread@^1.1.1": version "1.4.2" resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-1.4.2.tgz#e6786b6af5799f82f7ab3a82e53f6182d2b91a58" @@ -4735,6 +4742,15 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -5006,7 +5022,7 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.2.1: +buffer@^5.2.1, buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -5046,6 +5062,11 @@ byte-size@^5.0.1: resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-5.0.1.tgz#4b651039a5ecd96767e71a3d7ed380e48bed4191" integrity sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw== +bytes-iec@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/bytes-iec/-/bytes-iec-3.1.1.tgz#94cd36bf95c2c22a82002c247df8772d1d591083" + integrity sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA== + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -5358,7 +5379,7 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.2.2, chokidar@^3.4.1: +chokidar@^3.2.2, chokidar@^3.4.1, chokidar@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== @@ -5393,6 +5414,11 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +ci-job-number@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/ci-job-number/-/ci-job-number-1.2.2.tgz#f4e5918fcaeeda95b604f214be7d7d4a961fe0c0" + integrity sha512-CLOGsVDrVamzv8sXJGaILUVI6dsuAkouJP/n6t+OxLPeeA4DDby7zn9SB6EUpa1H7oIKoE+rMmkW80zYsFfUjA== + cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -5464,7 +5490,7 @@ cli-highlight@^2.1.4: parse5-htmlparser2-tree-adapter "^6.0.0" yargs "^16.0.0" -cli-spinners@^2.0.0: +cli-spinners@^2.0.0, cli-spinners@^2.5.0: version "2.6.0" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.0.tgz#36c7dc98fb6a9a76bd6238ec3f77e2425627e939" integrity sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q== @@ -8608,7 +8634,7 @@ globby@11.0.1: merge2 "^1.3.0" slash "^3.0.0" -globby@^11.0.0, globby@^11.0.1, globby@^11.0.2: +globby@^11.0.0, globby@^11.0.1, globby@^11.0.2, globby@^11.0.3: version "11.0.3" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== @@ -9671,6 +9697,11 @@ is-installed-globally@^0.3.1: global-dirs "^2.0.1" is-path-inside "^3.0.1" +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" @@ -9860,6 +9891,11 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" @@ -10774,6 +10810,11 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lilconfig@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.3.tgz#68f3005e921dafbd2a2afb48379986aa6d2579fd" + integrity sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg== + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -10990,6 +11031,14 @@ log-symbols@^2.2.0: dependencies: chalk "^2.0.1" +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + loglevel@^1.6.7, loglevel@^1.6.8: version "1.7.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" @@ -12283,6 +12332,21 @@ ora@^3.4.0: strip-ansi "^5.2.0" wcwidth "^1.0.1" +ora@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + original@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" @@ -14194,7 +14258,7 @@ read@1, read@~1.0.1: string_decoder "~1.1.1" util-deprecate "~1.0.1" -"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: +"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -14882,7 +14946,7 @@ semver@7.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== -semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: +semver@7.3.5, semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -15067,6 +15131,20 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== +size-limit@^4.12.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/size-limit/-/size-limit-4.12.0.tgz#ecc9c0448c049a40b10e76b5e1b4a20f99a54468" + integrity sha512-LwlUDPxFJbJDIJsBE5bKo8kFMuxmuewBMDjgfSoQwnO27V8DSK+j6881nsrX3GoM3bJMFIeEq56thqBEdYC8bw== + dependencies: + bytes-iec "^3.1.1" + chokidar "^3.5.1" + ci-job-number "^1.2.2" + colorette "^1.2.2" + globby "^11.0.3" + lilconfig "^2.0.3" + ora "^5.4.1" + read-pkg-up "^7.0.1" + slash@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" From 1585c4b4fc2e183f3c9ec5219fb3b549d0d9dd59 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 16 Jun 2021 20:27:18 +0200 Subject: [PATCH 078/117] fixed typos in Storages Class --- packages/core/src/state/index.ts | 6 +- packages/core/src/storages/index.ts | 155 ++++++++++-------- .../template.json | 10 +- packages/cra-template-agile/template.json | 8 +- 4 files changed, 101 insertions(+), 78 deletions(-) diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index 49829a47..63d73091 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -183,7 +183,7 @@ export class State { /** * Assigns a new value to the State - * and rerenders all subscribed Components. + * and re-renders all subscribed UI-Components. * * [Learn more..](https://agile-ts.org/docs/core/state/methods/#set) * @@ -832,14 +832,14 @@ export type StateWatcherCallback = (value: T, key: string) => void; export type ComputeValueMethod = (value: T) => T; export type ComputeExistsMethod = (value: T) => boolean; -export type SideEffectFunctionType> = ( +export type SideEffectFunctionType = ( instance: Instance, properties?: { [key: string]: any; } ) => void; -export interface SideEffectInterface> { +export interface SideEffectInterface { /** * Callback function to be called on every State value change. * @return () => {} diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index 7d1df906..ec746bb7 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -10,17 +10,23 @@ import { } from '../internal'; export class Storages { + // Agile Instance the Storages belongs to public agileInstance: () => Agile; public config: StoragesConfigInterface; - public storages: { [key: string]: Storage } = {}; // All registered Storages + + // Registered Storages + public storages: { [key: string]: Storage } = {}; + // Persistent from Instances that were persisted public persistentInstances: Set = new Set(); /** + * The Storages Class manages all external Storages for an Agile Instance + * and provides an interface to easily store, load and remove values from multiple Storages at once. + * * @internal - * Storages - Manages Storages of Agile - * @param agileInstance - An Instance of Agile - * @param config - Config + * @param agileInstance - Instance of Agile the Storages belongs to. + * @param config - Configuration object */ constructor( agileInstance: Agile, @@ -35,15 +41,16 @@ export class Storages { if (config.localStorage) this.instantiateLocalStorage(); } - //========================================================================================================= - // Instantiate Local Storage - //========================================================================================================= /** + * Instantiates and registers the + * [Local Storage](https://developer.mozilla.org/de/docs/Web/API/Window/localStorage). + * + * Note that this only works in a web environment! + * * @internal - * Instantiates Local Storage */ public instantiateLocalStorage(): boolean { - // Check if Local Storage is Available + // Check if Local Storage is available in this environment if (!Storages.localStorageAvailable()) { LogCodeManager.log('11:02:00'); return false; @@ -62,14 +69,14 @@ export class Storages { return this.register(_localStorage, { default: true }); } - //========================================================================================================= - // Register - //========================================================================================================= /** + * Registers the specified Storage with AgileTs + * and updates the Persistents that have already attempted + * to use the now registered Storage. + * * @internal - * Register new Storage as Agile Storage - * @param storage - new Storage - * @param config - Config + * @param storage - Storage to be registered with AgileTs. + * @param config - Configuration object */ public register( storage: Storage, @@ -83,7 +90,7 @@ export class Storages { return false; } - // Set first added Storage as default Storage + // Assign first added Storage as default Storage if (!hasRegisteredAnyStorage && config.default === false) LogCodeManager.log('11:02:01'); if (!hasRegisteredAnyStorage) config.default = true; @@ -93,15 +100,16 @@ export class Storages { if (config.default) this.config.defaultStorageKey = storage.key; this.persistentInstances.forEach((persistent) => { - // Revalidate Persistent that includes the newly registered StorageKey + // Revalidate Persistent that includes the newly registered storage key if (persistent.storageKeys.includes(storage.key)) { const isValid = persistent.validatePersistent(); if (isValid) persistent.initialLoading(); return; } - // If persistent has no default StorageKey (reassign StorageKeys since this registered Storage might be tagged as default Storage) - if (!persistent.config.defaultStorageKey) { + // If Persistent has no default storage key + // (reassign storage keys since this registered Storage might be tagged as default Storage) + if (persistent.config.defaultStorageKey == null) { persistent.assignStorageKeys(); const isValid = persistent.validatePersistent(); if (isValid) persistent.initialLoading(); @@ -111,13 +119,13 @@ export class Storages { return true; } - //========================================================================================================= - // Get Storage - //========================================================================================================= /** - * @internal - * Get Storage at Key/Name - * @param storageKey - Key/Name of Storage + * Retrieves a single Storage with the specified key/name identifier from the Storages Class. + * + * If the to retrieve Storage doesn't exist, `undefined` is returned. + * + * @public + * @param storageKey - Key/Name identifier of the Storage. */ public getStorage( storageKey: StorageKey | undefined | null @@ -140,14 +148,17 @@ export class Storages { return storage; } - //========================================================================================================= - // Get - //========================================================================================================= /** + * Retrieves the stored value at the specified Storage Item key + * from the defined external Storage (`storageKey`). + * + * When no Storage has been specified, + * the value is retrieved from the default Storage. + * * @internal - * Gets value at provided Key - * @param storageItemKey - Key of Storage property - * @param storageKey - Key/Name of Storage from which the Item is fetched (if not provided default Storage will be used) + * @param storageItemKey - Key/Name identifier of the value to be retrieved. + * @param storageKey - Key/Name identifier of the external Storage + * from which the value is to be retrieved. */ public get( storageItemKey: StorageItemKey, @@ -158,28 +169,31 @@ export class Storages { return Promise.resolve(undefined); } - // Call get Method in specific Storage + // Call get method on specified Storage if (storageKey) { const storage = this.getStorage(storageKey); if (storage) return storage.get(storageItemKey); } - // Call get Method in default Storage + // Call get method on default Storage const defaultStorage = this.getStorage(this.config.defaultStorageKey); return ( defaultStorage?.get(storageItemKey) || Promise.resolve(undefined) ); } - //========================================================================================================= - // Set - //========================================================================================================= /** + * Stores or updates the value at the specified Storage Item key + * in the defined external Storages (`storageKeys`). + * + * When no Storage has been specified, + * the value is stored/updated in the default Storage + * * @internal - * Saves/Updates value at provided Key - * @param storageItemKey - Key of Storage property - * @param value - new Value that gets set at provided Key - * @param storageKeys - Key/Name of Storages where the Value gets set (if not provided default Storage will be used) + * @param storageItemKey - Key/Name identifier of the value to be stored. + * @param value - Value to be stored in an external Storage. + * @param storageKeys - Key/Name identifier of the external Storage + * where the value is to be stored. */ public set( storageItemKey: StorageItemKey, @@ -191,26 +205,29 @@ export class Storages { return; } - // Call set Method in specific Storages + // Call set method on specified Storages if (storageKeys) { for (const storageKey of storageKeys) this.getStorage(storageKey)?.set(storageItemKey, value); return; } - // Call set Method in default Storage + // Call set method on default Storage const defaultStorage = this.getStorage(this.config.defaultStorageKey); defaultStorage?.set(storageItemKey, value); } - //========================================================================================================= - // Remove - //========================================================================================================= /** + * Removes the value at the specified Storage Item key + * from the defined external Storages (`storageKeys`). + * + * When no Storage has been specified, + * the value is removed from the default Storage + * * @internal - * Removes value at provided Key - * @param storageItemKey - Key of Storage property - * @param storageKeys - Key/Name of Storages where the Value gets removed (if not provided default Storage will be used) + * @param storageItemKey - Key/Name identifier of the value to be removed. + * @param storageKeys - Key/Name identifier of the external Storage + * from which the value is to be removed. */ public remove( storageItemKey: StorageItemKey, @@ -221,35 +238,34 @@ export class Storages { return; } - // Call remove Method in specific Storages + // Call remove method on specified Storages if (storageKeys) { for (const storageKey of storageKeys) this.getStorage(storageKey)?.remove(storageItemKey); return; } - // Call remove Method in default Storage + // Call remove method on default Storage const defaultStorage = this.getStorage(this.config.defaultStorageKey); defaultStorage?.remove(storageItemKey); } - //========================================================================================================= - // Has Storage - //========================================================================================================= /** + * Returns a boolean indicating whether any Storage + * has been registered or not. + * * @internal - * Check if at least one Storage got registered */ public hasStorage(): boolean { return notEqual(this.storages, {}); } - //========================================================================================================= - // Local Storage Available - //========================================================================================================= /** + * Returns a boolean indication whether the + * [Local Storage](https://developer.mozilla.org/de/docs/Web/API/Window/localStorage) + * is available in this environment. + * * @internal - * Checks if localStorage is available in this Environment */ static localStorageAvailable(): boolean { try { @@ -262,25 +278,28 @@ export class Storages { } } -/** - * @param localStorage - If Local Storage should be instantiated - * @param defaultStorage - Default Storage Key - */ export interface CreateStoragesConfigInterface { + /** + * Whether the Local Storage should be registered by default. + * @default true + */ localStorage?: boolean; + /** + * Storage key of the Storage that should be the default Storage. + */ defaultStorageKey?: StorageKey; } -/** - * @param defaultStorage - Default Storage Key - */ export interface StoragesConfigInterface { + /** + * Storage key of the Storage that should be the default Storage. + */ defaultStorageKey: StorageKey | null; } -/** - * @param default - If the registered Storage gets the default Storage - */ export interface RegisterConfigInterface { + /** + * Whether the to register Storage should get the default Storage. + */ default?: boolean; } diff --git a/packages/cra-template-agile-typescript/template.json b/packages/cra-template-agile-typescript/template.json index bac07209..21245663 100644 --- a/packages/cra-template-agile-typescript/template.json +++ b/packages/cra-template-agile-typescript/template.json @@ -3,18 +3,20 @@ "dependencies": { "@agile-ts/core": "^0.0.13", "@agile-ts/react": "^0.0.13", + "typescript": "^4.1.2", + "web-vitals": "^1.0.1" + }, + "devDependencies": { "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "@types/node": "^12.0.0", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", - "@types/jest": "^26.0.15", - "typescript": "^4.1.2", - "web-vitals": "^1.0.1" + "@types/jest": "^26.0.15" }, "eslintConfig": { "extends": ["react-app", "react-app/jest"] } } -} \ No newline at end of file +} diff --git a/packages/cra-template-agile/template.json b/packages/cra-template-agile/template.json index 7371aa91..64697fb5 100644 --- a/packages/cra-template-agile/template.json +++ b/packages/cra-template-agile/template.json @@ -3,13 +3,15 @@ "dependencies": { "@agile-ts/core": "^0.0.13", "@agile-ts/react": "^0.0.13", + "web-vitals": "^1.0.1" + }, + "devDependencies": { "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", - "@testing-library/user-event": "^12.1.10", - "web-vitals": "^1.0.1" + "@testing-library/user-event": "^12.1.10" }, "eslintConfig": { "extends": ["react-app", "react-app/jest"] } } -} \ No newline at end of file +} From fa7ec0e072dfc72041cf492d9927b88373b559a8 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 17 Jun 2021 17:19:01 +0200 Subject: [PATCH 079/117] fixed typo --- .github/workflows/release.yaml | 2 +- packages/core/src/integrations/index.ts | 8 +- packages/core/src/integrations/integration.ts | 2 +- packages/core/src/storages/index.ts | 44 ++++--- packages/core/src/storages/storage.ts | 111 ++++++++++++------ 5 files changed, 109 insertions(+), 58 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ba0242fc..a46c1a84 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -31,4 +31,4 @@ jobs: title: Next Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} \ No newline at end of file + NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} diff --git a/packages/core/src/integrations/index.ts b/packages/core/src/integrations/index.ts index 53c41b6b..e72df24c 100644 --- a/packages/core/src/integrations/index.ts +++ b/packages/core/src/integrations/index.ts @@ -28,7 +28,7 @@ export class Integrations { * Integrates the specified Integration into AgileTs * and sets it to ready when the binding was successful. * - * @internal + * @public * @param integration - Integration to be integrated into AgileTs. */ public async integrate(integration: Integration): Promise { @@ -59,7 +59,7 @@ export class Integrations { * In doing so, it calls the `updateMethod()` method * in all registered Integrations with the specified parameters. * - * @internal + * @public * @param componentInstance - Component Instance to be updated. * @param updatedData - Data object with updated data. */ @@ -76,9 +76,9 @@ export class Integrations { /** * Returns a boolean indicating whether any Integration - * has been registered or not. + * has been registered with the Agile Instance or not. * - * @internal + * @public */ public hasIntegration(): boolean { return this.integrations.size > 0; diff --git a/packages/core/src/integrations/integration.ts b/packages/core/src/integrations/integration.ts index f8f0b69b..f2c409ac 100644 --- a/packages/core/src/integrations/integration.ts +++ b/packages/core/src/integrations/integration.ts @@ -13,7 +13,7 @@ export class Integration { public methods: IntegrationMethods; /** - * An Integrations is an interface to a UI-Framework, + * An Integration is an interface to a UI-Framework, * and allows the easy interaction with that Framework. * * Due to the Integration, AgileTs can be integrated into almost any UI-Framework diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index ec746bb7..d3eaefb7 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -74,7 +74,7 @@ export class Storages { * and updates the Persistents that have already attempted * to use the now registered Storage. * - * @internal + * @public * @param storage - Storage to be registered with AgileTs. * @param config - Configuration object */ @@ -120,7 +120,8 @@ export class Storages { } /** - * Retrieves a single Storage with the specified key/name identifier from the Storages Class. + * Retrieves a single Storage with the specified key/name identifier + * from the Storages Class. * * If the to retrieve Storage doesn't exist, `undefined` is returned. * @@ -155,7 +156,7 @@ export class Storages { * When no Storage has been specified, * the value is retrieved from the default Storage. * - * @internal + * @public * @param storageItemKey - Key/Name identifier of the value to be retrieved. * @param storageKey - Key/Name identifier of the external Storage * from which the value is to be retrieved. @@ -187,9 +188,9 @@ export class Storages { * in the defined external Storages (`storageKeys`). * * When no Storage has been specified, - * the value is stored/updated in the default Storage + * the value is stored/updated in the default Storage. * - * @internal + * @public * @param storageItemKey - Key/Name identifier of the value to be stored. * @param value - Value to be stored in an external Storage. * @param storageKeys - Key/Name identifier of the external Storage @@ -222,9 +223,9 @@ export class Storages { * from the defined external Storages (`storageKeys`). * * When no Storage has been specified, - * the value is removed from the default Storage + * the value is removed from the default Storage. * - * @internal + * @public * @param storageItemKey - Key/Name identifier of the value to be removed. * @param storageKeys - Key/Name identifier of the external Storage * from which the value is to be removed. @@ -252,9 +253,9 @@ export class Storages { /** * Returns a boolean indicating whether any Storage - * has been registered or not. + * has been registered with the Agile Instance or not. * - * @internal + * @public */ public hasStorage(): boolean { return notEqual(this.storages, {}); @@ -265,7 +266,7 @@ export class Storages { * [Local Storage](https://developer.mozilla.org/de/docs/Web/API/Window/localStorage) * is available in this environment. * - * @internal + * @public */ static localStorageAvailable(): boolean { try { @@ -281,25 +282,40 @@ export class Storages { export interface CreateStoragesConfigInterface { /** * Whether the Local Storage should be registered by default. - * @default true + * @default false */ localStorage?: boolean; /** - * Storage key of the Storage that should be the default Storage. + * Key/Name identifier of the Storage to become the default Storage. + * + * When no specified Storage has been defined in methods like `get()`, `set()`, `remove()`, + * the default Storage will be used. + * + * @default undefined */ defaultStorageKey?: StorageKey; } export interface StoragesConfigInterface { /** - * Storage key of the Storage that should be the default Storage. + * Key/Name identifier of the Storage to become the default Storage. + * + * When no specified Storage has been defined in methods like `get()`, `set()`, `remove()`, + * the default Storage will be used. + * + * @default undefined */ defaultStorageKey: StorageKey | null; } export interface RegisterConfigInterface { /** - * Whether the to register Storage should get the default Storage. + * Whether the to register Storage should become the default Storage. + * + * When no specified Storage has been defined in methods like `get()`, `set()`, `remove()`, + * the default Storage will be used. + * + * @default false */ default?: boolean; } diff --git a/packages/core/src/storages/storage.ts b/packages/core/src/storages/storage.ts index f8751e4a..524b2d50 100644 --- a/packages/core/src/storages/storage.ts +++ b/packages/core/src/storages/storage.ts @@ -8,15 +8,24 @@ import { } from '../internal'; export class Storage { + public config: StorageConfigInterface; + + // Key/Name identifier of the Storage public key: StorageKey; + // Whether the Storage is ready and is able to persist values public ready = false; + // Methods to interact with the external Storage (get, set, remove) public methods: StorageMethodsInterface; - public config: StorageConfigInterface; /** + * An Storage is an interface to an external Storage, + * and allows the easy interaction with that Storage. + * + * Due to the Storage, AgileTs can easily persist its Instances in almost any Storage + * without a huge overhead. + * * @public - * Storage - Interface for storing Items permanently - * @param config - Config + * @param config - Configuration object */ constructor(config: CreateStorageConfigInterface) { config = defineConfig(config, { @@ -41,12 +50,11 @@ export class Storage { this.config.async = true; } - //========================================================================================================= - // Validate - //========================================================================================================= /** + * Returns a boolean indicating whether the Storage is valid + * and can be used to persist Instances in it or not. + * * @public - * Validates Storage Methods */ public validate(): boolean { if (!isFunction(this.methods?.get)) { @@ -64,20 +72,20 @@ export class Storage { return true; } - //========================================================================================================= - // Normal Get - //========================================================================================================= /** - * @internal - * Gets value at provided Key (normal) - * Note: Only use this if you are 100% sure this Storage doesn't work async - * @param key - Key of Storage property + * Synchronously retrieves the stored value + * at the specified Storage Item key from the Storage. + * + * When the retrieved value is a JSON-String it is parsed automatically. + * + * @public + * @param key - Key/Name identifier of the value to be retrieved. */ public normalGet(key: StorageItemKey): GetTpe | undefined { if (!this.ready || !this.methods.get) return undefined; if (isAsyncFunction(this.methods.get)) LogCodeManager.log('13:02:00'); - // Get Value + // Retrieve value const res = this.methods.get(this.getStorageKey(key)); const _res = isJsonString(res) ? JSON.parse(res) : res; @@ -91,22 +99,23 @@ export class Storage { return _res; } - //========================================================================================================= - // Async Get - //========================================================================================================= /** - * @internal - * Gets value at provided Key (async) - * @param key - Key of Storage property + * Asynchronously retrieves the stored value + * at the specified Storage Item key from the Storage. + * + * When the retrieved value is a JSON-String it is parsed automatically. + * + * @public + * @param key - Key/Name identifier of the value to be retrieved. */ public get(key: StorageItemKey): Promise { if (!this.ready || !this.methods.get) return Promise.resolve(undefined); - // Get Value in 'dummy' promise if get method isn't async + // Retrieve value from not promise based Storage if (!isAsyncFunction(this.methods.get)) return Promise.resolve(this.normalGet(key)); - // Get Value (async) + // Retrieve value from promise based Storage return new Promise((resolve, reject) => { this.methods ?.get(this.getStorageKey(key)) @@ -129,14 +138,12 @@ export class Storage { }); } - //========================================================================================================= - // Set - //========================================================================================================= /** + * Stores or updates the value at the specified Storage Item key in the Storage. + * * @public - * Saves/Updates value at provided Key - * @param key - Key of Storage property - * @param value - new Value that gets set + * @param key - Key/Name identifier of the value to be stored. + * @param value - Value to be stored. */ public set(key: StorageItemKey, value: any): void { if (!this.ready || !this.methods.set) return; @@ -151,13 +158,11 @@ export class Storage { this.methods.set(this.getStorageKey(key), JSON.stringify(value)); } - //========================================================================================================= - // Remove - //========================================================================================================= /** + * Removes the value at the specified Storage Item key from the Storage. + * * @public - * Removes value at provided Key - * @param key - Key of Storage property + * @param key - Key/Name identifier of the value to be removed. */ public remove(key: StorageItemKey): void { if (!this.ready || !this.methods.remove) return; @@ -171,12 +176,10 @@ export class Storage { this.methods.remove(this.getStorageKey(key)); } - //========================================================================================================= - // Get Storage Key - //========================================================================================================= /** + * Generates and returns a valid Storage key based on the specified key. + * * @internal - * Creates Storage Key from provided key * @param key - Key that gets converted into a Storage Key */ public getStorageKey(key: StorageItemKey): string { @@ -194,7 +197,15 @@ export type StorageItemKey = string | number; * @param methods - Storage methods like (get, set, remove) */ export interface CreateStorageConfigInterface extends StorageConfigInterface { + /** + * Key/Name identifier of the Storage + * @default undefined + */ key: string; + /** + * Storage methods for interacting with the external Storage. + * @default undefined + */ methods: StorageMethodsInterface; } @@ -204,8 +215,24 @@ export interface CreateStorageConfigInterface extends StorageConfigInterface { * @param remove - Remove Methods of Storage (removes items from storage) */ export interface StorageMethodsInterface { + /** + * Method to retrieve a value at the specified key from the external Storage. + * + * @param key - Key/Name identifier of the value to be retrieved. + */ get: (key: string) => any; + /** + * Method to store a value at the specified key in the external Storage. + * + * @param key - Key/Name identifier of the value to be stored. + * @param value - Value to be stored. + */ set: (key: string, value: any) => void; + /** + * Method to remove a value at the specified key from the external Storage. + * + * @param key - Key/Name identifier of the value to be removed. + */ remove: (key: string) => void; } @@ -214,6 +241,14 @@ export interface StorageMethodsInterface { * @param prefix - Prefix of Storage Property */ export interface StorageConfigInterface { + /** + * Whether the external Storage works async. + * @default Automatically detected via `isAsyncFunction()` + */ async?: boolean; + /** + * Prefix to be added before each persisted value key/name identifier. + * @default 'agile' + */ prefix?: string; } From e9c2b99afe6d815473bcf85b763b376c9d8aab75 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 17 Jun 2021 20:26:02 +0200 Subject: [PATCH 080/117] optimised descriptions in Persistent --- .../src/collection/collection.persistent.ts | 4 +- packages/core/src/collection/index.ts | 14 +- packages/core/src/runtime/observer.ts | 4 +- packages/core/src/state/index.ts | 12 +- packages/core/src/state/state.persistent.ts | 4 +- packages/core/src/storages/persistent.ts | 205 +++++++++++------- 6 files changed, 152 insertions(+), 91 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 6454ea63..1ed5abb9 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -74,7 +74,7 @@ export class CollectionPersistent< * @internal * @param storageItemKey - Prefix Storage key of the persisted Collection Instances. * | default = Persistent.key | - * @return Whether the loading and the setting up of the side effects was successful. + * @return Whether the loading of the persisted value and the setting up of the side effects was successful. */ public async loadPersistedValue( storageItemKey?: PersistentKey @@ -180,7 +180,7 @@ export class CollectionPersistent< * @internal * @param storageItemKey - Prefix Storage key of the persisted Collection Instances. * | default = Persistent.key | - * @return Whether the persisting and the setting up of the side effects was successful. + * @return Whether the persisting of the value and the setting up of the side effects was successful. */ public async persistValue(storageItemKey?: PersistentKey): Promise { if (!this.ready) return false; diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 8ac6a0e0..edf12d81 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -1091,7 +1091,7 @@ export class Collection { } /** - * Updates key/name identifier of the Item + * Updates the key/name identifier of the Item * and returns a boolean indicating * whether the Item identifier was updated successfully. * @@ -1629,14 +1629,16 @@ export interface CollectionPersistentConfigInterface { /** * Key/Name identifier of Storages * in which the Collection value should be or is persisted. - * @default [AgileTs default Storage key] + * @default [`defaultStorageKey`] */ storageKeys?: StorageKey[]; /** - * Default Storage key of the specified Storage keys. - * The Collection value is loaded from the default Storage - * and is only loaded from the remaining Storages (storageKeys) - * if the loading of the default Storage failed. + * Key/Name identifier of the default Storage of the specified Storage keys. + * + * The Collection value is loaded from the default Storage by default + * and is only loaded from the remaining Storages (`storageKeys`) + * if the loading from the default Storage failed. + * * @default first index of the specified Storage keys or the AgileTs default Storage key */ defaultStorageKey?: StorageKey; diff --git a/packages/core/src/runtime/observer.ts b/packages/core/src/runtime/observer.ts index eb76f80c..ebffc0fb 100644 --- a/packages/core/src/runtime/observer.ts +++ b/packages/core/src/runtime/observer.ts @@ -31,7 +31,7 @@ export class Observer { /** * An Observer manages the subscriptions to Subscription Containers (UI-Components) * and dependencies to other Observers (Agile Classes) - * for an Agile Class like the `State Class`. + * for an Agile Class such as the `State Class`. * * Agile Classes often use an Observer as an interface to the Runtime. * In doing so, they ingest their own Observer into the Runtime @@ -136,7 +136,7 @@ export class Observer { * * Note that this method should be overwritten * to correctly apply the changes to the Agile Class - * to which the Observer belongs. + * the Observer belongs to. * * @public * @param job - Runtime-Job to be performed. diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index 63d73091..ac5cedaa 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -815,14 +815,16 @@ export interface StatePersistentConfigInterface { /** * Key/Name identifier of Storages * in which the State value should be or is persisted. - * @default [AgileTs default Storage key] + * @default [`defaultStorageKey`] */ storageKeys?: StorageKey[]; /** - * Default Storage key of the specified Storage keys. - * The State value is loaded from the default Storage - * and is only loaded from the remaining Storages (storageKeys) - * if the loading of the default Storage failed. + * Key/Name identifier of the default Storage of the specified Storage keys. + * + * The State value is loaded from the default Storage by default + * and is only loaded from the remaining Storages (`storageKeys`) + * if the loading from the default Storage failed. + * * @default first index of the specified Storage keys or the AgileTs default Storage key */ defaultStorageKey?: StorageKey; diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index d1a8243f..2b30165f 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -63,7 +63,7 @@ export class StatePersistent extends Persistent { * @internal * @param storageItemKey - Storage key of the persisted State Instance. * | default = Persistent.key | - * @return Whether the loading and the setting up of the side effects was successful. + * @return Whether the loading of the persisted value and the setting up of the side effects was successful. */ public async loadPersistedValue( storageItemKey?: PersistentKey @@ -99,7 +99,7 @@ export class StatePersistent extends Persistent { * @internal * @param storageItemKey - Storage key of the persisted State Instance. * | default = Persistent.key | - * @return Whether the persisting and the setting up of the side effects was successful. + * @return Whether the persisting of the value and the setting up of the side effects was successful. */ public async persistValue(storageItemKey?: PersistentKey): Promise { if (!this.ready) return false; diff --git a/packages/core/src/storages/persistent.ts b/packages/core/src/storages/persistent.ts index 6c521778..eaec9937 100644 --- a/packages/core/src/storages/persistent.ts +++ b/packages/core/src/storages/persistent.ts @@ -7,25 +7,35 @@ import { } from '../internal'; export class Persistent { + // Agile Instance the Persistent belongs to public agileInstance: () => Agile; public static placeHolderKey = '__THIS_IS_A_PLACEHOLDER__'; + public config: PersistentConfigInterface; + // Key/Name identifier of the Persistent public _key: PersistentKey; + // Whether the Persistent is ready and is allowed to persist values public ready = false; - public isPersisted = false; // If Value is stored in Agile Storage - public onLoad: ((success: boolean) => void) | undefined; // Gets called if PersistValue got loaded for the first Time + // Whether the Persistent value is stored in the corresponding Storage/s + public isPersisted = false; + // Callback that is called when the persisted value was loaded into the Persistent for the first time + public onLoad: ((success: boolean) => void) | undefined; - // Storages in which the Persisted Value is saved + // Key/Name identifier of the Storages the Persistent value is stored in public storageKeys: StorageKey[] = []; /** + * A Persistent manages the permanent persistence + * of an Agile Class such as the `State Class` in external Storages. + * + * Note that the Persistent itself is no standalone class + * and should be adapted to the Agile Class needs it belongs to. + * * @internal - * Persistent - Handles storing of Agile Instances - * Note: No stand alone class!! - * @param agileInstance - An instance of Agile - * @param config - Config + * @param agileInstance - Instance of Agile the Persistent belongs to. + * @param config - Configuration object */ constructor( agileInstance: Agile, @@ -71,7 +81,7 @@ export class Persistent { } /** - * Updates key/name identifier of Persistent. + * Updates the key/name identifier of the Persistent. * * @public * @param value - New key/name identifier. @@ -86,28 +96,29 @@ export class Persistent { const isValid = this.validatePersistent(); - // Try to initial load value if persistent wasn't ready before + // Try to initial load value if Persistent hasn't been ready before if (!wasReady) { if (isValid) await this.initialLoading(); return; } - // Remove persisted values with the old key + // Remove persisted value that is located at the old key await this.removePersistedValue(oldKey); - // Persist Collection values with the new key + // Persist value at the new key if (isValid) await this.persistValue(value); } - //========================================================================================================= - // Instantiate Persistent - //========================================================================================================= /** + * Instantiates the Persistent by assigning the specified Storage keys + * and validating the Persistent. + * + * This was moved out of the `constructor` + * because some classes that extend the Persistent need to configure some + * things before they can properly instantiate the parent Persistent. + * * @internal - * Instantiates this Class - * Note: Had to outsource it from the constructor because some extending classes - * have to define some stuff before being able to instantiate the parent (this) - * @param config - Config + * @param config - Configuration object */ public instantiatePersistent( config: InstantiatePersistentConfigInterface = {} @@ -117,29 +128,31 @@ export class Persistent { this.validatePersistent(); } - //========================================================================================================= - // Validate Persistent - //========================================================================================================= /** + * Returns a boolean indicating whether the Persistent was setup correctly + * and is able to persist a value permanently in an external Storage. + * + * Based on this tapped boolean value, + * the Persistent's `ready` property is updated. + * * @internal - * Validates Persistent and updates its 'ready' property */ public validatePersistent(): boolean { let isValid = true; - // Validate Key + // Validate Persistent key/name identifier if (this._key === Persistent.placeHolderKey) { LogCodeManager.log('12:03:00'); isValid = false; } - // Validate StorageKeys + // Validate Storage keys if (!this.config.defaultStorageKey || this.storageKeys.length <= 0) { LogCodeManager.log('12:03:01'); isValid = false; } - // Check if Storages exist + // Check if the Storages exist at the specified Storage keys this.storageKeys.map((key) => { if (!this.agileInstance().storages.storages[key]) { LogCodeManager.log('12:03:02', [this._key, key]); @@ -151,14 +164,14 @@ export class Persistent { return isValid; } - //========================================================================================================= - // Assign StorageKeys - //========================================================================================================= /** + * Assigns the specified Storage keys to the Persistent + * and overwrites the old ones. + * + * * @internal - * Assign new StorageKeys to Persistent and overwrite the old ones - * @param storageKeys - New Storage Keys - * @param defaultStorageKey - Key of default Storage + * @param storageKeys - Key/Name identifiers to be assigned. + * @param defaultStorageKey - Key/Name identifier of the default Storage. */ public assignStorageKeys( storageKeys: StorageKey[] = [], @@ -167,11 +180,13 @@ export class Persistent { const storages = this.agileInstance().storages; const _storageKeys = copy(storageKeys); - // Add passed default Storage Key to 'storageKeys' + // Assign specified default Storage key to the 'storageKeys' array if (defaultStorageKey && !_storageKeys.includes(defaultStorageKey)) _storageKeys.push(defaultStorageKey); - // Add default Storage of AgileTs to storageKeys and assign it as default Storage Key of Persistent if no storageKeys provided + // Assign default Storage of AgileTs to the `storageKeys' array + // and assign it as default Storage key of the Persistent + // if no valid 'storageKeys' were provided if (_storageKeys.length <= 0) { this.config.defaultStorageKey = storages.config.defaultStorageKey as any; _storageKeys.push(storages.config.defaultStorageKey as any); @@ -182,12 +197,10 @@ export class Persistent { this.storageKeys = _storageKeys; } - //========================================================================================================= - // Initial Loading - //========================================================================================================= /** + * Stores or loads the Persistent value from the external Storages for the first time. + * * @internal - * Loads/Saves Storage Value for the first Time */ public async initialLoading(): Promise { const success = await this.loadPersistedValue(); @@ -195,15 +208,17 @@ export class Persistent { if (!success) await this.persistValue(); } - //========================================================================================================= - // Load Value - //========================================================================================================= /** + * Loads the Persistent value from the corresponding Storage. + * + * Note that this method should be overwritten + * to correctly apply the changes to the Agile Class + * the Persistent belongs to. + * * @internal - * Loads Value from Storage - * @param storageItemKey - Storage key of the persisted Instance. + * @param storageItemKey - Storage key of the persisted value. * | default = Persistent.key | - * @return Success? + * @return Whether loading of the persisted value was successful. */ public async loadPersistedValue( storageItemKey?: PersistentKey @@ -212,30 +227,35 @@ export class Persistent { return false; } - //========================================================================================================= - // Update Value - //========================================================================================================= /** + * Persists the Persistent value in the corresponding Storage. + * + * Note that this method should be overwritten + * to correctly apply the changes to the Agile Class + * the Persistent belongs to. + * * @internal - * Saves/Updates Value in Storage - * @param storageItemKey - Storage key of the persisted Instance. + * @param storageItemKey - Storage key of the persisted value * | default = Persistent.key | - * @return Success? + * @return Whether the persisting of the value was successful. */ public async persistValue(storageItemKey?: PersistentKey): Promise { LogCodeManager.log('00:03:00', ['persistValue', 'Persistent']); return false; } - //========================================================================================================= - // Remove Value - //========================================================================================================= /** + * Removes the Persistent value from the corresponding Storage. + * -> Persistent value is no longer persisted + * + * Note that this method should be overwritten + * to correctly apply the changes to the Agile Class + * the Persistent belongs to. + * * @internal - * Removes Value form Storage - * @param storageItemKey - Storage key of the persisted Instance. + * @param storageItemKey - Storage key of the persisted value. * | default = Persistent.key | - * @return Success? + * @return Whether the removal of the persisted value was successful. */ public async removePersistedValue( storageItemKey?: PersistentKey @@ -244,13 +264,16 @@ export class Persistent { return false; } - //========================================================================================================= - // Format Key - //========================================================================================================= /** + * Formats the specified key so that it can be used as a valid Storage key + * and returns the formatted variant of it. + * + * Note that this method should be overwritten + * to correctly apply the changes to the Agile Class + * the Persistent belongs to. + * * @internal - * Validates Storage Key - * @param key - Key that gets validated + * @param key - Key to be formatted. */ public formatKey(key?: PersistentKey): PersistentKey | undefined { return key; @@ -259,33 +282,67 @@ export class Persistent { export type PersistentKey = string | number; -/** - * @param key - Key/Name of Persistent - * @param storageKeys - Keys of Storages in that the persisted Value gets saved - * @param defaultStorage - Default Storage Key - * @param instantiate - If Persistent gets Instantiated immediately - */ export interface CreatePersistentConfigInterface { + /** + * Key/Name identifier of the Persistent. + */ key?: PersistentKey; + /** + * Key/Name identifier of Storages + * in which the Persistent value should be or is persisted. + * @default [`defaultStorageKey`] + */ storageKeys?: StorageKey[]; + /** + * Key/Name identifier of the default Storage of the specified Storage keys. + * + * The Persistent value is loaded from the default Storage by default + * and is only loaded from the remaining Storages (`storageKeys) + * if the loading from the default Storage failed. + * + * @default first index of the specified Storage keys or the AgileTs default Storage key + */ defaultStorageKey?: StorageKey; + /** + * Whether the Persistent should be instantiated immediately + * or whether this should be done manually. + * @default true + */ instantiate?: boolean; } -/** - * @param defaultStorageKey - Default Storage Key - */ export interface PersistentConfigInterface { + /** + * Key/Name identifier of the default Storage of the specified Storage keys. + * + * The Persistent value is loaded from the default Storage by default + * and is only loaded from the remaining Storages (`storageKeys) + * if the loading from the default Storage failed. + * + * @default first index of the specified Storage keys or the AgileTs default Storage key + */ defaultStorageKey: StorageKey | null; } -/** - * @param key - Key/Name of Persistent - * @param storageKeys - Keys of Storages in that the persisted Value gets saved - * @param defaultStorageKey - Default Storage Key (if not provided it takes the first index of storageKeys or the AgileTs default Storage) - */ export interface InstantiatePersistentConfigInterface { + /** + * Key/Name identifier of the Persistent. + */ key?: PersistentKey; + /** + * Key/Name identifier of Storages + * in which the Persistent value should be or is persisted. + * @default [`defaultStorageKey`] + */ storageKeys?: StorageKey[]; + /** + * Key/Name identifier of the default Storage of the specified Storage keys. + * + * The Persistent value is loaded from the default Storage by default + * and is only loaded from the remaining Storages (`storageKeys) + * if the loading from the default Storage failed. + * + * @default first index of the specified Storage keys or the AgileTs default Storage key + */ defaultStorageKey?: StorageKey; } From 95948a74846bfdee172b69b35cae0756771cffbb Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 18 Jun 2021 20:44:09 +0200 Subject: [PATCH 081/117] fixed typos --- .../src/collection/collection.persistent.ts | 18 ++--- packages/core/src/integrations/index.ts | 2 +- packages/core/src/logCodeManager.ts | 37 +++++---- packages/core/src/state/state.persistent.ts | 21 +++-- packages/core/src/storages/index.ts | 66 +++++++++------- packages/core/src/storages/persistent.ts | 77 +++++++++++-------- packages/core/src/storages/storage.ts | 41 ++++------ packages/core/src/utils.ts | 59 +++++++------- 8 files changed, 169 insertions(+), 152 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 1ed5abb9..f7425ff1 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -72,9 +72,9 @@ export class CollectionPersistent< * the Storage value when the Collection (Instances) changes. * * @internal - * @param storageItemKey - Prefix Storage key of the persisted Collection Instances. + * @param storageItemKey - Prefix Storage key of the to load Collection Instances. * | default = Persistent.key | - * @return Whether the loading of the persisted value and the setting up of the side effects was successful. + * @return Whether the loading of the persisted Collection Instances and setting up of the corresponding side effects was successful. */ public async loadPersistedValue( storageItemKey?: PersistentKey @@ -178,9 +178,9 @@ export class CollectionPersistent< * the Storage value when the Collection (Instances) changes. * * @internal - * @param storageItemKey - Prefix Storage key of the persisted Collection Instances. + * @param storageItemKey - Prefix Storage key of the to persist Collection Instances. * | default = Persistent.key | - * @return Whether the persisting of the value and the setting up of the side effects was successful. + * @return Whether the persisting of the Collection Instances and the setting up of the corresponding side effects was successful. */ public async persistValue(storageItemKey?: PersistentKey): Promise { if (!this.ready) return false; @@ -229,7 +229,7 @@ export class CollectionPersistent< * with the Collection (Instances) value. * * @internal - * @param storageItemKey - Prefix Storage key of the persisted Collection Instances. + * @param storageItemKey - Prefix Storage key of the to remove Collection Instances. * | default = Persistent.key | */ public setupSideEffects(storageItemKey?: PersistentKey): void { @@ -253,7 +253,7 @@ export class CollectionPersistent< * @internal * @param storageItemKey - Prefix Storage key of the persisted Collection Instances. * | default = Persistent.key | - * @return Whether the removal of the persisted values was successful. + * @return Whether the removal of the Collection Instances was successful. */ public async removePersistedValue( storageItemKey?: PersistentKey @@ -294,11 +294,11 @@ export class CollectionPersistent< * Formats the specified key so that it can be used as a valid Storage key * and returns the formatted variant of it. * - * If no formatable key (undefined/null) was provided, - * an attempt is made to use the Collection identifier key. + * If no formatable key (`undefined`/`null`) was provided, + * an attempt is made to use the Collection identifier key as Storage key. * * @internal - * @param key - Key to be formatted. + * @param key - Storage key to be formatted. */ public formatKey(key: StorageKey | undefined | null): StorageKey | undefined { if (key == null && this.collection()._key) return this.collection()._key; diff --git a/packages/core/src/integrations/index.ts b/packages/core/src/integrations/index.ts index e72df24c..038166db 100644 --- a/packages/core/src/integrations/index.ts +++ b/packages/core/src/integrations/index.ts @@ -10,7 +10,7 @@ export class Integrations { /** * The Integrations Class manages all Integrations for an Agile Instance * and provides an interface to easily update - * or invoke functions in all registered Integrations. + * and invoke functions in all registered Integrations. * * @internal * @param agileInstance - Instance of Agile the Integrations belongs to. diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index 28694b93..9df75ad7 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -1,11 +1,19 @@ import { Agile } from './agile'; +// The Log Code Manager keeps track +// and manages all important Logs of AgileTs. +// +// How does the identification of Log Messages work? +// Let's take a look at this example: // 00:00:00 +// // |00|:00:00 first digits are based on the Agile Class // 00 = General // 10 = Agile // 11 = Storage // .. +// +// --- // 00:|00|:00 second digits are based on the Log Type const logCodeTypes = { '00': 'success', @@ -13,6 +21,8 @@ const logCodeTypes = { '02': 'warn', '03': 'error', }; +// +// --- // 00:00:|00| third digits are based on the Log Message (ascending counted) const logCodeMessages = { @@ -161,15 +171,13 @@ const logCodeMessages = { '00:03:01': "'${0}' has to be of the type ${1}!", }; -//========================================================================================================= -// Get Log -//========================================================================================================= /** + * Returns the log message according to the specified log code. + * * @internal - * Returns the log message according to the passed logCode - * @param logCode - Log Code of Message + * @param logCode - Log code of the message to be returned. * @param replacers - Instances that replace these '${x}' placeholders based on the index - * For example: replacers[0] replaces '${0}', replacers[1] replaces '${1}', ... + * For example: 'replacers[0]' replaces '${0}', 'replacers[1]' replaces '${1}', .. */ function getLog>( logCode: T, @@ -185,16 +193,15 @@ function getLog>( return result; } -//========================================================================================================= -// Log -//========================================================================================================= /** + * Logs the log message according to the specified log code + * with the Agile Logger. + * * @internal - * Logs message at the provided logCode with the Agile.logger - * @param logCode - Log Code of Message + * @param logCode - Log code of the message to be returned. * @param replacers - Instances that replace these '${x}' placeholders based on the index - * For example: replacers[0] replaces '${0}', replacers[1] replaces '${1}', .. - * @param data - Data attached to the end of the log message + * For example: 'replacers[0]' replaces '${0}', 'replacers[1]' replaces '${1}', .. + * @param data - Data to be attached to the end of the log message. */ function log>( logCode: T, @@ -207,8 +214,10 @@ function log>( } /** + * The Log Code Manager keeps track + * and manages all important Logs of AgileTs. + * * @internal - * Manages logCode based logging of AgileTs */ export const LogCodeManager = { getLog, diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index 2b30165f..03029ff0 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -61,9 +61,9 @@ export class StatePersistent extends Persistent { * the Storage value when the State changes. * * @internal - * @param storageItemKey - Storage key of the persisted State Instance. + * @param storageItemKey - Storage key of the to load State Instance. * | default = Persistent.key | - * @return Whether the loading of the persisted value and the setting up of the side effects was successful. + * @return Whether the loading of the persisted State Instance and the setting up of the corresponding side effects was successful. */ public async loadPersistedValue( storageItemKey?: PersistentKey @@ -97,9 +97,9 @@ export class StatePersistent extends Persistent { * the Storage value when the State changes. * * @internal - * @param storageItemKey - Storage key of the persisted State Instance. + * @param storageItemKey - Storage key of the to persist State Instance. * | default = Persistent.key | - * @return Whether the persisting of the value and the setting up of the side effects was successful. + * @return Whether the persisting of the State Instance and setting up of the corresponding side effects was successful. */ public async persistValue(storageItemKey?: PersistentKey): Promise { if (!this.ready) return false; @@ -126,9 +126,6 @@ export class StatePersistent extends Persistent { */ public setupSideEffects(storageItemKey?: PersistentKey) { const _storageItemKey = storageItemKey ?? this._key; - - // Add side effect to the State - // that updates the Storage value based on the current State value this.state().addSideEffect( StatePersistent.storeValueSideEffectKey, (instance, config) => { @@ -143,9 +140,9 @@ export class StatePersistent extends Persistent { * -> State is no longer persisted * * @internal - * @param storageItemKey - Storage key of the persisted State Instance. + * @param storageItemKey - Storage key of the to remove State Instance. * | default = Persistent.key | - * @return Whether the removal of the persisted value was successful. + * @return Whether the removal of the persisted State Instance was successful. */ public async removePersistedValue( storageItemKey?: PersistentKey @@ -162,11 +159,11 @@ export class StatePersistent extends Persistent { * Formats the specified key so that it can be used as a valid Storage key * and returns the formatted variant of it. * - * If no formatable key (undefined/null) was provided, - * an attempt is made to use the State identifier key. + * If no formatable key (`undefined`/`null`) was provided, + * an attempt is made to use the State identifier key as Storage key. * * @internal - * @param key - Key to be formatted. + * @param key - Storage key to be formatted. */ public formatKey( key: PersistentKey | undefined | null diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index d3eaefb7..552a96b7 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -17,12 +17,13 @@ export class Storages { // Registered Storages public storages: { [key: string]: Storage } = {}; - // Persistent from Instances that were persisted + // Persistent from Instances (for example States) that were persisted public persistentInstances: Set = new Set(); /** * The Storages Class manages all external Storages for an Agile Instance - * and provides an interface to easily store, load and remove values from multiple Storages at once. + * and provides an interface to easily store, + * load and remove values from multiple Storages at once. * * @internal * @param agileInstance - Instance of Agile the Storages belongs to. @@ -45,18 +46,15 @@ export class Storages { * Instantiates and registers the * [Local Storage](https://developer.mozilla.org/de/docs/Web/API/Window/localStorage). * - * Note that this only works in a web environment! + * Note that the Local Storage is only available in a web environment. * * @internal */ public instantiateLocalStorage(): boolean { - // Check if Local Storage is available in this environment if (!Storages.localStorageAvailable()) { LogCodeManager.log('11:02:00'); return false; } - - // Create and register Local Storage const _localStorage = new Storage({ key: 'localStorage', async: false, @@ -71,8 +69,8 @@ export class Storages { /** * Registers the specified Storage with AgileTs - * and updates the Persistents that have already attempted - * to use the now registered Storage. + * and updates the Persistent Instances that have already attempted + * to use the previously unregistered Storage. * * @public * @param storage - Storage to be registered with AgileTs. @@ -90,7 +88,7 @@ export class Storages { return false; } - // Assign first added Storage as default Storage + // Assign Storage as default Storage if it is the first one added if (!hasRegisteredAnyStorage && config.default === false) LogCodeManager.log('11:02:01'); if (!hasRegisteredAnyStorage) config.default = true; @@ -100,15 +98,16 @@ export class Storages { if (config.default) this.config.defaultStorageKey = storage.key; this.persistentInstances.forEach((persistent) => { - // Revalidate Persistent that includes the newly registered storage key + // Revalidate Persistent, which contains key/name identifier of the newly registered Storage if (persistent.storageKeys.includes(storage.key)) { const isValid = persistent.validatePersistent(); if (isValid) persistent.initialLoading(); return; } - // If Persistent has no default storage key - // (reassign storage keys since this registered Storage might be tagged as default Storage) + // If Persistent has no default Storage key, + // reassign Storage keys since the now registered Storage + // might be tagged as default Storage of AgileTs if (persistent.config.defaultStorageKey == null) { persistent.assignStorageKeys(); const isValid = persistent.validatePersistent(); @@ -133,19 +132,14 @@ export class Storages { ): Storage | undefined { if (!storageKey) return undefined; const storage = this.storages[storageKey]; - - // Check if Storage exists if (!storage) { LogCodeManager.log('11:03:01', [storageKey]); return undefined; } - - // Check if Storage is ready if (!storage.ready) { LogCodeManager.log('11:03:02', [storageKey]); return undefined; } - return storage; } @@ -207,7 +201,7 @@ export class Storages { } // Call set method on specified Storages - if (storageKeys) { + if (storageKeys != null) { for (const storageKey of storageKeys) this.getStorage(storageKey)?.set(storageItemKey, value); return; @@ -264,7 +258,7 @@ export class Storages { /** * Returns a boolean indication whether the * [Local Storage](https://developer.mozilla.org/de/docs/Web/API/Window/localStorage) - * is available in this environment. + * is available in the current environment. * * @public */ @@ -281,15 +275,21 @@ export class Storages { export interface CreateStoragesConfigInterface { /** - * Whether the Local Storage should be registered by default. + * Whether to register the Local Storage by default. + * Note that the Local Storage is only available in a web environment. * @default false */ localStorage?: boolean; /** - * Key/Name identifier of the Storage to become the default Storage. + * Key/Name identifier of the default Storage. + * + * The default Storage represents the default Storage of the Storages Class, + * on which executed actions are performed if no specific Storage was specified. * - * When no specified Storage has been defined in methods like `get()`, `set()`, `remove()`, - * the default Storage will be used. + * Also, the persisted value is loaded from the default Storage by default, + * since only one persisted value can be applied. + * If the loading of the value from the default Storage failed, + * an attempt is made to load the value from the remaining Storages. * * @default undefined */ @@ -298,10 +298,15 @@ export interface CreateStoragesConfigInterface { export interface StoragesConfigInterface { /** - * Key/Name identifier of the Storage to become the default Storage. + * Key/Name identifier of the default Storage. * - * When no specified Storage has been defined in methods like `get()`, `set()`, `remove()`, - * the default Storage will be used. + * The default Storage represents the default Storage of the Storages Class, + * on which executed actions are performed if no specific Storage was specified. + * + * Also, the persisted value is loaded from the default Storage by default, + * since only one persisted value can be applied. + * If the loading of the value from the default Storage failed, + * an attempt is made to load the value from the remaining Storages. * * @default undefined */ @@ -312,8 +317,13 @@ export interface RegisterConfigInterface { /** * Whether the to register Storage should become the default Storage. * - * When no specified Storage has been defined in methods like `get()`, `set()`, `remove()`, - * the default Storage will be used. + * The default Storage represents the default Storage of the Storages Class, + * on which executed actions are performed if no specific Storage was specified. + * + * Also, the persisted value is loaded from the default Storage by default, + * since only one persisted value can be applied. + * If the loading of the value from the default Storage failed, + * an attempt is made to load the value from the remaining Storages. * * @default false */ diff --git a/packages/core/src/storages/persistent.ts b/packages/core/src/storages/persistent.ts index eaec9937..a335d982 100644 --- a/packages/core/src/storages/persistent.ts +++ b/packages/core/src/storages/persistent.ts @@ -16,9 +16,10 @@ export class Persistent { // Key/Name identifier of the Persistent public _key: PersistentKey; - // Whether the Persistent is ready and is allowed to persist values + // Whether the Persistent is ready + // and is able to persist values in an external Storage public ready = false; - // Whether the Persistent value is stored in the corresponding Storage/s + // Whether the Persistent value is stored in a corresponding external Storage/s public isPersisted = false; // Callback that is called when the persisted value was loaded into the Persistent for the first time public onLoad: ((success: boolean) => void) | undefined; @@ -110,11 +111,11 @@ export class Persistent { } /** - * Instantiates the Persistent by assigning the specified Storage keys - * and validating the Persistent. + * Instantiates the Persistent by assigning the specified Storage keys to it + * and validating it to make sure everything was setup correctly. * - * This was moved out of the `constructor` - * because some classes that extend the Persistent need to configure some + * This was moved out of the `constructor()` + * because some classes (that extend the Persistent) need to configure some * things before they can properly instantiate the parent Persistent. * * @internal @@ -132,7 +133,7 @@ export class Persistent { * Returns a boolean indicating whether the Persistent was setup correctly * and is able to persist a value permanently in an external Storage. * - * Based on this tapped boolean value, + * Based on the tapped boolean value, * the Persistent's `ready` property is updated. * * @internal @@ -165,12 +166,14 @@ export class Persistent { } /** - * Assigns the specified Storage keys to the Persistent - * and overwrites the old ones. + * Assigns the specified Storage identifiers to the Persistent + * and extracts the default Storage if necessary. * + * When no Storage key was provided the default Storage + * of the Agile Instance is applied to the Persistent. * * @internal - * @param storageKeys - Key/Name identifiers to be assigned. + * @param storageKeys - Key/Name identifier of the Storages to be assigned. * @param defaultStorageKey - Key/Name identifier of the default Storage. */ public assignStorageKeys( @@ -184,21 +187,22 @@ export class Persistent { if (defaultStorageKey && !_storageKeys.includes(defaultStorageKey)) _storageKeys.push(defaultStorageKey); - // Assign default Storage of AgileTs to the `storageKeys' array - // and assign it as default Storage key of the Persistent - // if no valid 'storageKeys' were provided + // Assign the default Storage key of the Agile Instance to the 'storageKeys' array + // and specify it as the Persistent's default Storage key + // if no valid Storage key was provided if (_storageKeys.length <= 0) { this.config.defaultStorageKey = storages.config.defaultStorageKey as any; _storageKeys.push(storages.config.defaultStorageKey as any); } else { - this.config.defaultStorageKey = defaultStorageKey || _storageKeys[0]; + this.config.defaultStorageKey = defaultStorageKey ?? _storageKeys[0]; } this.storageKeys = _storageKeys; } /** - * Stores or loads the Persistent value from the external Storages for the first time. + * Stores or loads the Persistent value + * from the external Storages for the first time. * * @internal */ @@ -216,9 +220,9 @@ export class Persistent { * the Persistent belongs to. * * @internal - * @param storageItemKey - Storage key of the persisted value. + * @param storageItemKey - Storage key of the to load value. * | default = Persistent.key | - * @return Whether loading of the persisted value was successful. + * @return Whether the loading of the persisted value was successful. */ public async loadPersistedValue( storageItemKey?: PersistentKey @@ -235,7 +239,7 @@ export class Persistent { * the Persistent belongs to. * * @internal - * @param storageItemKey - Storage key of the persisted value + * @param storageItemKey - Storage key of the to persist value * | default = Persistent.key | * @return Whether the persisting of the value was successful. */ @@ -253,7 +257,7 @@ export class Persistent { * the Persistent belongs to. * * @internal - * @param storageItemKey - Storage key of the persisted value. + * @param storageItemKey - Storage key of the to remove value. * | default = Persistent.key | * @return Whether the removal of the persisted value was successful. */ @@ -273,7 +277,7 @@ export class Persistent { * the Persistent belongs to. * * @internal - * @param key - Key to be formatted. + * @param key - Storage key to be formatted. */ public formatKey(key?: PersistentKey): PersistentKey | undefined { return key; @@ -289,18 +293,20 @@ export interface CreatePersistentConfigInterface { key?: PersistentKey; /** * Key/Name identifier of Storages - * in which the Persistent value should be or is persisted. + * in which the Persistent value is to be persisted + * or is already persisted. * @default [`defaultStorageKey`] */ storageKeys?: StorageKey[]; /** * Key/Name identifier of the default Storage of the specified Storage keys. * - * The Persistent value is loaded from the default Storage by default - * and is only loaded from the remaining Storages (`storageKeys) - * if the loading from the default Storage failed. + * The persisted value is loaded from the default Storage by default, + * since only one persisted value can be applied. + * If the loading of the value from the default Storage failed, + * an attempt is made to load the value from the remaining Storages. * - * @default first index of the specified Storage keys or the AgileTs default Storage key + * @default first index of the specified Storage keys or the Agile Instance's default Storage key */ defaultStorageKey?: StorageKey; /** @@ -315,11 +321,12 @@ export interface PersistentConfigInterface { /** * Key/Name identifier of the default Storage of the specified Storage keys. * - * The Persistent value is loaded from the default Storage by default - * and is only loaded from the remaining Storages (`storageKeys) - * if the loading from the default Storage failed. + * The persisted value is loaded from the default Storage by default, + * since only one persisted value can be applied. + * If the loading of the value from the default Storage failed, + * an attempt is made to load the value from the remaining Storages. * - * @default first index of the specified Storage keys or the AgileTs default Storage key + * @default first index of the specified Storage keys or the Agile Instance's default Storage key */ defaultStorageKey: StorageKey | null; } @@ -331,18 +338,20 @@ export interface InstantiatePersistentConfigInterface { key?: PersistentKey; /** * Key/Name identifier of Storages - * in which the Persistent value should be or is persisted. + * in which the Persistent value is to be persisted + * or is already persisted. * @default [`defaultStorageKey`] */ storageKeys?: StorageKey[]; /** * Key/Name identifier of the default Storage of the specified Storage keys. * - * The Persistent value is loaded from the default Storage by default - * and is only loaded from the remaining Storages (`storageKeys) - * if the loading from the default Storage failed. + * The persisted value is loaded from the default Storage by default, + * since only one persisted value can be applied. + * If the loading of the value from the default Storage failed, + * an attempt is made to load the value from the remaining Storages. * - * @default first index of the specified Storage keys or the AgileTs default Storage key + * @default first index of the specified Storage keys or the Agile Instance's default Storage key */ defaultStorageKey?: StorageKey; } diff --git a/packages/core/src/storages/storage.ts b/packages/core/src/storages/storage.ts index 524b2d50..894a2a6d 100644 --- a/packages/core/src/storages/storage.ts +++ b/packages/core/src/storages/storage.ts @@ -12,13 +12,13 @@ export class Storage { // Key/Name identifier of the Storage public key: StorageKey; - // Whether the Storage is ready and is able to persist values + // Whether the Storage is ready and is able to store values public ready = false; // Methods to interact with the external Storage (get, set, remove) public methods: StorageMethodsInterface; /** - * An Storage is an interface to an external Storage, + * A Storage is an interface to an external Storage, * and allows the easy interaction with that Storage. * * Due to the Storage, AgileTs can easily persist its Instances in almost any Storage @@ -74,7 +74,7 @@ export class Storage { /** * Synchronously retrieves the stored value - * at the specified Storage Item key from the Storage. + * at the specified Storage Item key from the external Storage. * * When the retrieved value is a JSON-String it is parsed automatically. * @@ -101,7 +101,7 @@ export class Storage { /** * Asynchronously retrieves the stored value - * at the specified Storage Item key from the Storage. + * at the specified Storage Item key from the external Storage. * * When the retrieved value is a JSON-String it is parsed automatically. * @@ -139,10 +139,11 @@ export class Storage { } /** - * Stores or updates the value at the specified Storage Item key in the Storage. + * Stores or updates the value at the specified Storage Item key + * in the external Storage. * * @public - * @param key - Key/Name identifier of the value to be stored. + * @param key - Key/Name identifier of the value to be stored or updated. * @param value - Value to be stored. */ public set(key: StorageItemKey, value: any): void { @@ -159,7 +160,8 @@ export class Storage { } /** - * Removes the value at the specified Storage Item key from the Storage. + * Removes the value at the specified Storage Item key + * from the external Storage. * * @public * @param key - Key/Name identifier of the value to be removed. @@ -180,7 +182,7 @@ export class Storage { * Generates and returns a valid Storage key based on the specified key. * * @internal - * @param key - Key that gets converted into a Storage Key + * @param key - Key to be converted into a valid Storage key. */ public getStorageKey(key: StorageItemKey): string { return this.config.prefix @@ -192,13 +194,9 @@ export class Storage { export type StorageKey = string | number; export type StorageItemKey = string | number; -/** - * @param key - Key/Name of Storage - * @param methods - Storage methods like (get, set, remove) - */ export interface CreateStorageConfigInterface extends StorageConfigInterface { /** - * Key/Name identifier of the Storage + * Key/Name identifier of the Storage. * @default undefined */ key: string; @@ -209,11 +207,6 @@ export interface CreateStorageConfigInterface extends StorageConfigInterface { methods: StorageMethodsInterface; } -/** - * @param get - Get Method of Storage (gets items from storage) - * @param set - Set Method of Storage (saves/updates items in storage) - * @param remove - Remove Methods of Storage (removes items from storage) - */ export interface StorageMethodsInterface { /** * Method to retrieve a value at the specified key from the external Storage. @@ -222,9 +215,9 @@ export interface StorageMethodsInterface { */ get: (key: string) => any; /** - * Method to store a value at the specified key in the external Storage. + * Method to store or update a value at the specified key in the external Storage. * - * @param key - Key/Name identifier of the value to be stored. + * @param key - Key/Name identifier of the value to be stored or updated. * @param value - Value to be stored. */ set: (key: string, value: any) => void; @@ -236,14 +229,10 @@ export interface StorageMethodsInterface { remove: (key: string) => void; } -/** - * @param async - If its an async storage - * @param prefix - Prefix of Storage Property - */ export interface StorageConfigInterface { /** - * Whether the external Storage works async. - * @default Automatically detected via `isAsyncFunction()` + * Whether the external Storage represented by the Storage Class works async. + * @default Automatically detected via the `isAsyncFunction()` method */ async?: boolean; /** diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index d998f602..a36692dd 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -7,18 +7,17 @@ import { LogCodeManager, } from './internal'; -//========================================================================================================= -// Get Agile Instance -//========================================================================================================= /** + * Extracts an Instance of Agile from the specified Instance. + * When no valid Agile Instance was found, + * it returns the global bound Agile Instance or `undefined`. + * * @internal - * Tries to get an Instance of Agile from provided Instance - * If no agileInstance found it returns the global bound Agile Instance - * @param instance - Instance that might hold an Agile Instance + * @param instance - Instance to extract the Agile Instance from. */ export function getAgileInstance(instance: any): Agile | undefined { try { - // Try to get agileInstance from passed Instance + // Try to get Agile Instance from specified Instance if (instance) { const _agileInstance = isFunction(instance['agileInstance']) ? instance['agileInstance']() @@ -26,7 +25,7 @@ export function getAgileInstance(instance: any): Agile | undefined { if (_agileInstance) return _agileInstance; } - // Return global bound agileInstance + // Return global bound Agile Instance return globalThis[Agile.globalKey]; } catch (e) { LogCodeManager.log('20:03:00', [], instance); @@ -35,13 +34,11 @@ export function getAgileInstance(instance: any): Agile | undefined { return undefined; } -//========================================================================================================= -// Extract Observers -//========================================================================================================= /** + * Extracts the Observers from the specified Instances. + * * @internal - * Extract Observers from specific Instances - * @param instances - Instances that will be formatted + * @param instances - Instances to extract the Observers from. */ export function extractObservers(instances: any): Array { const instancesArray: Array = []; @@ -51,13 +48,16 @@ export function extractObservers(instances: any): Array { // Get Observers from Instances for (const instance of tempInstancesArray) { - // If Instance is undefined (We have to add undefined to build a proper return value in for instance 'useAgile' later) - if (!instance) { + // If the Instance equals to 'undefined' + // (We have to add 'undefined' to the return value + // in order to properly build the return value of, + // for example, the 'useAgile()' hook later) + if (instance == null) { instancesArray.push(undefined); continue; } - // If Instance is Collection + // If the Instance equals to a Collection if (instance instanceof Collection) { instancesArray.push( instance.getGroupWithReference(instance.config.defaultGroupKey).observer @@ -65,36 +65,40 @@ export function extractObservers(instances: any): Array { continue; } - // If Instance has property that is an Observer + // If the Instance contains a property that is an Observer if (instance['observer'] && instance['observer'] instanceof Observer) { instancesArray.push(instance['observer']); continue; } - // If Instance is Observer + // If the Instance equals to an Observer if (instance instanceof Observer) { instancesArray.push(instance); continue; } - // Push undefined if no Observer could be found (We have to add undefined to build a proper return value in for instance 'useAgile' later) + // Push 'undefined' if no valid Observer was found + // (We have to add 'undefined' to the return value + // in order to properly build the return value of, + // for example, the 'useAgile()' hook later) instancesArray.push(undefined); } return instancesArray; } -//========================================================================================================= -// Global Bind -//========================================================================================================= /** - * @internal - * Binds passed Instance globally at passed Key + * Binds the specified Instance globally at the provided key identifier. + * + * Learn more about global bound instances: * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis * https://blog.logrocket.com/what-is-globalthis-why-use-it/ - * @param key - Key/Name of Instance - * @param instance - Instance - * @param overwrite - If already existing instance at passed Key gets overwritten + * + * @public + * @param key - Key/Name identifier of the specified Instance. + * @param instance - Instance to be bound globally. + * @param overwrite - When already an Instance exists globally at the specified key, + * whether to overwrite it with the new Instance. */ export function globalBind( key: string, @@ -106,7 +110,6 @@ export function globalBind( globalThis[key] = instance; return true; } - if (globalThis[key] == null) { globalThis[key] = instance; return true; From 4ad078f268a84b429f26ba2bffca681682ebc955 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 19 Jun 2021 08:06:38 +0200 Subject: [PATCH 082/117] added basic async to computed --- packages/core/src/agile.ts | 14 +++--- packages/core/src/computed/index.ts | 61 +++++++++++++++++------ packages/core/src/runtime/index.ts | 2 +- packages/core/src/runtime/observer.ts | 3 ++ packages/core/src/state/state.observer.ts | 12 +++-- 5 files changed, 65 insertions(+), 27 deletions(-) diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index d47775ae..ed4a0a81 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -21,6 +21,8 @@ import { LogCodeManager, ComputedConfigInterface, SubscribableAgileInstancesType, + CreateComputedConfigInterface, + ComputeFunctionType, } from './internal'; export class Agile { @@ -195,8 +197,8 @@ export class Agile { * @param config - Configuration object */ public createComputed( - computeFunction: () => ComputedValueType, - config?: ComputedConfigInterface + computeFunction: ComputeFunctionType, + config?: CreateComputedConfigInterface ): Computed; /** * Returns a newly created Computed. @@ -217,16 +219,16 @@ export class Agile { * @param deps - Hard-coded dependencies on which the Computed Class should depend. */ public createComputed( - computeFunction: () => ComputedValueType, + computeFunction: ComputeFunctionType, deps?: Array ): Computed; public createComputed( - computeFunction: () => ComputedValueType, + computeFunction: ComputeFunctionType, configOrDeps?: - | ComputedConfigInterface + | CreateComputedConfigInterface | Array ): Computed { - let _config: ComputedConfigInterface = {}; + let _config: CreateComputedConfigInterface = {}; if (Array.isArray(configOrDeps)) { _config = flatMerge(_config, { diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index f0d95f6b..73af3fad 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -10,6 +10,7 @@ import { StateIngestConfigInterface, removeProperties, LogCodeManager, + isAsyncFunction, } from '../internal'; export class Computed extends State< @@ -18,8 +19,10 @@ export class Computed extends State< // Agile Instance the Computed belongs to public agileInstance: () => Agile; + public config: ComputedConfigInterface; + // Function to compute the Computed Class value - public computeFunction: () => ComputedValueType; + public computeFunction: ComputeFunctionType; // All dependencies the Computed Class depends on (including hardCoded and automatically detected dependencies) public deps: Array = []; // Only hardCoded dependencies the Computed Class depends on @@ -44,18 +47,23 @@ export class Computed extends State< */ constructor( agileInstance: Agile, - computeFunction: () => ComputedValueType, - config: ComputedConfigInterface = {} + computeFunction: ComputeFunctionType, + config: CreateComputedConfigInterface = {} ) { - super(agileInstance, computeFunction(), { + super(agileInstance, null as any, { key: config.key, dependents: config.dependents, }); config = defineConfig(config, { computedDeps: [], + autodetect: true, }); this.agileInstance = () => agileInstance; this.computeFunction = computeFunction; + this.observer.async = true; // isAsyncFunction(computeFunction); // Not reliable enough + this.config = { + autodetect: config.autodetect as any, + }; // Extract Observer of passed hardcoded dependency instances this.hardCodedDeps = extractObservers(config.computedDeps).filter( @@ -64,7 +72,7 @@ export class Computed extends State< this.deps = this.hardCodedDeps; // Initial recompute to assign initial value and autodetect missing dependencies - this.recompute({ autodetect: true }); + this.recompute({ autodetect: config.autodetect, overwrite: true }); } /** @@ -79,10 +87,12 @@ export class Computed extends State< config = defineConfig(config, { autodetect: false, }); - this.observer.ingestValue( - this.compute({ autodetect: config.autodetect }), - removeProperties(config, ['autodetect']) - ); + this.compute({ autodetect: config.autodetect }).then((result) => { + this.observer.ingestValue( + result, + removeProperties(config, ['autodetect']) + ); + }); return this; } @@ -109,7 +119,7 @@ export class Computed extends State< ): this { config = defineConfig(config, { overwriteDeps: true, - autodetect: true, + autodetect: this.config.autodetect, }); // Update dependencies of Computed @@ -130,21 +140,23 @@ export class Computed extends State< } /** - * Computes the new value of the Computed Class + * Computes and returns the new value of the Computed Class * and autodetects used dependencies in the compute function. * * @internal * @param config - Configuration object */ - public compute(config: ComputeConfigInterface = {}): ComputedValueType { + public async compute( + config: ComputeConfigInterface = {} + ): Promise { config = defineConfig(config, { - autodetect: true, + autodetect: this.config.autodetect, }); // Start auto tracking of Observers on which the computeFunction might depend if (config.autodetect) ComputedTracker.track(); - const computedValue = this.computeFunction(); + const computedValue = await this.computeFunction(); // Handle auto tracked Observers if (config.autodetect) { @@ -188,12 +200,31 @@ export class Computed extends State< } } -export interface ComputedConfigInterface extends StateConfigInterface { +export type ComputeFunctionType = () => + | ComputedValueType + | Promise; + +export interface CreateComputedConfigInterface extends StateConfigInterface { /** * Hard-coded dependencies on which the Computed Class should depend. * @default [] */ computedDeps?: Array; + /** + * Whether the Computed can automatically detect + * used dependencies in the compute method. + * @default true + */ + autodetect?: boolean; +} + +export interface ComputedConfigInterface { + /** + * Whether the Computed can automatically detect + * used dependencies in the compute method. + * @default true when compute method isn't async otherwise false + */ + autodetect: boolean; } export interface ComputeConfigInterface { diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index 9a93ce62..8404f951 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -105,7 +105,7 @@ export class Runtime { // Ingest dependents of the Observer into runtime, // since they depend on the Observer and therefore have properly changed too job.observer.dependents.forEach((observer) => - observer.ingest({ perform: false }) + observer.ingest({ perform: observer.async }) ); // Add Job to rerender queue and reset current Job property diff --git a/packages/core/src/runtime/observer.ts b/packages/core/src/runtime/observer.ts index ebffc0fb..a9c1b9e7 100644 --- a/packages/core/src/runtime/observer.ts +++ b/packages/core/src/runtime/observer.ts @@ -23,6 +23,9 @@ export class Observer { // Subscription Containers (UI-Components) the Observer is subscribed to public subscribedTo: Set = new Set(); + // Whether the Observer works async + public async = false; + // Current value of the Observer public value?: ValueType; // Previous value of the Observer diff --git a/packages/core/src/state/state.observer.ts b/packages/core/src/state/state.observer.ts index b5a4ee2e..75d0fa96 100644 --- a/packages/core/src/state/state.observer.ts +++ b/packages/core/src/state/state.observer.ts @@ -55,12 +55,14 @@ export class StateObserver extends Observer { */ public ingest(config: StateIngestConfigInterface = {}): void { const state = this.state(); - let newStateValue: ValueType; - if (state instanceof Computed) newStateValue = state.compute(); - else newStateValue = state.nextStateValue; - - this.ingestValue(newStateValue, config); + if (state instanceof Computed) { + state.compute().then((result) => { + this.ingestValue(result, config); + }); + } else { + this.ingestValue(state.nextStateValue, config); + } } /** From cf8e7d02235253a037d994a9092b379446eb2027 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 19 Jun 2021 10:50:31 +0200 Subject: [PATCH 083/117] fixed typos --- packages/core/src/computed/index.ts | 71 +++++++++++++++++---------- packages/core/src/runtime/observer.ts | 10 ++++ 2 files changed, 55 insertions(+), 26 deletions(-) diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index 73af3fad..f75dd31d 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -10,7 +10,6 @@ import { StateIngestConfigInterface, removeProperties, LogCodeManager, - isAsyncFunction, } from '../internal'; export class Computed extends State< @@ -24,7 +23,7 @@ export class Computed extends State< // Function to compute the Computed Class value public computeFunction: ComputeFunctionType; // All dependencies the Computed Class depends on (including hardCoded and automatically detected dependencies) - public deps: Array = []; + public deps: Set = new Set(); // Only hardCoded dependencies the Computed Class depends on public hardCodedDeps: Array = []; @@ -69,7 +68,12 @@ export class Computed extends State< this.hardCodedDeps = extractObservers(config.computedDeps).filter( (dep): dep is Observer => dep !== undefined ); - this.deps = this.hardCodedDeps; + this.deps = new Set(this.hardCodedDeps); + + // Make this Observer depend on the hard coded dep Observers + this.deps.forEach((observer) => { + observer.addDependent(this.observer); + }); // Initial recompute to assign initial value and autodetect missing dependencies this.recompute({ autodetect: config.autodetect, overwrite: true }); @@ -115,20 +119,27 @@ export class Computed extends State< public updateComputeFunction( computeFunction: () => ComputedValueType, deps: Array = [], - config: UpdateComputeFunctionConfigInterface = {} + config: RecomputeConfigInterface = {} ): this { config = defineConfig(config, { - overwriteDeps: true, autodetect: this.config.autodetect, }); + // Make this Observer no longer depend on the old dep Observers + this.deps.forEach((observer) => { + observer.removeDependent(this.observer); + }); + // Update dependencies of Computed - const newDeps = extractObservers(deps).filter( + this.hardCodedDeps = extractObservers(deps).filter( (dep): dep is Observer => dep !== undefined ); - if (config.overwriteDeps) this.hardCodedDeps = newDeps; - else this.hardCodedDeps = this.hardCodedDeps.concat(newDeps); - this.deps = this.hardCodedDeps; + this.deps = new Set(this.hardCodedDeps); + + // Make this Observer depend on the new hard coded dep Observers + this.deps.forEach((observer) => { + observer.addDependent(this.observer); + }); // Update computeFunction this.computeFunction = computeFunction; @@ -161,15 +172,24 @@ export class Computed extends State< // Handle auto tracked Observers if (config.autodetect) { const foundDeps = ComputedTracker.getTrackedObservers(); - const newDeps: Array = []; - this.hardCodedDeps.concat(foundDeps).forEach((observer) => { - newDeps.push(observer); - // Make this Observer depend on the found dep Observers - observer.addDependent(this.observer); + // Clean up old dependencies + this.deps.forEach((observer) => { + if ( + !foundDeps.includes(observer) && + !this.hardCodedDeps.includes(observer) + ) { + observer.removeDependent(this.observer); + } }); - this.deps = newDeps; + // Make this Observer depend on the newly found dep Observers + foundDeps.forEach((observer) => { + if (!this.deps.has(observer)) { + this.deps.add(observer); + observer.addDependent(this.observer); + } + }); } return computedValue; @@ -222,6 +242,10 @@ export interface ComputedConfigInterface { /** * Whether the Computed can automatically detect * used dependencies in the compute method. + * + * Note that automatic dependency tracking does not work + * in an asynchronous calculation method! + * * @default true when compute method isn't async otherwise false */ autodetect: boolean; @@ -229,22 +253,17 @@ export interface ComputedConfigInterface { export interface ComputeConfigInterface { /** - * Whether to automatically detect used dependencies in the compute method. + * Whether the Computed can automatically detect + * used dependencies in the compute method. + * + * Note that automatic dependency tracking does not work + * in an asynchronous calculation method! + * * @default true */ autodetect?: boolean; } -export interface UpdateComputeFunctionConfigInterface - extends RecomputeConfigInterface { - /** - * Whether to overwrite the old hard-coded dependencies with the new ones - * or merge them into the new ones. - * @default false - */ - overwriteDeps?: boolean; -} - export interface RecomputeConfigInterface extends StateIngestConfigInterface, ComputeConfigInterface {} diff --git a/packages/core/src/runtime/observer.ts b/packages/core/src/runtime/observer.ts index a9c1b9e7..1f394c49 100644 --- a/packages/core/src/runtime/observer.ts +++ b/packages/core/src/runtime/observer.ts @@ -160,6 +160,16 @@ export class Observer { public addDependent(observer: Observer): void { if (!this.dependents.has(observer)) this.dependents.add(observer); } + + /** + * Makes the specified Observer no longer depend on the Observer. + * + * @public + * @param observer - Observer to no longer depend on the Observer. + */ + public removeDependent(observer: Observer): void { + if (!this.dependents.has(observer)) this.dependents.delete(observer); + } } export interface CreateObserverConfigInterface { From b001292fcb77990798748dd717a4ce1a24914087 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 19 Jun 2021 15:13:17 +0200 Subject: [PATCH 084/117] fixed async functionality --- .../develop/functional-component-ts/.env | 1 - examples/vue/develop/my-project/src/App.vue | 4 +++- examples/vue/develop/my-project/src/core.js | 22 ++++++++++++++----- packages/core/src/computed/index.ts | 3 +-- packages/core/src/runtime/index.ts | 4 +--- packages/core/src/runtime/observer.ts | 3 --- 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/examples/react/develop/functional-component-ts/.env b/examples/react/develop/functional-component-ts/.env index 62d6f47b..6f809cc2 100644 --- a/examples/react/develop/functional-component-ts/.env +++ b/examples/react/develop/functional-component-ts/.env @@ -1,2 +1 @@ SKIP_PREFLIGHT_CHECK=true -BROWSER=chrome \ No newline at end of file diff --git a/examples/vue/develop/my-project/src/App.vue b/examples/vue/develop/my-project/src/App.vue index c20ba41d..b63e420e 100644 --- a/examples/vue/develop/my-project/src/App.vue +++ b/examples/vue/develop/my-project/src/App.vue @@ -2,6 +2,7 @@
Vue logo

myState: {{ sharedState.myState }}

+

myComputed: {{ sharedState.myComputed }}

@@ -9,7 +10,7 @@