From faad86775444598ec83bfb01bfd152c000420318 Mon Sep 17 00:00:00 2001 From: chradek Date: Thu, 18 Jun 2020 17:14:10 -0700 Subject: [PATCH 01/28] [event-hubs] adds EventHubConsumerClientOptions --- .../event-hubs/review/event-hubs.api.md | 24 ++++++--- .../event-hubs/src/eventHubConsumerClient.ts | 43 ++++++++++----- sdk/eventhub/event-hubs/src/index.ts | 6 ++- sdk/eventhub/event-hubs/src/models/public.ts | 53 ++++++++++++++++++- 4 files changed, 105 insertions(+), 21 deletions(-) diff --git a/sdk/eventhub/event-hubs/review/event-hubs.api.md b/sdk/eventhub/event-hubs/review/event-hubs.api.md index 0eae6f1289f4..ed3f94ed4d47 100644 --- a/sdk/eventhub/event-hubs/review/event-hubs.api.md +++ b/sdk/eventhub/event-hubs/review/event-hubs.api.md @@ -81,12 +81,12 @@ export interface EventHubClientOptions { // @public export class EventHubConsumerClient { - constructor(consumerGroup: string, connectionString: string, options?: EventHubClientOptions); - constructor(consumerGroup: string, connectionString: string, checkpointStore: CheckpointStore, options?: EventHubClientOptions); - constructor(consumerGroup: string, connectionString: string, eventHubName: string, options?: EventHubClientOptions); - constructor(consumerGroup: string, connectionString: string, eventHubName: string, checkpointStore: CheckpointStore, options?: EventHubClientOptions); - constructor(consumerGroup: string, fullyQualifiedNamespace: string, eventHubName: string, credential: TokenCredential, options?: EventHubClientOptions); - constructor(consumerGroup: string, fullyQualifiedNamespace: string, eventHubName: string, credential: TokenCredential, checkpointStore: CheckpointStore, options?: EventHubClientOptions); + constructor(consumerGroup: string, connectionString: string, options?: EventHubConsumerClientOptions); + constructor(consumerGroup: string, connectionString: string, checkpointStore: CheckpointStore, options?: EventHubConsumerClientOptions); + constructor(consumerGroup: string, connectionString: string, eventHubName: string, options?: EventHubConsumerClientOptions); + constructor(consumerGroup: string, connectionString: string, eventHubName: string, checkpointStore: CheckpointStore, options?: EventHubConsumerClientOptions); + constructor(consumerGroup: string, fullyQualifiedNamespace: string, eventHubName: string, credential: TokenCredential, options?: EventHubConsumerClientOptions); + constructor(consumerGroup: string, fullyQualifiedNamespace: string, eventHubName: string, credential: TokenCredential, checkpointStore: CheckpointStore, options?: EventHubConsumerClientOptions); close(): Promise; static defaultConsumerGroupName: string; get eventHubName(): string; @@ -98,6 +98,11 @@ export class EventHubConsumerClient { subscribe(partitionId: string, handlers: SubscriptionEventHandlers, options?: SubscribeOptions): Subscription; } +// @public +export interface EventHubConsumerClientOptions extends EventHubClientOptions { + loadBalancingOptions?: LoadBalancingOptions; +} + // @public export class EventHubProducerClient { constructor(connectionString: string, options?: EventHubClientOptions); @@ -152,6 +157,13 @@ export interface LastEnqueuedEventProperties { // @public export const latestEventPosition: EventPosition; +// @public +export interface LoadBalancingOptions { + partitionOwnershipExpirationIntervalInMs?: number; + strategy?: "balanced" | "greedy"; + updateIntervalInMs?: number; +} + // @public export const logger: import("@azure/logger").AzureLogger; diff --git a/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts b/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts index 4189cc64675b..bfc3e11ee0c0 100644 --- a/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts +++ b/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts @@ -3,10 +3,11 @@ import { ConnectionContext, createConnectionContext } from "./connectionContext"; import { - EventHubClientOptions, + EventHubConsumerClientOptions, GetEventHubPropertiesOptions, GetPartitionIdsOptions, - GetPartitionPropertiesOptions + GetPartitionPropertiesOptions, + LoadBalancingOptions } from "./models/public"; import { InMemoryCheckpointStore } from "./inMemoryCheckpointStore"; import { CheckpointStore, EventProcessor, FullEventProcessorOptions } from "./eventProcessor"; @@ -78,6 +79,11 @@ export class EventHubConsumerClient { private _checkpointStore: CheckpointStore; private _userChoseCheckpointStore: boolean; + /** + * Options for configuring load balancing. + */ + private readonly _loadBalancingOptions: LoadBalancingOptions; + /** * @property * @readonly @@ -111,7 +117,11 @@ export class EventHubConsumerClient { * - `webSocketOptions`: Configures the channelling of the AMQP connection over Web Sockets. * - `userAgent` : A string to append to the built in user agent string that is passed to the service. */ - constructor(consumerGroup: string, connectionString: string, options?: EventHubClientOptions); // #1 + constructor( + consumerGroup: string, + connectionString: string, + options?: EventHubConsumerClientOptions + ); // #1 /** * @constructor * The `EventHubConsumerClient` class is used to consume events from an Event Hub. @@ -133,7 +143,7 @@ export class EventHubConsumerClient { consumerGroup: string, connectionString: string, checkpointStore: CheckpointStore, - options?: EventHubClientOptions + options?: EventHubConsumerClientOptions ); // #1.1 /** * @constructor @@ -154,7 +164,7 @@ export class EventHubConsumerClient { consumerGroup: string, connectionString: string, eventHubName: string, - options?: EventHubClientOptions + options?: EventHubConsumerClientOptions ); // #2 /** * @constructor @@ -179,7 +189,7 @@ export class EventHubConsumerClient { connectionString: string, eventHubName: string, checkpointStore: CheckpointStore, - options?: EventHubClientOptions + options?: EventHubConsumerClientOptions ); // #2.1 /** * @constructor @@ -202,7 +212,7 @@ export class EventHubConsumerClient { fullyQualifiedNamespace: string, eventHubName: string, credential: TokenCredential, - options?: EventHubClientOptions + options?: EventHubConsumerClientOptions ); // #3 /** * @constructor @@ -229,19 +239,23 @@ export class EventHubConsumerClient { eventHubName: string, credential: TokenCredential, checkpointStore: CheckpointStore, - options?: EventHubClientOptions + options?: EventHubConsumerClientOptions ); // #3.1 constructor( private _consumerGroup: string, connectionStringOrFullyQualifiedNamespace2: string, - checkpointStoreOrEventHubNameOrOptions3?: CheckpointStore | EventHubClientOptions | string, + checkpointStoreOrEventHubNameOrOptions3?: + | CheckpointStore + | EventHubConsumerClientOptions + | string, checkpointStoreOrCredentialOrOptions4?: | CheckpointStore - | EventHubClientOptions + | EventHubConsumerClientOptions | TokenCredential, - checkpointStoreOrOptions5?: CheckpointStore | EventHubClientOptions, - options6?: EventHubClientOptions + checkpointStoreOrOptions5?: CheckpointStore | EventHubConsumerClientOptions, + options6?: EventHubConsumerClientOptions ) { + let eventHubConsumerClientOptions: EventHubConsumerClientOptions | undefined; if (isTokenCredential(checkpointStoreOrCredentialOrOptions4)) { // #3 or 3.1 logger.info("Creating EventHubConsumerClient with TokenCredential."); @@ -307,6 +321,11 @@ export class EventHubConsumerClient { this._clientOptions ); } + this._loadBalancingOptions = this._clientOptions?.loadBalancingOptions ?? { + strategy: "balanced", + updateIntervalInMs: 10000, + partitionOwnershipExpirationIntervalInMs: 60000 + }; } /** diff --git a/sdk/eventhub/event-hubs/src/index.ts b/sdk/eventhub/event-hubs/src/index.ts index c5aba75bbfc3..b54f4f6ce75d 100644 --- a/sdk/eventhub/event-hubs/src/index.ts +++ b/sdk/eventhub/event-hubs/src/index.ts @@ -9,11 +9,13 @@ export { LastEnqueuedEventProperties } from "./eventHubReceiver"; export { OperationOptions } from "./util/operationOptions"; export { EventHubClientOptions, + EventHubConsumerClientOptions, + LoadBalancingOptions, SendBatchOptions, CreateBatchOptions, GetPartitionIdsOptions, GetPartitionPropertiesOptions, - GetEventHubPropertiesOptions + GetEventHubPropertiesOptions, } from "./models/public"; export { EventHubConsumerClient } from "./eventHubConsumerClient"; export { EventHubProducerClient } from "./eventHubProducerClient"; @@ -25,7 +27,7 @@ export { ProcessErrorHandler, ProcessInitializeHandler, ProcessCloseHandler, - ProcessEventsHandler + ProcessEventsHandler, } from "./eventHubConsumerClientModels"; export { EventPosition, latestEventPosition, earliestEventPosition } from "./eventPosition"; export { PartitionProperties, EventHubProperties } from "./managementClient"; diff --git a/sdk/eventhub/event-hubs/src/models/public.ts b/sdk/eventhub/event-hubs/src/models/public.ts index d761d5e42562..4ce493fd7c33 100644 --- a/sdk/eventhub/event-hubs/src/models/public.ts +++ b/sdk/eventhub/event-hubs/src/models/public.ts @@ -85,7 +85,7 @@ export enum CloseReason { /** * The EventProcessor was shutdown. */ - Shutdown = "Shutdown" + Shutdown = "Shutdown", } /** @@ -128,6 +128,57 @@ export interface EventHubClientOptions { userAgent?: string; } +/** + * Describes the options that can be provided while creating the EventHubConsumerClient. + */ +export interface EventHubConsumerClientOptions extends EventHubClientOptions { + /** + * An options bag to configure load balancing settings. + * - `loadBalancingOptions`: Options to tune how the EventHubConsumerClient claims partitions. + * - `userAgent` : A string to append to the built in user agent string that is passed as a connection property + * to the service. + * - `webSocketOptions` : Options to configure the channelling of the AMQP connection over Web Sockets. + * - `websocket` : The WebSocket constructor used to create an AMQP connection if you choose to make the connection + * over a WebSocket. + * - `webSocketConstructorOptions` : Options to pass to the Websocket constructor when you choose to make the connection + * over a WebSocket. + * - `retryOptions` : The retry options for all the operations on the EventHubConsumerClient. + * A simple usage can be `{ "maxRetries": 4 }`. + * + * Example usage: + * ```js + * { + * retryOptions: { + * maxRetries: 4 + * } + * } + * ``` + */ + loadBalancingOptions?: LoadBalancingOptions; +} + +/** + * An options bag to configure load balancing settings. + */ +export interface LoadBalancingOptions { + /** + * Whether to apply a greedy or a more balanced approach when + * claiming partitions. + * Default: balanced + */ + strategy?: "balanced" | "greedy"; + /** + * The length of time between attempts to claim partitions. + * Default: 10000 + */ + updateIntervalInMs?: number; + /** + * The length of time a partition claim is valid. + * Default: 60000 + */ + partitionOwnershipExpirationIntervalInMs?: number; +} + /** * Options to configure the `createBatch` method on the `EventHubProducerClient`. * - `partitionKey` : A value that is hashed to produce a partition assignment. From daa5920ecb01ab6ddbff1a4b825778323cdc1874 Mon Sep 17 00:00:00 2001 From: chradek Date: Mon, 22 Jun 2020 18:19:49 -0700 Subject: [PATCH 02/28] fix conflicts from rebase --- sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts b/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts index bfc3e11ee0c0..a9c58ddf29e1 100644 --- a/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts +++ b/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts @@ -59,7 +59,7 @@ export class EventHubConsumerClient { /** * The options passed by the user when creating the EventHubClient instance. */ - private _clientOptions: EventHubClientOptions; + private _clientOptions: EventHubConsumerClientOptions; private _partitionGate = new PartitionGate(); private _id = uuid(); @@ -255,7 +255,6 @@ export class EventHubConsumerClient { checkpointStoreOrOptions5?: CheckpointStore | EventHubConsumerClientOptions, options6?: EventHubConsumerClientOptions ) { - let eventHubConsumerClientOptions: EventHubConsumerClientOptions | undefined; if (isTokenCredential(checkpointStoreOrCredentialOrOptions4)) { // #3 or 3.1 logger.info("Creating EventHubConsumerClient with TokenCredential."); @@ -285,7 +284,7 @@ export class EventHubConsumerClient { // 2.1 this._checkpointStore = checkpointStoreOrCredentialOrOptions4; this._userChoseCheckpointStore = true; - this._clientOptions = (checkpointStoreOrOptions5 as EventHubClientOptions) || {}; + this._clientOptions = (checkpointStoreOrOptions5 as EventHubConsumerClientOptions) || {}; } else { // 2 this._checkpointStore = new InMemoryCheckpointStore(); @@ -307,13 +306,13 @@ export class EventHubConsumerClient { this._checkpointStore = checkpointStoreOrEventHubNameOrOptions3; this._userChoseCheckpointStore = true; this._clientOptions = - (checkpointStoreOrCredentialOrOptions4 as EventHubClientOptions) || {}; + (checkpointStoreOrCredentialOrOptions4 as EventHubConsumerClientOptions) || {}; } else { // 1 this._checkpointStore = new InMemoryCheckpointStore(); this._userChoseCheckpointStore = false; this._clientOptions = - (checkpointStoreOrEventHubNameOrOptions3 as EventHubClientOptions) || {}; + (checkpointStoreOrEventHubNameOrOptions3 as EventHubConsumerClientOptions) || {}; } this._context = createConnectionContext( From 6b1e02912a2e78876a9849e86bcdda4a79070ea3 Mon Sep 17 00:00:00 2001 From: chradek Date: Tue, 23 Jun 2020 10:37:12 -0700 Subject: [PATCH 03/28] refactor load balancer to get all claimable partitions --- .../loadBalancerStrategy.ts | 364 ++++++++++++++++++ 1 file changed, 364 insertions(+) create mode 100644 sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancerStrategy.ts diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancerStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancerStrategy.ts new file mode 100644 index 000000000000..36580fcdb08c --- /dev/null +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancerStrategy.ts @@ -0,0 +1,364 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { PartitionOwnership } from "../eventProcessor"; +import { logger } from "../log"; + +export interface LoadBalancerStrategy { + /** + * Implements load balancing by taking into account current ownership and + * the new set of partitions to add. + * @param ownerId The id we should assume is _our_ id when checking for ownership. + * @param claimedPartitionOwnershipMap The current claimed ownerships for partitions. + * @param partitionIds Partitions to assign owners to. + * @returns Partition ids to claim. + */ + identifyPartitionsToClaim( + ownerId: string, + claimedPartitionOwnershipMap: Map, + partitionIds: string[] + ): string[]; +} + +/** + * Counts of the EventProcessors that currently own partitions. + * @internal + * @ignore + */ +interface EventProcessorCounts { + /** + * The # of EventProcessors that only own the required # of + * partitions. + */ + haveRequiredPartitions: number; + /** + * The # of EventProcessors that currently own the required # + * of partitions + 1 additional (ie, handling the case where + * the number of partitions is not evenly divisible by the # of + * EventProcessors). + */ + haveAdditionalPartition: number; + /** + * EventProcessors which have more than the required or even required + 1 + * number of partitions. These will eventually be downsized by other + * EventProcessors as they acquire their required number of partitions. + */ + haveTooManyPartitions: number; +} + +/** + * This method will create a new map of partition id and PartitionOwnership containing only those partitions + * that are actively owned. + * All entries in the original map that haven't been modified for a duration of time greater than the allowed + * inactivity time limit are assumed to be owned by dead event processors. + * These will not be included in the map returned by this method. + * + * @param partitionOwnershipMap The existing PartitionOwnerships mapped by partition. + * @param expirationIntervalInMs The length of time a PartitionOwnership claim is valid. + * @ignore + * @internal + */ +export function getActivePartitionOwnerships( + partitionOwnershipMap: Map, + expirationIntervalInMs: number +): Map { + const activePartitionOwnershipMap: Map = new Map(); + partitionOwnershipMap.forEach((partitionOwnership: PartitionOwnership, partitionId: string) => { + // If lastModifiedtimeInMs is missing, assume it is inactive. + if (!partitionOwnership.lastModifiedTimeInMs) { + return; + } + + const timeSincePartitionClaimed = Date.now() - partitionOwnership.lastModifiedTimeInMs; + if (timeSincePartitionClaimed < expirationIntervalInMs && partitionOwnership.ownerId) { + activePartitionOwnershipMap.set(partitionId, partitionOwnership); + } + }); + + return activePartitionOwnershipMap; +} + +export function calculateBalancedLoadCounts( + ownerToPartitionOwnershipMap: Map, + partitionIds: string[] +): { minPartitionsPerOwner: number; requiredNumberOfOwnersWithExtraPartition: number } { + // Calculate the minimum number of partitions every EventProcessor should own when the load + // is evenly distributed. + const minPartitionsPerOwner = Math.floor(partitionIds.length / ownerToPartitionOwnershipMap.size); + + // If the number of partitions in the Event Hub is not evenly divisible by the number of active + // EventProcesrrors, some EventProcessors may own 1 partition in addition to the minimum when the + // load is balanced. + // Calculate the number of EventProcessors that can own an additional partition. + const requiredNumberOfOwnersWithExtraPartition = + partitionIds.length % ownerToPartitionOwnershipMap.size; + + return { + minPartitionsPerOwner, + requiredNumberOfOwnersWithExtraPartition + }; +} + +/** + * Counts the EventProcessors and tallies them by type. + * + * To be in balance we need to make sure that each EventProcessor is only consuming + * their fair share. + * + * When the partitions are divvied up we will sometimes end up with some EventProcessors + * that will have 1 more partition than others. + * This can happen if the number of partitions is not evenly divisible by the number of EventProcessors. + * + * So this function largely exists to support isLoadBalanced() and + * shouldOwnMorePartitions(), both of which depend on knowing if our current list + * of EventProcessors is actually in the proper state. + * + * @param minPartitionsPerOwner The number of required partitions per EventProcessor. + * @param ownerToOwnershipMap The current ownerships for partitions. + * @internal + * @ignore + */ +export function getEventProcessorCounts( + minPartitionsPerOwner: number, + ownerToOwnershipMap: Map +): EventProcessorCounts { + const counts: EventProcessorCounts = { + haveRequiredPartitions: 0, + haveAdditionalPartition: 0, + haveTooManyPartitions: 0 + }; + + for (const ownershipList of ownerToOwnershipMap.values()) { + const numberOfPartitions = ownershipList.length; + + // there are basically three kinds of partition counts + // for a processor: + + // 1. Has _exactly_ the required number of partitions + if (numberOfPartitions === minPartitionsPerOwner) { + counts.haveRequiredPartitions++; + } + + // 2. Has the required number plus one extra (correct in cases) + // where the # of partitions is not evenly divisible by the + // number of processors. + if (numberOfPartitions === minPartitionsPerOwner + 1) { + counts.haveAdditionalPartition++; + } + + // 3. has more than the possible # of partitions required + if (numberOfPartitions > minPartitionsPerOwner + 1) { + counts.haveTooManyPartitions++; + } + } + + return counts; +} + +/** + * Validates that we are currently in a balanced state - all EventProcessors own the + * minimum required number of partitions (and additional partitions, if the # of partitions + * is not evenly divisible by the # of EventProcessors). + * + * @param requiredNumberOfOwnersWithExtraPartition The # of EventProcessors that process an additional partition, in addition to the required minimum. + * @param totalExpectedProcessors The total # of EventProcessors we expect. + * @param eventProcessorCounts EventProcessor counts, grouped by criteria. + * @ignore + * @internal + */ +export function isLoadBalanced( + requiredNumberOfOwnersWithExtraPartition: number, + totalExpectedEventProcessors: number, + { haveAdditionalPartition, haveRequiredPartitions }: EventProcessorCounts +): boolean { + return ( + haveAdditionalPartition === requiredNumberOfOwnersWithExtraPartition && + haveRequiredPartitions + haveAdditionalPartition === totalExpectedEventProcessors + ); +} + +/** + * Determines the number of new partitions to claim for this particular processor. + * + * @param minRequired The minimum required number of partitions. + * @param requiredNumberOfOwnersWithExtraPartition The current number of processors that should have an additional partition. + * @param numPartitionsOwnedByUs The number of partitions we currently own. + * @param eventProcessorCounts Processors, grouped by criteria. + * @ignore + * @internal + */ +export function getNumberOfPartitionsToClaim( + minRequiredPartitionCount: number, + requiredNumberOfOwnersWithExtraPartition: number, + numPartitionsOwnedByUs: number, + { haveAdditionalPartition, haveTooManyPartitions }: EventProcessorCounts +): number { + let actualRequiredPartitionCount = minRequiredPartitionCount; + + if ( + requiredNumberOfOwnersWithExtraPartition > 0 && + // Eventually the `haveTooManyPartitions` will decay into `haveAdditionalPartition` + // EventProcessors as partitions are balanced to consumers that aren't at par. + // We can consider them to be `haveAdditionalPartition` EventProcessors for our purposes. + haveAdditionalPartition + haveTooManyPartitions < requiredNumberOfOwnersWithExtraPartition + ) { + // Overall we don't have enough EventProcessors that are taking on an additional partition + // so we should attempt to. + actualRequiredPartitionCount = minRequiredPartitionCount + 1; + } + return numPartitionsOwnedByUs - actualRequiredPartitionCount; +} + +export function findPartitionsToSteal( + numberOfPartitionsToClaim: number, + minPartitionsPerOwner: number, + requiredNumberOfOwnersWithExtraPartition: number, + ourOwnerId: string, + ownerToOwnershipMap: Map +): string[] { + const partitionsToSteal: string[] = []; + // Create a list of PartitionOwnership lists that we can steal from. + const listOfPartitionOwnerships: Array = []; + ownerToOwnershipMap.forEach((partitionOwnerships, ownerId) => { + if (ownerId === ourOwnerId || partitionOwnerships.length <= minPartitionsPerOwner) return; + listOfPartitionOwnerships.push(partitionOwnerships); + }); + + // Sort the list in descending order based on the length of each element. + listOfPartitionOwnerships.sort((a, b) => { + if (a.length > b.length) return -1; + if (a.length < b.length) return 1; + return 0; + }); + + // Attempt to steal partitions from EventProcessors that have the most partitions 1st, + // then work our way down. + let ownersEncounteredWithExtraPartitions = 0; + let currentPartitionOwnershipList = listOfPartitionOwnerships.shift(); + while (numberOfPartitionsToClaim > 0 && currentPartitionOwnershipList) { + let ownersExpectedPartitionCount = minPartitionsPerOwner; + // Determine if the current owner should be allowed to have an extra partition. + if (ownersEncounteredWithExtraPartitions < requiredNumberOfOwnersWithExtraPartition) { + ownersExpectedPartitionCount++; + } + ownersEncounteredWithExtraPartitions++; + + let numberAvailableToSteal = + currentPartitionOwnershipList.length - ownersExpectedPartitionCount; + // Claim as many random partitions as possible. + while (Math.min(numberOfPartitionsToClaim, numberAvailableToSteal)) { + const indexToClaim = Math.floor(Math.random() * currentPartitionOwnershipList.length); + currentPartitionOwnershipList.push(currentPartitionOwnershipList.splice(indexToClaim, 1)[0]); + numberOfPartitionsToClaim--; + numberAvailableToSteal--; + } + + // Move on to the next list of PartitionOwnership. + currentPartitionOwnershipList = listOfPartitionOwnerships.shift(); + } + + return partitionsToSteal; +} + +export function identifyClaimablePartitions( + ownerId: string, + claimedPartitionOwnershipMap: Map, + partitionIds: string[], + expirationIntervalInMs: number +): string[] { + if (!partitionIds.length) { + return []; + } + + // Collect only the PartitionOwnership that have been updated within the expiration interval. + // Any PartitionOwnership that has been updated outside the expiration interval can be claimed. + const activePartitionOwnershipMap = getActivePartitionOwnerships( + claimedPartitionOwnershipMap, + expirationIntervalInMs + ); + logger.verbose( + `[${ownerId}] Number of active ownership records: ${activePartitionOwnershipMap.size}.` + ); + + if (activePartitionOwnershipMap.size === 0) { + // All partitions in this Event Hub are available to claim. + return partitionIds; + } + + // Map ownerIds to the partitions they own so that we can determine how many each owner has. + const ownerToOwnershipMap = new Map(); + for (const activeOwnership of activePartitionOwnershipMap.values()) { + const partitionOwnershipList = ownerToOwnershipMap.get(activeOwnership.ownerId) || []; + + partitionOwnershipList.push(activeOwnership); + ownerToOwnershipMap.set(activeOwnership.ownerId, partitionOwnershipList); + } + + // Add the current EventProcessor to the map of owners to ownerships if it doesn't exist. + if (!ownerToOwnershipMap.has(ownerId)) { + ownerToOwnershipMap.set(ownerId, []); + } + + logger.info(`[${ownerId}] Number of active event processors: ${ownerToOwnershipMap.size}.`); + + const { + minPartitionsPerOwner, + requiredNumberOfOwnersWithExtraPartition + } = calculateBalancedLoadCounts(ownerToOwnershipMap, partitionIds); + + logger.verbose( + `[${ownerId}] Expected minimum number of partitions per event processor: ${minPartitionsPerOwner},` + + `expected number of event processors with additional partition: ${requiredNumberOfOwnersWithExtraPartition}.` + ); + + // Get some stats representing the current state the world with regards to how balanced the + // partitions are across EventProcessors. + const eventProcessorCounts = getEventProcessorCounts(minPartitionsPerOwner, ownerToOwnershipMap); + + if ( + isLoadBalanced( + requiredNumberOfOwnersWithExtraPartition, + ownerToOwnershipMap.size, + eventProcessorCounts + ) + ) { + // When the partitions are evenly distributed, no change required. + return []; + } + + let numberOfPartitionsToClaim = getNumberOfPartitionsToClaim( + minPartitionsPerOwner, + requiredNumberOfOwnersWithExtraPartition, + ownerToOwnershipMap.get(ownerId)!.length, + eventProcessorCounts + ); + + if (numberOfPartitionsToClaim <= 0) { + return []; + } + + const partitionsToClaim: string[] = []; + const unclaimedPartitionIds = partitionIds.filter((id) => !activePartitionOwnershipMap.has(id)); + + // Prioritize getting unclaimed partitions first. + while (Math.min(numberOfPartitionsToClaim, unclaimedPartitionIds.length)) { + const indexToClaim = Math.floor(Math.random() * unclaimedPartitionIds.length); + partitionsToClaim.push(unclaimedPartitionIds.splice(indexToClaim, 1)[0]); + numberOfPartitionsToClaim--; + } + + if (numberOfPartitionsToClaim === 0) { + return partitionsToClaim; + } + + // Find partitions that can be stolen from other EventProcessors. + const partitionsToSteal = findPartitionsToSteal( + numberOfPartitionsToClaim, + minPartitionsPerOwner, + requiredNumberOfOwnersWithExtraPartition, + ownerId, + ownerToOwnershipMap + ); + + return partitionsToClaim.concat(partitionsToSteal); +} From 13cb66995abedf5fb59ab7956e71d124c055e8fb Mon Sep 17 00:00:00 2001 From: chradek Date: Tue, 23 Jun 2020 10:37:41 -0700 Subject: [PATCH 04/28] add balanced and greedy load balancer strategies --- .../balancedStrategy.ts | 42 +++++++++++++++++++ .../loadBalancerStrategies/greedyStrategy.ts | 39 +++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts create mode 100644 sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts new file mode 100644 index 000000000000..5a7bedcb877d --- /dev/null +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { PartitionOwnership } from "../eventProcessor"; +import { LoadBalancerStrategy, identifyClaimablePartitions } from "./loadBalancerStrategy"; + +/** + * @internal + * @ignore + */ +export class BalancedLoadBalancerStrategy implements LoadBalancerStrategy { + /** + * Creates an instance of BalancedLoadBalancerStrategy. + * + * @param _partitionOwnershipExpirationIntervalInMs The length of time a partition claim is valid. + */ + constructor(private readonly _partitionOwnershipExpirationIntervalInMs: number) {} + + /** + * Implements load balancing by taking into account current ownership and + * the new set of partitions to add. + * @param ourOwnerId The id we should assume is _our_ id when checking for ownership. + * @param claimedPartitionOwnershipMap The current claimed ownerships for partitions. + * @param partitionIds Partitions to assign owners to. + * @returns Partition ids to claim. + */ + public identifyPartitionsToClaim( + ourOwnerId: string, + claimedPartitionOwnershipMap: Map, + partitionIds: string[] + ): string[] { + const claimablePartitions = identifyClaimablePartitions( + ourOwnerId, + claimedPartitionOwnershipMap, + partitionIds, + this._partitionOwnershipExpirationIntervalInMs + ); + + const randomIndex = Math.floor(Math.random() * claimablePartitions.length); + return [claimablePartitions[randomIndex]]; + } +} diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts new file mode 100644 index 000000000000..78e680f1fffd --- /dev/null +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { PartitionOwnership } from "../eventProcessor"; +import { LoadBalancerStrategy, identifyClaimablePartitions } from "./loadBalancerStrategy"; + +/** + * @internal + * @ignore + */ +export class GreedyLoadBalancerStrategy implements LoadBalancerStrategy { + /** + * Creates an instance of BalancedLoadBalancerStrategy. + * + * @param _partitionOwnershipExpirationIntervalInMs The length of time a partition claim is valid. + */ + constructor(private readonly _partitionOwnershipExpirationIntervalInMs: number) {} + + /** + * Implements load balancing by taking into account current ownership and + * the new set of partitions to add. + * @param ourOwnerId The id we should assume is _our_ id when checking for ownership. + * @param claimedPartitionOwnershipMap The current claimed ownerships for partitions. + * @param partitionIds Partitions to assign owners to. + * @returns Partition ids to claim. + */ + public identifyPartitionsToClaim( + ourOwnerId: string, + claimedPartitionOwnershipMap: Map, + partitionIds: string[] + ): string[] { + return identifyClaimablePartitions( + ourOwnerId, + claimedPartitionOwnershipMap, + partitionIds, + this._partitionOwnershipExpirationIntervalInMs + ); + } +} From 5a0a56ebdf13a74d2639066a0008aa11b2574f38 Mon Sep 17 00:00:00 2001 From: chradek Date: Tue, 23 Jun 2020 13:31:24 -0700 Subject: [PATCH 05/28] add unbalancedLoadBalancingStrategy --- .../balancedStrategy.ts | 6 ++-- .../loadBalancerStrategies/greedyStrategy.ts | 6 ++-- ...erStrategy.ts => loadBalancingStrategy.ts} | 2 +- .../unbalancedStrategy.ts | 32 +++++++++++++++++++ 4 files changed, 39 insertions(+), 7 deletions(-) rename sdk/eventhub/event-hubs/src/loadBalancerStrategies/{loadBalancerStrategy.ts => loadBalancingStrategy.ts} (99%) create mode 100644 sdk/eventhub/event-hubs/src/loadBalancerStrategies/unbalancedStrategy.ts diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts index 5a7bedcb877d..a67df15e35b1 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts @@ -2,15 +2,15 @@ // Licensed under the MIT license. import { PartitionOwnership } from "../eventProcessor"; -import { LoadBalancerStrategy, identifyClaimablePartitions } from "./loadBalancerStrategy"; +import { LoadBalancingStrategy, identifyClaimablePartitions } from "./loadBalancingStrategy"; /** * @internal * @ignore */ -export class BalancedLoadBalancerStrategy implements LoadBalancerStrategy { +export class BalancedLoadBalancingStrategy implements LoadBalancingStrategy { /** - * Creates an instance of BalancedLoadBalancerStrategy. + * Creates an instance of BalancedLoadBalancingStrategy. * * @param _partitionOwnershipExpirationIntervalInMs The length of time a partition claim is valid. */ diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts index 78e680f1fffd..af7c4d1ae24e 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts @@ -2,15 +2,15 @@ // Licensed under the MIT license. import { PartitionOwnership } from "../eventProcessor"; -import { LoadBalancerStrategy, identifyClaimablePartitions } from "./loadBalancerStrategy"; +import { LoadBalancingStrategy, identifyClaimablePartitions } from "./loadBalancingStrategy"; /** * @internal * @ignore */ -export class GreedyLoadBalancerStrategy implements LoadBalancerStrategy { +export class GreedyLoadBalancingStrategy implements LoadBalancingStrategy { /** - * Creates an instance of BalancedLoadBalancerStrategy. + * Creates an instance of GreedyLoadBalancingStrategy. * * @param _partitionOwnershipExpirationIntervalInMs The length of time a partition claim is valid. */ diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancerStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts similarity index 99% rename from sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancerStrategy.ts rename to sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts index 36580fcdb08c..a767ea8473ea 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancerStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts @@ -4,7 +4,7 @@ import { PartitionOwnership } from "../eventProcessor"; import { logger } from "../log"; -export interface LoadBalancerStrategy { +export interface LoadBalancingStrategy { /** * Implements load balancing by taking into account current ownership and * the new set of partitions to add. diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/unbalancedStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/unbalancedStrategy.ts new file mode 100644 index 000000000000..7f8d90f70c47 --- /dev/null +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/unbalancedStrategy.ts @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { PartitionOwnership } from "../eventProcessor"; +import { LoadBalancingStrategy } from "./loadBalancingStrategy"; + +/** + * @internal + * @ignore + */ +export class UnbalancedLoadBalancingStrategy implements LoadBalancingStrategy { + /** + * Creates an instance of UnbalancedLoadBalancingStrategy. + */ + constructor() {} + + /** + * Implements load balancing by taking into account current ownership and + * the new set of partitions to add. + * @param ourOwnerId The id we should assume is _our_ id when checking for ownership. + * @param claimedPartitionOwnershipMap The current claimed ownerships for partitions. + * @param partitionIds Partitions to assign owners to. + * @returns Partition ids to claim. + */ + public identifyPartitionsToClaim( + _ourOwnerId: string, + _claimedPartitionOwnershipMap: Map, + partitionIds: string[] + ): string[] { + return partitionIds; + } +} From aed400663e5a0151ae5695c4920514acca70d8ad Mon Sep 17 00:00:00 2001 From: chradek Date: Tue, 23 Jun 2020 13:31:52 -0700 Subject: [PATCH 06/28] update pumpManager to expose receivingFromPartitions --- sdk/eventhub/event-hubs/src/pumpManager.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sdk/eventhub/event-hubs/src/pumpManager.ts b/sdk/eventhub/event-hubs/src/pumpManager.ts index 1471e4a435b4..2c4d9a37e84a 100644 --- a/sdk/eventhub/event-hubs/src/pumpManager.ts +++ b/sdk/eventhub/event-hubs/src/pumpManager.ts @@ -41,6 +41,13 @@ export interface PumpManager { */ isReceivingFromPartition(partitionId: string): boolean; + /** + * Returns a list of partition ids that the pump manager is actively receiving events from. + * @ignore + * @internal + */ + receivingFromPartitions(): string[]; + /** * Stops all PartitionPumps and removes them from the internal map. * @param reason The reason for removing the pump. From c3656e2b1baebc4cd49e29b40dae10ba82442537 Mon Sep 17 00:00:00 2001 From: chradek Date: Wed, 24 Jun 2020 15:43:57 -0700 Subject: [PATCH 07/28] updates EventProcessor to use LoadBalancingStrategies --- .../event-hubs/src/eventHubConsumerClient.ts | 38 +- sdk/eventhub/event-hubs/src/eventProcessor.ts | 186 +++--- .../balancedStrategy.ts | 12 +- .../loadBalancingStrategy.ts | 62 +- .../unbalancedStrategy.ts | 14 +- sdk/eventhub/event-hubs/src/models/private.ts | 13 +- .../event-hubs/src/partitionLoadBalancer.ts | 403 ------------ .../test/eventHubConsumerClient.spec.ts | 22 +- .../event-hubs/test/eventProcessor.spec.ts | 84 ++- .../event-hubs/test/loadBalancer.spec.ts | 310 --------- .../test/loadBalancingStrategy.spec.ts | 593 ++++++++++++++++++ 11 files changed, 874 insertions(+), 863 deletions(-) delete mode 100644 sdk/eventhub/event-hubs/src/partitionLoadBalancer.ts delete mode 100644 sdk/eventhub/event-hubs/test/loadBalancer.spec.ts create mode 100644 sdk/eventhub/event-hubs/test/loadBalancingStrategy.spec.ts diff --git a/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts b/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts index a9c58ddf29e1..d6d2a93ac7f8 100644 --- a/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts +++ b/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts @@ -11,7 +11,6 @@ import { } from "./models/public"; import { InMemoryCheckpointStore } from "./inMemoryCheckpointStore"; import { CheckpointStore, EventProcessor, FullEventProcessorOptions } from "./eventProcessor"; -import { GreedyPartitionLoadBalancer } from "./partitionLoadBalancer"; import { Constants, TokenCredential } from "@azure/core-amqp"; import { logger } from "./log"; @@ -25,6 +24,10 @@ import { EventHubProperties, PartitionProperties } from "./managementClient"; import { PartitionGate } from "./impl/partitionGate"; import { v4 as uuid } from "uuid"; import { validateEventPositions } from "./eventPosition"; +import { LoadBalancingStrategy } from "./loadBalancerStrategies/loadBalancingStrategy"; +import { UnbalancedLoadBalancingStrategy } from "./loadBalancerStrategies/unbalancedStrategy"; +import { GreedyLoadBalancingStrategy } from "./loadBalancerStrategies/greedyStrategy"; +import { BalancedLoadBalancingStrategy } from "./loadBalancerStrategies/balancedStrategy"; const defaultConsumerClientOptions: Required; private _abortController?: AbortController; - private _processingTarget: PartitionLoadBalancer | string; - private _loopIntervalInMs = 10000; - private _inactiveTimeLimitInMs = 60000; + /** + * A specific partition to target. + */ + private _processingTarget?: string; + /** + * Determines which partitions to claim as part of load balancing. + */ + private _loadBalancingStrategy: LoadBalancingStrategy; + /** + * The amount of time between load balancing attempts. + */ + private _loopIntervalInMs: number; private _eventHubName: string; private _fullyQualifiedNamespace: string; @@ -214,12 +231,9 @@ export class EventProcessor { this._processorOptions = options; this._pumpManager = options.pumpManager || new PumpManagerImpl(this._id, this._processorOptions); - const inactiveTimeLimitInMS = options.inactiveTimeLimitInMs || this._inactiveTimeLimitInMs; - this._processingTarget = - options.processingTarget || new FairPartitionLoadBalancer(inactiveTimeLimitInMS); - if (options.loopIntervalInMs) { - this._loopIntervalInMs = options.loopIntervalInMs; - } + this._processingTarget = options.processingTarget; + this._loopIntervalInMs = options.loopIntervalInMs; + this._loadBalancingStrategy = options.loadBalancingStrategy; } /** @@ -383,11 +397,10 @@ export class EventProcessor { * When a new partition is claimed, this method is also responsible for starting a partition pump that creates an * EventHubConsumer for processing events from that partition. */ - private async _runLoopWithLoadBalancing( - loadBalancer: PartitionLoadBalancer, + loadBalancingStrategy: LoadBalancingStrategy, abortSignal: AbortSignalLike - ): Promise { + ) { let cancelLoopResolver; // This provides a mechanism for exiting the loop early // if the subscription has had `close` called. @@ -400,73 +413,34 @@ export class EventProcessor { abortSignal.addEventListener("abort", resolve); }); - // periodically check if there is any partition not being processed and process it + // Periodically check if any partitions need to be claimed and claim them. while (!abortSignal.aborted) { + const iterationStartTimeInMs = Date.now(); try { - const partitionOwnershipMap: Map = new Map(); - // Retrieve current partition ownership details from the datastore. - const partitionOwnership = await this._checkpointStore.listOwnership( - this._fullyQualifiedNamespace, - this._eventHubName, - this._consumerGroup - ); - - const abandonedMap: Map = new Map(); - - for (const ownership of partitionOwnership) { - if (isAbandoned(ownership)) { - abandonedMap.set(ownership.partitionId, ownership); - continue; - } - - partitionOwnershipMap.set(ownership.partitionId, ownership); - } const { partitionIds } = await this._context.managementSession!.getEventHubProperties({ abortSignal: abortSignal }); - if (abortSignal.aborted) { - return; - } - - if (partitionIds.length > 0) { - const partitionsToClaim = loadBalancer.loadBalance( - this._id, - partitionOwnershipMap, - partitionIds - ); - if (partitionsToClaim) { - for (const partitionToClaim of partitionsToClaim) { - let ownershipRequest: PartitionOwnership; - - if (abandonedMap.has(partitionToClaim)) { - ownershipRequest = this._createPartitionOwnershipRequest( - abandonedMap, - partitionToClaim - ); - } else { - ownershipRequest = this._createPartitionOwnershipRequest( - partitionOwnershipMap, - partitionToClaim - ); - } - - await this._claimOwnership(ownershipRequest, abortSignal); - } - } - } + // Renew the EventProcessor's partition ownerships that are still active. + // await this._renewPartitionOwnership(abortSignal); + + await this._performLoadBalancing(loadBalancingStrategy, partitionIds, abortSignal); } catch (err) { - logger.warning(`[${this._id}] An error occured within the EventProcessor loop: ${err}`); + logger.warning( + `[${this._id}] An error occurred within the EventProcessor loop: ${err?.message}` + ); logErrorStackTrace(err); // Protect against the scenario where the user awaits on subscription.close() from inside processError. await Promise.race([this._handleSubscriptionError(err), cancelLoopPromise]); } finally { - // sleep for some time, then continue the loop again. + // Sleep for some time, then continue the loop. + const iterationDeltaInMs = Date.now() - iterationStartTimeInMs; + const delayDurationInMs = Math.max(this._loopIntervalInMs - iterationDeltaInMs, 0); logger.verbose( - `[${this._id}] Pausing the EventProcessor loop for ${this._loopIntervalInMs} ms.` + `[${this._id}] Pausing the EventProcessor loop for ${delayDurationInMs} ms.` ); - // swallow the error since it's fine to exit early from delay - await delayWithoutThrow(this._loopIntervalInMs, abortSignal); + // Swallow the error since it's fine to exit early from the delay. + await delayWithoutThrow(delayDurationInMs, abortSignal); } } @@ -476,6 +450,74 @@ export class EventProcessor { this._isRunning = false; } + private async _performLoadBalancing( + loadBalancingStrategy: LoadBalancingStrategy, + partitionIds: string[], + abortSignal: AbortSignalLike + ) { + if (abortSignal.aborted) return; + + // Retrieve current partition ownership details from the datastore. + const partitionOwnership = await this._checkpointStore.listOwnership( + this._fullyQualifiedNamespace, + this._eventHubName, + this._consumerGroup + ); + + if (abortSignal.aborted) return; + + const partitionOwnershipMap = new Map(); + const abandonedPartitionOwnershipMap = new Map(); + const partitionsToRenew: string[] = []; + + // Separate abandoned ownerships from claimed ownerships. + // We only want to pass active partition ownerships to the + // load balancer, but we need to hold onto the abandoned + // partition ownerships because we need the etag to claim them. + for (const ownership of partitionOwnership) { + if (isAbandoned(ownership)) { + abandonedPartitionOwnershipMap.set(ownership.partitionId, ownership); + } else { + partitionOwnershipMap.set(ownership.partitionId, ownership); + } + if ( + ownership.ownerId === this._id && + this._pumpManager.isReceivingFromPartition(ownership.partitionId) + ) { + partitionsToRenew.push(ownership.partitionId); + } + } + + // Pass the list of all the partition ids and the collection of claimed partition ownerships + // to the load balance strategy. + // We exclude the abandoned partition ownerships to simplify the load balancing logic. + const partitionsToClaim = loadBalancingStrategy.identifyPartitionsToClaim( + this._id, + partitionOwnershipMap, + partitionIds + ); + partitionsToClaim.push(...partitionsToRenew); + + const uniquePartitionsToClaim = new Set(partitionsToClaim); + for (const partitionToClaim of uniquePartitionsToClaim) { + let partitionOwnershipRequest: PartitionOwnership; + + if (abandonedPartitionOwnershipMap.has(partitionToClaim)) { + partitionOwnershipRequest = this._createPartitionOwnershipRequest( + abandonedPartitionOwnershipMap, + partitionToClaim + ); + } else { + partitionOwnershipRequest = this._createPartitionOwnershipRequest( + partitionOwnershipMap, + partitionToClaim + ); + } + + await this._claimOwnership(partitionOwnershipRequest, abortSignal); + } + } + /** * This is called when there are errors that are not specific to a partition (ex: load balancing) */ @@ -523,7 +565,7 @@ export class EventProcessor { this._abortController = new AbortController(); logger.verbose(`[${this._id}] Starting an EventProcessor.`); - if (targetWithoutOwnership(this._processingTarget)) { + if (this._processingTarget) { logger.verbose(`[${this._id}] Single partition target: ${this._processingTarget}`); this._loopTask = this._runLoopForSinglePartition( this._processingTarget, @@ -532,7 +574,7 @@ export class EventProcessor { } else { logger.verbose(`[${this._id}] Multiple partitions, using load balancer`); this._loopTask = this._runLoopWithLoadBalancing( - this._processingTarget, + this._loadBalancingStrategy, this._abortController.signal ); } @@ -571,7 +613,7 @@ export class EventProcessor { logger.verbose(`[${this._id}] EventProcessor stopped.`); } - if (targetWithoutOwnership(this._processingTarget)) { + if (this._processingTarget) { logger.verbose(`[${this._id}] No partitions owned, skipping abandoning.`); } else { await this.abandonPartitionOwnerships(); @@ -620,7 +662,3 @@ function getStartPosition( return startPosition; } - -function targetWithoutOwnership(target: PartitionLoadBalancer | string): target is string { - return typeof target === "string"; -} diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts index a67df15e35b1..bb3fef0519b2 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts @@ -5,6 +5,12 @@ import { PartitionOwnership } from "../eventProcessor"; import { LoadBalancingStrategy, identifyClaimablePartitions } from "./loadBalancingStrategy"; /** + * The BalancedLoadBalancerStrategy is meant to be used when the user + * wants to reach a load balanced state with less partition 'thrashing'. + * + * Partition trashing - where a partition changes owners - is minimized + * by only returning a single partition to claim at a time. + * This minimizes the number of times a partition should need to be stolen. * @internal * @ignore */ @@ -18,7 +24,7 @@ export class BalancedLoadBalancingStrategy implements LoadBalancingStrategy { /** * Implements load balancing by taking into account current ownership and - * the new set of partitions to add. + * the full set of partitions in the Event Hub. * @param ourOwnerId The id we should assume is _our_ id when checking for ownership. * @param claimedPartitionOwnershipMap The current claimed ownerships for partitions. * @param partitionIds Partitions to assign owners to. @@ -36,6 +42,10 @@ export class BalancedLoadBalancingStrategy implements LoadBalancingStrategy { this._partitionOwnershipExpirationIntervalInMs ); + if (!claimablePartitions.length) { + return []; + } + const randomIndex = Math.floor(Math.random() * claimablePartitions.length); return [claimablePartitions[randomIndex]]; } diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts index a767ea8473ea..17996705077d 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts @@ -4,11 +4,16 @@ import { PartitionOwnership } from "../eventProcessor"; import { logger } from "../log"; +/** + * Determines which partitions to claim as part of load balancing. + * @internal + * @ignore + */ export interface LoadBalancingStrategy { /** * Implements load balancing by taking into account current ownership and - * the new set of partitions to add. - * @param ownerId The id we should assume is _our_ id when checking for ownership. + * the full set of partitions in the Event Hub. + * @param ourOwnerId The id we should assume is _our_ id when checking for ownership. * @param claimedPartitionOwnershipMap The current claimed ownerships for partitions. * @param partitionIds Partitions to assign owners to. * @returns Partition ids to claim. @@ -58,7 +63,7 @@ interface EventProcessorCounts { * @ignore * @internal */ -export function getActivePartitionOwnerships( +function getActivePartitionOwnerships( partitionOwnershipMap: Map, expirationIntervalInMs: number ): Map { @@ -78,20 +83,27 @@ export function getActivePartitionOwnerships( return activePartitionOwnershipMap; } -export function calculateBalancedLoadCounts( - ownerToPartitionOwnershipMap: Map, +/** + * Calculates the minimum number of partitions each EventProcessor should own, + * and the number of EventProcessors that should have an extra partition assigned. + * @param ownerToOwnershipMap The current ownerships for partitions. + * @param partitionIds The full list of the Event Hub's partition ids. + * @ignore + * @internal + */ +function calculateBalancedLoadCounts( + ownerToOwnershipMap: Map, partitionIds: string[] ): { minPartitionsPerOwner: number; requiredNumberOfOwnersWithExtraPartition: number } { // Calculate the minimum number of partitions every EventProcessor should own when the load // is evenly distributed. - const minPartitionsPerOwner = Math.floor(partitionIds.length / ownerToPartitionOwnershipMap.size); + const minPartitionsPerOwner = Math.floor(partitionIds.length / ownerToOwnershipMap.size); // If the number of partitions in the Event Hub is not evenly divisible by the number of active // EventProcesrrors, some EventProcessors may own 1 partition in addition to the minimum when the // load is balanced. // Calculate the number of EventProcessors that can own an additional partition. - const requiredNumberOfOwnersWithExtraPartition = - partitionIds.length % ownerToPartitionOwnershipMap.size; + const requiredNumberOfOwnersWithExtraPartition = partitionIds.length % ownerToOwnershipMap.size; return { minPartitionsPerOwner, @@ -118,7 +130,7 @@ export function calculateBalancedLoadCounts( * @internal * @ignore */ -export function getEventProcessorCounts( +function getEventProcessorCounts( minPartitionsPerOwner: number, ownerToOwnershipMap: Map ): EventProcessorCounts { @@ -166,7 +178,7 @@ export function getEventProcessorCounts( * @ignore * @internal */ -export function isLoadBalanced( +function isLoadBalanced( requiredNumberOfOwnersWithExtraPartition: number, totalExpectedEventProcessors: number, { haveAdditionalPartition, haveRequiredPartitions }: EventProcessorCounts @@ -187,7 +199,7 @@ export function isLoadBalanced( * @ignore * @internal */ -export function getNumberOfPartitionsToClaim( +function getNumberOfPartitionsToClaim( minRequiredPartitionCount: number, requiredNumberOfOwnersWithExtraPartition: number, numPartitionsOwnedByUs: number, @@ -206,10 +218,21 @@ export function getNumberOfPartitionsToClaim( // so we should attempt to. actualRequiredPartitionCount = minRequiredPartitionCount + 1; } - return numPartitionsOwnedByUs - actualRequiredPartitionCount; + return actualRequiredPartitionCount - numPartitionsOwnedByUs; } -export function findPartitionsToSteal( +/** + * Determines which partitions can be stolen from other owners while maintaining + * a balanced state. + * @param numberOfPartitionsToClaim The number of partitions the owner needs to claim to reach a balanced state. + * @param minPartitionsPerOwner The minimum number of partitions each owner needs for the partition load to be balanced. + * @param requiredNumberOfOwnersWithExtraPartition The number of owners that should have 1 extra partition. + * @param ourOwnerId The id of _our_ owner. + * @param ownerToOwnershipMap The current ownerships for partitions. + * @internal + * @ignore + */ +function findPartitionsToSteal( numberOfPartitionsToClaim: number, minPartitionsPerOwner: number, requiredNumberOfOwnersWithExtraPartition: number, @@ -248,7 +271,7 @@ export function findPartitionsToSteal( // Claim as many random partitions as possible. while (Math.min(numberOfPartitionsToClaim, numberAvailableToSteal)) { const indexToClaim = Math.floor(Math.random() * currentPartitionOwnershipList.length); - currentPartitionOwnershipList.push(currentPartitionOwnershipList.splice(indexToClaim, 1)[0]); + partitionsToSteal.push(currentPartitionOwnershipList.splice(indexToClaim, 1)[0].partitionId); numberOfPartitionsToClaim--; numberAvailableToSteal--; } @@ -260,6 +283,17 @@ export function findPartitionsToSteal( return partitionsToSteal; } +/** + * Identifies all of the partitions that can be claimed by the specified owner for + * that owner to reach a balanced state. + * @param OwnerId The id we should assume is _our_ id when checking for ownership. + * @param claimedPartitionOwnershipMap The current claimed ownerships for partitions. + * @param partitionIds Partitions to assign owners to. + * @param expirationIntervalInMs The length of time a partition claim is valid. + * @returns Partition ids that may be claimed. + * @internal + * @ignore + */ export function identifyClaimablePartitions( ownerId: string, claimedPartitionOwnershipMap: Map, diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/unbalancedStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/unbalancedStrategy.ts index 7f8d90f70c47..580c0503a9af 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/unbalancedStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/unbalancedStrategy.ts @@ -5,20 +5,18 @@ import { PartitionOwnership } from "../eventProcessor"; import { LoadBalancingStrategy } from "./loadBalancingStrategy"; /** + * The UnbalancedLoadBalancingStrategy does no actual load balancing. + * It is intended to be used when you want to avoid load balancing + * and consume a set of partitions. * @internal * @ignore */ export class UnbalancedLoadBalancingStrategy implements LoadBalancingStrategy { - /** - * Creates an instance of UnbalancedLoadBalancingStrategy. - */ - constructor() {} - /** * Implements load balancing by taking into account current ownership and - * the new set of partitions to add. - * @param ourOwnerId The id we should assume is _our_ id when checking for ownership. - * @param claimedPartitionOwnershipMap The current claimed ownerships for partitions. + * the full set of partitions in the Event Hub. + * @param _ourOwnerId The id we should assume is _our_ id when checking for ownership. + * @param _claimedPartitionOwnershipMap The current claimed ownerships for partitions. * @param partitionIds Partitions to assign owners to. * @returns Partition ids to claim. */ diff --git a/sdk/eventhub/event-hubs/src/models/private.ts b/sdk/eventhub/event-hubs/src/models/private.ts index af05c2e35f2d..2288397adcd3 100644 --- a/sdk/eventhub/event-hubs/src/models/private.ts +++ b/sdk/eventhub/event-hubs/src/models/private.ts @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { PartitionLoadBalancer } from "../partitionLoadBalancer"; import { RetryOptions } from "@azure/core-amqp"; import { SubscribeOptions } from "../eventHubConsumerClientModels"; +import { LoadBalancingStrategy } from "../loadBalancerStrategies/loadBalancingStrategy"; /** * The set of options to configure the behavior of an `EventHubProducer`. @@ -40,7 +40,7 @@ export type OperationNames = "getEventHubProperties" | "getPartitionIds" | "getP * @internal * @ignore */ -export interface CommonEventProcessorOptions // make the 'maxBatchSize', 'maxWaitTimeInSeconds', 'ownerLevel' fields required extends // for our internal classes (these are optional for external users) +export interface CommonEventProcessorOptions // make the 'maxBatchSize', 'maxWaitTimeInSeconds', 'ownerLevel' fields required extends for our internal classes (these are optional for external users) extends Required>, Pick< SubscribeOptions, @@ -51,14 +51,9 @@ export interface CommonEventProcessorOptions // make the 'maxBatchSize', 'maxWa > > { /** - * The amount of time to wait between each attempt at claiming partitions. + * A load balancing strategy that determines how to claim partitions. */ - loopIntervalInMs?: number; - - /** - * A load balancer to use to find targets or a specific partition to target. - */ - processingTarget?: PartitionLoadBalancer | string; + loadBalancingStrategy: LoadBalancingStrategy; /** * An optional ownerId to use rather than using an internally generated ID diff --git a/sdk/eventhub/event-hubs/src/partitionLoadBalancer.ts b/sdk/eventhub/event-hubs/src/partitionLoadBalancer.ts deleted file mode 100644 index 65d608dbd410..000000000000 --- a/sdk/eventhub/event-hubs/src/partitionLoadBalancer.ts +++ /dev/null @@ -1,403 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { PartitionOwnership } from "./eventProcessor"; -import { logger } from "./log"; - -/** - * Implements a load balancing algorithm for determining which consumers - * own which partitions. - * @ignore - * @internal - */ -export interface PartitionLoadBalancer { - /** - * Implements load balancing by taking into account current ownership and - * the new set of partitions to add. - * @param ownerId The id we should assume is _our_ id when checking for ownership. - * @param partitionOwnershipMap The current ownerships for partitions. - * @param partitionsToAdd New partitions to assign owners to. - * @returns Partition ids to claim. - */ - loadBalance( - ownerId: string, - partitionOwnershipMap: Map, - partitionsToAdd: string[] - ): string[]; -} - -/** - * This class does no load balancing - it's intended to be used when - * you want to avoid load balancing and consume a set of partitions (or all - * available partitions) - * @internal - * @ignore - */ -export class GreedyPartitionLoadBalancer implements PartitionLoadBalancer { - private partitionsToClaim?: Set; - - /** - * @param partitionIds An optional set of partition IDs. undefined means all partitions. - */ - constructor(partitionIds?: string[]) { - logger.verbose( - `GreedyPartitionLoadBalancer created. Watching ${ - partitionIds ? "(" + partitionIds.join(",") + ")" : "all" - }.` - ); - this.partitionsToClaim = partitionIds && new Set(partitionIds); - } - - loadBalance( - _ownerId: string, - _partitionOwnershipMap: Map, - partitionsToAdd: string[] - ): string[] { - let potential: string[] = partitionsToAdd; - - if (this.partitionsToClaim) { - const partitionsToClaim = this.partitionsToClaim; - potential = partitionsToAdd.filter((part) => partitionsToClaim.has(part)); - } - - return potential; - } -} - -/** - * This class is responsible for balancing the load of processing events from all partitions of an Event Hub by - * distributing the number of partitions uniformly among all the active EventProcessors. - * - * This load balancer will retrieve partition ownership details from the CheckpointStore to find the number of - * active EventProcessor. It uses the last modified time to decide if an EventProcessor is active. If a - * partition ownership entry has not be updated for a specified duration of time, the owner of that partition is - * considered inactive and the partition is available for other EventProcessors to own. - * @class PartitionLoadBalancer - * @internal - * @ignore - */ -export class FairPartitionLoadBalancer implements PartitionLoadBalancer { - private _inactiveTimeLimitInMS: number; - - /** - * Creates an instance of PartitionBasedLoadBalancer. - * - * @param ownerId The identifier of the Event Processor that owns this load balancer. - * @param inactiveTimeLimitInMS The time to wait for an update on an ownership record before - * assuming the owner of the partition is inactive. - * */ - constructor(inactiveTimeLimitInMS: number) { - logger.verbose( - `FairPartitionLoadBalancer created inactive time limit: ${inactiveTimeLimitInMS}ms` - ); - this._inactiveTimeLimitInMS = inactiveTimeLimitInMS; - } - - /* - * Find the event processor that owns the maximum number of partitions and steal a random partition - * from it. - */ - private _findPartitionToSteal( - ourOwnerId: string, - ownerPartitionMap: Map - ): string { - let maxList: PartitionOwnership[] = []; - let maxPartitionsOwnedByAnyEventProcessor = Number.MIN_VALUE; - let ownerId; - ownerPartitionMap.forEach((ownershipList: PartitionOwnership[], ownerId: string) => { - if (ownershipList.length > maxPartitionsOwnedByAnyEventProcessor) { - maxPartitionsOwnedByAnyEventProcessor = ownershipList.length; - maxList = ownershipList; - ownerId = ownerId; - } - }); - logger.verbose( - `[${ourOwnerId}] Owner id ${ownerId} owns ${maxList.length} partitions, stealing a partition from it.` - ); - return maxList[Math.floor(Math.random() * maxList.length)].partitionId; - } - - /** - * Whether we should attempt to claim more partitions for this particular processor. - * - * @param minRequired The minimum required number of partitions. - * @param numEventProcessorsWithAdditionalPartition The current number of processors that have an additional partition. - * @param numPartitionsOwnedByUs The number of partitions we currently own. - * @param processorCounts Processors, grouped by criteria. - */ - private _shouldOwnMorePartitions( - minRequired: number, - numEventProcessorsWithAdditionalPartition: number, - numPartitionsOwnedByUs: number, - processorCounts: ProcessorCounts - ): boolean { - let actualRequired = minRequired; - - if ( - numEventProcessorsWithAdditionalPartition > 0 && - // eventually the `haveTooManyPartitions` will get decay into `haveAdditionalPartition` - // processors as partitions are balanced to consumers that aren't at par. We can - // consider them to be `haveAdditionalPartition` processors for our purposes. - processorCounts.haveAdditionalPartition + processorCounts.haveTooManyPartitions < - numEventProcessorsWithAdditionalPartition - ) { - // overall we don't have enough processors that are taking on an additional partition - // so we should attempt to. - actualRequired = minRequired + 1; - } - - return numPartitionsOwnedByUs < actualRequired; - } - - /** - * Validates that we are currently in a balanced state - all processors own the - * minimum required number of partitions (and additional partitions, if the # of partitions - * is not evenly divisible by the # of processors). - * - * @param requiredNumberOfEventProcessorsWithAdditionalPartition The # of processors that process an additional partition, in addition to the required minimum. - * @param totalExpectedProcessors The total # of processors we expect. - * @param processorCounts Processors, grouped by criteria. - */ - private _isLoadBalanced( - requiredNumberOfEventProcessorsWithAdditionalPartition: number, - totalExpectedProcessors: number, - processorCounts: ProcessorCounts - ): boolean { - return ( - processorCounts.haveAdditionalPartition === - requiredNumberOfEventProcessorsWithAdditionalPartition && - processorCounts.haveRequiredPartitions + processorCounts.haveAdditionalPartition === - totalExpectedProcessors - ); - } - - /** - * Counts the processors and tallying them by type. - * - * To be in balance we need to make sure that each processor is only consuming - * their fair share. - * - * When the partitions are divvied up we will sometimes end up with some processors - * that will have 1 more partition than others. This can happen if the number of - * partitions is not evenly divisible by the number of processors. - * - * So this function largely exists to support _isLoadBalanced() and - * _shouldOwnMorePartitions(), both of which depend on knowing if our current list - * of processors is actually in the proper state. - * - * @param numPartitionsRequired The number of required partitions per processor. - * @param ownerPartitionMap The current ownerships for partitions. - */ - private _getProcessorCounts( - numPartitionsRequired: number, - ownerPartitionMap: Map - ): ProcessorCounts { - const counts: ProcessorCounts = { - haveRequiredPartitions: 0, - haveAdditionalPartition: 0, - haveTooManyPartitions: 0 - }; - - for (const ownershipList of ownerPartitionMap.values()) { - const numberOfPartitions = ownershipList.length; - - // there are basically three kinds of partition counts - // for a processor: - - // 1. Has _exactly_ the required number of partitions - if (numberOfPartitions === numPartitionsRequired) { - counts.haveRequiredPartitions++; - } - - // 2. Has the required number plus one extra (correct in cases) - // where the # of partitions is not evenly divisible by the - // number of processors. - if (numberOfPartitions === numPartitionsRequired + 1) { - counts.haveAdditionalPartition++; - } - - // 3. has more than the possible # of partitions required - if (numberOfPartitions > numPartitionsRequired + 1) { - counts.haveTooManyPartitions++; - } - } - - return counts; - } - - /* - * This method will create a new map of partition id and PartitionOwnership containing only those partitions - * that are actively owned. All entries in the original map returned by CheckpointStore that haven't been - * modified for a duration of time greater than the allowed inactivity time limit are assumed to be owned by - * dead event processors. These will not be included in the map returned by this method. - */ - private _removeInactivePartitionOwnerships( - partitionOwnershipMap: Map - ): Map { - const activePartitionOwnershipMap: Map = new Map(); - partitionOwnershipMap.forEach((partitionOwnership: PartitionOwnership, partitionId: string) => { - const date = new Date(); - if ( - partitionOwnership.lastModifiedTimeInMs && - date.getTime() - partitionOwnership.lastModifiedTimeInMs < this._inactiveTimeLimitInMS && - partitionOwnership.ownerId - ) { - activePartitionOwnershipMap.set(partitionId, partitionOwnership); - } - }); - - return activePartitionOwnershipMap; - } - - /* - * This method works with the given partition ownership details and Event Hub partitions to evaluate whether the - * current Event Processor should take on the responsibility of processing more partitions. - */ - loadBalance( - ourOwnerId: string, - partitionOwnershipMap: Map, - partitionsToAdd: string[] - ): string[] { - // Remove all partitions ownership that have not been modified within the configured period of time. This means that the previous - // event processor that owned the partition is probably down and the partition is now eligible to be - // claimed by other event processors. - const activePartitionOwnershipMap = this._removeInactivePartitionOwnerships( - partitionOwnershipMap - ); - logger.verbose( - `[${ourOwnerId}] Number of active ownership records: ${activePartitionOwnershipMap.size}.` - ); - if (activePartitionOwnershipMap.size === 0) { - // If the active partition ownership map is empty, this is the first time an event processor is - // running or all Event Processors are down for this Event Hub, consumer group combination. All - // partitions in this Event Hub are available to claim. Choose a random partition to claim ownership. - return [partitionsToAdd[Math.floor(Math.random() * partitionsToAdd.length)]]; - } - - // Create a map of owner id and a list of partitions it owns - const ownerPartitionMap: Map = new Map(); - for (const activePartitionOwnership of activePartitionOwnershipMap.values()) { - const partitionOwnershipArray = ownerPartitionMap.get(activePartitionOwnership.ownerId) || []; - partitionOwnershipArray.push(activePartitionOwnership); - ownerPartitionMap.set(activePartitionOwnership.ownerId, partitionOwnershipArray); - } - - // add the current event processor to the map if it doesn't exist - if (!ownerPartitionMap.has(ourOwnerId)) { - ownerPartitionMap.set(ourOwnerId, []); - } - logger.info(`[${ourOwnerId}] Number of active event processors: ${ownerPartitionMap.size}.`); - - // Include any partitions this entity already owns in the list of partitions to claim. - const partitionsToClaim = (ownerPartitionMap.get(ourOwnerId) || []).map( - (ownership) => ownership.partitionId - ); - - // Find the minimum number of partitions every event processor should own when the load is - // evenly distributed. - const minPartitionsPerEventProcessor = Math.floor( - partitionsToAdd.length / ownerPartitionMap.size - ); - // If the number of partitions in Event Hub is not evenly divisible by number of active event processors, - // a few Event Processors may own 1 additional partition than the minimum when the load is balanced. Calculate - // the number of event processors that can own an additional partition. - const requiredNumberOfEventProcessorsWithAdditionalPartition = - partitionsToAdd.length % ownerPartitionMap.size; - - logger.verbose( - `[${ourOwnerId}] Expected minimum number of partitions per event processor: ${minPartitionsPerEventProcessor}, - expected number of event processors with additional partition: ${requiredNumberOfEventProcessorsWithAdditionalPartition}.` - ); - - const processorCounts = this._getProcessorCounts( - minPartitionsPerEventProcessor, - ownerPartitionMap - ); - - if ( - this._isLoadBalanced( - requiredNumberOfEventProcessorsWithAdditionalPartition, - ownerPartitionMap.size, - processorCounts - ) - ) { - logger.info(`[${ourOwnerId}] Load is balanced.`); - // If the partitions are evenly distributed among all active event processors, no change required. - return partitionsToClaim; - } - - if ( - !this._shouldOwnMorePartitions( - minPartitionsPerEventProcessor, - requiredNumberOfEventProcessorsWithAdditionalPartition, - ownerPartitionMap.get(ourOwnerId)!.length, - processorCounts - ) - ) { - logger.verbose( - `[${ourOwnerId}] This event processor owns ${ - ownerPartitionMap.get(ourOwnerId)!.length - } partitions and shouldn't own more.` - ); - // This event processor already has enough partitions and shouldn't own more yet - return partitionsToClaim; - } - logger.info( - `[${ourOwnerId}] Load is unbalanced and this event processor should own more partitions.` - ); - // If we have reached this stage, this event processor has to claim/steal ownership of at least 1 more partition - - // If some partitions are unclaimed, this could be because an event processor is down and - // it's partitions are now available for others to own or because event processors are just - // starting up and gradually claiming partitions to own or new partitions were added to Event Hub. - // Find any partition that is not actively owned and claim it. - - // OR - - // Find a partition to steal from another event processor. Pick the event processor that owns the highest - // number of partitions. - const unOwnedPartitionIds = []; - - for (const partitionId of partitionsToAdd) { - if (!activePartitionOwnershipMap.has(partitionId)) { - unOwnedPartitionIds.push(partitionId); - } - } - if (unOwnedPartitionIds.length === 0) { - logger.info( - `[${ourOwnerId}] No unclaimed partitions, stealing from another event processor.` - ); - partitionsToClaim.push(this._findPartitionToSteal(ourOwnerId, ownerPartitionMap)); - } else { - partitionsToClaim.push( - unOwnedPartitionIds[Math.floor(Math.random() * unOwnedPartitionIds.length)] - ); - } - - return partitionsToClaim; - } -} - -/** - * Counts of the processors that currently own partitions. - */ -interface ProcessorCounts { - /** - * The # of processors that only own the required # of - * partitions. - */ - haveRequiredPartitions: number; - /** - * The # of processors that currently own the required # - * of partitions + 1 additional (ie, handling the case where - * the number of partitions is not evenly divisible by the # of - * processors). - */ - haveAdditionalPartition: number; - /** - * Processors which have more than the required or even required + 1 - * number of partitions. These will eventually be downsized by other - * processors as they acquire their required number of partitions. - */ - haveTooManyPartitions: number; -} diff --git a/sdk/eventhub/event-hubs/test/eventHubConsumerClient.spec.ts b/sdk/eventhub/event-hubs/test/eventHubConsumerClient.spec.ts index 5b990694be87..7174481a7d6a 100644 --- a/sdk/eventhub/event-hubs/test/eventHubConsumerClient.spec.ts +++ b/sdk/eventhub/event-hubs/test/eventHubConsumerClient.spec.ts @@ -130,7 +130,9 @@ describe("EventHubConsumerClient", () => { // and if you don't specify a CheckpointStore we also assume you just want to read all partitions // immediately so we bypass the FairPartitionLoadBalancer entirely - options.processingTarget!.constructor.name.should.equal("GreedyPartitionLoadBalancer"); + options.loadBalancingStrategy.constructor.name.should.equal( + "UnbalancedLoadBalancingStrategy" + ); }; const subscription = client.subscribe(subscriptionHandlers); @@ -152,6 +154,9 @@ describe("EventHubConsumerClient", () => { // We're falling back to the actual production load balancer // (which means we just don't override the partition load balancer field) should.not.exist(options.processingTarget); + options.loadBalancingStrategy.constructor.name.should.equal( + "BalancedLoadBalancingStrategy" + ); }; clientWithCheckpointStore.subscribe(subscriptionHandlers); @@ -160,7 +165,9 @@ describe("EventHubConsumerClient", () => { it("subscribe to all partitions, no checkpoint store", () => { validateOptions = (options) => { should.not.exist(options.ownerLevel); - options.processingTarget!.constructor.name.should.equal("GreedyPartitionLoadBalancer"); + options.loadBalancingStrategy.constructor.name.should.equal( + "UnbalancedLoadBalancingStrategy" + ); }; client.subscribe(subscriptionHandlers); @@ -575,10 +582,7 @@ describe("EventHubConsumerClient", () => { it("Receive from all partitions, no coordination", async function(): Promise { const logTester = new LogTester( - [ - "EventHubConsumerClient subscribing to all partitions, no checkpoint store.", - "GreedyPartitionLoadBalancer created. Watching all." - ], + ["EventHubConsumerClient subscribing to all partitions, no checkpoint store."], [ logger.verbose as debug.Debugger, logger.verbose as debug.Debugger, @@ -665,6 +669,8 @@ describe("EventHubConsumerClient", () => { ] ); + const checkpointStore = new InMemoryCheckpointStore(); + clients.push( new EventHubConsumerClient( EventHubConsumerClient.defaultConsumerGroupName, @@ -672,7 +678,7 @@ describe("EventHubConsumerClient", () => { service.path, // specifying your own checkpoint store activates the "production ready" code path that // also uses the FairPartitionLoadBalancer - new InMemoryCheckpointStore() + checkpointStore ) ); @@ -690,7 +696,7 @@ describe("EventHubConsumerClient", () => { service.path, // specifying your own checkpoint store activates the "production ready" code path that // also uses the FairPartitionLoadBalancer - new InMemoryCheckpointStore() + checkpointStore ) ); diff --git a/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts b/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts index 6fbe6112787e..607c70a66fdc 100644 --- a/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts +++ b/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts @@ -31,18 +31,21 @@ import { SubscriptionHandlerForTests, sendOneMessagePerPartition } from "./utils/subscriptionHandlerForTests"; -import { GreedyPartitionLoadBalancer, PartitionLoadBalancer } from "../src/partitionLoadBalancer"; import { AbortError, AbortSignal } from "@azure/abort-controller"; import { FakeSubscriptionEventHandlers } from "./utils/fakeSubscriptionEventHandlers"; import { isLatestPosition } from "../src/eventPosition"; import { AbortController } from "@azure/abort-controller"; +import { UnbalancedLoadBalancingStrategy } from "../src/loadBalancerStrategies/unbalancedStrategy"; +import { BalancedLoadBalancingStrategy } from "../src/loadBalancerStrategies/balancedStrategy"; const env = getEnvVars(); describe("Event Processor", function(): void { const defaultOptions: FullEventProcessorOptions = { maxBatchSize: 1, maxWaitTimeInSeconds: 60, - ownerLevel: 0 + ownerLevel: 0, + loopIntervalInMs: 10000, + loadBalancingStrategy: new UnbalancedLoadBalancingStrategy() }; const service = { @@ -74,6 +77,7 @@ describe("Event Processor", function(): void { afterEach("close the connection", async function(): Promise { await producerClient.close(); + await consumerClient.close(); }); describe("unit tests", () => { @@ -171,7 +175,9 @@ describe("Event Processor", function(): void { { startPosition, maxBatchSize: 1, - maxWaitTimeInSeconds: 1 + maxWaitTimeInSeconds: 1, + loadBalancingStrategy: defaultOptions.loadBalancingStrategy, + loopIntervalInMs: defaultOptions.loopIntervalInMs } ); } @@ -294,6 +300,10 @@ describe("Event Processor", function(): void { isReceivingFromPartition() { return false; + }, + + receivingFromPartitions() { + return []; } }; @@ -380,8 +390,12 @@ describe("Event Processor", function(): void { async removeAllPumps(): Promise {}, isReceivingFromPartition() { return false; + }, + receivingFromPartitions() { + return []; } - } + }, + loadBalancingStrategy: new BalancedLoadBalancingStrategy(60000) } ); @@ -392,9 +406,9 @@ describe("Event Processor", function(): void { // pick up an extra surprise partition // // This particular behavior is really specific to the FairPartitionLoadBalancer but that's okay for now. - const numTimesAbortedIsCheckedInLoop = 4; + const numTimesAbortedIsCheckedInLoop = 5; await ep["_runLoopWithLoadBalancing"]( - ep["_processingTarget"] as PartitionLoadBalancer, + ep["_loadBalancingStrategy"], triggerAbortedSignalAfterNumCalls(partitionIds.length * numTimesAbortedIsCheckedInLoop) ); @@ -480,6 +494,7 @@ describe("Event Processor", function(): void { it("claimOwnership throws and is reported to the user", async () => { const errors = []; + const partitionIds = await consumerClient.getPartitionIds(); const faultyCheckpointStore: CheckpointStore = { listOwnership: async () => [], @@ -501,8 +516,7 @@ describe("Event Processor", function(): void { }, faultyCheckpointStore, { - ...defaultOptions, - processingTarget: new GreedyPartitionLoadBalancer(["0"]) + ...defaultOptions } ); @@ -518,7 +532,7 @@ describe("Event Processor", function(): void { until: async () => errors.length !== 0 }); - errors.length.should.equal(1); + errors.length.should.equal(partitionIds.length); } finally { // this will also fail - we "abandon" all claimed partitions at // when a processor is stopped (which requires us to claim them @@ -533,19 +547,33 @@ describe("Event Processor", function(): void { it("errors thrown from the user's handlers are reported to processError()", async () => { const errors = new Set(); + const partitionIds = await consumerClient.getPartitionIds(); + + const processCloseErrorMessage = "processClose() error"; + const processEventsErrorMessage = "processEvents() error"; + const processInitializeErrorMessage = "processInitialize() error"; + const expectedErrorMessages: string[] = []; + for (let i = 0; i < partitionIds.length; i++) { + expectedErrorMessages.push( + processCloseErrorMessage, + processEventsErrorMessage, + processInitializeErrorMessage + ); + } + expectedErrorMessages.sort(); const eventProcessor = new EventProcessor( EventHubConsumerClient.defaultConsumerGroupName, consumerClient["_context"], { processClose: async () => { - throw new Error("processClose() error"); + throw new Error(processCloseErrorMessage); }, processEvents: async () => { - throw new Error("processEvents() error"); + throw new Error(processEventsErrorMessage); }, processInitialize: async () => { - throw new Error("processInitialize() error"); + throw new Error(processInitializeErrorMessage); }, processError: async (err, _) => { errors.add(err); @@ -555,7 +583,6 @@ describe("Event Processor", function(): void { new InMemoryCheckpointStore(), { ...defaultOptions, - processingTarget: new GreedyPartitionLoadBalancer(["0"]), startPosition: earliestEventPosition } ); @@ -569,17 +596,13 @@ describe("Event Processor", function(): void { name: "waiting for errors thrown from user's handlers", timeBetweenRunsMs: 1000, maxTimes: 30, - until: async () => errors.size >= 3 + until: async () => errors.size >= partitionIds.length * 3 }); const messages = [...errors].map((e) => e.message); messages.sort(); - messages.should.deep.equal([ - "processClose() error", - "processEvents() error", - "processInitialize() error" - ]); + messages.should.deep.equal(expectedErrorMessages); } finally { await eventProcessor.stop(); } @@ -637,7 +660,6 @@ describe("Event Processor", function(): void { new InMemoryCheckpointStore(), { ...defaultOptions, - processingTarget: new GreedyPartitionLoadBalancer(), startPosition: startPosition } ); @@ -694,7 +716,6 @@ describe("Event Processor", function(): void { subscriptionEventHandler, startPosition } = await SubscriptionHandlerForTests.startingFromHere(producerClient); - const partitionLoadBalancer = new GreedyPartitionLoadBalancer(); const processor = new EventProcessor( EventHubConsumerClient.defaultConsumerGroupName, @@ -702,7 +723,6 @@ describe("Event Processor", function(): void { subscriptionEventHandler, new InMemoryCheckpointStore(), { - processingTarget: partitionLoadBalancer, ...defaultOptions, startPosition: startPosition } @@ -756,7 +776,6 @@ describe("Event Processor", function(): void { new InMemoryCheckpointStore(), { ...defaultOptions, - processingTarget: new GreedyPartitionLoadBalancer(), startPosition: startPosition } ); @@ -1127,7 +1146,8 @@ describe("Event Processor", function(): void { { ...defaultOptions, startPosition: earliestEventPosition, - ...processor1LoadBalancingInterval + ...processor1LoadBalancingInterval, + loadBalancingStrategy: new BalancedLoadBalancingStrategy(60000) } ); @@ -1149,7 +1169,8 @@ describe("Event Processor", function(): void { { ...defaultOptions, startPosition: earliestEventPosition, - ...processor2LoadBalancingInterval + ...processor2LoadBalancingInterval, + loadBalancingStrategy: new BalancedLoadBalancingStrategy(60000) } ); @@ -1243,14 +1264,16 @@ describe("Event Processor", function(): void { for (let i = 0; i < 2; i++) { const processorName = `processor-${i}`; - processorByName[ - processorName - ] = new EventProcessor( + processorByName[processorName] = new EventProcessor( EventHubConsumerClient.defaultConsumerGroupName, consumerClient["_context"], new FooPartitionProcessor(), checkpointStore, - { ...defaultOptions, startPosition: earliestEventPosition } + { + ...defaultOptions, + startPosition: earliestEventPosition, + loadBalancingStrategy: new BalancedLoadBalancingStrategy(60000) + } ); processorByName[processorName].start(); await delay(12000); @@ -1349,7 +1372,8 @@ describe("Event Processor", function(): void { inactiveTimeLimitInMs: 3000, ownerLevel: 0, // For this test we don't want to actually checkpoint, just test ownership. - startPosition: latestEventPosition + startPosition: latestEventPosition, + loadBalancingStrategy: new BalancedLoadBalancingStrategy(60000) }; const processor1 = new EventProcessor( diff --git a/sdk/eventhub/event-hubs/test/loadBalancer.spec.ts b/sdk/eventhub/event-hubs/test/loadBalancer.spec.ts deleted file mode 100644 index 25bcd01a96d5..000000000000 --- a/sdk/eventhub/event-hubs/test/loadBalancer.spec.ts +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { - FairPartitionLoadBalancer, - GreedyPartitionLoadBalancer -} from "../src/partitionLoadBalancer"; -import { PartitionOwnership } from "../src/eventProcessor"; - -describe("PartitionLoadBalancer", () => { - describe("GreedyPartitionLoadBalancer", () => { - it("all", () => { - const m = new Map(); - const lb = new GreedyPartitionLoadBalancer(); - - lb.loadBalance("ownerId", m, ["1", "2", "3"]).should.deep.eq(["1", "2", "3"]); - m.should.be.empty; - }); - - it("filtered", () => { - const m = new Map(); - const lb = new GreedyPartitionLoadBalancer(["2"]); - - lb.loadBalance("ownerId", m, ["1", "2", "3"]).should.deep.eq(["2"]); - m.should.be.empty; - }); - - it("claim partitions we already own", () => { - const m = new Map(); - - m.set("1", { - consumerGroup: "", - fullyQualifiedNamespace: "", - eventHubName: "", - // we already own this so we won't - // try to reclaim it. - ownerId: "ownerId", - partitionId: "" - }); - - m.set("2", { - consumerGroup: "", - fullyQualifiedNamespace: "", - eventHubName: "", - // owned by someone else - we'll steal this - // partition - ownerId: "someOtherOwnerId", - partitionId: "" - }); - - const lb = new GreedyPartitionLoadBalancer(["1", "2", "3"]); - - lb.loadBalance("ownerId", m, ["1", "2", "3"]).should.deep.eq(["1", "2", "3"]); - }); - }); - - describe("FairPartitionLoadBalancer", () => { - const lb = new FairPartitionLoadBalancer(1000 * 60); - - it("odd number of partitions per processor", () => { - const allPartitions = ["0", "1", "2"]; - - // at this point 'a' has it's fair share of partitions (there are 3 total) - // and it's okay to have 1 extra. - let partitionsToOwn = lb.loadBalance( - "a", - createOwnershipMap({ - "1": "b", - "2": "a", - "3": "a" - }), - allPartitions - ); - partitionsToOwn.sort(); - partitionsToOwn.should.be.deep.equal( - ["2", "3"], - "we've gotten our fair share, shouldn't claim anything new" - ); - - // now the other side of this is when we're fighting for the ownership of an - // extra partition - partitionsToOwn = lb.loadBalance( - "a", - createOwnershipMap({ - "1": "b", - "2": "a" - }), - allPartitions - ); - partitionsToOwn.sort(); - partitionsToOwn.should.be.deep.equal( - ["0", "2"], - "we had our minimum fair share (1) but there's still one extra (uneven number of partitions per processor) and we should snag it" - ); - }); - - it("even number of partitions per processor", () => { - const allPartitions = ["0", "1", "2", "3"]; - - // at this point 'a' has it's fair share of partitions (there are 4 total) - // so it'll stop claiming additional partitions. - let partitionsToOwn = lb.loadBalance( - "a", - createOwnershipMap({ - "1": "b", - "2": "a", - "3": "a" - }), - allPartitions - ); - partitionsToOwn.sort(); - partitionsToOwn.should.be.deep.equal( - ["2", "3"], - "we've gotten our fair share, shouldn't claim anything new" - ); - - partitionsToOwn = lb.loadBalance( - "a", - createOwnershipMap({ - "0": "b", - "1": "b", - "2": "a", - "3": "a" - }), - allPartitions - ); - partitionsToOwn.sort(); - partitionsToOwn.should.be.deep.equal(["2", "3"], "load is balanced, won't grab any more."); - }); - - // when there are no freely available partitions (partitions that have either expired or are literally unowned) - // we'll need to steal from an existing processor. - // This can happen in a few ways: - // 1. we were simply racing against other processors - // 2. we're coming in later after all partitions have been allocated (ie, scaling out) - // 3. timing issues, death of a processor, etc... - it("stealing", () => { - // something like this could happen if 'a' were just the only processor - // and now we're spinning up 'b' - let partitionsToOwn = lb.loadBalance( - "b", - createOwnershipMap({ - "0": "a", - "1": "a", - "2": "a" - }), - ["0", "1", "2"] - ); - partitionsToOwn.sort(); - // we'll attempt to steal a partition from 'a'. - partitionsToOwn.length.should.equal( - 1, - "stealing with an odd number of partitions per processor" - ); - - // and now the same case as above, but with an even number of partitions per processor. - partitionsToOwn = lb.loadBalance( - "b", - createOwnershipMap({ - "0": "a", - "1": "a", - "2": "a", - "3": "a" - }), - ["0", "1", "2", "3"] - ); - partitionsToOwn.sort(); - // we'll attempt to steal a partition from 'a'. - partitionsToOwn.length.should.equal( - 1, - "stealing with an even number of partitions per processor" - ); - }); - - it("don't steal when you can just wait", () => { - // @chradek's case: let's say we have this partition layout: - // AAAABBBCCD - // - // Before, we'd let 'C' steal from 'A' - we see that we don't have enough - // +1 processors(exact match) and so 'C' attempts to become one. This can - // lead to some unnecessary thrash as 'A' loses partitions to a processor - // that has technically already met it's quota. - // - // Instead, we treat 'A' is a +1-ish specifically for when we ('C') - // are checking if we want to grab more partitions. - // - // This allows 'A' to just naturally decline as _actual_ processors grab - // their minimum required partitions rather than forcing it and possibly - // having a partition have to juggle between partitions as they try to - // meet the minimum. - const partitions = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; - - const lb = new FairPartitionLoadBalancer(1000 * 60); - - // we'll do 4 consumers - const initialOwnershipMap = createOwnershipMap({ - "0": "a", - "1": "a", - "2": "a", - "3": "a", - - "4": "b", - "5": "b", - "6": "b", - - "7": "c", - "8": "c", - - "9": "d" - }); - - const requestedPartitions = lb.loadBalance("c", initialOwnershipMap, partitions); - requestedPartitions.sort(); - - requestedPartitions.should.deep.equal( - ["7", "8"], - "c will not steal one partition since it sees that, eventually, 'a' will lose its partitions and become a +1 processor on it's own" - ); - }); - - it("avoid thrash", () => { - // this is a case where we shouldn't steal - we have - // the minimum number of partitions and stealing at this - // point will just keep thrashing both processors. - const partitionsToOwn = lb.loadBalance( - "b", - createOwnershipMap({ - "0": "a", - "1": "b", - "2": "a" - }), - ["0", "1", "2"] - ); - - partitionsToOwn.sort(); - partitionsToOwn.should.deep.equal(["1"], "should not re-steal when things are balanced"); - }); - - it("general cases", () => { - const allPartitions = ["0", "1", "2", "3"]; - - // in the presence of no owners we claim a random partition - let partitionsToOwn = lb.loadBalance("a", createOwnershipMap({}), allPartitions); - partitionsToOwn.length.should.be.equal(1, "nothing is owned, claim one"); - - // if there are other owners we should claim up to #partitions/#owners - partitionsToOwn = lb.loadBalance( - "a", - createOwnershipMap({ - "1": "b", - "3": "a" - }), - allPartitions - ); - partitionsToOwn.length.should.be.equal(2, "1 and 1 with another owner, should claim one"); - // better not try to claim 'b's partition when there are unowned partitions - partitionsToOwn.filter((p) => p === "1").length.should.equal(0); - - // 'b' should claim the last unowned partition - partitionsToOwn = lb.loadBalance( - "b", - createOwnershipMap({ - "1": "b", - "2": "a", - "3": "a" - }), - allPartitions - ); - partitionsToOwn.sort(); - partitionsToOwn.should.be.deep.equal(["0", "1"], "b grabbed the last available partition"); - - // we're balanced - processors now only grab the partitions that they own - partitionsToOwn = lb.loadBalance( - "b", - createOwnershipMap({ - "0": "b", - "1": "a", - "2": "b", - "3": "a" - }), - allPartitions - ); - partitionsToOwn.sort(); - partitionsToOwn.should.be.deep.equal( - ["0", "2"], - "balanced: b only grabbed it's already owned partitions" - ); - }); - - function createOwnershipMap( - partitionToOwner: Record - ): Map { - const ownershipMap = new Map(); - - for (const partitionId in partitionToOwner) { - ownershipMap.set(partitionId, { - consumerGroup: "$Default", - eventHubName: "eventhubname1", - fullyQualifiedNamespace: "fqdn", - ownerId: partitionToOwner[partitionId], - partitionId: partitionId, - etag: "etag", - lastModifiedTimeInMs: Date.now() - }); - } - - return ownershipMap; - } - }); -}); diff --git a/sdk/eventhub/event-hubs/test/loadBalancingStrategy.spec.ts b/sdk/eventhub/event-hubs/test/loadBalancingStrategy.spec.ts new file mode 100644 index 000000000000..ab96d3bc9fa5 --- /dev/null +++ b/sdk/eventhub/event-hubs/test/loadBalancingStrategy.spec.ts @@ -0,0 +1,593 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { PartitionOwnership } from "../src/eventProcessor"; +import { BalancedLoadBalancingStrategy } from "../src/loadBalancerStrategies/balancedStrategy"; +import { GreedyLoadBalancingStrategy } from "../src/loadBalancerStrategies/greedyStrategy"; +import { UnbalancedLoadBalancingStrategy } from "../src/loadBalancerStrategies/unbalancedStrategy"; + +describe("LoadBalancingStrategy", () => { + describe("UnbalancedLoadBalancingStrategy", () => { + it("all", () => { + const m = new Map(); + const lb = new UnbalancedLoadBalancingStrategy(); + + lb.identifyPartitionsToClaim("ownerId", m, ["1", "2", "3"]).should.deep.eq(["1", "2", "3"]); + m.should.be.empty; + }); + + it("claim partitions we already own", () => { + const m = new Map(); + + m.set("1", { + consumerGroup: "", + fullyQualifiedNamespace: "", + eventHubName: "", + // we already own this so we won't + // try to reclaim it. + ownerId: "ownerId", + partitionId: "" + }); + + m.set("2", { + consumerGroup: "", + fullyQualifiedNamespace: "", + eventHubName: "", + // owned by someone else - we'll steal this + // partition + ownerId: "someOtherOwnerId", + partitionId: "" + }); + + const lb = new UnbalancedLoadBalancingStrategy(); + + lb.identifyPartitionsToClaim("ownerId", m, ["1", "2", "3"]).should.deep.eq(["1", "2", "3"]); + }); + }); + + describe("BalancedLoadBalancingStrategy", () => { + const lb = new BalancedLoadBalancingStrategy(1000 * 60); + + it("odd number of partitions per processor", () => { + const allPartitions = ["0", "1", "2"]; + + // at this point 'a' has it's fair share of partitions (there are 3 total) + // and it's okay to have 1 extra. + let partitionsToOwn = lb.identifyPartitionsToClaim( + "a", + createOwnershipMap({ + "1": "b", + "2": "a", + "3": "a" + }), + allPartitions + ); + partitionsToOwn.sort(); + partitionsToOwn.should.be.deep.equal( + [], + "we've gotten our fair share, shouldn't claim anything new" + ); + + // now the other side of this is when we're fighting for the ownership of an + // extra partition + partitionsToOwn = lb.identifyPartitionsToClaim( + "a", + createOwnershipMap({ + "1": "b", + "2": "a" + }), + allPartitions + ); + partitionsToOwn.sort(); + partitionsToOwn.should.be.deep.equal( + ["0"], + "we had our minimum fair share (1) but there's still one extra (uneven number of partitions per processor) and we should snag it" + ); + }); + + it("even number of partitions per processor", () => { + const allPartitions = ["0", "1", "2", "3"]; + + // at this point 'a' has it's fair share of partitions (there are 4 total) + // so it'll stop claiming additional partitions. + let partitionsToOwn = lb.identifyPartitionsToClaim( + "a", + createOwnershipMap({ + "1": "b", + "2": "a", + "3": "a" + }), + allPartitions + ); + partitionsToOwn.sort(); + partitionsToOwn.should.be.deep.equal( + [], + "we've gotten our fair share, shouldn't claim anything new" + ); + + partitionsToOwn = lb.identifyPartitionsToClaim( + "a", + createOwnershipMap({ + "0": "b", + "1": "b", + "2": "a", + "3": "a" + }), + allPartitions + ); + partitionsToOwn.sort(); + partitionsToOwn.should.be.deep.equal([], "load is balanced, won't grab any more."); + }); + + // when there are no freely available partitions (partitions that have either expired or are literally unowned) + // we'll need to steal from an existing processor. + // This can happen in a few ways: + // 1. we were simply racing against other processors + // 2. we're coming in later after all partitions have been allocated (ie, scaling out) + // 3. timing issues, death of a processor, etc... + it("stealing", () => { + // something like this could happen if 'a' were just the only processor + // and now we're spinning up 'b' + let partitionsToOwn = lb.identifyPartitionsToClaim( + "b", + createOwnershipMap({ + "0": "a", + "1": "a", + "2": "a" + }), + ["0", "1", "2"] + ); + partitionsToOwn.sort(); + // we'll attempt to steal a partition from 'a'. + partitionsToOwn.length.should.equal( + 1, + "stealing with an odd number of partitions per processor" + ); + + // and now the same case as above, but with an even number of partitions per processor. + partitionsToOwn = lb.identifyPartitionsToClaim( + "b", + createOwnershipMap({ + "0": "a", + "1": "a", + "2": "a", + "3": "a" + }), + ["0", "1", "2", "3"] + ); + partitionsToOwn.sort(); + // we'll attempt to steal a partition from 'a'. + partitionsToOwn.length.should.equal( + 1, + "stealing with an even number of partitions per processor" + ); + }); + + it("don't steal when you can just wait", () => { + // @chradek's case: let's say we have this partition layout: + // AAAABBBCCD + // + // Before, we'd let 'C' steal from 'A' - we see that we don't have enough + // +1 processors(exact match) and so 'C' attempts to become one. This can + // lead to some unnecessary thrash as 'A' loses partitions to a processor + // that has technically already met it's quota. + // + // Instead, we treat 'A' is a +1-ish specifically for when we ('C') + // are checking if we want to grab more partitions. + // + // This allows 'A' to just naturally decline as _actual_ processors grab + // their minimum required partitions rather than forcing it and possibly + // having a partition have to juggle between partitions as they try to + // meet the minimum. + const partitions = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; + + const lb = new BalancedLoadBalancingStrategy(1000 * 60); + + // we'll do 4 consumers + const initialOwnershipMap = createOwnershipMap({ + "0": "a", + "1": "a", + "2": "a", + "3": "a", + + "4": "b", + "5": "b", + "6": "b", + + "7": "c", + "8": "c", + + "9": "d" + }); + + const requestedPartitions = lb.identifyPartitionsToClaim( + "c", + initialOwnershipMap, + partitions + ); + requestedPartitions.sort(); + + requestedPartitions.should.deep.equal( + [], + "c will not steal one partition since it sees that, eventually, 'a' will lose its partitions and become a +1 processor on it's own" + ); + }); + + it("avoid thrash", () => { + // this is a case where we shouldn't steal - we have + // the minimum number of partitions and stealing at this + // point will just keep thrashing both processors. + const partitionsToOwn = lb.identifyPartitionsToClaim( + "b", + createOwnershipMap({ + "0": "a", + "1": "b", + "2": "a" + }), + ["0", "1", "2"] + ); + + partitionsToOwn.sort(); + partitionsToOwn.should.deep.equal([], "should not re-steal when things are balanced"); + }); + + it("general cases", () => { + const allPartitions = ["0", "1", "2", "3"]; + + // in the presence of no owners we claim a random partition + let partitionsToOwn = lb.identifyPartitionsToClaim( + "a", + createOwnershipMap({}), + allPartitions + ); + partitionsToOwn.length.should.be.equal(1, "nothing is owned, claim one"); + + // if there are other owners we should claim up to #partitions/#owners + partitionsToOwn = lb.identifyPartitionsToClaim( + "a", + createOwnershipMap({ + "1": "b", + "3": "a" + }), + allPartitions + ); + partitionsToOwn.length.should.be.equal(1, "1 and 1 with another owner, should claim one"); + // better not try to claim 'b's partition when there are unowned partitions + partitionsToOwn.filter((p) => p === "1").length.should.equal(0); + + // 'b' should claim the last unowned partition + partitionsToOwn = lb.identifyPartitionsToClaim( + "b", + createOwnershipMap({ + "1": "b", + "2": "a", + "3": "a" + }), + allPartitions + ); + partitionsToOwn.sort(); + partitionsToOwn.should.be.deep.equal(["0"], "b grabbed the last available partition"); + + // we're balanced - processors now only grab the partitions that they own + partitionsToOwn = lb.identifyPartitionsToClaim( + "b", + createOwnershipMap({ + "0": "b", + "1": "a", + "2": "b", + "3": "a" + }), + allPartitions + ); + partitionsToOwn.sort(); + partitionsToOwn.should.be.deep.equal([], "balanced: b should not grab anymore partitions"); + }); + + function createOwnershipMap( + partitionToOwner: Record + ): Map { + const ownershipMap = new Map(); + + for (const partitionId in partitionToOwner) { + ownershipMap.set(partitionId, { + consumerGroup: "$Default", + eventHubName: "eventhubname1", + fullyQualifiedNamespace: "fqdn", + ownerId: partitionToOwner[partitionId], + partitionId: partitionId, + etag: "etag", + lastModifiedTimeInMs: Date.now() + }); + } + + return ownershipMap; + } + }); + + describe("GreedyLoadBalancingStrategy", () => { + const lb = new GreedyLoadBalancingStrategy(1000 * 60); + + it("odd number of partitions per processor", () => { + const allPartitions = ["0", "1", "2"]; + + // at this point 'a' has it's fair share of partitions (there are 3 total) + // and it's okay to have 1 extra. + let partitionsToOwn = lb.identifyPartitionsToClaim( + "a", + createOwnershipMap({ + "1": "b", + "2": "a", + "3": "a" + }), + allPartitions + ); + partitionsToOwn.sort(); + partitionsToOwn.should.be.deep.equal( + [], + "we've gotten our fair share, shouldn't claim anything new" + ); + + // now the other side of this is when we're fighting for the ownership of an + // extra partition + partitionsToOwn = lb.identifyPartitionsToClaim( + "a", + createOwnershipMap({ + "1": "b", + "2": "a" + }), + allPartitions + ); + partitionsToOwn.sort(); + partitionsToOwn.should.be.deep.equal( + ["0"], + "we had our minimum fair share (1) but there's still one extra (uneven number of partitions per processor) and we should snag it" + ); + }); + + it("even number of partitions per processor", () => { + const allPartitions = ["0", "1", "2", "3"]; + + // at this point 'a' has it's fair share of partitions (there are 4 total) + // so it'll stop claiming additional partitions. + let partitionsToOwn = lb.identifyPartitionsToClaim( + "a", + createOwnershipMap({ + "1": "b", + "2": "a", + "3": "a" + }), + allPartitions + ); + partitionsToOwn.sort(); + partitionsToOwn.should.be.deep.equal( + [], + "we've gotten our fair share, shouldn't claim anything new" + ); + + partitionsToOwn = lb.identifyPartitionsToClaim( + "a", + createOwnershipMap({ + "0": "b", + "1": "b", + "2": "a", + "3": "a" + }), + allPartitions + ); + partitionsToOwn.sort(); + partitionsToOwn.should.be.deep.equal([], "load is balanced, won't grab any more."); + }); + + // when there are no freely available partitions (partitions that have either expired or are literally unowned) + // we'll need to steal from an existing processor. + // This can happen in a few ways: + // 1. we were simply racing against other processors + // 2. we're coming in later after all partitions have been allocated (ie, scaling out) + // 3. timing issues, death of a processor, etc... + it("stealing", () => { + // something like this could happen if 'a' were just the only processor + // and now we're spinning up 'b' + let partitionsToOwn = lb.identifyPartitionsToClaim( + "b", + createOwnershipMap({ + "0": "a", + "1": "a", + "2": "a" + }), + ["0", "1", "2"] + ); + partitionsToOwn.sort(); + // we'll attempt to steal a partition from 'a'. + partitionsToOwn.length.should.equal( + 1, + "stealing with an odd number of partitions per processor" + ); + + // and now the same case as above, but with an even number of partitions per processor. + partitionsToOwn = lb.identifyPartitionsToClaim( + "b", + createOwnershipMap({ + "0": "a", + "1": "a", + "2": "a", + "3": "a" + }), + ["0", "1", "2", "3"] + ); + partitionsToOwn.sort(); + // we'll attempt to steal a partition from 'a'. + partitionsToOwn.length.should.equal( + 2, + "stealing with an even number of partitions per processor" + ); + }); + + it("claims unowned then steals", () => { + const allPartitions = []; + for (let i = 0; i < 8; i++) { + allPartitions.push(`${i}`); + } + + let partitionsToOwn = lb.identifyPartitionsToClaim( + "a", + createOwnershipMap({ + "0": "", + // skip 1, 2 + "3": "b", + "4": "b", + "5": "b", + "6": "b", + "7": "b" + }), + allPartitions + ); + partitionsToOwn.sort(); + // "a" should have 4 partitions in order to be balanced. + // Partitions "0", "1", "2" should be chosen before any are stolen. + partitionsToOwn.length.should.equal(4, "should have claimed half of the partitions."); + partitionsToOwn + .slice(0, 3) + .should.deep.equal(["0", "1", "2"], "should have claimed unclaimed partitions first."); + }); + + it("don't steal when you can just wait", () => { + // @chradek's case: let's say we have this partition layout: + // AAAABBBCCD + // + // Before, we'd let 'C' steal from 'A' - we see that we don't have enough + // +1 processors(exact match) and so 'C' attempts to become one. This can + // lead to some unnecessary thrash as 'A' loses partitions to a processor + // that has technically already met it's quota. + // + // Instead, we treat 'A' is a +1-ish specifically for when we ('C') + // are checking if we want to grab more partitions. + // + // This allows 'A' to just naturally decline as _actual_ processors grab + // their minimum required partitions rather than forcing it and possibly + // having a partition have to juggle between partitions as they try to + // meet the minimum. + const partitions = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; + + const lb = new BalancedLoadBalancingStrategy(1000 * 60); + + // we'll do 4 consumers + const initialOwnershipMap = createOwnershipMap({ + "0": "a", + "1": "a", + "2": "a", + "3": "a", + + "4": "b", + "5": "b", + "6": "b", + + "7": "c", + "8": "c", + + "9": "d" + }); + + const requestedPartitions = lb.identifyPartitionsToClaim( + "c", + initialOwnershipMap, + partitions + ); + requestedPartitions.sort(); + + requestedPartitions.should.deep.equal( + [], + "c will not steal one partition since it sees that, eventually, 'a' will lose its partitions and become a +1 processor on it's own" + ); + }); + + it("avoid thrash", () => { + // this is a case where we shouldn't steal - we have + // the minimum number of partitions and stealing at this + // point will just keep thrashing both processors. + const partitionsToOwn = lb.identifyPartitionsToClaim( + "b", + createOwnershipMap({ + "0": "a", + "1": "b", + "2": "a" + }), + ["0", "1", "2"] + ); + + partitionsToOwn.sort(); + partitionsToOwn.should.deep.equal([], "should not re-steal when things are balanced"); + }); + + it("general cases", () => { + const allPartitions = ["0", "1", "2", "3"]; + + // in the presence of no owners we claim a random partition + let partitionsToOwn = lb.identifyPartitionsToClaim( + "a", + createOwnershipMap({}), + allPartitions + ); + partitionsToOwn.length.should.be.equal(4, "nothing is owned, claim all"); + + // if there are other owners we should claim up to #partitions/#owners + partitionsToOwn = lb.identifyPartitionsToClaim( + "a", + createOwnershipMap({ + "1": "b", + "3": "a" + }), + allPartitions + ); + partitionsToOwn.length.should.be.equal(1, "1 and 1 with another owner, should claim one"); + // better not try to claim 'b's partition when there are unowned partitions + partitionsToOwn.filter((p) => p === "1").length.should.equal(0); + + // 'b' should claim the last unowned partition + partitionsToOwn = lb.identifyPartitionsToClaim( + "b", + createOwnershipMap({ + "1": "b", + "2": "a", + "3": "a" + }), + allPartitions + ); + partitionsToOwn.sort(); + partitionsToOwn.should.be.deep.equal(["0"], "b grabbed the last available partition"); + + // we're balanced - processors now only grab the partitions that they own + partitionsToOwn = lb.identifyPartitionsToClaim( + "b", + createOwnershipMap({ + "0": "b", + "1": "a", + "2": "b", + "3": "a" + }), + allPartitions + ); + partitionsToOwn.sort(); + partitionsToOwn.should.be.deep.equal([], "balanced: b should not grab anymore partitions"); + }); + + function createOwnershipMap( + partitionToOwner: Record + ): Map { + const ownershipMap = new Map(); + + for (const partitionId in partitionToOwner) { + ownershipMap.set(partitionId, { + consumerGroup: "$Default", + eventHubName: "eventhubname1", + fullyQualifiedNamespace: "fqdn", + ownerId: partitionToOwner[partitionId], + partitionId: partitionId, + etag: "etag", + lastModifiedTimeInMs: Date.now() + }); + } + + return ownershipMap; + } + }); +}); From f2205b8f1793c1727111a08967f0653eae65c6f5 Mon Sep 17 00:00:00 2001 From: chradek Date: Wed, 24 Jun 2020 16:04:48 -0700 Subject: [PATCH 08/28] add tests for the partitionOwnershipExpirationIntervalInMs --- .../test/loadBalancingStrategy.spec.ts | 98 ++++++++++++------- 1 file changed, 62 insertions(+), 36 deletions(-) diff --git a/sdk/eventhub/event-hubs/test/loadBalancingStrategy.spec.ts b/sdk/eventhub/event-hubs/test/loadBalancingStrategy.spec.ts index ab96d3bc9fa5..2537ea430b25 100644 --- a/sdk/eventhub/event-hubs/test/loadBalancingStrategy.spec.ts +++ b/sdk/eventhub/event-hubs/test/loadBalancingStrategy.spec.ts @@ -7,6 +7,26 @@ import { GreedyLoadBalancingStrategy } from "../src/loadBalancerStrategies/greed import { UnbalancedLoadBalancingStrategy } from "../src/loadBalancerStrategies/unbalancedStrategy"; describe("LoadBalancingStrategy", () => { + function createOwnershipMap( + partitionToOwner: Record + ): Map { + const ownershipMap = new Map(); + + for (const partitionId in partitionToOwner) { + ownershipMap.set(partitionId, { + consumerGroup: "$Default", + eventHubName: "eventhubname1", + fullyQualifiedNamespace: "fqdn", + ownerId: partitionToOwner[partitionId], + partitionId: partitionId, + etag: "etag", + lastModifiedTimeInMs: Date.now() + }); + } + + return ownershipMap; + } + describe("UnbalancedLoadBalancingStrategy", () => { it("all", () => { const m = new Map(); @@ -283,25 +303,26 @@ describe("LoadBalancingStrategy", () => { partitionsToOwn.should.be.deep.equal([], "balanced: b should not grab anymore partitions"); }); - function createOwnershipMap( - partitionToOwner: Record - ): Map { - const ownershipMap = new Map(); - - for (const partitionId in partitionToOwner) { - ownershipMap.set(partitionId, { - consumerGroup: "$Default", - eventHubName: "eventhubname1", - fullyQualifiedNamespace: "fqdn", - ownerId: partitionToOwner[partitionId], - partitionId: partitionId, - etag: "etag", - lastModifiedTimeInMs: Date.now() - }); - } + it("honors the partitionOwnershipExpirationIntervalInMs", () => { + const intervalInMs = 1000; + const lb = new BalancedLoadBalancingStrategy(intervalInMs); + const allPartitions = ["0", "1"]; + const ownershipMap = createOwnershipMap({ + "0": "b", + "1": "a" + }); - return ownershipMap; - } + // At this point, 'a' has its fair share of partitions, and none should be returned. + let partitionsToOwn = lb.identifyPartitionsToClaim("a", ownershipMap, allPartitions); + partitionsToOwn.length.should.equal(0, "Expected to not claim any new partitions."); + + // Change the ownership of partition "0" so it is older than the interval. + const ownership = ownershipMap.get("0")!; + ownership.lastModifiedTimeInMs = Date.now() - (intervalInMs + 1); // Add 1 to the interval to ensure it has just expired. + + partitionsToOwn = lb.identifyPartitionsToClaim("a", ownershipMap, allPartitions); + partitionsToOwn.should.deep.equal(["0"]); + }); }); describe("GreedyLoadBalancingStrategy", () => { @@ -570,24 +591,29 @@ describe("LoadBalancingStrategy", () => { partitionsToOwn.should.be.deep.equal([], "balanced: b should not grab anymore partitions"); }); - function createOwnershipMap( - partitionToOwner: Record - ): Map { - const ownershipMap = new Map(); - - for (const partitionId in partitionToOwner) { - ownershipMap.set(partitionId, { - consumerGroup: "$Default", - eventHubName: "eventhubname1", - fullyQualifiedNamespace: "fqdn", - ownerId: partitionToOwner[partitionId], - partitionId: partitionId, - etag: "etag", - lastModifiedTimeInMs: Date.now() - }); - } + it("honors the partitionOwnershipExpirationIntervalInMs", () => { + const intervalInMs = 1000; + const lb = new GreedyLoadBalancingStrategy(intervalInMs); + const allPartitions = ["0", "1", "2", "3"]; + const ownershipMap = createOwnershipMap({ + "0": "b", + "1": "a" + }); - return ownershipMap; - } + // At this point, "a" should only grab 1 partition since both "a" and "b" should end up with 2 partitions each. + let partitionsToOwn = lb.identifyPartitionsToClaim("a", ownershipMap, allPartitions); + partitionsToOwn.length.should.equal(1, "Expected to claim 1 new partitions."); + + // Change the ownership of partition "0" so it is older than the interval. + const ownership = ownershipMap.get("0")!; + ownership.lastModifiedTimeInMs = Date.now() - (intervalInMs + 1); // Add 1 to the interval to ensure it has just expired. + + // At this point, "a" should grab partitions 0, 2, and 3. + // This is because "b" only owned 1 partition and that claim is expired, + // so "a" as treated as if it is the only owner. + partitionsToOwn = lb.identifyPartitionsToClaim("a", ownershipMap, allPartitions); + partitionsToOwn.sort(); + partitionsToOwn.should.deep.equal(["0", "2", "3"]); + }); }); }); From ed1ff3bdd63bf62e8794e6b7b75857067357a348 Mon Sep 17 00:00:00 2001 From: chradek Date: Wed, 24 Jun 2020 17:48:25 -0700 Subject: [PATCH 09/28] add functional load balancing tests --- .../test/eventHubConsumerClient.spec.ts | 407 ++++++++++++++++-- .../event-hubs/test/eventProcessor.spec.ts | 404 ++++++++++++++++- 2 files changed, 775 insertions(+), 36 deletions(-) diff --git a/sdk/eventhub/event-hubs/test/eventHubConsumerClient.spec.ts b/sdk/eventhub/event-hubs/test/eventHubConsumerClient.spec.ts index 7174481a7d6a..74eee6afa19c 100644 --- a/sdk/eventhub/event-hubs/test/eventHubConsumerClient.spec.ts +++ b/sdk/eventhub/event-hubs/test/eventHubConsumerClient.spec.ts @@ -19,6 +19,8 @@ import { InMemoryCheckpointStore } from "../src/inMemoryCheckpointStore"; import { EventProcessor, FullEventProcessorOptions } from "../src/eventProcessor"; import { SinonStubbedInstance, createStubInstance } from "sinon"; import { ConnectionContext } from "../src/connectionContext"; +import { BalancedLoadBalancingStrategy } from "../src/loadBalancerStrategies/balancedStrategy"; +import { GreedyLoadBalancingStrategy } from "../src/loadBalancerStrategies/greedyStrategy"; const should = chai.should(); const env = getEnvVars(); @@ -57,6 +59,20 @@ describe("EventHubConsumerClient", () => { let clientWithCheckpointStore: EventHubConsumerClient; let subscriptionHandlers: SubscriptionEventHandlers; let fakeEventProcessor: SinonStubbedInstance; + const fakeEventProcessorConstructor = ( + connectionContext: ConnectionContext, + subscriptionEventHandlers: SubscriptionEventHandlers, + checkpointStore: CheckpointStore, + options: FullEventProcessorOptions + ) => { + subscriptionEventHandlers.should.equal(subscriptionHandlers); + should.exist(connectionContext.managementSession); + isCheckpointStore(checkpointStore).should.be.ok; + + validateOptions(options); + + return fakeEventProcessor; + }; let validateOptions: (options: FullEventProcessorOptions) => void; @@ -82,21 +98,6 @@ describe("EventHubConsumerClient", () => { processError: async () => {} }; - const fakeEventProcessorConstructor = ( - connectionContext: ConnectionContext, - subscriptionEventHandlers: SubscriptionEventHandlers, - checkpointStore: CheckpointStore, - options: FullEventProcessorOptions - ) => { - subscriptionEventHandlers.should.equal(subscriptionHandlers); - should.exist(connectionContext.managementSession); - isCheckpointStore(checkpointStore).should.be.ok; - - validateOptions(options); - - return fakeEventProcessor; - }; - (client as any)["_createEventProcessor"] = fakeEventProcessorConstructor; (clientWithCheckpointStore as any)["_createEventProcessor"] = fakeEventProcessorConstructor; }); @@ -119,66 +120,335 @@ describe("EventHubConsumerClient", () => { ); }); - it("subscribe to single partition, no checkpoint store", () => { + it("subscribe to single partition, no checkpoint store, no loadBalancingOptions", () => { validateOptions = (options) => { // when the user doesn't pass a checkpoint store we give them a really simple set of - // defaults: InMemoryCheckpointStore and the GreedyLoadBalancer. + // defaults: + // - InMemoryCheckpointStore + // - UnbalancedLoadBalancingStrategy + // - loopIntervalInMs: 10000 // So we don't set an ownerlevel here - it's all in-memory and you can have as many // as you want (the user still has the option to pass their own via SubscribeOptions). should.not.exist(options.ownerLevel); // and if you don't specify a CheckpointStore we also assume you just want to read all partitions - // immediately so we bypass the FairPartitionLoadBalancer entirely + // immediately so we use the UnbalancedLoadBalancingStrategy. options.loadBalancingStrategy.constructor.name.should.equal( "UnbalancedLoadBalancingStrategy" ); + + options.loopIntervalInMs.should.equal(10000); + options.processingTarget!.should.equal("0"); }; - const subscription = client.subscribe(subscriptionHandlers); + const subscription = client.subscribe("0", subscriptionHandlers); subscription.close(); fakeEventProcessor.stop.callCount.should.equal(1); }); - it("subscribe to single partition, WITH checkpoint store", () => { + it("subscribe to single partition, no checkpoint store, WITH loadBalancingOptions", () => { validateOptions = (options) => { - // when the user gives us a checkpoint store we treat their consumer client as - // a "production" ready client - they use their checkpoint store and - // the FairPartitionLoadBalancer + // When the user subscribes to a single partition, we always use the UnbalancedLoadBalancingStrategy. + // The loadBalancingOptions `strategy` and `partitionOwnershipExpirationIntervalInMs` fields are ignored. + // - InMemoryCheckpointStore + // - UnbalancedLoadBalancingStrategy + // - loopIntervalInMs: 10000 + + // So we don't set an ownerlevel here - it's all in-memory and you can have as many + // as you want (the user still has the option to pass their own via SubscribeOptions). + should.not.exist(options.ownerLevel); + + // and if you don't specify a CheckpointStore we also assume you just want to read all partitions + // immediately so we use the UnbalancedLoadBalancingStrategy. + options.loadBalancingStrategy.constructor.name.should.equal( + "UnbalancedLoadBalancingStrategy" + ); + + options.loopIntervalInMs.should.equal(20); + options.processingTarget!.should.equal("0"); + }; + + client = new EventHubConsumerClient( + EventHubConsumerClient.defaultConsumerGroupName, + service.connectionString!, + service.path, + { + loadBalancingOptions: { + strategy: "greedy", // ignored + partitionOwnershipExpirationIntervalInMs: 100, // ignored + updateIntervalInMs: 20 + } + } + ); + (client as any)["_createEventProcessor"] = fakeEventProcessorConstructor; + + const subscription = client.subscribe("0", subscriptionHandlers); + + subscription.close(); + fakeEventProcessor.stop.callCount.should.equal(1); + }); + + it("subscribe to single partition, WITH checkpoint store, no loadBalancingOptions", () => { + validateOptions = (options) => { + // when the user gives us a checkpoint store but subscribes to a single partition, + // - they use their checkpoint store and the following defaults: + // - UnbalancedLoadBalancingStrategy + // - loopIntervalInMs: 10000 // To coordinate properly we set an owner level - this lets us // cooperate properly with other consumers within this group. options.ownerLevel!.should.equal(0); - // We're falling back to the actual production load balancer - // (which means we just don't override the partition load balancer field) - should.not.exist(options.processingTarget); + options.processingTarget!.should.equal("0"); options.loadBalancingStrategy.constructor.name.should.equal( - "BalancedLoadBalancingStrategy" + "UnbalancedLoadBalancingStrategy" ); + options.loopIntervalInMs.should.equal(10000); }; - clientWithCheckpointStore.subscribe(subscriptionHandlers); + clientWithCheckpointStore.subscribe("0", subscriptionHandlers); }); - it("subscribe to all partitions, no checkpoint store", () => { + it("subscribe to single partition, WITH checkpoint store, WITH loadBalancingOptions", () => { validateOptions = (options) => { + // When the user subscribes to a single partition, we always use the UnbalancedLoadBalancingStrategy. + // The loadBalancingOptions `strategy` and `partitionOwnershipExpirationIntervalInMs` fields are ignored. + // - UnbalancedLoadBalancingStrategy + // - loopIntervalInMs: 10000 + + // To coordinate properly we set an owner level - this lets us + // cooperate properly with other consumers within this group. + options.ownerLevel!.should.equal(0); + + options.processingTarget!.should.equal("0"); + options.loadBalancingStrategy.constructor.name.should.equal( + "UnbalancedLoadBalancingStrategy" + ); + options.loopIntervalInMs.should.equal(20); + }; + + clientWithCheckpointStore = new EventHubConsumerClient( + EventHubConsumerClient.defaultConsumerGroupName, + service.connectionString!, + service.path, + // it doesn't actually matter _what_ checkpoint store gets passed in + new InMemoryCheckpointStore(), + { + loadBalancingOptions: { + strategy: "greedy", // ignored + partitionOwnershipExpirationIntervalInMs: 100, // ignored + updateIntervalInMs: 20 + } + } + ); + (clientWithCheckpointStore as any)["_createEventProcessor"] = fakeEventProcessorConstructor; + + clientWithCheckpointStore.subscribe("0", subscriptionHandlers); + }); + + it("subscribe to all partitions, no checkpoint store, no loadBalancingOptions", () => { + validateOptions = (options) => { + // when the user doesn't pass a checkpoint store we give them a really simple set of + // defaults: + // - InMemoryCheckpointStore + // - UnbalancedLoadBalancingStrategy + // - loopIntervalInMs: 10000 + should.not.exist(options.ownerLevel); + options.loadBalancingStrategy.constructor.name.should.equal( + "UnbalancedLoadBalancingStrategy" + ); + options.loopIntervalInMs.should.equal(10000); + }; + + client.subscribe(subscriptionHandlers); + }); + + it("subscribe to all partitions, no checkpoint store, WITH loadBalancingOptions", () => { + validateOptions = (options) => { + // When the user doesn't provide a checkpoint store, we always use the UnbalancedLoadBalancingStrategy. + // The loadBalancingOptions `strategy` and `partitionOwnershipExpirationIntervalInMs` fields are ignored. + // - InMemoryCheckpointStore + // - UnbalancedLoadBalancingStrategy should.not.exist(options.ownerLevel); options.loadBalancingStrategy.constructor.name.should.equal( "UnbalancedLoadBalancingStrategy" ); + options.loopIntervalInMs.should.equal(20); }; + client = new EventHubConsumerClient( + EventHubConsumerClient.defaultConsumerGroupName, + service.connectionString!, + service.path, + { + loadBalancingOptions: { + strategy: "greedy", // ignored + partitionOwnershipExpirationIntervalInMs: 100, // ignored + updateIntervalInMs: 20 + } + } + ); + (client as any)["_createEventProcessor"] = fakeEventProcessorConstructor; + client.subscribe(subscriptionHandlers); }); - it("subscribe to all partitions, WITH checkpoint store", () => { + it("subscribe to all partitions, WITH checkpoint store, no loadBalancingOptions", () => { + validateOptions = (options) => { + // when the user gives us a checkpoint store we treat their consumer client as + // a "production" ready client - they use their checkpoint store and the following + // defaults: + // - BalancedLoadBalancingStrategy + // - loopIntervalInMs: 10000 + // - partitionOwnershipExpirationIntervalInMs: 60000 + options.ownerLevel!.should.equal(0); + should.not.exist(options.processingTarget); + options.loadBalancingStrategy.constructor.name.should.equal( + "BalancedLoadBalancingStrategy" + ); + (options.loadBalancingStrategy as BalancedLoadBalancingStrategy)[ + "_partitionOwnershipExpirationIntervalInMs" + ].should.equal(60000); + options.loopIntervalInMs.should.equal(10000); + }; + + clientWithCheckpointStore.subscribe(subscriptionHandlers); + }); + + it("subscribe to all partitions, WITH checkpoint store, WITH loadBalancingOptions (greedy, updateInterval, expirationInterval)", () => { validateOptions = (options) => { + // when the user gives us a checkpoint store and subscribes to all partitions, + // we use their loadBalancingOptions when provided. options.ownerLevel!.should.equal(0); should.not.exist(options.processingTarget); + options.loadBalancingStrategy.constructor.name.should.equal( + "GreedyLoadBalancingStrategy" + ); + (options.loadBalancingStrategy as GreedyLoadBalancingStrategy)[ + "_partitionOwnershipExpirationIntervalInMs" + ].should.equal(100); + options.loopIntervalInMs.should.equal(20); }; + clientWithCheckpointStore = new EventHubConsumerClient( + EventHubConsumerClient.defaultConsumerGroupName, + service.connectionString!, + service.path, + // it doesn't actually matter _what_ checkpoint store gets passed in + new InMemoryCheckpointStore(), + { + loadBalancingOptions: { + strategy: "greedy", + partitionOwnershipExpirationIntervalInMs: 100, + updateIntervalInMs: 20 + } + } + ); + (clientWithCheckpointStore as any)["_createEventProcessor"] = fakeEventProcessorConstructor; + + clientWithCheckpointStore.subscribe(subscriptionHandlers); + }); + + it("subscribe to all partitions, WITH checkpoint store, WITH loadBalancingOptions (balanced, updateInterval, expirationInterval)", () => { + validateOptions = (options) => { + // when the user gives us a checkpoint store and subscribes to all partitions, + // we use their loadBalancingOptions when provided. + options.ownerLevel!.should.equal(0); + should.not.exist(options.processingTarget); + options.loadBalancingStrategy.constructor.name.should.equal( + "BalancedLoadBalancingStrategy" + ); + (options.loadBalancingStrategy as BalancedLoadBalancingStrategy)[ + "_partitionOwnershipExpirationIntervalInMs" + ].should.equal(100); + options.loopIntervalInMs.should.equal(20); + }; + + clientWithCheckpointStore = new EventHubConsumerClient( + EventHubConsumerClient.defaultConsumerGroupName, + service.connectionString!, + service.path, + // it doesn't actually matter _what_ checkpoint store gets passed in + new InMemoryCheckpointStore(), + { + loadBalancingOptions: { + strategy: "balanced", + partitionOwnershipExpirationIntervalInMs: 100, + updateIntervalInMs: 20 + } + } + ); + (clientWithCheckpointStore as any)["_createEventProcessor"] = fakeEventProcessorConstructor; + + clientWithCheckpointStore.subscribe(subscriptionHandlers); + }); + + it("subscribe to all partitions, WITH checkpoint store, WITH loadBalancingOptions (updateInterval, expirationInterval)", () => { + validateOptions = (options) => { + // when the user gives us a checkpoint store and subscribes to all partitions, + // we use their loadBalancingOptions when provided. + options.ownerLevel!.should.equal(0); + should.not.exist(options.processingTarget); + options.loadBalancingStrategy.constructor.name.should.equal( + "BalancedLoadBalancingStrategy" + ); + (options.loadBalancingStrategy as BalancedLoadBalancingStrategy)[ + "_partitionOwnershipExpirationIntervalInMs" + ].should.equal(100); + options.loopIntervalInMs.should.equal(20); + }; + + clientWithCheckpointStore = new EventHubConsumerClient( + EventHubConsumerClient.defaultConsumerGroupName, + service.connectionString!, + service.path, + // it doesn't actually matter _what_ checkpoint store gets passed in + new InMemoryCheckpointStore(), + { + loadBalancingOptions: { + // default 'strategy' is 'balanced' + partitionOwnershipExpirationIntervalInMs: 100, + updateIntervalInMs: 20 + } + } + ); + (clientWithCheckpointStore as any)["_createEventProcessor"] = fakeEventProcessorConstructor; + + clientWithCheckpointStore.subscribe(subscriptionHandlers); + }); + + it("subscribe to all partitions, WITH checkpoint store, WITH loadBalancingOptions (strategy)", () => { + validateOptions = (options) => { + // when the user gives us a checkpoint store and subscribes to all partitions, + // we use their loadBalancingOptions when provided. + options.ownerLevel!.should.equal(0); + should.not.exist(options.processingTarget); + options.loadBalancingStrategy.constructor.name.should.equal( + "GreedyLoadBalancingStrategy" + ); + (options.loadBalancingStrategy as GreedyLoadBalancingStrategy)[ + "_partitionOwnershipExpirationIntervalInMs" + ].should.equal(60000); + options.loopIntervalInMs.should.equal(10000); + }; + + clientWithCheckpointStore = new EventHubConsumerClient( + EventHubConsumerClient.defaultConsumerGroupName, + service.connectionString!, + service.path, + // it doesn't actually matter _what_ checkpoint store gets passed in + new InMemoryCheckpointStore(), + { + loadBalancingOptions: { + strategy: "greedy" + // defaults are used for the rest of the parameters. + } + } + ); + (clientWithCheckpointStore as any)["_createEventProcessor"] = fakeEventProcessorConstructor; + clientWithCheckpointStore.subscribe(subscriptionHandlers); }); @@ -651,7 +921,7 @@ describe("EventHubConsumerClient", () => { logTester.assert(); }); - it("Receive from all partitions, coordinating with the same partition manager and using the FairPartitionLoadBalancer", async function(): Promise< + it("Receive from all partitions, coordinating with the same partition manager and using the default LoadBalancingStrategy", async function(): Promise< void > { // fast forward our partition manager so it starts reading from the latest offset @@ -677,7 +947,7 @@ describe("EventHubConsumerClient", () => { service.connectionString!, service.path, // specifying your own checkpoint store activates the "production ready" code path that - // also uses the FairPartitionLoadBalancer + // also uses the BalancedLoadBalancingStrategy checkpointStore ) ); @@ -695,7 +965,7 @@ describe("EventHubConsumerClient", () => { service.connectionString!, service.path, // specifying your own checkpoint store activates the "production ready" code path that - // also uses the FairPartitionLoadBalancer + // also uses the BalancedLoadBalancingStrategy checkpointStore ) ); @@ -714,6 +984,77 @@ describe("EventHubConsumerClient", () => { logTester.assert(); }); + it("Receive from all partitions, coordinating with the same partition manager and using the GreedyLoadBalancingStrategy", async function(): Promise< + void + > { + // fast forward our partition manager so it starts reading from the latest offset + // instead of the beginning of time. + const logTester = new LogTester( + [ + "EventHubConsumerClient subscribing to all partitions, using a checkpoint store.", + /Starting event processor with ID /, + "Abandoning owned partitions" + ], + [ + logger.verbose as debug.Debugger, + logger.verbose as debug.Debugger, + logger.verbose as debug.Debugger + ] + ); + + const checkpointStore = new InMemoryCheckpointStore(); + + clients.push( + new EventHubConsumerClient( + EventHubConsumerClient.defaultConsumerGroupName, + service.connectionString!, + service.path, + // specifying your own checkpoint store activates the "production ready" code path that + checkpointStore, + { + loadBalancingOptions: { + strategy: "greedy" + } + } + ) + ); + + const tester = new ReceivedMessagesTester(partitionIds, true); + + const subscriber1 = clients[0].subscribe(tester, { + startPosition: latestEventPosition + }); + subscriptions.push(subscriber1); + + clients.push( + new EventHubConsumerClient( + EventHubConsumerClient.defaultConsumerGroupName, + service.connectionString!, + service.path, + // specifying your own checkpoint store activates the "production ready" code path that + checkpointStore, + { + loadBalancingOptions: { + strategy: "greedy" + } + } + ) + ); + + const subscriber2 = clients[1].subscribe(tester, { + startPosition: latestEventPosition + }); + subscriptions.push(subscriber2); + + await tester.runTestAndPoll(producerClient); + + // or else we won't see the abandoning message + for (const subscription of subscriptions) { + await subscription.close(); + } + logTester.assert(); + }); + it("Stops receiving events if close is immediately called, single partition.", async function(): Promise< void > { diff --git a/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts b/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts index 607c70a66fdc..b638a495b2bb 100644 --- a/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts +++ b/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts @@ -37,6 +37,7 @@ import { isLatestPosition } from "../src/eventPosition"; import { AbortController } from "@azure/abort-controller"; import { UnbalancedLoadBalancingStrategy } from "../src/loadBalancerStrategies/unbalancedStrategy"; import { BalancedLoadBalancingStrategy } from "../src/loadBalancerStrategies/balancedStrategy"; +import { GreedyLoadBalancingStrategy } from "../src/loadBalancerStrategies/greedyStrategy"; const env = getEnvVars(); describe("Event Processor", function(): void { @@ -1078,7 +1079,7 @@ describe("Event Processor", function(): void { ); }); - it("should 'steal' partitions until all the processors have reached a steady-state", async function(): Promise< + it("should 'steal' partitions until all the processors have reached a steady-state (BalancedLoadBalancingStrategy)", async function(): Promise< void > { loggerForTest("starting up the stealing test"); @@ -1235,7 +1236,164 @@ describe("Event Processor", function(): void { } }); - it("should ensure that all the processors reach a steady-state where all partitions are being processed", async function(): Promise< + it("should 'steal' partitions until all the processors have reached a steady-state (GreedyLoadBalancingStrategy)", async function(): Promise< + void + > { + loggerForTest("starting up the stealing test"); + + const processorByName: Dictionary = {}; + const checkpointStore = new InMemoryCheckpointStore(); + const partitionIds = await producerClient.getPartitionIds(); + const partitionOwnershipArr = new Set(); + + const partitionResultsMap = new Map< + string, + { events: string[]; initialized: boolean; closeReason?: CloseReason } + >(); + partitionIds.forEach((id) => partitionResultsMap.set(id, { events: [], initialized: false })); + let didGetReceiverDisconnectedError = false; + + // The partitionProcess will need to add events to the partitionResultsMap as they are received + class FooPartitionProcessor implements Required { + async processInitialize(context: PartitionContext) { + loggerForTest(`processInitialize(${context.partitionId})`); + partitionResultsMap.get(context.partitionId)!.initialized = true; + } + async processClose(reason: CloseReason, context: PartitionContext) { + loggerForTest(`processClose(${context.partitionId})`); + partitionResultsMap.get(context.partitionId)!.closeReason = reason; + } + async processEvents(events: ReceivedEventData[], context: PartitionContext) { + partitionOwnershipArr.add(context.partitionId); + const existingEvents = partitionResultsMap.get(context.partitionId)!.events; + existingEvents.push(...events.map((event) => event.body)); + } + async processError(err: Error, context: PartitionContext) { + loggerForTest(`processError(${context.partitionId})`); + const errorName = (err as any).code; + if (errorName === "ReceiverDisconnectedError") { + didGetReceiverDisconnectedError = true; + } + } + } + + // create messages + const expectedMessagePrefix = "EventProcessor test - multiple partitions - "; + for (const partitionId of partitionIds) { + await producerClient.sendBatch([{ body: expectedMessagePrefix + partitionId }], { + partitionId + }); + } + + const processor1LoadBalancingInterval = { + loopIntervalInMs: 1000 + }; + + // working around a potential deadlock - this allows `processor-2` to more + // aggressively pursue getting its required partitions and avoid being in + // lockstep with `processor-1` + const processor2LoadBalancingInterval = { + loopIntervalInMs: processor1LoadBalancingInterval.loopIntervalInMs / 2 + }; + + processorByName[`processor-1`] = new EventProcessor( + EventHubConsumerClient.defaultConsumerGroupName, + consumerClient["_context"], + new FooPartitionProcessor(), + checkpointStore, + { + ...defaultOptions, + startPosition: earliestEventPosition, + ...processor1LoadBalancingInterval, + loadBalancingStrategy: new GreedyLoadBalancingStrategy(60000) + } + ); + + processorByName[`processor-1`].start(); + + await loopUntil({ + name: "All partitions are owned", + maxTimes: 60, + timeBetweenRunsMs: 1000, + until: async () => partitionOwnershipArr.size === partitionIds.length, + errorMessageFn: () => `${partitionOwnershipArr.size}/${partitionIds.length}` + }); + + processorByName[`processor-2`] = new EventProcessor( + EventHubConsumerClient.defaultConsumerGroupName, + consumerClient["_context"], + new FooPartitionProcessor(), + checkpointStore, + { + ...defaultOptions, + startPosition: earliestEventPosition, + ...processor2LoadBalancingInterval, + loadBalancingStrategy: new GreedyLoadBalancingStrategy(60000) + } + ); + + partitionOwnershipArr.size.should.equal(partitionIds.length); + processorByName[`processor-2`].start(); + + await loopUntil({ + name: "Processors are balanced", + maxTimes: 60, + timeBetweenRunsMs: 1000, + until: async () => { + // it should be impossible for 'processor-2' to have obtained the number of + // partitions it needed without having stolen some from 'processor-1' + // so if we haven't see any `ReceiverDisconnectedError`'s then that stealing + // hasn't occurred yet. + if (!didGetReceiverDisconnectedError) { + return false; + } + + const partitionOwnership = await checkpointStore.listOwnership( + consumerClient.fullyQualifiedNamespace, + consumerClient.eventHubName, + EventHubConsumerClient.defaultConsumerGroupName + ); + + // map of ownerId as a key and partitionIds as a value + const partitionOwnershipMap: Map = ownershipListToMap( + partitionOwnership + ); + + // if stealing has occurred we just want to make sure that _all_ + // the stealing has completed. + const isBalanced = (friendlyName: string) => { + const n = Math.floor(partitionIds.length / 2); + const numPartitions = partitionOwnershipMap.get(processorByName[friendlyName].id)! + .length; + return numPartitions == n || numPartitions == n + 1; + }; + + if (!isBalanced(`processor-1`) || !isBalanced(`processor-2`)) { + return false; + } + + return true; + } + }); + + for (const processor in processorByName) { + await processorByName[processor].stop(); + } + + // now that all the dust has settled let's make sure that + // a. we received some events from each partition (doesn't matter which processor) + // did the work + // b. each partition was initialized + // c. each partition should have received at least one shutdown event + for (const partitionId of partitionIds) { + const results = partitionResultsMap.get(partitionId)!; + results.events.length.should.be.gte(1); + results.initialized.should.be.true; + (results.closeReason === CloseReason.Shutdown).should.be.true; + } + }); + + it("should ensure that all the processors reach a steady-state where all partitions are being processed (BalancedLoadBalancingStrategy)", async function(): Promise< void > { const processorByName: Dictionary = {}; @@ -1316,7 +1474,84 @@ describe("Event Processor", function(): void { partitionOwnershipMap.get(processorByName[`processor-1`].id)!.length.should.oneOf([n, n + 1]); }); - it("should ensure that all the processors maintain a steady-state when all partitions are being processed", async function(): Promise< + it("should ensure that all the processors reach a steady-state where all partitions are being processed (GreedyLoadBalancingStrategy)", async function(): Promise< + void + > { + const processorByName: Dictionary = {}; + const partitionIds = await producerClient.getPartitionIds(); + const checkpointStore = new InMemoryCheckpointStore(); + const partitionOwnershipArr = new Set(); + + // The partitionProcess will need to add events to the partitionResultsMap as they are received + class FooPartitionProcessor { + async processEvents(_events: ReceivedEventData[], context: PartitionContext) { + partitionOwnershipArr.add(context.partitionId); + } + async processError() {} + } + + // create messages + const expectedMessagePrefix = "EventProcessor test - multiple partitions - "; + for (const partitionId of partitionIds) { + await producerClient.sendBatch([{ body: expectedMessagePrefix + partitionId }], { + partitionId + }); + } + + for (let i = 0; i < 2; i++) { + const processorName = `processor-${i}`; + processorByName[processorName] = new EventProcessor( + EventHubConsumerClient.defaultConsumerGroupName, + consumerClient["_context"], + new FooPartitionProcessor(), + checkpointStore, + { + ...defaultOptions, + startPosition: earliestEventPosition, + loadBalancingStrategy: new GreedyLoadBalancingStrategy(60000) + } + ); + processorByName[processorName].start(); + await delay(12000); + } + + await loopUntil({ + name: "partitionownership", + timeBetweenRunsMs: 5000, + maxTimes: 10, + until: async () => partitionOwnershipArr.size === partitionIds.length + }); + + // map of ownerId as a key and partitionIds as a value + const partitionOwnershipMap: Map = new Map(); + + const partitionOwnership = await checkpointStore.listOwnership( + consumerClient.fullyQualifiedNamespace, + consumerClient.eventHubName, + EventHubConsumerClient.defaultConsumerGroupName + ); + + partitionOwnershipArr.size.should.equal(partitionIds.length); + for (const processor in processorByName) { + await processorByName[processor].stop(); + } + + for (const ownership of partitionOwnership) { + if (!partitionOwnershipMap.has(ownership.ownerId)) { + partitionOwnershipMap.set(ownership.ownerId, [ownership.partitionId]); + } else { + const arr = partitionOwnershipMap.get(ownership.ownerId); + arr!.push(ownership.partitionId); + partitionOwnershipMap.set(ownership.ownerId, arr!); + } + } + + const n = Math.floor(partitionIds.length / 2); + partitionOwnershipMap.get(processorByName[`processor-0`].id)!.length.should.oneOf([n, n + 1]); + partitionOwnershipMap.get(processorByName[`processor-1`].id)!.length.should.oneOf([n, n + 1]); + }); + + it("should ensure that all the processors maintain a steady-state when all partitions are being processed (BalancedLoadBalancingStrategy)", async function(): Promise< void > { const partitionIds = await producerClient.getPartitionIds(); @@ -1478,6 +1713,169 @@ describe("Event Processor", function(): void { ); } }); + + it("should ensure that all the processors maintain a steady-state when all partitions are being processed (GreedyLoadBalancingStrategy)", async function(): Promise< + void + > { + const partitionIds = await producerClient.getPartitionIds(); + const checkpointStore = new InMemoryCheckpointStore(); + const claimedPartitionsMap = {} as { [eventProcessorId: string]: Set }; + + const partitionOwnershipHistory: string[] = []; + + let allPartitionsClaimed = false; + let thrashAfterSettling = false; + const handlers: SubscriptionEventHandlers = { + async processInitialize(context) { + const eventProcessorId: string = (context as any).eventProcessorId; + const partitionId = context.partitionId; + + partitionOwnershipHistory.push(`${eventProcessorId}: init ${partitionId}`); + + loggerForTest(`[${eventProcessorId}] Claimed partition ${partitionId}`); + if (allPartitionsClaimed) { + thrashAfterSettling = true; + return; + } + + const claimedPartitions = claimedPartitionsMap[eventProcessorId] || new Set(); + claimedPartitions.add(partitionId); + claimedPartitionsMap[eventProcessorId] = claimedPartitions; + }, + async processEvents() {}, + async processError() {}, + async processClose(reason, context) { + const eventProcessorId: string = (context as any).eventProcessorId; + const partitionId = context.partitionId; + const claimedPartitions = claimedPartitionsMap[eventProcessorId]; + claimedPartitions.delete(partitionId); + loggerForTest( + `[${(context as any).eventProcessorId}] processClose(${reason}) on partition ${ + context.partitionId + }` + ); + if (reason === CloseReason.OwnershipLost && allPartitionsClaimed) { + loggerForTest( + `[${(context as any).eventProcessorId}] Lost partition ${context.partitionId}` + ); + thrashAfterSettling = true; + } + } + }; + + const eventProcessorOptions: FullEventProcessorOptions = { + maxBatchSize: 1, + maxWaitTimeInSeconds: 5, + loopIntervalInMs: 1000, + inactiveTimeLimitInMs: 3000, + ownerLevel: 0, + // For this test we don't want to actually checkpoint, just test ownership. + startPosition: latestEventPosition, + loadBalancingStrategy: new GreedyLoadBalancingStrategy(60000) + }; + + const processor1 = new EventProcessor( + EventHubConsumerClient.defaultConsumerGroupName, + consumerClient["_context"], + handlers, + checkpointStore, + eventProcessorOptions + ); + + const processor2 = new EventProcessor( + EventHubConsumerClient.defaultConsumerGroupName, + consumerClient["_context"], + handlers, + checkpointStore, + eventProcessorOptions + ); + + processor1.start(); + processor2.start(); + + // loop until all partitions are claimed + try { + let lastLoopError: Record = {}; + + await loopUntil({ + name: "partitionOwnership", + maxTimes: 30, + timeBetweenRunsMs: 10000, + + errorMessageFn: () => JSON.stringify(lastLoopError, undefined, " "), + until: async () => { + // Ensure the partition ownerships are balanced. + const eventProcessorIds = Object.keys(claimedPartitionsMap); + + // There are 2 processors, so we should see 2 entries. + if (eventProcessorIds.length !== 2) { + lastLoopError = { + reason: "Not all event processors have shown up", + eventProcessorIds, + partitionOwnershipHistory + }; + return false; + } + + const aProcessorPartitions = claimedPartitionsMap[eventProcessorIds[0]]; + const bProcessorPartitions = claimedPartitionsMap[eventProcessorIds[1]]; + + // The delta between number of partitions each processor owns can't be more than 1. + if (Math.abs(aProcessorPartitions.size - bProcessorPartitions.size) > 1) { + lastLoopError = { + reason: "Delta between partitions is greater than 1", + a: Array.from(aProcessorPartitions), + b: Array.from(bProcessorPartitions), + partitionOwnershipHistory + }; + return false; + } + + // All partitions must be claimed. + const allPartitionsClaimed = + aProcessorPartitions.size + bProcessorPartitions.size === partitionIds.length; + + if (!allPartitionsClaimed) { + lastLoopError = { + reason: "All partitions not claimed", + partitionIds, + a: Array.from(aProcessorPartitions), + b: Array.from(bProcessorPartitions), + partitionOwnershipHistory + }; + } + + return allPartitionsClaimed; + } + }); + } catch (err) { + // close processors + await Promise.all([processor1.stop(), processor2.stop()]); + throw err; + } + + loggerForTest(`All partitions have been claimed.`); + allPartitionsClaimed = true; + + try { + // loop for some time to see if thrashing occurs + await loopUntil({ + name: "partitionThrash", + maxTimes: 4, + timeBetweenRunsMs: 1000, + until: async () => thrashAfterSettling + }); + } catch (err) { + // swallow error, check trashAfterSettling for the condition in finally + } finally { + await Promise.all([processor1.stop(), processor2.stop()]); + should.equal( + thrashAfterSettling, + false, + "Detected PartitionOwnership thrashing after load-balancing has settled." + ); + } + }); }); }).timeout(90000); From 793d95d8c32af1e72c65a7dcf871ef1e55c3c6a5 Mon Sep 17 00:00:00 2001 From: chradek Date: Wed, 24 Jun 2020 17:49:11 -0700 Subject: [PATCH 10/28] update docs --- sdk/eventhub/event-hubs/src/models/public.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sdk/eventhub/event-hubs/src/models/public.ts b/sdk/eventhub/event-hubs/src/models/public.ts index 4ce493fd7c33..1f05d708cb8a 100644 --- a/sdk/eventhub/event-hubs/src/models/public.ts +++ b/sdk/eventhub/event-hubs/src/models/public.ts @@ -85,7 +85,7 @@ export enum CloseReason { /** * The EventProcessor was shutdown. */ - Shutdown = "Shutdown", + Shutdown = "Shutdown" } /** @@ -164,6 +164,10 @@ export interface LoadBalancingOptions { /** * Whether to apply a greedy or a more balanced approach when * claiming partitions. + * + * This option is ignored when either: + * - `CheckpointStore` is __not__ provided to the `EventHubConsumerClient`. + * - `subscribe()` is called for a single partition. * Default: balanced */ strategy?: "balanced" | "greedy"; From d5f15e5f08ad526aec3cd66ea7b5fe4d9d7f9aa4 Mon Sep 17 00:00:00 2001 From: chradek Date: Wed, 24 Jun 2020 18:09:46 -0700 Subject: [PATCH 11/28] update version to 5.3.0-preview.1 --- sdk/eventhub/event-hubs/package.json | 2 +- sdk/eventhub/event-hubs/src/util/constants.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/eventhub/event-hubs/package.json b/sdk/eventhub/event-hubs/package.json index 5b5f57ddbe03..2e7423b66d23 100644 --- a/sdk/eventhub/event-hubs/package.json +++ b/sdk/eventhub/event-hubs/package.json @@ -1,7 +1,7 @@ { "name": "@azure/event-hubs", "sdk-type": "client", - "version": "5.2.2", + "version": "5.3.0-preview.1", "description": "Azure Event Hubs SDK for JS.", "author": "Microsoft Corporation", "license": "MIT", diff --git a/sdk/eventhub/event-hubs/src/util/constants.ts b/sdk/eventhub/event-hubs/src/util/constants.ts index e9da46faef1f..96a3fb178469 100644 --- a/sdk/eventhub/event-hubs/src/util/constants.ts +++ b/sdk/eventhub/event-hubs/src/util/constants.ts @@ -6,5 +6,5 @@ */ export const packageJsonInfo = { name: "@azure/event-hubs", - version: "5.2.2" + version: "5.3.0-preview.1" }; From 961795ec6927da33a94abb3c80c62a74ca47a648 Mon Sep 17 00:00:00 2001 From: chradek Date: Wed, 24 Jun 2020 18:44:09 -0700 Subject: [PATCH 12/28] add changelog --- sdk/eventhub/event-hubs/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sdk/eventhub/event-hubs/CHANGELOG.md b/sdk/eventhub/event-hubs/CHANGELOG.md index 46598ff05d1f..61f8fd635a35 100644 --- a/sdk/eventhub/event-hubs/CHANGELOG.md +++ b/sdk/eventhub/event-hubs/CHANGELOG.md @@ -1,5 +1,11 @@ # Release History +## 5.3.0-preview.1 (Unreleased) + +- Adds `loadBalancingOptions` to the `EventHubConsumerClient` to add control around + how aggressively the client claims partitions while load balancing. + ([PR 9706](https://github.com/Azure/azure-sdk-for-js/pull/9706)) + ## 5.2.2 (Unreleased) - Fixes issue [#9289](https://github.com/Azure/azure-sdk-for-js/issues/9289) From 41202b538dbb15dd220a2381886fdb6b36bd4663 Mon Sep 17 00:00:00 2001 From: chradek Date: Wed, 24 Jun 2020 20:18:27 -0700 Subject: [PATCH 13/28] update pnpm-lock --- common/config/rush/pnpm-lock.yaml | 72 ++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index c15c1c1b3df9..386b6e7be206 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -37,6 +37,12 @@ dependencies: '@rush-temp/testhub': 'file:projects/testhub.tgz' lockfileVersion: 5.1 packages: + /@azure/abort-controller/1.0.1: + dependencies: + tslib: 1.13.0 + dev: false + resolution: + integrity: sha512-wP2Jw6uPp8DEDy0n4KNidvwzDjyVV2xnycEIq7nPzj1rHyb/r+t3OPeNT1INZePP2wy5ZqlwyuyOMTi0ePyY1A== /@azure/amqp-common/1.0.0-preview.9: dependencies: '@azure/ms-rest-nodeauth': 0.9.3 @@ -58,6 +64,44 @@ packages: dev: false resolution: integrity: sha512-RVG1Ad3Afv9gwFFmpeCXQAm+Sa0L8KEZRJJAAZEGoYDb6EoO1iQDVmoBz720h8mdrGpi0D60xNU/KhriIwuZfQ== + /@azure/core-amqp/1.1.3: + dependencies: + '@azure/abort-controller': 1.0.1 + '@azure/core-auth': 1.1.2 + '@azure/logger': 1.0.0 + '@types/async-lock': 1.1.2 + '@types/is-buffer': 2.0.0 + async-lock: 1.2.4 + buffer: 5.6.0 + debug: 4.1.1 + events: 3.1.0 + is-buffer: 2.0.4 + jssha: 2.4.2 + process: 0.11.10 + rhea: 1.0.22 + rhea-promise: 1.0.0 + stream-browserify: 2.0.2 + tslib: 1.13.0 + url: 0.11.0 + util: 0.12.3 + dev: false + engines: + node: '>=8.0.0' + resolution: + integrity: sha512-ngWnxjXy9CIu9BL+vE6r/oxGhwZMQAn2aqPaJdTmHcKW7rp3L1lfpI1I5pCj4B4alJA3s+P+O3iBsUVo4vM3BA== + /@azure/core-asynciterator-polyfill/1.0.0: + dev: false + resolution: + integrity: sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg== + /@azure/core-auth/1.1.2: + dependencies: + '@azure/abort-controller': 1.0.1 + '@azure/core-tracing': 1.0.0-preview.8 + '@opentelemetry/api': 0.6.1 + tslib: 1.13.0 + dev: false + resolution: + integrity: sha512-IUbP/f3v96dpHgXUwsAjUwDzjlUjawyUhWhGKKB6Qxy+iqppC/pVBPyc6kdpyTe7H30HN+4H3f0lar7Wp9Hx6A== /@azure/core-tracing/1.0.0-preview.8: dependencies: '@opencensus/web-types': 0.0.7 @@ -96,12 +140,34 @@ packages: dev: false resolution: integrity: sha512-CxaMaEjwtsmIhWtjHyGimKO7RmES0YxPqGQ9+jKqGygNlhG5NYHktDaiQu6w7k3g+I51VaLXtVSt+BVFd6VWfQ== + /@azure/event-hubs/5.2.1: + dependencies: + '@azure/abort-controller': 1.0.1 + '@azure/core-amqp': 1.1.3 + '@azure/core-asynciterator-polyfill': 1.0.0 + '@azure/core-tracing': 1.0.0-preview.8 + '@azure/logger': 1.0.0 + '@opentelemetry/api': 0.6.1 + buffer: 5.6.0 + process: 0.11.10 + rhea-promise: 1.0.0 + tslib: 1.13.0 + uuid: 8.1.0 + dev: false + resolution: + integrity: sha512-e9NTEokfU9WnBvDS+FfBsHpIxMa7XC2tGv2frQnVmP4XQjL7vr6cEuDhQve7HKml/2uMm3iwEyRqLBF5k/5yMA== /@azure/logger-js/1.3.2: dependencies: tslib: 1.13.0 dev: false resolution: integrity: sha512-h58oEROO2tniBTSmFmuHBGvuiFuYsHQBWTVdpT2AiOED4F2Kgf7rs0MPYPXiBcDvihC70M7QPRhIQ3JK1H/ygw== + /@azure/logger/1.0.0: + dependencies: + tslib: 1.13.0 + dev: false + resolution: + integrity: sha512-g2qLDgvmhyIxR3JVS8N67CyIOeFRKQlX/llxYJQr1OSGQqM3HTpVP8MjmjcEKbL/OIt2N9C9UFaNQuKOw1laOA== /@azure/ms-rest-azure-env/1.1.2: dev: false resolution: @@ -8194,6 +8260,7 @@ packages: version: 0.0.0 'file:projects/eventhubs-checkpointstore-blob.tgz': dependencies: + '@azure/event-hubs': 5.2.1 '@microsoft/api-extractor': 7.7.11 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-inject': 4.0.2_rollup@1.32.1 @@ -8253,7 +8320,7 @@ packages: dev: false name: '@rush-temp/eventhubs-checkpointstore-blob' resolution: - integrity: sha512-WeR08au7K0lgwkV/nQjy3NP/6dOrJ9YBd2C4fPnaW01kDV65CeAwtbNbDRR0gqfzGZYi62Q3gFEdfzsuJjL2Wg== + integrity: sha512-1+5KG38UVMR9/1H8zbBQrBzSXKxlw/gzdncuVOItYtANVhn10A9PnKkuIcYSNZ8SMWZNm/4lmfOOABT8Cp/cXg== tarball: 'file:projects/eventhubs-checkpointstore-blob.tgz' version: 0.0.0 'file:projects/identity.tgz': @@ -8973,6 +9040,7 @@ packages: assert: 1.5.0 chai: 4.2.0 cross-env: 7.0.2 + downlevel-dts: 0.4.0 eslint: 6.8.0 eslint-config-prettier: 6.11.0_eslint@6.8.0 eslint-plugin-no-null: 1.0.2_eslint@6.8.0 @@ -9005,7 +9073,7 @@ packages: dev: false name: '@rush-temp/tables' resolution: - integrity: sha512-pzHEh+D5Auu3UVyzd0cmFT2/gIbW7IkVUPVxCgHaoCH9i0V1lOksAtztdOD/JslOZ+R/wNsEn9vZVyOhJdnWSA== + integrity: sha512-PkcIhJI6GpcriJW2LEcqH851g5auP5vWnoIeyCFv1cmP5Hq4FdrrcYtP17BNGdm/UoVORZ2KFBvZEpFLXYJ+XA== tarball: 'file:projects/tables.tgz' version: 0.0.0 'file:projects/template.tgz': From dc03b06bfe479526b3c207ea94eaa39fa452a0d7 Mon Sep 17 00:00:00 2001 From: chradek Date: Thu, 25 Jun 2020 15:30:37 -0700 Subject: [PATCH 14/28] address feedback --- .../event-hubs/src/eventHubConsumerClient.ts | 15 ++++--- sdk/eventhub/event-hubs/src/eventProcessor.ts | 30 ++++--------- sdk/eventhub/event-hubs/src/models/public.ts | 42 +++++++++---------- 3 files changed, 39 insertions(+), 48 deletions(-) diff --git a/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts b/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts index d6d2a93ac7f8..8820fdf94fc4 100644 --- a/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts +++ b/sdk/eventhub/event-hubs/src/eventHubConsumerClient.ts @@ -85,7 +85,7 @@ export class EventHubConsumerClient { /** * Options for configuring load balancing. */ - private readonly _loadBalancingOptions: LoadBalancingOptions; + private readonly _loadBalancingOptions: Required; /** * @property @@ -323,10 +323,13 @@ export class EventHubConsumerClient { this._clientOptions ); } - this._loadBalancingOptions = this._clientOptions?.loadBalancingOptions ?? { + this._loadBalancingOptions = { + // default options strategy: "balanced", updateIntervalInMs: 10000, - partitionOwnershipExpirationIntervalInMs: 60000 + partitionOwnershipExpirationIntervalInMs: 60000, + // options supplied by user + ...this._clientOptions?.loadBalancingOptions }; } @@ -497,8 +500,8 @@ export class EventHubConsumerClient { return new UnbalancedLoadBalancingStrategy(); } - const partitionOwnershipExpirationIntervalInMs = - this._loadBalancingOptions.partitionOwnershipExpirationIntervalInMs || 60000; + const partitionOwnershipExpirationIntervalInMs = this._loadBalancingOptions + .partitionOwnershipExpirationIntervalInMs; if (this._loadBalancingOptions?.strategy === "greedy") { return new GreedyLoadBalancingStrategy(partitionOwnershipExpirationIntervalInMs); } @@ -536,7 +539,7 @@ export class EventHubConsumerClient { ownerId: this._id, retryOptions: this._clientOptions.retryOptions, loadBalancingStrategy, - loopIntervalInMs: this._loadBalancingOptions.updateIntervalInMs ?? 10000 + loopIntervalInMs: this._loadBalancingOptions.updateIntervalInMs } ); diff --git a/sdk/eventhub/event-hubs/src/eventProcessor.ts b/sdk/eventhub/event-hubs/src/eventProcessor.ts index d31c222959c5..337e2f0fc99f 100644 --- a/sdk/eventhub/event-hubs/src/eventProcessor.ts +++ b/sdk/eventhub/event-hubs/src/eventProcessor.ts @@ -420,10 +420,6 @@ export class EventProcessor { const { partitionIds } = await this._context.managementSession!.getEventHubProperties({ abortSignal: abortSignal }); - - // Renew the EventProcessor's partition ownerships that are still active. - // await this._renewPartitionOwnership(abortSignal); - await this._performLoadBalancing(loadBalancingStrategy, partitionIds, abortSignal); } catch (err) { logger.warning( @@ -467,7 +463,7 @@ export class EventProcessor { if (abortSignal.aborted) return; const partitionOwnershipMap = new Map(); - const abandonedPartitionOwnershipMap = new Map(); + const nonAbandonedPartitionOwnershipMap = new Map(); const partitionsToRenew: string[] = []; // Separate abandoned ownerships from claimed ownerships. @@ -475,10 +471,9 @@ export class EventProcessor { // load balancer, but we need to hold onto the abandoned // partition ownerships because we need the etag to claim them. for (const ownership of partitionOwnership) { - if (isAbandoned(ownership)) { - abandonedPartitionOwnershipMap.set(ownership.partitionId, ownership); - } else { - partitionOwnershipMap.set(ownership.partitionId, ownership); + partitionOwnershipMap.set(ownership.partitionId, ownership); + if (!isAbandoned(ownership)) { + nonAbandonedPartitionOwnershipMap.set(ownership.partitionId, ownership); } if ( ownership.ownerId === this._id && @@ -493,7 +488,7 @@ export class EventProcessor { // We exclude the abandoned partition ownerships to simplify the load balancing logic. const partitionsToClaim = loadBalancingStrategy.identifyPartitionsToClaim( this._id, - partitionOwnershipMap, + nonAbandonedPartitionOwnershipMap, partitionIds ); partitionsToClaim.push(...partitionsToRenew); @@ -502,17 +497,10 @@ export class EventProcessor { for (const partitionToClaim of uniquePartitionsToClaim) { let partitionOwnershipRequest: PartitionOwnership; - if (abandonedPartitionOwnershipMap.has(partitionToClaim)) { - partitionOwnershipRequest = this._createPartitionOwnershipRequest( - abandonedPartitionOwnershipMap, - partitionToClaim - ); - } else { - partitionOwnershipRequest = this._createPartitionOwnershipRequest( - partitionOwnershipMap, - partitionToClaim - ); - } + partitionOwnershipRequest = this._createPartitionOwnershipRequest( + partitionOwnershipMap, + partitionToClaim + ); await this._claimOwnership(partitionOwnershipRequest, abortSignal); } diff --git a/sdk/eventhub/event-hubs/src/models/public.ts b/sdk/eventhub/event-hubs/src/models/public.ts index 1f05d708cb8a..a596bd43d3f5 100644 --- a/sdk/eventhub/event-hubs/src/models/public.ts +++ b/sdk/eventhub/event-hubs/src/models/public.ts @@ -129,30 +129,30 @@ export interface EventHubClientOptions { } /** - * Describes the options that can be provided while creating the EventHubConsumerClient. + * An options bag to configure load balancing settings. + * - `loadBalancingOptions`: Options to tune how the EventHubConsumerClient claims partitions. + * - `userAgent` : A string to append to the built in user agent string that is passed as a connection property + * to the service. + * - `webSocketOptions` : Options to configure the channelling of the AMQP connection over Web Sockets. + * - `websocket` : The WebSocket constructor used to create an AMQP connection if you choose to make the connection + * over a WebSocket. + * - `webSocketConstructorOptions` : Options to pass to the Websocket constructor when you choose to make the connection + * over a WebSocket. + * - `retryOptions` : The retry options for all the operations on the EventHubConsumerClient. + * A simple usage can be `{ "maxRetries": 4 }`. + * + * Example usage: + * ```js + * { + * retryOptions: { + * maxRetries: 4 + * } + * } + * ``` */ export interface EventHubConsumerClientOptions extends EventHubClientOptions { /** - * An options bag to configure load balancing settings. - * - `loadBalancingOptions`: Options to tune how the EventHubConsumerClient claims partitions. - * - `userAgent` : A string to append to the built in user agent string that is passed as a connection property - * to the service. - * - `webSocketOptions` : Options to configure the channelling of the AMQP connection over Web Sockets. - * - `websocket` : The WebSocket constructor used to create an AMQP connection if you choose to make the connection - * over a WebSocket. - * - `webSocketConstructorOptions` : Options to pass to the Websocket constructor when you choose to make the connection - * over a WebSocket. - * - `retryOptions` : The retry options for all the operations on the EventHubConsumerClient. - * A simple usage can be `{ "maxRetries": 4 }`. - * - * Example usage: - * ```js - * { - * retryOptions: { - * maxRetries: 4 - * } - * } - * ``` + * Options to tune how the EventHubConsumerClient claims partitions. */ loadBalancingOptions?: LoadBalancingOptions; } From f67b0054fbe105ee628083f0a8eb5bdc9485fd6d Mon Sep 17 00:00:00 2001 From: chradek Date: Mon, 29 Jun 2020 14:26:54 -0700 Subject: [PATCH 15/28] add explicity existance check to partitionOwnership.lastModifiedTimeInMs --- .../src/loadBalancerStrategies/loadBalancingStrategy.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts index 17996705077d..eb96790e2edc 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts @@ -70,7 +70,10 @@ function getActivePartitionOwnerships( const activePartitionOwnershipMap: Map = new Map(); partitionOwnershipMap.forEach((partitionOwnership: PartitionOwnership, partitionId: string) => { // If lastModifiedtimeInMs is missing, assume it is inactive. - if (!partitionOwnership.lastModifiedTimeInMs) { + if ( + typeof partitionOwnership.lastModifiedTimeInMs === "undefined" || + partitionOwnership.lastModifiedTimeInMs === null + ) { return; } From 82828f1857cf5e8ed020bd7a30f1ee761e3e01c3 Mon Sep 17 00:00:00 2001 From: chradek Date: Mon, 29 Jun 2020 14:41:01 -0700 Subject: [PATCH 16/28] trashing -> thrashing --- .../event-hubs/src/loadBalancerStrategies/balancedStrategy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts index bb3fef0519b2..dfd5ca82cd29 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts @@ -8,7 +8,7 @@ import { LoadBalancingStrategy, identifyClaimablePartitions } from "./loadBalanc * The BalancedLoadBalancerStrategy is meant to be used when the user * wants to reach a load balanced state with less partition 'thrashing'. * - * Partition trashing - where a partition changes owners - is minimized + * Partition thrashing - where a partition changes owners - is minimized * by only returning a single partition to claim at a time. * This minimizes the number of times a partition should need to be stolen. * @internal From 9a97ab4b5d30b7fe4482af6ed5605ee0fe4e52ae Mon Sep 17 00:00:00 2001 From: chradek Date: Mon, 29 Jun 2020 14:42:04 -0700 Subject: [PATCH 17/28] be smarter about else-if statements --- .../loadBalancingStrategy.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts index eb96790e2edc..27715b1cb178 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts @@ -149,20 +149,16 @@ function getEventProcessorCounts( // there are basically three kinds of partition counts // for a processor: - // 1. Has _exactly_ the required number of partitions if (numberOfPartitions === minPartitionsPerOwner) { + // 1. Has _exactly_ the required number of partitions counts.haveRequiredPartitions++; - } - - // 2. Has the required number plus one extra (correct in cases) - // where the # of partitions is not evenly divisible by the - // number of processors. - if (numberOfPartitions === minPartitionsPerOwner + 1) { + } else if (numberOfPartitions === minPartitionsPerOwner + 1) { + // 2. Has the required number plus one extra (correct in cases) + // where the # of partitions is not evenly divisible by the + // number of processors. counts.haveAdditionalPartition++; - } - - // 3. has more than the possible # of partitions required - if (numberOfPartitions > minPartitionsPerOwner + 1) { + } else if (numberOfPartitions > minPartitionsPerOwner + 1) { + // 3. has more than the possible # of partitions required counts.haveTooManyPartitions++; } } From 0a6a35475f4e116402687b1a7bc5b0aa64834afa Mon Sep 17 00:00:00 2001 From: chradek Date: Mon, 29 Jun 2020 15:29:13 -0700 Subject: [PATCH 18/28] explain the magic number 6 --- sdk/eventhub/event-hubs/test/eventProcessor.spec.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts b/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts index b638a495b2bb..10bc808971d8 100644 --- a/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts +++ b/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts @@ -406,8 +406,14 @@ describe("Event Processor", function(): void { // we'll let one more go through just to make sure we're not going to // pick up an extra surprise partition // - // This particular behavior is really specific to the FairPartitionLoadBalancer but that's okay for now. - const numTimesAbortedIsCheckedInLoop = 5; + // There are 6 places where the abort signal is checked during the loop: + // - while condition + // - getEventHubProperties + // - _performLoadBalancing (start) + // - _performLoadBalancing (after listOwnership) + // - _performLoadBalancing (passed to _claimOwnership) + // - delay + const numTimesAbortedIsCheckedInLoop = 6; await ep["_runLoopWithLoadBalancing"]( ep["_loadBalancingStrategy"], triggerAbortedSignalAfterNumCalls(partitionIds.length * numTimesAbortedIsCheckedInLoop) From ceb7c9aff6a50570f359753efe713e943ab1baff Mon Sep 17 00:00:00 2001 From: chradek Date: Mon, 29 Jun 2020 15:32:09 -0700 Subject: [PATCH 19/28] identifyPartitionsToClaim -> getPartitionsToClaim --- sdk/eventhub/event-hubs/src/eventProcessor.ts | 2 +- .../balancedStrategy.ts | 2 +- .../loadBalancerStrategies/greedyStrategy.ts | 2 +- .../loadBalancingStrategy.ts | 2 +- .../unbalancedStrategy.ts | 2 +- .../test/loadBalancingStrategy.spec.ts | 78 ++++++++----------- 6 files changed, 36 insertions(+), 52 deletions(-) diff --git a/sdk/eventhub/event-hubs/src/eventProcessor.ts b/sdk/eventhub/event-hubs/src/eventProcessor.ts index 337e2f0fc99f..df8fec638dc5 100644 --- a/sdk/eventhub/event-hubs/src/eventProcessor.ts +++ b/sdk/eventhub/event-hubs/src/eventProcessor.ts @@ -486,7 +486,7 @@ export class EventProcessor { // Pass the list of all the partition ids and the collection of claimed partition ownerships // to the load balance strategy. // We exclude the abandoned partition ownerships to simplify the load balancing logic. - const partitionsToClaim = loadBalancingStrategy.identifyPartitionsToClaim( + const partitionsToClaim = loadBalancingStrategy.getPartitionsToCliam( this._id, nonAbandonedPartitionOwnershipMap, partitionIds diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts index dfd5ca82cd29..57b10f0ff23d 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts @@ -30,7 +30,7 @@ export class BalancedLoadBalancingStrategy implements LoadBalancingStrategy { * @param partitionIds Partitions to assign owners to. * @returns Partition ids to claim. */ - public identifyPartitionsToClaim( + public getPartitionsToCliam( ourOwnerId: string, claimedPartitionOwnershipMap: Map, partitionIds: string[] diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts index af7c4d1ae24e..7655421e2aab 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts @@ -24,7 +24,7 @@ export class GreedyLoadBalancingStrategy implements LoadBalancingStrategy { * @param partitionIds Partitions to assign owners to. * @returns Partition ids to claim. */ - public identifyPartitionsToClaim( + public getPartitionsToCliam( ourOwnerId: string, claimedPartitionOwnershipMap: Map, partitionIds: string[] diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts index 27715b1cb178..013f8994c83c 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts @@ -18,7 +18,7 @@ export interface LoadBalancingStrategy { * @param partitionIds Partitions to assign owners to. * @returns Partition ids to claim. */ - identifyPartitionsToClaim( + getPartitionsToCliam( ownerId: string, claimedPartitionOwnershipMap: Map, partitionIds: string[] diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/unbalancedStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/unbalancedStrategy.ts index 580c0503a9af..549b7970bb8e 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/unbalancedStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/unbalancedStrategy.ts @@ -20,7 +20,7 @@ export class UnbalancedLoadBalancingStrategy implements LoadBalancingStrategy { * @param partitionIds Partitions to assign owners to. * @returns Partition ids to claim. */ - public identifyPartitionsToClaim( + public getPartitionsToCliam( _ourOwnerId: string, _claimedPartitionOwnershipMap: Map, partitionIds: string[] diff --git a/sdk/eventhub/event-hubs/test/loadBalancingStrategy.spec.ts b/sdk/eventhub/event-hubs/test/loadBalancingStrategy.spec.ts index 2537ea430b25..927376da4d2b 100644 --- a/sdk/eventhub/event-hubs/test/loadBalancingStrategy.spec.ts +++ b/sdk/eventhub/event-hubs/test/loadBalancingStrategy.spec.ts @@ -32,7 +32,7 @@ describe("LoadBalancingStrategy", () => { const m = new Map(); const lb = new UnbalancedLoadBalancingStrategy(); - lb.identifyPartitionsToClaim("ownerId", m, ["1", "2", "3"]).should.deep.eq(["1", "2", "3"]); + lb.getPartitionsToCliam("ownerId", m, ["1", "2", "3"]).should.deep.eq(["1", "2", "3"]); m.should.be.empty; }); @@ -61,7 +61,7 @@ describe("LoadBalancingStrategy", () => { const lb = new UnbalancedLoadBalancingStrategy(); - lb.identifyPartitionsToClaim("ownerId", m, ["1", "2", "3"]).should.deep.eq(["1", "2", "3"]); + lb.getPartitionsToCliam("ownerId", m, ["1", "2", "3"]).should.deep.eq(["1", "2", "3"]); }); }); @@ -73,7 +73,7 @@ describe("LoadBalancingStrategy", () => { // at this point 'a' has it's fair share of partitions (there are 3 total) // and it's okay to have 1 extra. - let partitionsToOwn = lb.identifyPartitionsToClaim( + let partitionsToOwn = lb.getPartitionsToCliam( "a", createOwnershipMap({ "1": "b", @@ -90,7 +90,7 @@ describe("LoadBalancingStrategy", () => { // now the other side of this is when we're fighting for the ownership of an // extra partition - partitionsToOwn = lb.identifyPartitionsToClaim( + partitionsToOwn = lb.getPartitionsToCliam( "a", createOwnershipMap({ "1": "b", @@ -110,7 +110,7 @@ describe("LoadBalancingStrategy", () => { // at this point 'a' has it's fair share of partitions (there are 4 total) // so it'll stop claiming additional partitions. - let partitionsToOwn = lb.identifyPartitionsToClaim( + let partitionsToOwn = lb.getPartitionsToCliam( "a", createOwnershipMap({ "1": "b", @@ -125,7 +125,7 @@ describe("LoadBalancingStrategy", () => { "we've gotten our fair share, shouldn't claim anything new" ); - partitionsToOwn = lb.identifyPartitionsToClaim( + partitionsToOwn = lb.getPartitionsToCliam( "a", createOwnershipMap({ "0": "b", @@ -148,7 +148,7 @@ describe("LoadBalancingStrategy", () => { it("stealing", () => { // something like this could happen if 'a' were just the only processor // and now we're spinning up 'b' - let partitionsToOwn = lb.identifyPartitionsToClaim( + let partitionsToOwn = lb.getPartitionsToCliam( "b", createOwnershipMap({ "0": "a", @@ -165,7 +165,7 @@ describe("LoadBalancingStrategy", () => { ); // and now the same case as above, but with an even number of partitions per processor. - partitionsToOwn = lb.identifyPartitionsToClaim( + partitionsToOwn = lb.getPartitionsToCliam( "b", createOwnershipMap({ "0": "a", @@ -220,11 +220,7 @@ describe("LoadBalancingStrategy", () => { "9": "d" }); - const requestedPartitions = lb.identifyPartitionsToClaim( - "c", - initialOwnershipMap, - partitions - ); + const requestedPartitions = lb.getPartitionsToCliam("c", initialOwnershipMap, partitions); requestedPartitions.sort(); requestedPartitions.should.deep.equal( @@ -237,7 +233,7 @@ describe("LoadBalancingStrategy", () => { // this is a case where we shouldn't steal - we have // the minimum number of partitions and stealing at this // point will just keep thrashing both processors. - const partitionsToOwn = lb.identifyPartitionsToClaim( + const partitionsToOwn = lb.getPartitionsToCliam( "b", createOwnershipMap({ "0": "a", @@ -255,15 +251,11 @@ describe("LoadBalancingStrategy", () => { const allPartitions = ["0", "1", "2", "3"]; // in the presence of no owners we claim a random partition - let partitionsToOwn = lb.identifyPartitionsToClaim( - "a", - createOwnershipMap({}), - allPartitions - ); + let partitionsToOwn = lb.getPartitionsToCliam("a", createOwnershipMap({}), allPartitions); partitionsToOwn.length.should.be.equal(1, "nothing is owned, claim one"); // if there are other owners we should claim up to #partitions/#owners - partitionsToOwn = lb.identifyPartitionsToClaim( + partitionsToOwn = lb.getPartitionsToCliam( "a", createOwnershipMap({ "1": "b", @@ -276,7 +268,7 @@ describe("LoadBalancingStrategy", () => { partitionsToOwn.filter((p) => p === "1").length.should.equal(0); // 'b' should claim the last unowned partition - partitionsToOwn = lb.identifyPartitionsToClaim( + partitionsToOwn = lb.getPartitionsToCliam( "b", createOwnershipMap({ "1": "b", @@ -289,7 +281,7 @@ describe("LoadBalancingStrategy", () => { partitionsToOwn.should.be.deep.equal(["0"], "b grabbed the last available partition"); // we're balanced - processors now only grab the partitions that they own - partitionsToOwn = lb.identifyPartitionsToClaim( + partitionsToOwn = lb.getPartitionsToCliam( "b", createOwnershipMap({ "0": "b", @@ -313,14 +305,14 @@ describe("LoadBalancingStrategy", () => { }); // At this point, 'a' has its fair share of partitions, and none should be returned. - let partitionsToOwn = lb.identifyPartitionsToClaim("a", ownershipMap, allPartitions); + let partitionsToOwn = lb.getPartitionsToCliam("a", ownershipMap, allPartitions); partitionsToOwn.length.should.equal(0, "Expected to not claim any new partitions."); // Change the ownership of partition "0" so it is older than the interval. const ownership = ownershipMap.get("0")!; ownership.lastModifiedTimeInMs = Date.now() - (intervalInMs + 1); // Add 1 to the interval to ensure it has just expired. - partitionsToOwn = lb.identifyPartitionsToClaim("a", ownershipMap, allPartitions); + partitionsToOwn = lb.getPartitionsToCliam("a", ownershipMap, allPartitions); partitionsToOwn.should.deep.equal(["0"]); }); }); @@ -333,7 +325,7 @@ describe("LoadBalancingStrategy", () => { // at this point 'a' has it's fair share of partitions (there are 3 total) // and it's okay to have 1 extra. - let partitionsToOwn = lb.identifyPartitionsToClaim( + let partitionsToOwn = lb.getPartitionsToCliam( "a", createOwnershipMap({ "1": "b", @@ -350,7 +342,7 @@ describe("LoadBalancingStrategy", () => { // now the other side of this is when we're fighting for the ownership of an // extra partition - partitionsToOwn = lb.identifyPartitionsToClaim( + partitionsToOwn = lb.getPartitionsToCliam( "a", createOwnershipMap({ "1": "b", @@ -370,7 +362,7 @@ describe("LoadBalancingStrategy", () => { // at this point 'a' has it's fair share of partitions (there are 4 total) // so it'll stop claiming additional partitions. - let partitionsToOwn = lb.identifyPartitionsToClaim( + let partitionsToOwn = lb.getPartitionsToCliam( "a", createOwnershipMap({ "1": "b", @@ -385,7 +377,7 @@ describe("LoadBalancingStrategy", () => { "we've gotten our fair share, shouldn't claim anything new" ); - partitionsToOwn = lb.identifyPartitionsToClaim( + partitionsToOwn = lb.getPartitionsToCliam( "a", createOwnershipMap({ "0": "b", @@ -408,7 +400,7 @@ describe("LoadBalancingStrategy", () => { it("stealing", () => { // something like this could happen if 'a' were just the only processor // and now we're spinning up 'b' - let partitionsToOwn = lb.identifyPartitionsToClaim( + let partitionsToOwn = lb.getPartitionsToCliam( "b", createOwnershipMap({ "0": "a", @@ -425,7 +417,7 @@ describe("LoadBalancingStrategy", () => { ); // and now the same case as above, but with an even number of partitions per processor. - partitionsToOwn = lb.identifyPartitionsToClaim( + partitionsToOwn = lb.getPartitionsToCliam( "b", createOwnershipMap({ "0": "a", @@ -449,7 +441,7 @@ describe("LoadBalancingStrategy", () => { allPartitions.push(`${i}`); } - let partitionsToOwn = lb.identifyPartitionsToClaim( + let partitionsToOwn = lb.getPartitionsToCliam( "a", createOwnershipMap({ "0": "", @@ -508,11 +500,7 @@ describe("LoadBalancingStrategy", () => { "9": "d" }); - const requestedPartitions = lb.identifyPartitionsToClaim( - "c", - initialOwnershipMap, - partitions - ); + const requestedPartitions = lb.getPartitionsToCliam("c", initialOwnershipMap, partitions); requestedPartitions.sort(); requestedPartitions.should.deep.equal( @@ -525,7 +513,7 @@ describe("LoadBalancingStrategy", () => { // this is a case where we shouldn't steal - we have // the minimum number of partitions and stealing at this // point will just keep thrashing both processors. - const partitionsToOwn = lb.identifyPartitionsToClaim( + const partitionsToOwn = lb.getPartitionsToCliam( "b", createOwnershipMap({ "0": "a", @@ -543,15 +531,11 @@ describe("LoadBalancingStrategy", () => { const allPartitions = ["0", "1", "2", "3"]; // in the presence of no owners we claim a random partition - let partitionsToOwn = lb.identifyPartitionsToClaim( - "a", - createOwnershipMap({}), - allPartitions - ); + let partitionsToOwn = lb.getPartitionsToCliam("a", createOwnershipMap({}), allPartitions); partitionsToOwn.length.should.be.equal(4, "nothing is owned, claim all"); // if there are other owners we should claim up to #partitions/#owners - partitionsToOwn = lb.identifyPartitionsToClaim( + partitionsToOwn = lb.getPartitionsToCliam( "a", createOwnershipMap({ "1": "b", @@ -564,7 +548,7 @@ describe("LoadBalancingStrategy", () => { partitionsToOwn.filter((p) => p === "1").length.should.equal(0); // 'b' should claim the last unowned partition - partitionsToOwn = lb.identifyPartitionsToClaim( + partitionsToOwn = lb.getPartitionsToCliam( "b", createOwnershipMap({ "1": "b", @@ -577,7 +561,7 @@ describe("LoadBalancingStrategy", () => { partitionsToOwn.should.be.deep.equal(["0"], "b grabbed the last available partition"); // we're balanced - processors now only grab the partitions that they own - partitionsToOwn = lb.identifyPartitionsToClaim( + partitionsToOwn = lb.getPartitionsToCliam( "b", createOwnershipMap({ "0": "b", @@ -601,7 +585,7 @@ describe("LoadBalancingStrategy", () => { }); // At this point, "a" should only grab 1 partition since both "a" and "b" should end up with 2 partitions each. - let partitionsToOwn = lb.identifyPartitionsToClaim("a", ownershipMap, allPartitions); + let partitionsToOwn = lb.getPartitionsToCliam("a", ownershipMap, allPartitions); partitionsToOwn.length.should.equal(1, "Expected to claim 1 new partitions."); // Change the ownership of partition "0" so it is older than the interval. @@ -611,7 +595,7 @@ describe("LoadBalancingStrategy", () => { // At this point, "a" should grab partitions 0, 2, and 3. // This is because "b" only owned 1 partition and that claim is expired, // so "a" as treated as if it is the only owner. - partitionsToOwn = lb.identifyPartitionsToClaim("a", ownershipMap, allPartitions); + partitionsToOwn = lb.getPartitionsToCliam("a", ownershipMap, allPartitions); partitionsToOwn.sort(); partitionsToOwn.should.deep.equal(["0", "2", "3"]); }); From 62add8a63ca302fa4573eefc9fc51b7f4abb33c8 Mon Sep 17 00:00:00 2001 From: chradek Date: Mon, 29 Jun 2020 15:43:15 -0700 Subject: [PATCH 20/28] identifyClaimablePartitions -> listAvailablePartitions --- .../event-hubs/src/loadBalancerStrategies/balancedStrategy.ts | 4 ++-- .../event-hubs/src/loadBalancerStrategies/greedyStrategy.ts | 4 ++-- .../src/loadBalancerStrategies/loadBalancingStrategy.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts index 57b10f0ff23d..fe3212a2c07e 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/balancedStrategy.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { PartitionOwnership } from "../eventProcessor"; -import { LoadBalancingStrategy, identifyClaimablePartitions } from "./loadBalancingStrategy"; +import { LoadBalancingStrategy, listAvailablePartitions } from "./loadBalancingStrategy"; /** * The BalancedLoadBalancerStrategy is meant to be used when the user @@ -35,7 +35,7 @@ export class BalancedLoadBalancingStrategy implements LoadBalancingStrategy { claimedPartitionOwnershipMap: Map, partitionIds: string[] ): string[] { - const claimablePartitions = identifyClaimablePartitions( + const claimablePartitions = listAvailablePartitions( ourOwnerId, claimedPartitionOwnershipMap, partitionIds, diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts index 7655421e2aab..77b6604e99a7 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/greedyStrategy.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { PartitionOwnership } from "../eventProcessor"; -import { LoadBalancingStrategy, identifyClaimablePartitions } from "./loadBalancingStrategy"; +import { LoadBalancingStrategy, listAvailablePartitions } from "./loadBalancingStrategy"; /** * @internal @@ -29,7 +29,7 @@ export class GreedyLoadBalancingStrategy implements LoadBalancingStrategy { claimedPartitionOwnershipMap: Map, partitionIds: string[] ): string[] { - return identifyClaimablePartitions( + return listAvailablePartitions( ourOwnerId, claimedPartitionOwnershipMap, partitionIds, diff --git a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts index 013f8994c83c..1aa0de11dc81 100644 --- a/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts +++ b/sdk/eventhub/event-hubs/src/loadBalancerStrategies/loadBalancingStrategy.ts @@ -293,7 +293,7 @@ function findPartitionsToSteal( * @internal * @ignore */ -export function identifyClaimablePartitions( +export function listAvailablePartitions( ownerId: string, claimedPartitionOwnershipMap: Map, partitionIds: string[], From a313b661589944880a64971b98c2576d6d6260d7 Mon Sep 17 00:00:00 2001 From: chradek Date: Mon, 29 Jun 2020 15:45:41 -0700 Subject: [PATCH 21/28] add better summary for EventHubConsumerClientOptions --- sdk/eventhub/event-hubs/src/models/public.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/eventhub/event-hubs/src/models/public.ts b/sdk/eventhub/event-hubs/src/models/public.ts index a596bd43d3f5..12add1857ce1 100644 --- a/sdk/eventhub/event-hubs/src/models/public.ts +++ b/sdk/eventhub/event-hubs/src/models/public.ts @@ -129,7 +129,7 @@ export interface EventHubClientOptions { } /** - * An options bag to configure load balancing settings. + * Describes the options that can be provided while creating the EventHubConsumerClient. * - `loadBalancingOptions`: Options to tune how the EventHubConsumerClient claims partitions. * - `userAgent` : A string to append to the built in user agent string that is passed as a connection property * to the service. From 5bb4827e0f5ff3359b7c084f3699f841b926b91e Mon Sep 17 00:00:00 2001 From: chradek Date: Mon, 29 Jun 2020 15:58:31 -0700 Subject: [PATCH 22/28] add better docs around greedy and balanced strategies --- sdk/eventhub/event-hubs/src/models/public.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sdk/eventhub/event-hubs/src/models/public.ts b/sdk/eventhub/event-hubs/src/models/public.ts index 12add1857ce1..73f8deaddf6c 100644 --- a/sdk/eventhub/event-hubs/src/models/public.ts +++ b/sdk/eventhub/event-hubs/src/models/public.ts @@ -165,6 +165,14 @@ export interface LoadBalancingOptions { * Whether to apply a greedy or a more balanced approach when * claiming partitions. * + * - balanced: The `EventHubConsumerClient` will take a measured approach to + * requesting partition ownership when balancing work with other clients, + * slowly claiming partitions until a stabilized distribution is achieved. + * + * - greedy: The `EventHubConsumerClient` will attempt to claim ownership + * of its fair share of partitions aggressively when balancing work with + * other clients. + * * This option is ignored when either: * - `CheckpointStore` is __not__ provided to the `EventHubConsumerClient`. * - `subscribe()` is called for a single partition. From 2420c2b236d3624b8c8b7d94315d145eaf79ddc0 Mon Sep 17 00:00:00 2001 From: chradek Date: Mon, 29 Jun 2020 15:59:25 -0700 Subject: [PATCH 23/28] remove superfluous doc from CommonEventProcessorOptions --- sdk/eventhub/event-hubs/src/models/private.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/eventhub/event-hubs/src/models/private.ts b/sdk/eventhub/event-hubs/src/models/private.ts index 2288397adcd3..a5464e88ddb7 100644 --- a/sdk/eventhub/event-hubs/src/models/private.ts +++ b/sdk/eventhub/event-hubs/src/models/private.ts @@ -40,7 +40,7 @@ export type OperationNames = "getEventHubProperties" | "getPartitionIds" | "getP * @internal * @ignore */ -export interface CommonEventProcessorOptions // make the 'maxBatchSize', 'maxWaitTimeInSeconds', 'ownerLevel' fields required extends for our internal classes (these are optional for external users) +export interface CommonEventProcessorOptions extends Required>, Pick< SubscribeOptions, From f20320411c522f0ec821ccb3681f73b6e4baf071 Mon Sep 17 00:00:00 2001 From: chradek Date: Mon, 29 Jun 2020 16:01:16 -0700 Subject: [PATCH 24/28] add comment around why we have abandoned partitions --- sdk/eventhub/event-hubs/src/eventProcessor.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/eventhub/event-hubs/src/eventProcessor.ts b/sdk/eventhub/event-hubs/src/eventProcessor.ts index df8fec638dc5..e8c9cf226e48 100644 --- a/sdk/eventhub/event-hubs/src/eventProcessor.ts +++ b/sdk/eventhub/event-hubs/src/eventProcessor.ts @@ -485,7 +485,9 @@ export class EventProcessor { // Pass the list of all the partition ids and the collection of claimed partition ownerships // to the load balance strategy. - // We exclude the abandoned partition ownerships to simplify the load balancing logic. + // The load balancing strategy only needs to know the full list of partitions, + // and which of those are currently claimed. + // Since abandoned partitions are no longer claimed, we exclude them. const partitionsToClaim = loadBalancingStrategy.getPartitionsToCliam( this._id, nonAbandonedPartitionOwnershipMap, From e1a9fdb020cc99dbfd4c0feafc768c2ec8e39a17 Mon Sep 17 00:00:00 2001 From: chradek Date: Mon, 29 Jun 2020 16:24:15 -0700 Subject: [PATCH 25/28] throw AbortError instead of silent return --- sdk/eventhub/event-hubs/src/eventProcessor.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/eventhub/event-hubs/src/eventProcessor.ts b/sdk/eventhub/event-hubs/src/eventProcessor.ts index e8c9cf226e48..336466a55810 100644 --- a/sdk/eventhub/event-hubs/src/eventProcessor.ts +++ b/sdk/eventhub/event-hubs/src/eventProcessor.ts @@ -3,7 +3,7 @@ import { v4 as uuid } from "uuid"; import { PumpManager, PumpManagerImpl } from "./pumpManager"; -import { AbortController, AbortSignalLike } from "@azure/abort-controller"; +import { AbortController, AbortSignalLike, AbortError } from "@azure/abort-controller"; import { logErrorStackTrace, logger } from "./log"; import { Checkpoint, PartitionProcessor } from "./partitionProcessor"; import { SubscriptionEventHandlers } from "./eventHubConsumerClientModels"; @@ -451,7 +451,7 @@ export class EventProcessor { partitionIds: string[], abortSignal: AbortSignalLike ) { - if (abortSignal.aborted) return; + if (abortSignal.aborted) throw new AbortError("The operation was aborted."); // Retrieve current partition ownership details from the datastore. const partitionOwnership = await this._checkpointStore.listOwnership( @@ -460,7 +460,7 @@ export class EventProcessor { this._consumerGroup ); - if (abortSignal.aborted) return; + if (abortSignal.aborted) throw new AbortError("The operation was aborted."); const partitionOwnershipMap = new Map(); const nonAbandonedPartitionOwnershipMap = new Map(); From 12c3cd6da1857bdaf942ae1071054d0dc0869dc7 Mon Sep 17 00:00:00 2001 From: chradek Date: Mon, 29 Jun 2020 16:24:32 -0700 Subject: [PATCH 26/28] remove unneeded receivingFrom method --- sdk/eventhub/event-hubs/src/pumpManager.ts | 7 ------- sdk/eventhub/event-hubs/test/eventProcessor.spec.ts | 3 --- 2 files changed, 10 deletions(-) diff --git a/sdk/eventhub/event-hubs/src/pumpManager.ts b/sdk/eventhub/event-hubs/src/pumpManager.ts index 2c4d9a37e84a..1471e4a435b4 100644 --- a/sdk/eventhub/event-hubs/src/pumpManager.ts +++ b/sdk/eventhub/event-hubs/src/pumpManager.ts @@ -41,13 +41,6 @@ export interface PumpManager { */ isReceivingFromPartition(partitionId: string): boolean; - /** - * Returns a list of partition ids that the pump manager is actively receiving events from. - * @ignore - * @internal - */ - receivingFromPartitions(): string[]; - /** * Stops all PartitionPumps and removes them from the internal map. * @param reason The reason for removing the pump. diff --git a/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts b/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts index 10bc808971d8..b7ee6c80f8bb 100644 --- a/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts +++ b/sdk/eventhub/event-hubs/test/eventProcessor.spec.ts @@ -391,9 +391,6 @@ describe("Event Processor", function(): void { async removeAllPumps(): Promise {}, isReceivingFromPartition() { return false; - }, - receivingFromPartitions() { - return []; } }, loadBalancingStrategy: new BalancedLoadBalancingStrategy(60000) From c2c31a72382fbbcae39666303c6d1d236bce937d Mon Sep 17 00:00:00 2001 From: chradek Date: Mon, 29 Jun 2020 16:30:13 -0700 Subject: [PATCH 27/28] update pnpm-lock.yaml --- common/config/rush/pnpm-lock.yaml | 89 ++++++++++++++++++++++++------- 1 file changed, 70 insertions(+), 19 deletions(-) diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 386b6e7be206..c607e76b99ef 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -19,6 +19,7 @@ dependencies: '@rush-temp/event-processor-host': 'file:projects/event-processor-host.tgz' '@rush-temp/eventhubs-checkpointstore-blob': 'file:projects/eventhubs-checkpointstore-blob.tgz' '@rush-temp/identity': 'file:projects/identity.tgz' + '@rush-temp/keyvault-admin': 'file:projects/keyvault-admin.tgz' '@rush-temp/keyvault-certificates': 'file:projects/keyvault-certificates.tgz' '@rush-temp/keyvault-common': 'file:projects/keyvault-common.tgz' '@rush-temp/keyvault-keys': 'file:projects/keyvault-keys.tgz' @@ -7476,7 +7477,7 @@ packages: dev: false name: '@rush-temp/ai-form-recognizer' resolution: - integrity: sha512-P0PSfCHnohJwrhyAU9rO7IkxOvg1bO2qkravGsjWMLAo2Fcj1GQKcbkXf+DdjRqT+DX8UmtaCXHoRgX8UFV+ow== + integrity: sha512-J6oJMlzTfy+mkGykMrSHXdI0Ge3dssflDYu6k4JrlYy87e19yJ+tgMNoEoUFYri95KO2ts34+BM9/sLaAIRV7w== tarball: 'file:projects/ai-form-recognizer.tgz' version: 0.0.0 'file:projects/ai-text-analytics.tgz': @@ -7535,7 +7536,7 @@ packages: dev: false name: '@rush-temp/ai-text-analytics' resolution: - integrity: sha512-B0be6oZmrFXIvVTYM45Dj1ftw9Z8q2Vq/TGSqH9qipxxkz2WE+sB3V5+diwfoUPssDJYJybfvisjmz6AkS00DA== + integrity: sha512-0qtWNE3in6ZnLbonLVPQViYfV5ab8pF9+dZiqta/xszQziRU6maqTTXfHX3H92zfBFLJhkyXuUFp5RRxCRX+yg== tarball: 'file:projects/ai-text-analytics.tgz' version: 0.0.0 'file:projects/app-configuration.tgz': @@ -7544,6 +7545,7 @@ packages: '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.6.1 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-inject': 4.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 '@rollup/plugin-node-resolve': 8.0.1_rollup@1.32.1 @@ -7554,6 +7556,7 @@ packages: '@types/sinon': 9.0.4 assert: 1.5.0 chai: 4.2.0 + cross-env: 7.0.2 dotenv: 8.2.0 eslint: 6.8.0 eslint-config-prettier: 6.11.0_eslint@6.8.0 @@ -7561,6 +7564,17 @@ packages: eslint-plugin-no-only-tests: 2.4.0 eslint-plugin-promise: 4.2.1 esm: 3.2.25 + karma: 4.4.1 + karma-chrome-launcher: 3.1.0 + karma-coverage: 2.0.2 + karma-edge-launcher: 0.4.2_karma@4.4.1 + karma-env-preprocessor: 0.1.1 + karma-firefox-launcher: 1.3.0 + karma-ie-launcher: 1.0.0_karma@4.4.1 + karma-junit-reporter: 2.0.1_karma@4.4.1 + karma-mocha: 1.3.0 + karma-mocha-reporter: 2.2.5_karma@4.4.1 + karma-remap-istanbul: 0.6.0_karma@4.4.1 mocha: 7.2.0 mocha-junit-reporter: 1.23.3_mocha@7.2.0 nock: 12.0.3 @@ -7568,6 +7582,7 @@ packages: prettier: 1.19.1 rimraf: 3.0.2 rollup: 1.32.1 + rollup-plugin-shim: 1.0.0 rollup-plugin-sourcemaps: 0.4.2_rollup@1.32.1 rollup-plugin-terser: 5.3.0_rollup@1.32.1 sinon: 9.0.2 @@ -7578,7 +7593,7 @@ packages: dev: false name: '@rush-temp/app-configuration' resolution: - integrity: sha512-/l/1QpBF1T8FJGve4d5OQEXHRcYKCQQJpeqY1lv32gJoo09wot54tviqFeqCnaXioTNiYzipcuWPb4FtAZ0Jhw== + integrity: sha512-AF3tZ0LP0kLHCPSFZKZiFK8I3YmzBe/dqo8UMcu31ZU6gLIZEt0aUinJ56cNpj5MEZgjBM6DRvCV0sDoXV+RJg== tarball: 'file:projects/app-configuration.tgz' version: 0.0.0 'file:projects/core-amqp.tgz': @@ -7644,7 +7659,7 @@ packages: dev: false name: '@rush-temp/core-amqp' resolution: - integrity: sha512-vQuyRkXvsxNXFld08NdABYHJ7GykKzizzfXA/PjeGjlzVQGSkdycxTAG8iu/g+/IAw04CWNIvgo0UODO79lOdQ== + integrity: sha512-LsvJAYHJ5hn2n1Cv2+l5OinaagHK8Jo2b9hA3XeUG2+6KNy15LTqmYcbEHkYgfoDHSloa3jFKOUO4+9j01MHnw== tarball: 'file:projects/core-amqp.tgz' version: 0.0.0 'file:projects/core-arm.tgz': @@ -7677,7 +7692,7 @@ packages: dev: false name: '@rush-temp/core-arm' resolution: - integrity: sha512-hEat51eRrK1WH8H5b2lAHXb2p9iyuUsbzVg6OFxnyKIOFbseeTx4vyQgeNrpcA/qJhosheJR78ybSj9z3opvGw== + integrity: sha512-DCrEbS5wWu+Yw/s6djt/DAHg+YclXiOQ9F8vC2PZ4sU2qpLIlcltNSgRKb1S+7mTiDcm6RbV0YvS1+cUdkiqyA== tarball: 'file:projects/core-arm.tgz' version: 0.0.0 'file:projects/core-asynciterator-polyfill.tgz': @@ -7861,7 +7876,7 @@ packages: dev: false name: '@rush-temp/core-http' resolution: - integrity: sha512-B2ZGxHSjobksvIEc9HpR0NPX6fxxUOVIMKcioUy4z8EfgKCS4SCTWkeHgPPXa7fDG4CHitae+VmTzpnD5q/pWQ== + integrity: sha512-V25LzZa+6TCulYBLh4XGIeAa2l0NfHuPMmelAYAjp88LjTPmMpNnFgwCogj6vf7lzDgBf4ZOp8CH/W3g0bgrzA== tarball: 'file:projects/core-http.tgz' version: 0.0.0 'file:projects/core-https.tgz': @@ -7973,7 +7988,7 @@ packages: dev: false name: '@rush-temp/core-lro' resolution: - integrity: sha512-bI++qCtuicjHHHOYy/0UZeNfseXzdhVCQXrLUqKiw4ew8uGVnHrp+VYdQelc+ZJIBCBr9MC8eaVCmFKfhuu4Fw== + integrity: sha512-Ap0icykIshfugWcAS/2+rNvrDKVkcfp2NtHQJEKYOfUBx1DHEVnB9V28u5lYmHvWfbmaAm1l6E6Bae02xOB48g== tarball: 'file:projects/core-lro.tgz' version: 0.0.0 'file:projects/core-paging.tgz': @@ -8198,7 +8213,7 @@ packages: dev: false name: '@rush-temp/event-hubs' resolution: - integrity: sha512-J7EZ5qh4xzw3vYG95t2MkavJiaq9ViaD2YHAkFDOjjaLuh8MEc+lF3ocZQOcfnTYyh/IPPxHvbugQ4FEyS12sQ== + integrity: sha512-mFcVc6WBPBac/+1cHV7l6S98uQlnMNotM1tqKe1kvefV7IMCIpBK+LgUvXGiU88Xqo2a0mjuaIJ5rhYqwavWjw== tarball: 'file:projects/event-hubs.tgz' version: 0.0.0 'file:projects/event-processor-host.tgz': @@ -8376,9 +8391,44 @@ packages: dev: false name: '@rush-temp/identity' resolution: - integrity: sha512-T7FaEz4yC1c4dubSWqJidKMrfPaHTWHpuVZZtSpxWB1BY0H222XgzHVEvkhqeG409csPutfpQjvA/xu+AzF9kw== + integrity: sha512-fMltFDFjWqi7FV7o0rpozKWYF4NYhnbH4Ii1GQmEGznUZqCWdtuf77Dh1DU8EwN1tkC22cNPHb02UG1KSO8yUw== tarball: 'file:projects/identity.tgz' version: 0.0.0 + 'file:projects/keyvault-admin.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.0.1_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_2ce5ff4d4c428c35a55f8b98c167bebb + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.5 + cross-env: 7.0.2 + eslint: 6.8.0 + eslint-config-prettier: 6.11.0_eslint@6.8.0 + eslint-plugin-no-null: 1.0.2_eslint@6.8.0 + eslint-plugin-no-only-tests: 2.4.0 + eslint-plugin-promise: 4.2.1 + esm: 3.2.25 + prettier: 1.19.1 + rimraf: 3.0.2 + rollup: 1.32.1 + rollup-plugin-shim: 1.0.0 + rollup-plugin-sourcemaps: 0.4.2_rollup@1.32.1 + rollup-plugin-terser: 5.3.0_rollup@1.32.1 + rollup-plugin-visualizer: 4.0.4_rollup@1.32.1 + source-map-support: 0.5.19 + tslib: 2.0.0 + typescript: 3.9.5 + dev: false + name: '@rush-temp/keyvault-admin' + resolution: + integrity: sha512-VfS6x+BkW0cPqEmBaEDD50mQwBbEjnNm+oWNUmxpEIuGdjRYzM1Ko9l6tUOv4b5FkagtgmOrMNElZTqfLxpEMw== + tarball: 'file:projects/keyvault-admin.tgz' + version: 0.0.0 'file:projects/keyvault-certificates.tgz': dependencies: '@azure/core-tracing': 1.0.0-preview.8 @@ -8442,7 +8492,7 @@ packages: dev: false name: '@rush-temp/keyvault-certificates' resolution: - integrity: sha512-Z1PeLBZEpC6G6hzlKont/PQ8OKj0dzr31deJdnOXSN1mebC6JtMo7Pl79Qzl6F+FjAcxSBUAWbHaxW7WTQJN7A== + integrity: sha512-otmneyGWp0xCx4UwtBqDtU3ufO4PAaG5d6bEXAA4T76s3+cUlqLZtC5Zwqnd/DwnQiWVILhUxAI7OE1HM5EM/A== tarball: 'file:projects/keyvault-certificates.tgz' version: 0.0.0 'file:projects/keyvault-common.tgz': @@ -8452,7 +8502,7 @@ packages: dev: false name: '@rush-temp/keyvault-common' resolution: - integrity: sha512-0FhcouFj7LZXJzuUWtSIbX4sfRzNgUOFZBWzlTHpOrexSj13GUPi1AvuQ9sQkuzXwXIwosVFO8rVjfOgh2QtWQ== + integrity: sha512-8HjWYduon5drbrpCcg6nCz4gPCBF8KnLh67y2Usfv1JxpWvTzKuGKZrUgt/tGTu6U469ykVfC2Ehj/rwTwjCJw== tarball: 'file:projects/keyvault-common.tgz' version: 0.0.0 'file:projects/keyvault-keys.tgz': @@ -8518,7 +8568,7 @@ packages: dev: false name: '@rush-temp/keyvault-keys' resolution: - integrity: sha512-Rm8+Z6FjPz3tGtRCyfMzMFwwYbeCSM9P2C/kKDvpSNDXfegvLr4AXnj3XGkUhh8nJT10FGxtpOqX12ZXcbBAPA== + integrity: sha512-jzriUWCWYbtt/6Gqu1CLcaBoJ/v0kzeoW/JNV3riquZc7vgIhJsJzOW3FwIjiWyUUNePgNWxdATyW3e6NR/MEA== tarball: 'file:projects/keyvault-keys.tgz' version: 0.0.0 'file:projects/keyvault-secrets.tgz': @@ -8584,7 +8634,7 @@ packages: dev: false name: '@rush-temp/keyvault-secrets' resolution: - integrity: sha512-aUW6Ha5QeBesanZehFAF26ykukKgLITTSfO0KvlsN4H7PUeMqdoXfIKleqZKq5QUTiZUDP72MALnqra9doD1fg== + integrity: sha512-4QvZ/dB/AaMMJbqK7pKQcYfjcBWdhYgxZrRkta9Bd7Y3DZyTIUGCjxnWDZa4CyyV1BQSDY/rpAlnCneQQZZGjg== tarball: 'file:projects/keyvault-secrets.tgz' version: 0.0.0 'file:projects/logger.tgz': @@ -8696,7 +8746,7 @@ packages: dev: false name: '@rush-temp/search-documents' resolution: - integrity: sha512-dty3mODPX8OTZhW5GmWkYtvIK4HeGOrkNkIH6nph4iKH6PjR+Ed1+a3Me9Bo8CA4GBLu19HGNe0t3WHP6BnUGw== + integrity: sha512-d2Mn4FQL/FzuxABfvT9scg1+vRrfZ4c66sFtyYjrIybU4B37YLx3bryrj+Ufc8+m+kcZcb+kHzon2Pr41h47fQ== tarball: 'file:projects/search-documents.tgz' version: 0.0.0 'file:projects/service-bus.tgz': @@ -8772,7 +8822,7 @@ packages: dev: false name: '@rush-temp/service-bus' resolution: - integrity: sha512-wvwki9VUk1OBa5vOKDNUtNv48hISwA4O1yQGklLVg3K/WdRmz/a7r4stTezbEKbjTk0mOYSU2hX3HgUSssIisA== + integrity: sha512-wSAmks1T5tOckF82dT10wTLXXNblVKFh9YPilOMKyWGKQ32c1A23vPmY2NgUDLA9AkIjRuH+diL1Hqam3/Wh2g== tarball: 'file:projects/service-bus.tgz' version: 0.0.0 'file:projects/storage-blob.tgz': @@ -9073,7 +9123,7 @@ packages: dev: false name: '@rush-temp/tables' resolution: - integrity: sha512-PkcIhJI6GpcriJW2LEcqH851g5auP5vWnoIeyCFv1cmP5Hq4FdrrcYtP17BNGdm/UoVORZ2KFBvZEpFLXYJ+XA== + integrity: sha512-RXIuE5quJ7ivUUn7oSwZOP7Vhc5TWDjMrYKhPaMhx0XHYfNJbR7VIfSwzdqchV16UAogmu8vFTegqIHzE7wLKQ== tarball: 'file:projects/tables.tgz' version: 0.0.0 'file:projects/template.tgz': @@ -9123,7 +9173,7 @@ packages: dev: false name: '@rush-temp/template' resolution: - integrity: sha512-yn9mQtHrTPcgiJQ3EQJmjMFevQZTghhRirBjJLygW0/MGreO7RvTR598dbFkRZAITJ3vHHiIkDdOEbsJwcMYxA== + integrity: sha512-+dvbgqLz+ED05Nx6LQICU3EP+ftI7QEnEKovzKqf4IQgpyf2BKDgaeOv8fPJ4sI3b6/yHAorjffanNoFxf1HrQ== tarball: 'file:projects/template.tgz' version: 0.0.0 'file:projects/test-utils-perfstress.tgz': @@ -9150,7 +9200,7 @@ packages: dev: false name: '@rush-temp/test-utils-perfstress' resolution: - integrity: sha512-O7bReyg1fdESUEL1lqFWT/Ep00qElgRtkMiCOonBdfR+ZkQRJDdhtQSx2s7BsWzJOg5OyGxSewcRFN5nDXpAqg== + integrity: sha512-5hNzD8rPaOLuqwqEhVNpa3SMZMnNSBcGqf26N4gxzspZoMl4xpPIKh+CafaVLXBgZWnJxiwOnPZmO4f45yITzw== tarball: 'file:projects/test-utils-perfstress.tgz' version: 0.0.0 'file:projects/test-utils-recorder.tgz': @@ -9210,7 +9260,7 @@ packages: dev: false name: '@rush-temp/test-utils-recorder' resolution: - integrity: sha512-fvGz4wXaTJ6CW39tLAXW9eyu8cSiA6WaLCOYrocpeI7uwkPaCYk50hEHV5hMgtT1a1UQZyj8uemuBBGwldz6eA== + integrity: sha512-10CtIMD4P7n8J6FMHFvOz2ayYDLOq7/AAjEy+bcTBBbuReAgOqBqUEsno31YZZrqxjav+V3CIjoa7AiIo1CDLw== tarball: 'file:projects/test-utils-recorder.tgz' version: 0.0.0 'file:projects/testhub.tgz': @@ -9256,6 +9306,7 @@ specifiers: '@rush-temp/event-processor-host': 'file:./projects/event-processor-host.tgz' '@rush-temp/eventhubs-checkpointstore-blob': 'file:./projects/eventhubs-checkpointstore-blob.tgz' '@rush-temp/identity': 'file:./projects/identity.tgz' + '@rush-temp/keyvault-admin': 'file:./projects/keyvault-admin.tgz' '@rush-temp/keyvault-certificates': 'file:./projects/keyvault-certificates.tgz' '@rush-temp/keyvault-common': 'file:./projects/keyvault-common.tgz' '@rush-temp/keyvault-keys': 'file:./projects/keyvault-keys.tgz' From db43e3769151c3d0739b91896e95365e43620d6a Mon Sep 17 00:00:00 2001 From: chradek Date: Wed, 1 Jul 2020 16:57:46 -0700 Subject: [PATCH 28/28] rush update --- common/config/rush/pnpm-lock.yaml | 2851 +++++++++++++++-------------- 1 file changed, 1446 insertions(+), 1405 deletions(-) diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 191570ea9538..c8cbcc4828de 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -1,43 +1,43 @@ dependencies: - "@rush-temp/abort-controller": "file:projects/abort-controller.tgz" - "@rush-temp/ai-form-recognizer": "file:projects/ai-form-recognizer.tgz" - "@rush-temp/ai-text-analytics": "file:projects/ai-text-analytics.tgz" - "@rush-temp/app-configuration": "file:projects/app-configuration.tgz" - "@rush-temp/core-amqp": "file:projects/core-amqp.tgz" - "@rush-temp/core-arm": "file:projects/core-arm.tgz" - "@rush-temp/core-asynciterator-polyfill": "file:projects/core-asynciterator-polyfill.tgz" - "@rush-temp/core-auth": "file:projects/core-auth.tgz" - "@rush-temp/core-client": "file:projects/core-client.tgz" - "@rush-temp/core-http": "file:projects/core-http.tgz" - "@rush-temp/core-https": "file:projects/core-https.tgz" - "@rush-temp/core-lro": "file:projects/core-lro.tgz" - "@rush-temp/core-paging": "file:projects/core-paging.tgz" - "@rush-temp/core-tracing": "file:projects/core-tracing.tgz" - "@rush-temp/cosmos": "file:projects/cosmos.tgz" - "@rush-temp/eslint-plugin-azure-sdk": "file:projects/eslint-plugin-azure-sdk.tgz" - "@rush-temp/event-hubs": "file:projects/event-hubs.tgz" - "@rush-temp/event-processor-host": "file:projects/event-processor-host.tgz" - "@rush-temp/eventhubs-checkpointstore-blob": "file:projects/eventhubs-checkpointstore-blob.tgz" - "@rush-temp/identity": "file:projects/identity.tgz" - "@rush-temp/keyvault-admin": "file:projects/keyvault-admin.tgz" - "@rush-temp/keyvault-certificates": "file:projects/keyvault-certificates.tgz" - "@rush-temp/keyvault-common": "file:projects/keyvault-common.tgz" - "@rush-temp/keyvault-keys": "file:projects/keyvault-keys.tgz" - "@rush-temp/keyvault-secrets": "file:projects/keyvault-secrets.tgz" - "@rush-temp/logger": "file:projects/logger.tgz" - "@rush-temp/search-documents": "file:projects/search-documents.tgz" - "@rush-temp/service-bus": "file:projects/service-bus.tgz" - "@rush-temp/storage-blob": "file:projects/storage-blob.tgz" - "@rush-temp/storage-blob-changefeed": "file:projects/storage-blob-changefeed.tgz" - "@rush-temp/storage-file-datalake": "file:projects/storage-file-datalake.tgz" - "@rush-temp/storage-file-share": "file:projects/storage-file-share.tgz" - "@rush-temp/storage-internal-avro": "file:projects/storage-internal-avro.tgz" - "@rush-temp/storage-queue": "file:projects/storage-queue.tgz" - "@rush-temp/tables": "file:projects/tables.tgz" - "@rush-temp/template": "file:projects/template.tgz" - "@rush-temp/test-utils-perfstress": "file:projects/test-utils-perfstress.tgz" - "@rush-temp/test-utils-recorder": "file:projects/test-utils-recorder.tgz" - "@rush-temp/testhub": "file:projects/testhub.tgz" + '@rush-temp/abort-controller': 'file:projects/abort-controller.tgz' + '@rush-temp/ai-form-recognizer': 'file:projects/ai-form-recognizer.tgz' + '@rush-temp/ai-text-analytics': 'file:projects/ai-text-analytics.tgz' + '@rush-temp/app-configuration': 'file:projects/app-configuration.tgz' + '@rush-temp/core-amqp': 'file:projects/core-amqp.tgz' + '@rush-temp/core-arm': 'file:projects/core-arm.tgz' + '@rush-temp/core-asynciterator-polyfill': 'file:projects/core-asynciterator-polyfill.tgz' + '@rush-temp/core-auth': 'file:projects/core-auth.tgz' + '@rush-temp/core-client': 'file:projects/core-client.tgz' + '@rush-temp/core-http': 'file:projects/core-http.tgz' + '@rush-temp/core-https': 'file:projects/core-https.tgz' + '@rush-temp/core-lro': 'file:projects/core-lro.tgz' + '@rush-temp/core-paging': 'file:projects/core-paging.tgz' + '@rush-temp/core-tracing': 'file:projects/core-tracing.tgz' + '@rush-temp/cosmos': 'file:projects/cosmos.tgz' + '@rush-temp/eslint-plugin-azure-sdk': 'file:projects/eslint-plugin-azure-sdk.tgz' + '@rush-temp/event-hubs': 'file:projects/event-hubs.tgz' + '@rush-temp/event-processor-host': 'file:projects/event-processor-host.tgz' + '@rush-temp/eventhubs-checkpointstore-blob': 'file:projects/eventhubs-checkpointstore-blob.tgz' + '@rush-temp/identity': 'file:projects/identity.tgz' + '@rush-temp/keyvault-admin': 'file:projects/keyvault-admin.tgz' + '@rush-temp/keyvault-certificates': 'file:projects/keyvault-certificates.tgz' + '@rush-temp/keyvault-common': 'file:projects/keyvault-common.tgz' + '@rush-temp/keyvault-keys': 'file:projects/keyvault-keys.tgz' + '@rush-temp/keyvault-secrets': 'file:projects/keyvault-secrets.tgz' + '@rush-temp/logger': 'file:projects/logger.tgz' + '@rush-temp/search-documents': 'file:projects/search-documents.tgz' + '@rush-temp/service-bus': 'file:projects/service-bus.tgz' + '@rush-temp/storage-blob': 'file:projects/storage-blob.tgz' + '@rush-temp/storage-blob-changefeed': 'file:projects/storage-blob-changefeed.tgz' + '@rush-temp/storage-file-datalake': 'file:projects/storage-file-datalake.tgz' + '@rush-temp/storage-file-share': 'file:projects/storage-file-share.tgz' + '@rush-temp/storage-internal-avro': 'file:projects/storage-internal-avro.tgz' + '@rush-temp/storage-queue': 'file:projects/storage-queue.tgz' + '@rush-temp/tables': 'file:projects/tables.tgz' + '@rush-temp/template': 'file:projects/template.tgz' + '@rush-temp/test-utils-perfstress': 'file:projects/test-utils-perfstress.tgz' + '@rush-temp/test-utils-recorder': 'file:projects/test-utils-recorder.tgz' + '@rush-temp/testhub': 'file:projects/testhub.tgz' lockfileVersion: 5.1 packages: /@azure/abort-controller/1.0.1: @@ -48,9 +48,9 @@ packages: integrity: sha512-wP2Jw6uPp8DEDy0n4KNidvwzDjyVV2xnycEIq7nPzj1rHyb/r+t3OPeNT1INZePP2wy5ZqlwyuyOMTi0ePyY1A== /@azure/amqp-common/1.0.0-preview.9: dependencies: - "@azure/ms-rest-nodeauth": 0.9.3 - "@types/async-lock": 1.1.2 - "@types/is-buffer": 2.0.0 + '@azure/ms-rest-nodeauth': 0.9.3 + '@types/async-lock': 1.1.2 + '@types/is-buffer': 2.0.0 async-lock: 1.2.4 buffer: 5.6.0 debug: 3.2.6 @@ -67,30 +67,54 @@ packages: dev: false resolution: integrity: sha512-RVG1Ad3Afv9gwFFmpeCXQAm+Sa0L8KEZRJJAAZEGoYDb6EoO1iQDVmoBz720h8mdrGpi0D60xNU/KhriIwuZfQ== + /@azure/core-amqp/1.1.4: + dependencies: + '@azure/abort-controller': 1.0.1 + '@azure/core-auth': 1.1.3 + '@azure/logger': 1.0.0 + '@types/async-lock': 1.1.2 + '@types/is-buffer': 2.0.0 + async-lock: 1.2.4 + buffer: 5.6.0 + events: 3.1.0 + is-buffer: 2.0.4 + jssha: 3.1.0 + process: 0.11.10 + rhea: 1.0.23 + rhea-promise: 1.0.0 + stream-browserify: 3.0.0 + tslib: 2.0.0 + url: 0.11.0 + util: 0.12.3 + dev: false + engines: + node: '>=8.0.0' + resolution: + integrity: sha512-1kPDQMOYcmVRMoe9wAx4tqcM5MlkgCWeIq5gfu8u1dK9UWbVy3mDP9OQJOTZJxccOF1AKaJ7yGQhM+uNrSmwog== /@azure/core-asynciterator-polyfill/1.0.0: dev: false resolution: integrity: sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg== /@azure/core-auth/1.1.3: dependencies: - "@azure/abort-controller": 1.0.1 - "@azure/core-tracing": 1.0.0-preview.8 - "@opentelemetry/api": 0.6.1 + '@azure/abort-controller': 1.0.1 + '@azure/core-tracing': 1.0.0-preview.8 + '@opentelemetry/api': 0.6.1 tslib: 2.0.0 dev: false engines: - node: ">=8.0.0" + node: '>=8.0.0' resolution: integrity: sha512-A4xigW0YZZpkj1zK7dKuzbBpGwnhEcRk6WWuIshdHC32raR3EQ1j6VA9XZqE+RFsUgH6OAmIK5BWIz+mZjnd6Q== /@azure/core-http/1.1.3: dependencies: - "@azure/abort-controller": 1.0.1 - "@azure/core-auth": 1.1.3 - "@azure/core-tracing": 1.0.0-preview.8 - "@azure/logger": 1.0.0 - "@opentelemetry/api": 0.6.1 - "@types/node-fetch": 2.5.7 - "@types/tunnel": 0.0.1 + '@azure/abort-controller': 1.0.1 + '@azure/core-auth': 1.1.3 + '@azure/core-tracing': 1.0.0-preview.8 + '@azure/logger': 1.0.0 + '@opentelemetry/api': 0.6.1 + '@types/node-fetch': 2.5.7 + '@types/tunnel': 0.0.1 form-data: 3.0.0 node-fetch: 2.6.0 process: 0.11.10 @@ -104,8 +128,8 @@ packages: integrity: sha512-GysW3+BRVV4L9cs3GsuCbnlyibrQU6hh5mcJ7hlnk7tdUBzWybUvJ8/P/nHX49PgwRmi81pD5v1ht2jF0IzxAQ== /@azure/core-lro/1.0.2: dependencies: - "@azure/abort-controller": 1.0.1 - "@azure/core-http": 1.1.3 + '@azure/abort-controller': 1.0.1 + '@azure/core-http': 1.1.3 events: 3.1.0 tslib: 1.13.0 dev: false @@ -113,38 +137,38 @@ packages: integrity: sha512-Yr0JD7GKryOmbcb5wHCQoQ4KCcH5QJWRNorofid+UvudLaxnbCfvKh/cUfQsGUqRjO9L/Bw4X7FP824DcHdMxw== /@azure/core-paging/1.1.1: dependencies: - "@azure/core-asynciterator-polyfill": 1.0.0 + '@azure/core-asynciterator-polyfill': 1.0.0 dev: false resolution: integrity: sha512-hqEJBEGKan4YdOaL9ZG/GRG6PXaFd/Wb3SSjQW4LWotZzgl6xqG00h6wmkrpd2NNkbBkD1erLHBO3lPHApv+iQ== /@azure/core-tracing/1.0.0-preview.8: dependencies: - "@opencensus/web-types": 0.0.7 - "@opentelemetry/api": 0.6.1 + '@opencensus/web-types': 0.0.7 + '@opentelemetry/api': 0.6.1 tslib: 1.13.0 dev: false resolution: integrity: sha512-ZKUpCd7Dlyfn7bdc+/zC/sf0aRIaNQMDuSj2RhYRFe3p70hVAnYGp3TX4cnG2yoEALp/LTj/XnZGQ8Xzf6Ja/Q== /@azure/eslint-plugin-azure-sdk/2.0.1_984cbb313f9ea271f36cadd8f9814e06: dependencies: - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 eslint: 6.8.0 fast-levenshtein: 2.0.6 glob: 7.1.6 typescript: 3.9.6 - deprecated: "This package is now a private implementation detail of https://github.com/Azure/azure-sdk-for-js" + deprecated: 'This package is now a private implementation detail of https://github.com/Azure/azure-sdk-for-js' dev: false engines: - node: ">=8.0.0" + node: '>=8.0.0' peerDependencies: - "@typescript-eslint/parser": ^2.0.0 + '@typescript-eslint/parser': ^2.0.0 eslint: ^6.1.0 resolution: integrity: sha512-HszGr6szVke1FOr07hy+JojKm36qh9n3IfYdehZRx7Wi19cY1EUmFq1PT5OasbNC/DoVqB3yx8Olfw4t5YBxVA== /@azure/event-hubs/2.1.4: dependencies: - "@azure/amqp-common": 1.0.0-preview.9 - "@azure/ms-rest-nodeauth": 0.9.3 + '@azure/amqp-common': 1.0.0-preview.9 + '@azure/ms-rest-nodeauth': 0.9.3 async-lock: 1.2.4 debug: 3.2.6 is-buffer: 2.0.4 @@ -155,6 +179,22 @@ packages: dev: false resolution: integrity: sha512-CxaMaEjwtsmIhWtjHyGimKO7RmES0YxPqGQ9+jKqGygNlhG5NYHktDaiQu6w7k3g+I51VaLXtVSt+BVFd6VWfQ== + /@azure/event-hubs/5.2.2: + dependencies: + '@azure/abort-controller': 1.0.1 + '@azure/core-amqp': 1.1.4 + '@azure/core-asynciterator-polyfill': 1.0.0 + '@azure/core-tracing': 1.0.0-preview.8 + '@azure/logger': 1.0.0 + '@opentelemetry/api': 0.6.1 + buffer: 5.6.0 + process: 0.11.10 + rhea-promise: 1.0.0 + tslib: 2.0.0 + uuid: 8.2.0 + dev: false + resolution: + integrity: sha512-F/1jaTC9NxgNjMkO7SAs9Q9BndJ16AtRwQu0l21FNyRCN8kWl4Noiblsbsjtv+BPYa+ARrocR5POMlJ5eveR9w== /@azure/logger-js/1.3.2: dependencies: tslib: 1.13.0 @@ -173,7 +213,7 @@ packages: integrity: sha512-l7z0DPCi2Hp88w12JhDTtx5d0Y3+vhfE7JKJb9O7sEz71Cwp053N8piTtTnnk/tUor9oZHgEKi/p3tQQmLPjvA== /@azure/ms-rest-js/1.8.15: dependencies: - "@types/tunnel": 0.0.0 + '@types/tunnel': 0.0.0 axios: 0.19.2 form-data: 2.5.1 tough-cookie: 2.5.0 @@ -186,21 +226,21 @@ packages: integrity: sha512-kIB71V3DcrA4iysBbOsYcxd4WWlOE7OFtCUYNfflPODM0lbIR23A236QeTn5iAeYwcHmMjR/TAKp5KQQh/WqoQ== /@azure/ms-rest-nodeauth/0.9.3: dependencies: - "@azure/ms-rest-azure-env": 1.1.2 - "@azure/ms-rest-js": 1.8.15 + '@azure/ms-rest-azure-env': 1.1.2 + '@azure/ms-rest-js': 1.8.15 adal-node: 0.1.28 dev: false resolution: integrity: sha512-aFHRw/IHhg3I9ZJW+Va4L+sCirFHMVIu6B7lFdL5mGLfG3xC5vDIdd957LRXFgy2OiKFRUC0QaKknd0YCsQIqA== /@azure/storage-blob/12.1.2: dependencies: - "@azure/abort-controller": 1.0.1 - "@azure/core-http": 1.1.3 - "@azure/core-lro": 1.0.2 - "@azure/core-paging": 1.1.1 - "@azure/core-tracing": 1.0.0-preview.8 - "@azure/logger": 1.0.0 - "@opentelemetry/api": 0.6.1 + '@azure/abort-controller': 1.0.1 + '@azure/core-http': 1.1.3 + '@azure/core-lro': 1.0.2 + '@azure/core-paging': 1.1.1 + '@azure/core-tracing': 1.0.0-preview.8 + '@azure/logger': 1.0.0 + '@opentelemetry/api': 0.6.1 events: 3.1.0 tslib: 1.13.0 dev: false @@ -208,20 +248,20 @@ packages: integrity: sha512-PCHgG4r3xLt5FaFj+uiMqrRpuzD3TD17cvxCeA1JKK2bJEf8b07H3QRLQVf0DM1MmvYY8FgQagkWZTp+jr9yew== /@babel/code-frame/7.10.4: dependencies: - "@babel/highlight": 7.10.4 + '@babel/highlight': 7.10.4 dev: false resolution: integrity: sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== /@babel/core/7.10.4: dependencies: - "@babel/code-frame": 7.10.4 - "@babel/generator": 7.10.4 - "@babel/helper-module-transforms": 7.10.4 - "@babel/helpers": 7.10.4 - "@babel/parser": 7.10.4 - "@babel/template": 7.10.4 - "@babel/traverse": 7.10.4 - "@babel/types": 7.10.4 + '@babel/code-frame': 7.10.4 + '@babel/generator': 7.10.4 + '@babel/helper-module-transforms': 7.10.4 + '@babel/helpers': 7.10.4 + '@babel/parser': 7.10.4 + '@babel/template': 7.10.4 + '@babel/traverse': 7.10.4 + '@babel/types': 7.10.4 convert-source-map: 1.7.0 debug: 4.1.1 gensync: 1.0.0-beta.1 @@ -232,12 +272,12 @@ packages: source-map: 0.5.7 dev: false engines: - node: ">=6.9.0" + node: '>=6.9.0' resolution: integrity: sha512-3A0tS0HWpy4XujGc7QtOIHTeNwUgWaZc/WuS5YQrfhU67jnVmsD6OGPc1AKHH0LJHQICGncy3+YUjIhVlfDdcA== /@babel/generator/7.10.4: dependencies: - "@babel/types": 7.10.4 + '@babel/types': 7.10.4 jsesc: 2.5.2 lodash: 4.17.15 source-map: 0.5.7 @@ -246,67 +286,67 @@ packages: integrity: sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng== /@babel/helper-function-name/7.10.4: dependencies: - "@babel/helper-get-function-arity": 7.10.4 - "@babel/template": 7.10.4 - "@babel/types": 7.10.4 + '@babel/helper-get-function-arity': 7.10.4 + '@babel/template': 7.10.4 + '@babel/types': 7.10.4 dev: false resolution: integrity: sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== /@babel/helper-get-function-arity/7.10.4: dependencies: - "@babel/types": 7.10.4 + '@babel/types': 7.10.4 dev: false resolution: integrity: sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== /@babel/helper-member-expression-to-functions/7.10.4: dependencies: - "@babel/types": 7.10.4 + '@babel/types': 7.10.4 dev: false resolution: integrity: sha512-m5j85pK/KZhuSdM/8cHUABQTAslV47OjfIB9Cc7P+PvlAoBzdb79BGNfw8RhT5Mq3p+xGd0ZfAKixbrUZx0C7A== /@babel/helper-module-imports/7.10.4: dependencies: - "@babel/types": 7.10.4 + '@babel/types': 7.10.4 dev: false resolution: integrity: sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw== /@babel/helper-module-transforms/7.10.4: dependencies: - "@babel/helper-module-imports": 7.10.4 - "@babel/helper-replace-supers": 7.10.4 - "@babel/helper-simple-access": 7.10.4 - "@babel/helper-split-export-declaration": 7.10.4 - "@babel/template": 7.10.4 - "@babel/types": 7.10.4 + '@babel/helper-module-imports': 7.10.4 + '@babel/helper-replace-supers': 7.10.4 + '@babel/helper-simple-access': 7.10.4 + '@babel/helper-split-export-declaration': 7.10.4 + '@babel/template': 7.10.4 + '@babel/types': 7.10.4 lodash: 4.17.15 dev: false resolution: integrity: sha512-Er2FQX0oa3nV7eM1o0tNCTx7izmQtwAQsIiaLRWtavAAEcskb0XJ5OjJbVrYXWOTr8om921Scabn4/tzlx7j1Q== /@babel/helper-optimise-call-expression/7.10.4: dependencies: - "@babel/types": 7.10.4 + '@babel/types': 7.10.4 dev: false resolution: integrity: sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== /@babel/helper-replace-supers/7.10.4: dependencies: - "@babel/helper-member-expression-to-functions": 7.10.4 - "@babel/helper-optimise-call-expression": 7.10.4 - "@babel/traverse": 7.10.4 - "@babel/types": 7.10.4 + '@babel/helper-member-expression-to-functions': 7.10.4 + '@babel/helper-optimise-call-expression': 7.10.4 + '@babel/traverse': 7.10.4 + '@babel/types': 7.10.4 dev: false resolution: integrity: sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A== /@babel/helper-simple-access/7.10.4: dependencies: - "@babel/template": 7.10.4 - "@babel/types": 7.10.4 + '@babel/template': 7.10.4 + '@babel/types': 7.10.4 dev: false resolution: integrity: sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw== /@babel/helper-split-export-declaration/7.10.4: dependencies: - "@babel/types": 7.10.4 + '@babel/types': 7.10.4 dev: false resolution: integrity: sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg== @@ -316,15 +356,15 @@ packages: integrity: sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== /@babel/helpers/7.10.4: dependencies: - "@babel/template": 7.10.4 - "@babel/traverse": 7.10.4 - "@babel/types": 7.10.4 + '@babel/template': 7.10.4 + '@babel/traverse': 7.10.4 + '@babel/types': 7.10.4 dev: false resolution: integrity: sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA== /@babel/highlight/7.10.4: dependencies: - "@babel/helper-validator-identifier": 7.10.4 + '@babel/helper-validator-identifier': 7.10.4 chalk: 2.4.2 js-tokens: 4.0.0 dev: false @@ -333,26 +373,26 @@ packages: /@babel/parser/7.10.4: dev: false engines: - node: ">=6.0.0" + node: '>=6.0.0' hasBin: true resolution: integrity: sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA== /@babel/template/7.10.4: dependencies: - "@babel/code-frame": 7.10.4 - "@babel/parser": 7.10.4 - "@babel/types": 7.10.4 + '@babel/code-frame': 7.10.4 + '@babel/parser': 7.10.4 + '@babel/types': 7.10.4 dev: false resolution: integrity: sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== /@babel/traverse/7.10.4: dependencies: - "@babel/code-frame": 7.10.4 - "@babel/generator": 7.10.4 - "@babel/helper-function-name": 7.10.4 - "@babel/helper-split-export-declaration": 7.10.4 - "@babel/parser": 7.10.4 - "@babel/types": 7.10.4 + '@babel/code-frame': 7.10.4 + '@babel/generator': 7.10.4 + '@babel/helper-function-name': 7.10.4 + '@babel/helper-split-export-declaration': 7.10.4 + '@babel/parser': 7.10.4 + '@babel/types': 7.10.4 debug: 4.1.1 globals: 11.12.0 lodash: 4.17.15 @@ -361,7 +401,7 @@ packages: integrity: sha512-aSy7p5THgSYm4YyxNGz6jZpXf+Ok40QF3aA2LyIONkDHpAcJzDUqlCKXv6peqYUs2gmic849C/t2HKw2a2K20Q== /@babel/types/7.10.4: dependencies: - "@babel/helper-validator-identifier": 7.10.4 + '@babel/helper-validator-identifier': 7.10.4 lodash: 4.17.15 to-fast-properties: 2.0.0 dev: false @@ -373,28 +413,28 @@ packages: lazy-ass: 1.6.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-YqW3hPS0RXriqjcCrLOTJj+LWe3c8JpwlL83k1ka1Q8U05ZjAKbGQZYeTzUd0NFEnnfPtsUiKGpFEBJG6kFuvg== /@istanbuljs/schema/0.1.2: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== /@microsoft/api-extractor-model/7.7.10: dependencies: - "@microsoft/tsdoc": 0.12.19 - "@rushstack/node-core-library": 3.19.6 + '@microsoft/tsdoc': 0.12.19 + '@rushstack/node-core-library': 3.19.6 dev: false resolution: integrity: sha512-gMFDXwUgoQYz9TgatyNPALDdZN4xBC3Un3fGwlzME+vM13PoJ26pGuqI7kv/OlK9+q2sgrEdxWns8D3UnLf2TA== /@microsoft/api-extractor/7.7.11: dependencies: - "@microsoft/api-extractor-model": 7.7.10 - "@microsoft/tsdoc": 0.12.19 - "@rushstack/node-core-library": 3.19.6 - "@rushstack/ts-command-line": 4.3.13 + '@microsoft/api-extractor-model': 7.7.10 + '@microsoft/tsdoc': 0.12.19 + '@rushstack/node-core-library': 3.19.6 + '@rushstack/ts-command-line': 4.3.13 colors: 1.2.5 lodash: 4.17.15 resolve: 1.8.1 @@ -411,26 +451,26 @@ packages: /@opencensus/web-types/0.0.7: dev: false engines: - node: ">=6.0" + node: '>=6.0' resolution: integrity: sha512-xB+w7ZDAu3YBzqH44rCmG9/RlrOmFuDPt/bpf17eJr8eZSrLt7nc7LnWdxM9Mmoj/YKMHpxRg28txu3TcpiL+g== /@opentelemetry/api/0.6.1: dependencies: - "@opentelemetry/context-base": 0.6.1 + '@opentelemetry/context-base': 0.6.1 dev: false engines: - node: ">=8.0.0" + node: '>=8.0.0' resolution: integrity: sha512-wpufGZa7tTxw7eAsjXJtiyIQ42IWQdX9iUQp7ACJcKo1hCtuhLU+K2Nv1U6oRwT1oAlZTE6m4CgWKZBhOiau3Q== /@opentelemetry/context-base/0.6.1: dev: false engines: - node: ">=8.0.0" + node: '>=8.0.0' resolution: integrity: sha512-5bHhlTBBq82ti3qPT15TRxkYTFPPQWbnkkQkmHPtqiS1XcTB69cEKd3Jm7Cfi/vkPoyxapmePE9tyA7EzLt8SQ== /@rollup/plugin-commonjs/11.0.2_rollup@1.32.1: dependencies: - "@rollup/pluginutils": 3.1.0_rollup@1.32.1 + '@rollup/pluginutils': 3.1.0_rollup@1.32.1 estree-walker: 1.0.1 is-reference: 1.2.1 magic-string: 0.25.7 @@ -438,14 +478,14 @@ packages: rollup: 1.32.1 dev: false engines: - node: ">= 8.0.0" + node: '>= 8.0.0' peerDependencies: rollup: ^1.20.0 resolution: integrity: sha512-MPYGZr0qdbV5zZj8/2AuomVpnRVXRU5XKXb3HVniwRoRCreGlf5kOE081isNWeiLIi6IYkwTX9zE0/c7V8g81g== /@rollup/plugin-inject/4.0.2_rollup@1.32.1: dependencies: - "@rollup/pluginutils": 3.1.0_rollup@1.32.1 + '@rollup/pluginutils': 3.1.0_rollup@1.32.1 estree-walker: 1.0.1 magic-string: 0.25.7 rollup: 1.32.1 @@ -456,7 +496,7 @@ packages: integrity: sha512-TSLMA8waJ7Dmgmoc8JfPnwUwVZgLjjIAM6MqeIFqPO2ODK36JqE0Cf2F54UTgCUuW8da93Mvoj75a6KAVWgylw== /@rollup/plugin-json/4.1.0_rollup@1.32.1: dependencies: - "@rollup/pluginutils": 3.1.0_rollup@1.32.1 + '@rollup/pluginutils': 3.1.0_rollup@1.32.1 rollup: 1.32.1 dev: false peerDependencies: @@ -474,8 +514,8 @@ packages: integrity: sha512-Gcp9E8y68Kx+Jo8zy/ZpiiAkb0W01cSqnxOz6h9bPR7MU3gaoTEdRf7xXYplwli1SBFEswXX588ESj+50Brfxw== /@rollup/plugin-node-resolve/8.1.0_rollup@1.32.1: dependencies: - "@rollup/pluginutils": 3.1.0_rollup@1.32.1 - "@types/resolve": 0.0.8 + '@rollup/pluginutils': 3.1.0_rollup@1.32.1 + '@types/resolve': 0.0.8 builtin-modules: 3.1.0 deep-freeze: 0.0.1 deepmerge: 4.2.2 @@ -484,14 +524,14 @@ packages: rollup: 1.32.1 dev: false engines: - node: ">= 8.0.0" + node: '>= 8.0.0' peerDependencies: rollup: ^1.20.0||^2.0.0 resolution: integrity: sha512-ovq7ZM3JJYUUmEjjO+H8tnUdmQmdQudJB7xruX8LFZ1W2q8jXdPUS6SsIYip8ByOApu4RR7729Am9WhCeCMiHA== /@rollup/plugin-replace/2.3.3_rollup@1.32.1: dependencies: - "@rollup/pluginutils": 3.1.0_rollup@1.32.1 + '@rollup/pluginutils': 3.1.0_rollup@1.32.1 magic-string: 0.25.7 rollup: 1.32.1 dev: false @@ -501,20 +541,20 @@ packages: integrity: sha512-XPmVXZ7IlaoWaJLkSCDaa0Y6uVo5XQYHhiMFzOd5qSv5rE+t/UJToPIOE56flKIxBFQI27ONsxb7dqHnwSsjKQ== /@rollup/pluginutils/3.1.0_rollup@1.32.1: dependencies: - "@types/estree": 0.0.39 + '@types/estree': 0.0.39 estree-walker: 1.0.1 picomatch: 2.2.2 rollup: 1.32.1 dev: false engines: - node: ">= 8.0.0" + node: '>= 8.0.0' peerDependencies: rollup: ^1.20.0||^2.0.0 resolution: integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== /@rushstack/node-core-library/3.19.6: dependencies: - "@types/node": 10.17.13 + '@types/node': 10.17.13 colors: 1.2.5 fs-extra: 7.0.1 jju: 1.4.0 @@ -526,7 +566,7 @@ packages: integrity: sha512-1+FoymIdr9W9k0D8fdZBBPwi5YcMwh/RyESuL5bY29rLICFxSLOPK+ImVZ1OcWj9GEMsvDx5pNpJ311mHQk+MA== /@rushstack/ts-command-line/4.3.13: dependencies: - "@types/argparse": 1.0.33 + '@types/argparse': 1.0.33 argparse: 1.0.10 colors: 1.2.5 dev: false @@ -540,20 +580,20 @@ packages: integrity: sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q== /@sinonjs/fake-timers/6.0.1: dependencies: - "@sinonjs/commons": 1.8.0 + '@sinonjs/commons': 1.8.0 dev: false resolution: integrity: sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== /@sinonjs/formatio/5.0.1: dependencies: - "@sinonjs/commons": 1.8.0 - "@sinonjs/samsam": 5.0.3 + '@sinonjs/commons': 1.8.0 + '@sinonjs/samsam': 5.0.3 dev: false resolution: integrity: sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ== /@sinonjs/samsam/5.0.3: dependencies: - "@sinonjs/commons": 1.8.0 + '@sinonjs/commons': 1.8.0 lodash.get: 4.4.2 type-detect: 4.0.8 dev: false @@ -577,20 +617,20 @@ packages: integrity: sha512-dIOxFfI0C+jz89g6lQ+TqhGgPQ0MxSnh/E4xuC0blhFtyW269+mPG5QeLgbdwst/LvdP8o1y0o/Gz5EHXLec/g== /@types/body-parser/1.19.0: dependencies: - "@types/connect": 3.4.33 - "@types/node": 8.10.61 + '@types/connect': 3.4.33 + '@types/node': 8.10.61 dev: false resolution: integrity: sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== /@types/chai-as-promised/7.1.2: dependencies: - "@types/chai": 4.2.11 + '@types/chai': 4.2.11 dev: false resolution: integrity: sha512-PO2gcfR3Oxa+u0QvECLe1xKXOqYTzCmWf0FhLhjREoW3fPAVamjihL7v1MOVLJLsnAMdLcjkfrs01yvDMwVK4Q== /@types/chai-string/1.4.2: dependencies: - "@types/chai": 4.2.11 + '@types/chai': 4.2.11 dev: false resolution: integrity: sha512-ld/1hV5qcPRGuwlPdvRfvM3Ka/iofOk2pH4VkasK4b1JJP1LjNmWWn0LsISf6RRzyhVOvs93rb9tM09e+UuF8Q== @@ -604,7 +644,7 @@ packages: integrity: sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== /@types/connect/3.4.33: dependencies: - "@types/node": 8.10.61 + '@types/node': 8.10.61 dev: false resolution: integrity: sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A== @@ -618,8 +658,8 @@ packages: integrity: sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== /@types/eslint/4.16.8: dependencies: - "@types/estree": 0.0.39 - "@types/json-schema": 7.0.5 + '@types/estree': 0.0.39 + '@types/json-schema': 7.0.5 dev: false resolution: integrity: sha512-n0ZvaIpPeBxproRvV+tZoCHRxIoNAk+k+XMvQefKgx3qM3IundoogQBAwiNEnqW0GDP1j1ATe5lFy9xxutFAHg== @@ -633,18 +673,18 @@ packages: integrity: sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== /@types/express-serve-static-core/4.17.8: dependencies: - "@types/node": 8.10.61 - "@types/qs": 6.9.3 - "@types/range-parser": 1.2.3 + '@types/node': 8.10.61 + '@types/qs': 6.9.3 + '@types/range-parser': 1.2.3 dev: false resolution: integrity: sha512-1SJZ+R3Q/7mLkOD9ewCBDYD2k0WyZQtWYqF/2VvoNN2/uhI49J9CDN4OAm+wGMA0DbArA4ef27xl4+JwMtGggw== /@types/express/4.17.6: dependencies: - "@types/body-parser": 1.19.0 - "@types/express-serve-static-core": 4.17.8 - "@types/qs": 6.9.3 - "@types/serve-static": 1.13.4 + '@types/body-parser': 1.19.0 + '@types/express-serve-static-core': 4.17.8 + '@types/qs': 6.9.3 + '@types/serve-static': 1.13.4 dev: false resolution: integrity: sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w== @@ -654,20 +694,20 @@ packages: integrity: sha512-mky/O83TXmGY39P1H9YbUpjV6l6voRYlufqfFCvel8l1phuy8HRjdWc1rrPuN53ITBJlbyMSV6z3niOySO5pgQ== /@types/fs-extra/8.1.1: dependencies: - "@types/node": 8.10.61 + '@types/node': 8.10.61 dev: false resolution: integrity: sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w== /@types/glob/7.1.2: dependencies: - "@types/minimatch": 3.0.3 - "@types/node": 8.10.61 + '@types/minimatch': 3.0.3 + '@types/node': 8.10.61 dev: false resolution: integrity: sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA== /@types/is-buffer/2.0.0: dependencies: - "@types/node": 8.10.61 + '@types/node': 8.10.61 dev: false resolution: integrity: sha512-0f7N/e3BAz32qDYvgB4d2cqv1DqUwvGxHkXsrucICn8la1Vb6Yl6Eg8mPScGwUiqHJeE7diXlzaK+QMA9m4Gxw== @@ -677,7 +717,7 @@ packages: integrity: sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== /@types/jws/3.2.2: dependencies: - "@types/node": 8.10.61 + '@types/node': 8.10.61 dev: false resolution: integrity: sha512-S0ohSSX8ioT65zu8KbG99xKyFV3InIjbM3c8roYqWy4+5HpYPyUHLYykfhM6MEI5B/3s7KSZPGFyCzCrZ2TOZA== @@ -687,7 +727,7 @@ packages: integrity: sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== /@types/md5/2.2.0: dependencies: - "@types/node": 8.10.61 + '@types/node': 8.10.61 dev: false resolution: integrity: sha512-JN8OVL/wiDlCWTPzplsgMPu0uE9Q6blwp68rYsfk2G8aokRUQ8XD9MEhZwihfAiQvoyE+m31m6i3GFXwYWomKQ== @@ -709,13 +749,13 @@ packages: integrity: sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w== /@types/mock-fs/4.10.0: dependencies: - "@types/node": 8.10.61 + '@types/node': 8.10.61 dev: false resolution: integrity: sha512-FQ5alSzmHMmliqcL36JqIA4Yyn9jyJKvRSGV3mvPh108VFatX7naJDzSG4fnFQNZFq9dIx0Dzoe6ddflMB2Xkg== /@types/mock-require/2.0.0: dependencies: - "@types/node": 8.10.61 + '@types/node': 8.10.61 dev: false resolution: integrity: sha512-nOgjoE5bBiDeiA+z41i95makyHUSMWQMOPocP+J67Pqx/68HAXaeWN1NFtrAYYV6LrISIZZ8vKHm/a50k0f6Sg== @@ -725,7 +765,7 @@ packages: integrity: sha512-DPxmjiDwubsNmguG5X4fEJ+XCyzWM3GXWsqQlvUcjJKa91IOoJUy51meDr0GkzK64qqNcq85ymLlyjoct9tInw== /@types/node-fetch/2.5.7: dependencies: - "@types/node": 8.10.61 + '@types/node': 8.10.61 form-data: 3.0.0 dev: false resolution: @@ -756,7 +796,7 @@ packages: integrity: sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== /@types/resolve/0.0.8: dependencies: - "@types/node": 8.10.61 + '@types/node': 8.10.61 dev: false resolution: integrity: sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== @@ -766,14 +806,14 @@ packages: integrity: sha512-YD+lyrPhrsJdSOaxmA9K1lzsCoN0J29IsQGMKd67SbkPDXxJPdwdqpok1sytD19NEozUaFpjIsKOWnJDOYO/GA== /@types/serve-static/1.13.4: dependencies: - "@types/express-serve-static-core": 4.17.8 - "@types/mime": 2.0.2 + '@types/express-serve-static-core': 4.17.8 + '@types/mime': 2.0.2 dev: false resolution: integrity: sha512-jTDt0o/YbpNwZbQmE/+2e+lfjJEJJR0I3OFaKQKPWkASkCoW3i6fsUnqudSMcNAfbtmADGu8f4MV4q+GqULmug== /@types/sinon/9.0.4: dependencies: - "@types/sinonjs__fake-timers": 6.0.1 + '@types/sinonjs__fake-timers': 6.0.1 dev: false resolution: integrity: sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw== @@ -787,13 +827,13 @@ packages: integrity: sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A== /@types/tunnel/0.0.0: dependencies: - "@types/node": 8.10.61 + '@types/node': 8.10.61 dev: false resolution: integrity: sha512-FGDp0iBRiBdPjOgjJmn1NH0KDLN+Z8fRmo+9J7XGBhubq1DPrGrbmG4UTlGzrpbCpesMqD0sWkzi27EYkOMHyg== /@types/tunnel/0.0.1: dependencies: - "@types/node": 8.10.61 + '@types/node': 8.10.61 dev: false resolution: integrity: sha512-AOqu6bQu5MSWwYvehMXLukFHnupHrpZ8nvgae5Ggie9UwzDR1CCwoXgSSWNZJuyOlCdfdsWMA5F2LlmvyoTv8A== @@ -807,13 +847,13 @@ packages: integrity: sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== /@types/ws/7.2.6: dependencies: - "@types/node": 8.10.61 + '@types/node': 8.10.61 dev: false resolution: integrity: sha512-Q07IrQUSNpr+cXU4E4LtkSIBPie5GLZyyMC1QtQYRLWz701+XcoVygGUZgvLqElq1nU4ICldMYPnexlBsg3dqQ== /@types/xml2js/0.4.5: dependencies: - "@types/node": 8.10.61 + '@types/node': 8.10.61 dev: false resolution: integrity: sha512-yohU3zMn0fkhlape1nxXG2bLEGZRc1FeqF80RoHaYXJN7uibaauXfhzhOJr1Xh36sn+/tx21QAOf07b/xYVk1w== @@ -823,20 +863,20 @@ packages: integrity: sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== /@types/yargs/15.0.5: dependencies: - "@types/yargs-parser": 15.0.0 + '@types/yargs-parser': 15.0.0 dev: false resolution: integrity: sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w== /@types/yauzl/2.9.1: dependencies: - "@types/node": 10.17.13 + '@types/node': 10.17.13 dev: false optional: true resolution: integrity: sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== - ? /@typescript-eslint/eslint-plugin-tslint/2.34.0_f8f62cb1f34b48259c049dd0f60912e9 - : dependencies: - "@typescript-eslint/experimental-utils": 2.34.0_eslint@6.8.0+typescript@3.9.6 + /@typescript-eslint/eslint-plugin-tslint/2.34.0_f8f62cb1f34b48259c049dd0f60912e9: + dependencies: + '@typescript-eslint/experimental-utils': 2.34.0_eslint@6.8.0+typescript@3.9.6 eslint: 6.8.0 lodash: 4.17.15 tslint: 5.20.1_typescript@3.9.6 @@ -847,13 +887,13 @@ packages: peerDependencies: eslint: ^5.0.0 || ^6.0.0 tslint: ^5.0.0 || ^6.0.0 - typescript: "*" + typescript: '*' resolution: integrity: sha512-sCPCbFm1qRTzloeMUlHEKfgQH/2u9bUcW7tX5wjzRw1LWzsr+iNXS8I+2or9ep8mlqqE0Vy6hsMm4vVF82M2jw== /@typescript-eslint/eslint-plugin/2.34.0_3787943315ebc5ea524d5c102dc9e452: dependencies: - "@typescript-eslint/experimental-utils": 2.34.0_eslint@6.8.0+typescript@3.9.6 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + '@typescript-eslint/experimental-utils': 2.34.0_eslint@6.8.0+typescript@3.9.6 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 eslint: 6.8.0 functional-red-black-tree: 1.0.1 regexpp: 3.1.0 @@ -863,9 +903,9 @@ packages: engines: node: ^8.10.0 || ^10.13.0 || >=11.10.1 peerDependencies: - "@typescript-eslint/parser": ^2.0.0 + '@typescript-eslint/parser': ^2.0.0 eslint: ^5.0.0 || ^6.0.0 - typescript: "*" + typescript: '*' peerDependenciesMeta: typescript: optional: true @@ -873,8 +913,8 @@ packages: integrity: sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ== /@typescript-eslint/experimental-utils/2.34.0_eslint@6.8.0+typescript@3.9.6: dependencies: - "@types/json-schema": 7.0.5 - "@typescript-eslint/typescript-estree": 2.34.0_typescript@3.9.6 + '@types/json-schema': 7.0.5 + '@typescript-eslint/typescript-estree': 2.34.0_typescript@3.9.6 eslint: 6.8.0 eslint-scope: 5.1.0 eslint-utils: 2.1.0 @@ -883,15 +923,15 @@ packages: engines: node: ^8.10.0 || ^10.13.0 || >=11.10.1 peerDependencies: - eslint: "*" - typescript: "*" + eslint: '*' + typescript: '*' resolution: integrity: sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== /@typescript-eslint/parser/2.34.0_eslint@6.8.0+typescript@3.9.6: dependencies: - "@types/eslint-visitor-keys": 1.0.0 - "@typescript-eslint/experimental-utils": 2.34.0_eslint@6.8.0+typescript@3.9.6 - "@typescript-eslint/typescript-estree": 2.34.0_typescript@3.9.6 + '@types/eslint-visitor-keys': 1.0.0 + '@typescript-eslint/experimental-utils': 2.34.0_eslint@6.8.0+typescript@3.9.6 + '@typescript-eslint/typescript-estree': 2.34.0_typescript@3.9.6 eslint: 6.8.0 eslint-visitor-keys: 1.3.0 typescript: 3.9.6 @@ -900,7 +940,7 @@ packages: node: ^8.10.0 || ^10.13.0 || >=11.10.1 peerDependencies: eslint: ^5.0.0 || ^6.0.0 - typescript: "*" + typescript: '*' peerDependenciesMeta: typescript: optional: true @@ -920,7 +960,7 @@ packages: engines: node: ^8.10.0 || ^10.13.0 || >=11.10.1 peerDependencies: - typescript: "*" + typescript: '*' peerDependenciesMeta: typescript: optional: true @@ -936,7 +976,7 @@ packages: negotiator: 0.6.2 dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== /acorn-jsx/5.2.0_acorn@7.3.1: @@ -950,13 +990,13 @@ packages: /acorn/7.3.1: dev: false engines: - node: ">=0.4.0" + node: '>=0.4.0' hasBin: true resolution: integrity: sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== /adal-node/0.1.28: dependencies: - "@types/node": 8.10.61 + '@types/node': 8.10.61 async: 3.2.0 date-utils: 1.2.21 jws: 3.2.2 @@ -967,7 +1007,7 @@ packages: xpath.js: 1.1.0 dev: false engines: - node: ">= 0.6.15" + node: '>= 0.6.15' resolution: integrity: sha1-RoxLs+u9lrEnBmn0ucuk4AZepIU= /after/0.8.2: @@ -979,7 +1019,7 @@ packages: es6-promisify: 5.0.0 dev: false engines: - node: ">= 4.0.0" + node: '>= 4.0.0' resolution: integrity: sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== /agent-base/4.3.0: @@ -987,13 +1027,13 @@ packages: es6-promisify: 5.0.0 dev: false engines: - node: ">= 4.0.0" + node: '>= 4.0.0' resolution: integrity: sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== /agent-base/5.1.1: dev: false engines: - node: ">= 6.0.0" + node: '>= 6.0.0' resolution: integrity: sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== /agent-base/6.0.0: @@ -1001,7 +1041,7 @@ packages: debug: 4.1.1 dev: false engines: - node: ">= 6.0.0" + node: '>= 6.0.0' resolution: integrity: sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw== /ajv/6.12.2: @@ -1016,13 +1056,13 @@ packages: /amdefine/1.0.1: dev: false engines: - node: ">=0.4.2" + node: '>=0.4.2' resolution: integrity: sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= /ansi-colors/3.2.3: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== /ansi-escapes/4.3.1: @@ -1030,7 +1070,7 @@ packages: type-fest: 0.11.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== /ansi-gray/0.1.1: @@ -1038,37 +1078,37 @@ packages: ansi-wrap: 0.1.0 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-KWLPVOyXksSFEKPetSRDaGHvclE= /ansi-regex/2.1.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8= /ansi-regex/3.0.0: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= /ansi-regex/4.1.0: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== /ansi-regex/5.0.0: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== /ansi-styles/2.2.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= /ansi-styles/3.2.1: @@ -1076,22 +1116,22 @@ packages: color-convert: 1.9.3 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== /ansi-styles/4.2.1: dependencies: - "@types/color-name": 1.1.1 + '@types/color-name': 1.1.1 color-convert: 2.0.1 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== /ansi-wrap/0.1.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-qCJQ3bABXponyoLoLqYDu/pF768= /anymatch/3.1.1: @@ -1100,7 +1140,7 @@ packages: picomatch: 2.2.2 dev: false engines: - node: ">= 8" + node: '>= 8' resolution: integrity: sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== /append-transform/1.0.0: @@ -1108,7 +1148,7 @@ packages: default-require-extensions: 2.0.0 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw== /aproba/1.2.0: @@ -1143,13 +1183,13 @@ packages: /arr-union/3.1.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= /array-differ/1.0.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-7/UuN1gknTO+QCuLuOVkuytdQDE= /array-filter/1.0.0: @@ -1159,7 +1199,7 @@ packages: /array-find-index/1.0.2: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= /array-flatten/1.1.1: @@ -1169,7 +1209,7 @@ packages: /array-uniq/1.0.3: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= /arraybuffer.slice/0.0.7: @@ -1189,7 +1229,7 @@ packages: /assert-plus/1.0.0: dev: false engines: - node: ">=0.8" + node: '>=0.8' resolution: integrity: sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= /assert/1.5.0: @@ -1206,19 +1246,19 @@ packages: /ast-types/0.13.3: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-XTZ7xGML849LkQP86sWdQzfhwbt3YwIO6MqbX9mUNYY98VKaaVZP7YNNm70IpwecbkkxmfC5IYAzOQ/2p29zRA== /astral-regex/1.0.0: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== /async-array-reduce/0.2.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-yL4BCitc0A3qlsgRFgNGk9/dgtE= /async-limiter/1.0.1: @@ -1250,7 +1290,7 @@ packages: /atob/2.1.2: dev: false engines: - node: ">= 4.5.0" + node: '>= 4.5.0' hasBin: true resolution: integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== @@ -1259,7 +1299,7 @@ packages: array-filter: 1.0.0 dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ== /aws-sign2/0.7.0: @@ -1291,7 +1331,7 @@ packages: xmlbuilder: 9.0.7 dev: false engines: - node: ">= 0.8.26" + node: '>= 0.8.26' resolution: integrity: sha512-IGLs5Xj6kO8Ii90KerQrrwuJKexLgSwYC4oLWmc11mzKe7Jt2E5IVg+ZQ8K53YWZACtVTMBNO3iGuA+4ipjJxQ== /babel-runtime/6.26.0: @@ -1318,7 +1358,7 @@ packages: /base64-arraybuffer/0.1.5: dev: false engines: - node: ">= 0.6.0" + node: '>= 0.6.0' resolution: integrity: sha1-c5JncZI7Whl0etZmqlzUv5xunOg= /base64-js/1.3.1: @@ -1328,7 +1368,7 @@ packages: /base64id/1.0.0: dev: false engines: - node: ">= 0.4.0" + node: '>= 0.4.0' resolution: integrity: sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY= /bcrypt-pbkdf/1.0.2: @@ -1340,7 +1380,7 @@ packages: /beeper/1.1.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak= /better-assert/1.0.2: @@ -1352,7 +1392,7 @@ packages: /binary-extensions/2.1.0: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== /bl/4.0.2: @@ -1385,7 +1425,7 @@ packages: type-is: 1.6.18 dev: false engines: - node: ">= 0.8" + node: '>= 0.8' resolution: integrity: sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== /brace-expansion/1.1.11: @@ -1400,7 +1440,7 @@ packages: fill-range: 7.0.1 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== /browser-stdout/1.3.1: @@ -1448,19 +1488,19 @@ packages: /builtin-modules/1.1.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= /builtin-modules/3.1.0: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== /bytes/3.1.0: dev: false engines: - node: ">= 0.8" + node: '>= 0.8' resolution: integrity: sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== /caching-transform/3.0.2: @@ -1471,7 +1511,7 @@ packages: write-file-atomic: 2.4.3 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w== /callsite/1.0.0: @@ -1481,7 +1521,7 @@ packages: /callsites/3.1.0: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== /camelcase-keys/2.1.0: @@ -1490,19 +1530,19 @@ packages: map-obj: 1.0.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-MIvur/3ygRkFHvodkyITyRuPkuc= /camelcase/2.1.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= /camelcase/5.3.1: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== /caseless/0.12.0: @@ -1515,7 +1555,7 @@ packages: check-error: 1.0.2 dev: false peerDependencies: - chai: ">= 2.1.2 < 5" + chai: '>= 2.1.2 < 5' resolution: integrity: sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA== /chai-exclude/2.0.2_chai@4.2.0: @@ -1524,7 +1564,7 @@ packages: fclone: 1.0.11 dev: false peerDependencies: - chai: ">= 4.0.0 < 5" + chai: '>= 4.0.0 < 5' resolution: integrity: sha512-QmNVnvdSw8Huccdjm49mKu3HtoHxvjdavgYkY0KPQ5MI5UWfbc9sX1YqRgaMPf2GGtDXPoF2ram3AeNS4945Xw== /chai-string/1.5.0_chai@4.2.0: @@ -1545,7 +1585,7 @@ packages: type-detect: 4.0.8 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== /chalk/1.1.3: @@ -1557,7 +1597,7 @@ packages: supports-color: 2.0.0 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= /chalk/2.4.2: @@ -1567,7 +1607,7 @@ packages: supports-color: 5.5.0 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== /chalk/3.0.0: @@ -1576,7 +1616,7 @@ packages: supports-color: 7.1.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== /chardet/0.7.0: @@ -1594,7 +1634,7 @@ packages: /check-more-types/2.24.0: dev: false engines: - node: ">= 0.8.0" + node: '>= 0.8.0' resolution: integrity: sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= /chokidar/3.3.0: @@ -1608,7 +1648,7 @@ packages: readdirp: 3.2.0 dev: false engines: - node: ">= 8.10.0" + node: '>= 8.10.0' optionalDependencies: fsevents: 2.1.3 resolution: @@ -1624,7 +1664,7 @@ packages: readdirp: 3.4.0 dev: false engines: - node: ">= 8.10.0" + node: '>= 8.10.0' optionalDependencies: fsevents: 2.1.3 resolution: @@ -1642,7 +1682,7 @@ packages: restore-cursor: 3.1.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== /cli-width/2.2.1: @@ -1672,20 +1712,20 @@ packages: /clone/1.0.4: dev: false engines: - node: ">=0.8" + node: '>=0.8' resolution: integrity: sha1-2jCcwmPfFZlMaIypAheco8fNfH4= /co/4.6.0: dev: false engines: - iojs: ">= 1.0.0" - node: ">= 0.12.0" + iojs: '>= 1.0.0' + node: '>= 0.12.0' resolution: integrity: sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= /code-point-at/1.1.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= /color-convert/1.9.3: @@ -1699,7 +1739,7 @@ packages: color-name: 1.1.4 dev: false engines: - node: ">=7.0.0" + node: '>=7.0.0' resolution: integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== /color-name/1.1.3: @@ -1718,13 +1758,13 @@ packages: /colors/1.2.5: dev: false engines: - node: ">=0.1.90" + node: '>=0.1.90' resolution: integrity: sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg== /colors/1.4.0: dev: false engines: - node: ">=0.1.90" + node: '>=0.1.90' resolution: integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== /combined-stream/1.0.8: @@ -1732,7 +1772,7 @@ packages: delayed-stream: 1.0.0 dev: false engines: - node: ">= 0.8" + node: '>= 0.8' resolution: integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== /commander/2.20.3: @@ -1742,7 +1782,7 @@ packages: /common-tags/1.8.0: dev: false engines: - node: ">=4.0.0" + node: '>=4.0.0' resolution: integrity: sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== /commondir/1.0.1: @@ -1773,7 +1813,7 @@ packages: utils-merge: 1.0.1 dev: false engines: - node: ">= 0.10.0" + node: '>= 0.10.0' resolution: integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== /console-control-strings/1.1.0: @@ -1785,13 +1825,13 @@ packages: safe-buffer: 5.1.2 dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== /content-type/1.0.4: dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== /convert-source-map/1.7.0: @@ -1807,17 +1847,17 @@ packages: /cookie/0.3.1: dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= /cookie/0.4.0: dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== /core-js/2.6.11: - deprecated: "core-js@<3 is no longer maintained and not recommended for usage due to the number of issues. Please, upgrade your dependencies to the actual version of core-js@3." + deprecated: 'core-js@<3 is no longer maintained and not recommended for usage due to the number of issues. Please, upgrade your dependencies to the actual version of core-js@3.' dev: false requiresBuild: true resolution: @@ -1840,7 +1880,7 @@ packages: safe-buffer: 5.2.1 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA== /cross-env/7.0.2: @@ -1848,9 +1888,9 @@ packages: cross-spawn: 7.0.3 dev: false engines: - node: ">=10.14" - npm: ">=6" - yarn: ">=1" + node: '>=10.14' + npm: '>=6' + yarn: '>=1' hasBin: true resolution: integrity: sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw== @@ -1870,7 +1910,7 @@ packages: which: 1.3.1 dev: false engines: - node: ">=4.8" + node: '>=4.8' resolution: integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== /cross-spawn/7.0.3: @@ -1880,7 +1920,7 @@ packages: which: 2.0.2 dev: false engines: - node: ">= 8" + node: '>= 8' resolution: integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== /crypt/0.0.2: @@ -1892,7 +1932,7 @@ packages: array-find-index: 1.0.2 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-mI3zP+qxke95mmE2nddsF635V+o= /custom-event/1.0.1: @@ -1904,7 +1944,7 @@ packages: assert-plus: 1.0.0 dev: false engines: - node: ">=0.10" + node: '>=0.10' resolution: integrity: sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= /data-uri-to-buffer/1.2.0: @@ -1914,13 +1954,13 @@ packages: /date-format/2.1.0: dev: false engines: - node: ">=4.0" + node: '>=4.0' resolution: integrity: sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA== /date-utils/1.2.21: dev: false engines: - node: ">0.4.0" + node: '>0.4.0' resolution: integrity: sha1-YfsWzcEnSzyayq/+n8ad+HIKK2Q= /dateformat/1.0.12: @@ -1966,13 +2006,13 @@ packages: /decamelize/1.2.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= /decode-uri-component/0.2.0: dev: false engines: - node: ">=0.10" + node: '>=0.10' resolution: integrity: sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= /decompress-response/4.2.1: @@ -1980,7 +2020,7 @@ packages: mimic-response: 2.1.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== /deep-eql/3.0.1: @@ -1988,13 +2028,13 @@ packages: type-detect: 4.0.8 dev: false engines: - node: ">=0.12" + node: '>=0.12' resolution: integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== /deep-extend/0.6.0: dev: false engines: - node: ">=4.0.0" + node: '>=4.0.0' resolution: integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== /deep-freeze/0.0.1: @@ -2008,7 +2048,7 @@ packages: /deepmerge/4.2.2: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== /default-require-extensions/2.0.0: @@ -2016,7 +2056,7 @@ packages: strip-bom: 3.0.0 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc= /define-properties/1.1.3: @@ -2024,7 +2064,7 @@ packages: object-keys: 1.1.1 dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== /degenerator/1.0.4: @@ -2038,13 +2078,13 @@ packages: /delay/4.3.0: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-Lwaf3zVFDMBop1yDuFZ19F9WyGcZcGacsbdlZtWjQmM50tOcMntm1njF/Nb/Vjij3KaSvCF+sEYGKrrjObu2NA== /delayed-stream/1.0.0: dev: false engines: - node: ">=0.4.0" + node: '>=0.4.0' resolution: integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk= /delegates/1.0.0: @@ -2054,7 +2094,7 @@ packages: /depd/1.1.2: dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= /destroy/1.0.4: @@ -2064,7 +2104,7 @@ packages: /detect-libc/1.0.3: dev: false engines: - node: ">=0.10" + node: '>=0.10' hasBin: true resolution: integrity: sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= @@ -2075,13 +2115,13 @@ packages: /diff/3.5.0: dev: false engines: - node: ">=0.3.1" + node: '>=0.3.1' resolution: integrity: sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== /diff/4.0.2: dev: false engines: - node: ">=0.3.1" + node: '>=0.3.1' resolution: integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== /disparity/3.0.0: @@ -2090,7 +2130,7 @@ packages: diff: 4.0.2 dev: false engines: - node: ">=8" + node: '>=8' hasBin: true resolution: integrity: sha512-n94Rzbv2ambRaFzrnBf34IEiyOdIci7maRpMkoQWB6xFYGA7Nbs0Z5YQzMfTeyQeelv23nayqOcssBoc6rKrgw== @@ -2099,7 +2139,7 @@ packages: esutils: 2.0.3 dev: false engines: - node: ">=6.0.0" + node: '>=6.0.0' resolution: integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== /dom-serialize/2.2.1: @@ -2118,7 +2158,7 @@ packages: /dotenv/8.2.0: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== /downlevel-dts/0.4.0: @@ -2167,7 +2207,7 @@ packages: /encodeurl/1.0.2: dev: false engines: - node: ">= 0.8" + node: '>= 0.8' resolution: integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= /end-of-stream/1.4.4: @@ -2238,7 +2278,7 @@ packages: string.prototype.trimstart: 1.0.1 dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== /es-to-primitive/1.2.1: @@ -2248,7 +2288,7 @@ packages: is-symbol: 1.0.3 dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== /es6-error/4.1.1: @@ -2272,7 +2312,7 @@ packages: /escape-goat/2.1.1: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== /escape-html/1.0.3: @@ -2288,7 +2328,7 @@ packages: /escape-string-regexp/1.0.5: dev: false engines: - node: ">=0.8.0" + node: '>=0.8.0' resolution: integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= /escodegen/1.14.3: @@ -2299,7 +2339,7 @@ packages: optionator: 0.8.3 dev: false engines: - node: ">=4.0" + node: '>=4.0' hasBin: true optionalDependencies: source-map: 0.6.1 @@ -2313,7 +2353,7 @@ packages: optionator: 0.8.3 dev: false engines: - node: ">=0.12.0" + node: '>=0.12.0' hasBin: true optionalDependencies: source-map: 0.2.0 @@ -2326,7 +2366,7 @@ packages: dev: false hasBin: true peerDependencies: - eslint: ">=3.14.1" + eslint: '>=3.14.1' resolution: integrity: sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA== /eslint-plugin-no-null/1.0.2_eslint@6.8.0: @@ -2334,21 +2374,21 @@ packages: eslint: 6.8.0 dev: false engines: - node: ">=5.0.0" + node: '>=5.0.0' peerDependencies: - eslint: ">=3.0.0" + eslint: '>=3.0.0' resolution: integrity: sha1-EjaoEjkTkKGHetQAfCbnRTQclR8= /eslint-plugin-no-only-tests/2.4.0: dev: false engines: - node: ">=4.0.0" + node: '>=4.0.0' resolution: integrity: sha512-azP9PwQYfGtXJjW273nIxQH9Ygr+5/UyeW2wEjYoDtVYPI+WPKwbj0+qcAKYUXFZLRumq4HKkFaoDBAwBoXImQ== /eslint-plugin-promise/4.2.1: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== /eslint-scope/5.1.0: @@ -2357,7 +2397,7 @@ packages: estraverse: 4.3.0 dev: false engines: - node: ">=8.0.0" + node: '>=8.0.0' resolution: integrity: sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== /eslint-utils/1.4.3: @@ -2365,7 +2405,7 @@ packages: eslint-visitor-keys: 1.3.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== /eslint-utils/2.1.0: @@ -2373,18 +2413,18 @@ packages: eslint-visitor-keys: 1.3.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== /eslint-visitor-keys/1.3.0: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== /eslint/6.8.0: dependencies: - "@babel/code-frame": 7.10.4 + '@babel/code-frame': 7.10.4 ajv: 6.12.2 chalk: 2.4.2 cross-spawn: 6.0.5 @@ -2430,7 +2470,7 @@ packages: /esm/3.2.25: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== /espree/6.2.1: @@ -2440,27 +2480,27 @@ packages: eslint-visitor-keys: 1.3.0 dev: false engines: - node: ">=6.0.0" + node: '>=6.0.0' resolution: integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== /esprima/2.7.3: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' hasBin: true resolution: integrity: sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE= /esprima/3.1.3: dev: false engines: - node: ">=4" + node: '>=4' hasBin: true resolution: integrity: sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= /esprima/4.0.1: dev: false engines: - node: ">=4" + node: '>=4' hasBin: true resolution: integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -2469,7 +2509,7 @@ packages: estraverse: 5.1.0 dev: false engines: - node: ">=0.10" + node: '>=0.10' resolution: integrity: sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== /esrecurse/4.2.1: @@ -2477,25 +2517,25 @@ packages: estraverse: 4.3.0 dev: false engines: - node: ">=4.0" + node: '>=4.0' resolution: integrity: sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== /estraverse/1.9.3: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q= /estraverse/4.3.0: dev: false engines: - node: ">=4.0" + node: '>=4.0' resolution: integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== /estraverse/5.1.0: dev: false engines: - node: ">=4.0" + node: '>=4.0' resolution: integrity: sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== /estree-walker/0.6.1: @@ -2509,13 +2549,13 @@ packages: /esutils/2.0.3: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== /etag/1.8.1: dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= /eventemitter3/4.0.4: @@ -2525,7 +2565,7 @@ packages: /events/3.1.0: dev: false engines: - node: ">=0.8.x" + node: '>=0.8.x' resolution: integrity: sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== /execa/1.0.0: @@ -2539,7 +2579,7 @@ packages: strip-eof: 1.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== /execa/3.4.0: @@ -2562,7 +2602,7 @@ packages: /expand-template/2.0.3: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== /expand-tilde/2.0.2: @@ -2570,7 +2610,7 @@ packages: homedir-polyfill: 1.0.3 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= /express/4.17.1: @@ -2607,7 +2647,7 @@ packages: vary: 1.1.2 dev: false engines: - node: ">= 0.10.0" + node: '>= 0.10.0' resolution: integrity: sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== /extend/3.0.2: @@ -2621,7 +2661,7 @@ packages: tmp: 0.0.33 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== /extract-zip/2.0.1: @@ -2631,16 +2671,16 @@ packages: yauzl: 2.10.0 dev: false engines: - node: ">= 10.17.0" + node: '>= 10.17.0' hasBin: true optionalDependencies: - "@types/yauzl": 2.9.1 + '@types/yauzl': 2.9.1 resolution: integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== /extsprintf/1.3.0: dev: false engines: - "0": node >=0.6.0 + '0': node >=0.6.0 resolution: integrity: sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= /fancy-log/1.3.3: @@ -2651,7 +2691,7 @@ packages: time-stamp: 1.1.0 dev: false engines: - node: ">= 0.10" + node: '>= 0.10' resolution: integrity: sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw== /fast-deep-equal/3.1.3: @@ -2690,9 +2730,9 @@ packages: whatwg-url: 6.5.0 dev: false engines: - node: ">=4.0.0" + node: '>=4.0.0' peerDependencies: - node-fetch: "*" + node-fetch: '*' peerDependenciesMeta: node-fetch: optional: true @@ -2703,7 +2743,7 @@ packages: escape-string-regexp: 1.0.5 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== /file-entry-cache/5.0.1: @@ -2711,7 +2751,7 @@ packages: flat-cache: 2.0.1 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== /file-uri-to-path/1.0.0: @@ -2723,7 +2763,7 @@ packages: to-regex-range: 5.0.1 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== /finalhandler/1.1.2: @@ -2737,7 +2777,7 @@ packages: unpipe: 1.0.0 dev: false engines: - node: ">= 0.8" + node: '>= 0.8' resolution: integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== /find-cache-dir/2.1.0: @@ -2747,7 +2787,7 @@ packages: pkg-dir: 3.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== /find-up/1.1.2: @@ -2756,7 +2796,7 @@ packages: pinkie-promise: 2.0.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= /find-up/3.0.0: @@ -2764,7 +2804,7 @@ packages: locate-path: 3.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== /find-up/4.1.0: @@ -2773,7 +2813,7 @@ packages: path-exists: 4.0.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== /flat-cache/2.0.1: @@ -2783,7 +2823,7 @@ packages: write: 1.0.3 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== /flat/4.1.0: @@ -2804,7 +2844,7 @@ packages: /follow-redirects/1.12.1: dev: false engines: - node: ">=4.0" + node: '>=4.0' resolution: integrity: sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg== /follow-redirects/1.5.10: @@ -2812,7 +2852,7 @@ packages: debug: 3.1.0 dev: false engines: - node: ">=4.0" + node: '>=4.0' resolution: integrity: sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== /foreach/2.0.5: @@ -2837,7 +2877,7 @@ packages: mime-types: 2.1.27 dev: false engines: - node: ">= 0.12" + node: '>= 0.12' resolution: integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== /form-data/2.5.1: @@ -2847,7 +2887,7 @@ packages: mime-types: 2.1.27 dev: false engines: - node: ">= 0.12" + node: '>= 0.12' resolution: integrity: sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== /form-data/3.0.0: @@ -2857,19 +2897,19 @@ packages: mime-types: 2.1.27 dev: false engines: - node: ">= 6" + node: '>= 6' resolution: integrity: sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg== /forwarded/0.1.2: dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= /fresh/0.5.2: dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= /fs-constants/1.0.0: @@ -2883,7 +2923,7 @@ packages: universalify: 0.1.2 dev: false engines: - node: ">=6 <7 || >=8" + node: '>=6 <7 || >=8' resolution: integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== /fs-extra/8.1.0: @@ -2893,7 +2933,7 @@ packages: universalify: 0.1.2 dev: false engines: - node: ">=6 <7 || >=8" + node: '>=6 <7 || >=8' resolution: integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== /fs.realpath/1.0.0: @@ -2915,7 +2955,7 @@ packages: xregexp: 2.0.0 dev: false engines: - node: ">=0.8.0" + node: '>=0.8.0' resolution: integrity: sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0= /function-bind/1.1.1: @@ -2942,7 +2982,7 @@ packages: /gensync/1.0.0-beta.1: dev: false engines: - node: ">=6.9.0" + node: '>=6.9.0' resolution: integrity: sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== /get-caller-file/1.0.3: @@ -2962,13 +3002,13 @@ packages: /get-stdin/4.0.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= /get-stdin/6.0.0: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== /get-stream/4.1.0: @@ -2976,7 +3016,7 @@ packages: pump: 3.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== /get-stream/5.1.0: @@ -2984,7 +3024,7 @@ packages: pump: 3.0.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== /get-uri/2.0.4: @@ -3013,7 +3053,7 @@ packages: is-glob: 4.0.1 dev: false engines: - node: ">= 6" + node: '>= 6' resolution: integrity: sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== /glob-to-regexp/0.4.1: @@ -3059,7 +3099,7 @@ packages: resolve-dir: 1.0.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== /global-prefix/1.0.2: @@ -3071,7 +3111,7 @@ packages: which: 1.3.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= /global/4.4.0: @@ -3084,7 +3124,7 @@ packages: /globals/11.12.0: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== /globals/12.4.0: @@ -3092,7 +3132,7 @@ packages: type-fest: 0.8.1 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== /glogg/1.0.2: @@ -3100,7 +3140,7 @@ packages: sparkles: 1.0.1 dev: false engines: - node: ">= 0.10" + node: '>= 0.10' resolution: integrity: sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA== /graceful-fs/4.2.4: @@ -3110,7 +3150,7 @@ packages: /growl/1.10.5: dev: false engines: - node: ">=4.x" + node: '>=4.x' resolution: integrity: sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== /guid-typescript/1.0.9: @@ -3137,10 +3177,10 @@ packages: replace-ext: 0.0.1 through2: 2.0.1 vinyl: 0.5.3 - deprecated: "gulp-util is deprecated - replace it, following the guidelines at https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5" + deprecated: 'gulp-util is deprecated - replace it, following the guidelines at https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5' dev: false engines: - node: ">=0.10" + node: '>=0.10' resolution: integrity: sha1-eJJcS4+LSQBawBoBHFV+YhiUHLs= /gulplog/1.0.0: @@ -3148,7 +3188,7 @@ packages: glogg: 1.0.2 dev: false engines: - node: ">= 0.10" + node: '>= 0.10' resolution: integrity: sha1-4oxNRdBey77YGDY86PnFkmIp/+U= /handlebars/4.7.6: @@ -3159,7 +3199,7 @@ packages: wordwrap: 1.0.0 dev: false engines: - node: ">=0.4.7" + node: '>=0.4.7' hasBin: true optionalDependencies: uglify-js: 3.10.0 @@ -3168,7 +3208,7 @@ packages: /har-schema/2.0.0: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= /har-validator/5.1.3: @@ -3177,7 +3217,7 @@ packages: har-schema: 2.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== /has-ansi/2.0.0: @@ -3185,7 +3225,7 @@ packages: ansi-regex: 2.1.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= /has-binary2/1.0.3: @@ -3201,19 +3241,19 @@ packages: /has-flag/1.0.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= /has-flag/3.0.0: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0= /has-flag/4.0.0: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== /has-glob/1.0.0: @@ -3221,7 +3261,7 @@ packages: is-glob: 3.1.0 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-mqqe7b/7G6OZCnsAEPtnjuAIEgc= /has-gulplog/0.1.0: @@ -3229,19 +3269,19 @@ packages: sparkles: 1.0.1 dev: false engines: - node: ">= 0.10" + node: '>= 0.10' resolution: integrity: sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4= /has-only/1.1.1: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-3GuFy9rDw0xvovCHb4SOKiRImbZ+a8boFBUyGNRPVd2mRyQOzYdau5G9nodUXC1ZKYN59hrHFkW1lgBQscYfTg== /has-symbols/1.0.1: dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== /has-unicode/2.0.1: @@ -3253,7 +3293,7 @@ packages: function-bind: 1.1.1 dev: false engines: - node: ">= 0.4.0" + node: '>= 0.4.0' resolution: integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== /hash-base/3.1.0: @@ -3263,7 +3303,7 @@ packages: safe-buffer: 5.2.1 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== /hasha/3.0.0: @@ -3271,7 +3311,7 @@ packages: is-stream: 1.1.0 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk= /he/1.2.0: @@ -3288,7 +3328,7 @@ packages: parse-passwd: 1.0.0 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== /hosted-git-info/2.8.8: @@ -3308,7 +3348,7 @@ packages: toidentifier: 1.0.0 dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== /http-errors/1.7.3: @@ -3320,7 +3360,7 @@ packages: toidentifier: 1.0.0 dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== /http-proxy-agent/2.1.0: @@ -3329,7 +3369,7 @@ packages: debug: 3.1.0 dev: false engines: - node: ">= 4.5.0" + node: '>= 4.5.0' resolution: integrity: sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== /http-proxy/1.18.1: @@ -3339,7 +3379,7 @@ packages: requires-port: 1.0.0 dev: false engines: - node: ">=8.0.0" + node: '>=8.0.0' resolution: integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== /http-signature/1.2.0: @@ -3349,8 +3389,8 @@ packages: sshpk: 1.16.1 dev: false engines: - node: ">=0.8" - npm: ">=1.3.7" + node: '>=0.8' + npm: '>=1.3.7' resolution: integrity: sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= /https-proxy-agent/3.0.1: @@ -3359,7 +3399,7 @@ packages: debug: 3.2.6 dev: false engines: - node: ">= 4.5.0" + node: '>= 4.5.0' resolution: integrity: sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg== /https-proxy-agent/4.0.0: @@ -3368,7 +3408,7 @@ packages: debug: 4.1.1 dev: false engines: - node: ">= 6.0.0" + node: '>= 6.0.0' resolution: integrity: sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== /https-proxy-agent/5.0.0: @@ -3377,13 +3417,13 @@ packages: debug: 4.1.1 dev: false engines: - node: ">= 6" + node: '>= 6' resolution: integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== /human-signals/1.1.1: dev: false engines: - node: ">=8.12.0" + node: '>=8.12.0' resolution: integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== /iconv-lite/0.4.24: @@ -3391,7 +3431,7 @@ packages: safer-buffer: 2.1.2 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== /ieee754/1.1.13: @@ -3401,7 +3441,7 @@ packages: /ignore/4.0.6: dev: false engines: - node: ">= 4" + node: '>= 4' resolution: integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== /import-fresh/3.2.1: @@ -3410,13 +3450,13 @@ packages: resolve-from: 4.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== /imurmurhash/0.1.4: dev: false engines: - node: ">=0.8.19" + node: '>=0.8.19' resolution: integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o= /indent-string/2.1.0: @@ -3424,7 +3464,7 @@ packages: repeating: 2.0.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= /indexof/0.0.1: @@ -3471,13 +3511,13 @@ packages: through: 2.3.8 dev: false engines: - node: ">=8.0.0" + node: '>=8.0.0' resolution: integrity: sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ== /interpret/1.4.0: dev: false engines: - node: ">= 0.10" + node: '>= 0.10' resolution: integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== /ip/1.1.5: @@ -3487,13 +3527,13 @@ packages: /ipaddr.js/1.9.1: dev: false engines: - node: ">= 0.10" + node: '>= 0.10' resolution: integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== /is-arguments/1.0.4: dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== /is-arrayish/0.2.1: @@ -3505,7 +3545,7 @@ packages: binary-extensions: 2.1.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== /is-buffer/1.1.6: @@ -3515,13 +3555,13 @@ packages: /is-buffer/2.0.4: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== /is-callable/1.2.0: dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== /is-ci/2.0.0: @@ -3534,25 +3574,25 @@ packages: /is-date-object/1.0.2: dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== /is-docker/2.0.0: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ== /is-extglob/2.1.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= /is-finite/1.1.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== /is-fullwidth-code-point/1.0.0: @@ -3560,25 +3600,25 @@ packages: number-is-nan: 1.0.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-754xOG8DGn8NZDr4L95QxFfvAMs= /is-fullwidth-code-point/2.0.0: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= /is-fullwidth-code-point/3.0.0: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== /is-generator-function/1.0.7: dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw== /is-glob/3.1.0: @@ -3586,7 +3626,7 @@ packages: is-extglob: 2.1.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= /is-glob/4.0.1: @@ -3594,7 +3634,7 @@ packages: is-extglob: 2.1.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== /is-module/1.0.0: @@ -3604,12 +3644,12 @@ packages: /is-number/7.0.0: dev: false engines: - node: ">=0.12.0" + node: '>=0.12.0' resolution: integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== /is-reference/1.2.1: dependencies: - "@types/estree": 0.0.45 + '@types/estree': 0.0.45 dev: false resolution: integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== @@ -3618,19 +3658,19 @@ packages: has-symbols: 1.0.1 dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== /is-stream/1.1.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-EtSj3U5o4Lec6428hBc66A2RykQ= /is-stream/2.0.0: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== /is-subset/0.1.1: @@ -3642,7 +3682,7 @@ packages: has-symbols: 1.0.1 dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== /is-typed-array/1.1.3: @@ -3653,7 +3693,7 @@ packages: has-symbols: 1.0.1 dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ== /is-typedarray/1.0.0: @@ -3667,13 +3707,13 @@ packages: /is-valid-glob/1.0.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao= /is-windows/1.0.2: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== /is-wsl/2.2.0: @@ -3681,7 +3721,7 @@ packages: is-docker: 2.0.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== /isarray/0.0.1: @@ -3701,7 +3741,7 @@ packages: buffer-alloc: 1.2.0 dev: false engines: - node: ">=0.6.0" + node: '>=0.6.0' resolution: integrity: sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw== /isexe/2.0.0: @@ -3715,13 +3755,13 @@ packages: /istanbul-lib-coverage/2.0.5: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== /istanbul-lib-coverage/3.0.0: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== /istanbul-lib-hook/2.0.7: @@ -3729,32 +3769,32 @@ packages: append-transform: 1.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA== /istanbul-lib-instrument/3.3.0: dependencies: - "@babel/generator": 7.10.4 - "@babel/parser": 7.10.4 - "@babel/template": 7.10.4 - "@babel/traverse": 7.10.4 - "@babel/types": 7.10.4 + '@babel/generator': 7.10.4 + '@babel/parser': 7.10.4 + '@babel/template': 7.10.4 + '@babel/traverse': 7.10.4 + '@babel/types': 7.10.4 istanbul-lib-coverage: 2.0.5 semver: 6.3.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== /istanbul-lib-instrument/4.0.3: dependencies: - "@babel/core": 7.10.4 - "@istanbuljs/schema": 0.1.2 + '@babel/core': 7.10.4 + '@istanbuljs/schema': 0.1.2 istanbul-lib-coverage: 3.0.0 semver: 6.3.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== /istanbul-lib-report/2.0.8: @@ -3764,7 +3804,7 @@ packages: supports-color: 6.1.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ== /istanbul-lib-report/3.0.0: @@ -3774,7 +3814,7 @@ packages: supports-color: 7.1.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== /istanbul-lib-source-maps/3.0.6: @@ -3786,7 +3826,7 @@ packages: source-map: 0.6.1 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== /istanbul-lib-source-maps/4.0.0: @@ -3796,7 +3836,7 @@ packages: source-map: 0.6.1 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== /istanbul-reports/2.2.7: @@ -3804,7 +3844,7 @@ packages: html-escaper: 2.0.2 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg== /istanbul-reports/3.0.2: @@ -3813,7 +3853,7 @@ packages: istanbul-lib-report: 3.0.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== /istanbul/0.4.5: @@ -3843,7 +3883,7 @@ packages: /its-name/1.0.0: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha1-IGXxiD7LVoxl9xEt2/EjQB+uSvA= /jest-worker/24.9.0: @@ -3852,7 +3892,7 @@ packages: supports-color: 6.1.0 dev: false engines: - node: ">= 6" + node: '>= 6' resolution: integrity: sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== /jju/1.4.0: @@ -3890,7 +3930,7 @@ packages: /jsesc/2.5.2: dev: false engines: - node: ">=4" + node: '>=4' hasBin: true resolution: integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== @@ -3925,7 +3965,7 @@ packages: minimist: 1.2.5 dev: false engines: - node: ">=6" + node: '>=6' hasBin: true resolution: integrity: sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== @@ -3938,7 +3978,7 @@ packages: /jsonparse/1.2.0: dev: false engines: - "0": node >= 0.2.0 + '0': node >= 0.2.0 resolution: integrity: sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70= /jsprim/1.4.1: @@ -3949,7 +3989,7 @@ packages: verror: 1.10.0 dev: false engines: - "0": node >=0.6.0 + '0': node >=0.6.0 resolution: integrity: sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= /jssha/2.4.2: @@ -4000,8 +4040,8 @@ packages: karma: 4.4.1 dev: false peerDependencies: - chai: "*" - karma: ">=0.10.9" + chai: '*' + karma: '>=0.10.9' resolution: integrity: sha1-vuWtQEAFF4Ea40u5RfdikJEIt5o= /karma-chrome-launcher/3.1.0: @@ -4020,7 +4060,7 @@ packages: minimatch: 3.0.4 dev: false engines: - node: ">=10.0.0" + node: '>=10.0.0' resolution: integrity: sha512-zge5qiGEIKDdzWciQwP4p0LSac4k/L6VfrBsERMUn5mpDvxhv1sPVOrSlpzpi70T7NhuEy4bgnpAKIYuumIMCw== /karma-edge-launcher/0.4.2_karma@4.4.1: @@ -4029,9 +4069,9 @@ packages: karma: 4.4.1 dev: false engines: - node: ">=4" + node: '>=4' peerDependencies: - karma: ">=0.9" + karma: '>=0.9' resolution: integrity: sha512-YAJZb1fmRcxNhMIWYsjLuxwODBjh2cSHgTW/jkVmdpGguJjLbs9ZgIK/tEJsMQcBLUkO+yO4LBbqYxqgGW2HIw== /karma-env-preprocessor/0.1.1: @@ -4050,7 +4090,7 @@ packages: lodash: 4.17.15 dev: false peerDependencies: - karma: ">=0.9" + karma: '>=0.9' resolution: integrity: sha1-SXmGhCxJAZA0bNifVJTKmDDG1Zw= /karma-json-preprocessor/0.3.3_karma@4.4.1: @@ -4058,7 +4098,7 @@ packages: karma: 4.4.1 dev: false peerDependencies: - karma: ">=0.9" + karma: '>=0.9' resolution: integrity: sha1-X36ZW+uuS06PCiy1IVBVSq8LHi4= /karma-json-to-file-reporter/1.0.1: @@ -4074,9 +4114,9 @@ packages: xmlbuilder: 12.0.0 dev: false engines: - node: ">= 8" + node: '>= 8' peerDependencies: - karma: ">=0.9" + karma: '>=0.9' resolution: integrity: sha512-VtcGfE0JE4OE1wn0LK8xxDKaTP7slN8DO3I+4xg6gAi1IoAHAXOJ1V9G/y45Xg6sxdxPOR3THCFtDlAfBo9Afw== /karma-mocha-reporter/2.2.5_karma@4.4.1: @@ -4087,7 +4127,7 @@ packages: strip-ansi: 4.0.0 dev: false peerDependencies: - karma: ">=0.13" + karma: '>=0.13' resolution: integrity: sha1-FRIAlejtgZGG5HoLAS8810GJVWA= /karma-mocha/1.3.0: @@ -4103,7 +4143,7 @@ packages: remap-istanbul: 0.9.6 dev: false peerDependencies: - karma: ">=0.9" + karma: '>=0.9' resolution: integrity: sha1-l/O3cAZSVPm0ck8tm+SjouG69vw= /karma-rollup-preprocessor/7.0.5_rollup@1.32.1: @@ -4113,9 +4153,9 @@ packages: rollup: 1.32.1 dev: false engines: - node: ">= 8.0.0" + node: '>= 8.0.0' peerDependencies: - rollup: ">= 1.0.0" + rollup: '>= 1.0.0' resolution: integrity: sha512-VhZI81l8LZBvBrSf4xaojsbur7bcycsSlxXkYaTOjV6DQwx1gtAM0CQVdue7LuIbXB1AohYIg0S5at+dqDtMxQ== /karma-sourcemap-loader/0.3.7: @@ -4154,7 +4194,7 @@ packages: useragent: 2.3.0 dev: false engines: - node: ">= 8" + node: '>= 8' hasBin: true resolution: integrity: sha512-L5SIaXEYqzrh6b1wqYC42tNsFMx2PWuxky84pK9coK09MvmL7mxii3G3bZBh/0rvD27lqDd0le9jyhzvwif73A== @@ -4169,7 +4209,7 @@ packages: /lazy-ass/1.6.0: dev: false engines: - node: "> 0.8" + node: '> 0.8' resolution: integrity: sha1-eZllXoZGwX8In90YfRUNMyTVRRM= /levn/0.3.0: @@ -4178,7 +4218,7 @@ packages: type-check: 0.3.2 dev: false engines: - node: ">= 0.8.0" + node: '>= 0.8.0' resolution: integrity: sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= /load-json-file/1.1.0: @@ -4190,7 +4230,7 @@ packages: strip-bom: 2.0.0 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= /load-json-file/4.0.0: @@ -4201,7 +4241,7 @@ packages: strip-bom: 3.0.0 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-L19Fq5HjMhYjT9U62rZo607AmTs= /locate-path/3.0.0: @@ -4210,7 +4250,7 @@ packages: path-exists: 3.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== /locate-path/5.0.0: @@ -4218,7 +4258,7 @@ packages: p-locate: 4.1.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== /lodash._basecopy/3.0.1: @@ -4329,7 +4369,7 @@ packages: chalk: 2.4.2 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== /log-symbols/3.0.0: @@ -4337,7 +4377,7 @@ packages: chalk: 2.4.2 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== /log4js/4.5.1: @@ -4349,7 +4389,7 @@ packages: streamroller: 1.0.6 dev: false engines: - node: ">=6.0" + node: '>=6.0' resolution: integrity: sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw== /long/4.0.0: @@ -4362,7 +4402,7 @@ packages: signal-exit: 3.0.3 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= /lru-cache/4.1.5: @@ -4385,7 +4425,7 @@ packages: /macos-release/2.4.0: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-ko6deozZYiAkqa/0gmcsz+p4jSy3gY7/ZsCEokPaYd8k+6/aXGkiTgr61+Owup7Sf+xjqW8u2ElhoM9SEcEfuA== /magic-string/0.25.7: @@ -4400,7 +4440,7 @@ packages: semver: 5.7.1 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== /make-dir/3.1.0: @@ -4408,7 +4448,7 @@ packages: semver: 6.3.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== /make-error/1.3.6: @@ -4418,13 +4458,13 @@ packages: /map-obj/1.0.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= /marked/0.8.2: dev: false engines: - node: ">= 8.16.2" + node: '>= 8.16.2' hasBin: true resolution: integrity: sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw== @@ -4438,7 +4478,7 @@ packages: resolve-dir: 1.0.1 dev: false engines: - node: ">= 0.12.0" + node: '>= 0.12.0' resolution: integrity: sha512-7ivM1jFZVTOOS77QsR+TtYHH0ecdLclMkqbf5qiJdX2RorqfhsL65QHySPZgDE0ZjHoh+mQUNHTanNXIlzXd0Q== /md5.js/1.3.4: @@ -4459,13 +4499,13 @@ packages: /media-typer/0.3.0: dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= /memorystream/0.3.1: dev: false engines: - node: ">= 0.10.0" + node: '>= 0.10.0' resolution: integrity: sha1-htcJCzDORV1j+64S3aUaR93K+bI= /meow/3.7.0: @@ -4482,7 +4522,7 @@ packages: trim-newlines: 1.0.0 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= /merge-descriptors/1.0.1: @@ -4502,13 +4542,13 @@ packages: /methods/1.1.2: dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= /mime-db/1.44.0: dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== /mime-types/2.1.27: @@ -4516,33 +4556,33 @@ packages: mime-db: 1.44.0 dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== /mime/1.6.0: dev: false engines: - node: ">=4" + node: '>=4' hasBin: true resolution: integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== /mime/2.4.6: dev: false engines: - node: ">=4.0.0" + node: '>=4.0.0' hasBin: true resolution: integrity: sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== /mimic-fn/2.1.0: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== /mimic-response/2.1.0: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== /min-document/2.19.0: @@ -4602,7 +4642,7 @@ packages: xml: 1.0.1 dev: false peerDependencies: - mocha: ">=2.2.5" + mocha: '>=2.2.5' resolution: integrity: sha512-ed8LqbRj1RxZfjt/oC9t12sfrWsjZ3gNnbhV1nuj9R/Jb5/P3Xb4duv2eCfCDMYH+fEu0mqca7m4wsiVjsxsvA== /mocha/7.2.0: @@ -4633,7 +4673,7 @@ packages: yargs-unparser: 1.6.0 dev: false engines: - node: ">= 8.10.0" + node: '>= 8.10.0' hasBin: true resolution: integrity: sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ== @@ -4647,7 +4687,7 @@ packages: normalize-path: 2.1.1 dev: false engines: - node: ">=4.3.0" + node: '>=4.3.0' resolution: integrity: sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg== /moment/2.27.0: @@ -4671,7 +4711,7 @@ packages: tslib: 1.13.0 dev: false engines: - node: ">=0.8.0" + node: '>=0.8.0' resolution: integrity: sha512-vhcpM/ELL+UI7i4HzCegcbSfPMLqf3kp8mAT840bK1ZaDcb7Z1mOJik1jg202V0yfnh/bBPxZhQP6xFgD9g5eA== /multipipe/0.1.2: @@ -4706,7 +4746,7 @@ packages: /negotiator/0.6.2: dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== /neo-async/2.6.1: @@ -4720,7 +4760,7 @@ packages: /netmask/1.0.6: dev: false engines: - node: ">= 0.4.0" + node: '>= 0.4.0' resolution: integrity: sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= /nice-try/1.0.5: @@ -4729,9 +4769,9 @@ packages: integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== /nise/4.0.4: dependencies: - "@sinonjs/commons": 1.8.0 - "@sinonjs/fake-timers": 6.0.1 - "@sinonjs/text-encoding": 0.7.1 + '@sinonjs/commons': 1.8.0 + '@sinonjs/fake-timers': 6.0.1 + '@sinonjs/text-encoding': 0.7.1 just-extend: 4.1.0 path-to-regexp: 1.8.0 dev: false @@ -4745,7 +4785,7 @@ packages: propagate: 2.0.1 dev: false engines: - node: ">= 10.13" + node: '>= 10.13' resolution: integrity: sha512-QNb/j8kbFnKCiyqi9C5DD0jH/FubFGj5rt9NQFONXwQm3IPB0CULECg/eS3AU1KgZb/6SwUa4/DTRKhVxkGABw== /node-abi/2.18.0: @@ -4796,13 +4836,13 @@ packages: remove-trailing-separator: 1.1.0 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= /normalize-path/3.0.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== /npm-run-all/4.1.5: @@ -4818,7 +4858,7 @@ packages: string.prototype.padend: 3.1.0 dev: false engines: - node: ">= 4" + node: '>= 4' hasBin: true resolution: integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== @@ -4827,7 +4867,7 @@ packages: path-key: 2.0.1 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= /npm-run-path/4.0.1: @@ -4835,7 +4875,7 @@ packages: path-key: 3.1.1 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== /npmlog/4.1.2: @@ -4850,7 +4890,7 @@ packages: /number-is-nan/1.0.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= /nyc/14.1.1: @@ -4882,7 +4922,7 @@ packages: yargs-parser: 13.1.2 dev: false engines: - node: ">=6" + node: '>=6' hasBin: true resolution: integrity: sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw== @@ -4893,13 +4933,13 @@ packages: /object-assign/3.0.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I= /object-assign/4.1.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= /object-component/0.0.3: @@ -4913,7 +4953,7 @@ packages: /object-keys/1.1.1: dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== /object.assign/4.1.0: @@ -4924,7 +4964,7 @@ packages: object-keys: 1.1.1 dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== /object.getownpropertydescriptors/2.1.0: @@ -4933,7 +4973,7 @@ packages: es-abstract: 1.17.6 dev: false engines: - node: ">= 0.8" + node: '>= 0.8' resolution: integrity: sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== /on-finished/2.3.0: @@ -4941,7 +4981,7 @@ packages: ee-first: 1.1.1 dev: false engines: - node: ">= 0.8" + node: '>= 0.8' resolution: integrity: sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= /once/1.4.0: @@ -4955,7 +4995,7 @@ packages: mimic-fn: 2.1.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== /open/7.0.4: @@ -4964,7 +5004,7 @@ packages: is-wsl: 2.2.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-brSA+/yq+b08Hsr4c8fsEW2CRzk1BmfN3SAK/5VCHQ9bdoZJ4qa/+AfR0xHjlbbZUyPkUHs1b8x1RqdyZdkVqQ== /optimist/0.6.1: @@ -4984,13 +5024,13 @@ packages: word-wrap: 1.2.3 dev: false engines: - node: ">= 0.8.0" + node: '>= 0.8.0' resolution: integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== /os-homedir/1.0.2: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-/7xJiDNuDoM94MFox+8VISGqf7M= /os-name/3.1.0: @@ -4999,25 +5039,25 @@ packages: windows-release: 3.3.1 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg== /os-tmpdir/1.0.2: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= /p-finally/1.0.0: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= /p-finally/2.0.1: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== /p-limit/2.3.0: @@ -5025,7 +5065,7 @@ packages: p-try: 2.2.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== /p-locate/3.0.0: @@ -5033,7 +5073,7 @@ packages: p-limit: 2.3.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== /p-locate/4.1.0: @@ -5041,13 +5081,13 @@ packages: p-limit: 2.3.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== /p-try/2.2.0: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== /pac-proxy-agent/3.0.1: @@ -5081,7 +5121,7 @@ packages: release-zalgo: 1.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA== /parent-module/1.0.1: @@ -5089,7 +5129,7 @@ packages: callsites: 3.1.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== /parse-json/2.2.0: @@ -5097,7 +5137,7 @@ packages: error-ex: 1.3.2 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= /parse-json/4.0.0: @@ -5106,19 +5146,19 @@ packages: json-parse-better-errors: 1.0.2 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= /parse-node-version/1.0.1: dev: false engines: - node: ">= 0.10" + node: '>= 0.10' resolution: integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== /parse-passwd/1.0.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= /parseqs/0.0.5: @@ -5136,7 +5176,7 @@ packages: /parseurl/1.3.3: dev: false engines: - node: ">= 0.8" + node: '>= 0.8' resolution: integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== /path-browserify/1.0.1: @@ -5148,37 +5188,37 @@ packages: pinkie-promise: 2.0.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= /path-exists/3.0.0: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= /path-exists/4.0.0: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== /path-is-absolute/1.0.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18= /path-key/2.0.1: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= /path-key/3.1.1: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== /path-parse/1.0.6: @@ -5206,7 +5246,7 @@ packages: pinkie-promise: 2.0.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= /path-type/3.0.0: @@ -5214,7 +5254,7 @@ packages: pify: 3.0.0 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== /pathval/1.1.0: @@ -5232,32 +5272,32 @@ packages: /picomatch/2.2.2: dev: false engines: - node: ">=8.6" + node: '>=8.6' resolution: integrity: sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== /pidtree/0.3.1: dev: false engines: - node: ">=0.10" + node: '>=0.10' hasBin: true resolution: integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA== /pify/2.3.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-7RQaasBDqEnqWISY59yosVMw6Qw= /pify/3.0.0: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= /pify/4.0.1: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== /pinkie-promise/2.0.1: @@ -5265,13 +5305,13 @@ packages: pinkie: 2.0.4 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-ITXW36ejWMBprJsXh3YogihFD/o= /pinkie/2.0.4: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-clVrgM+g1IqXToDnckjoDtT3+HA= /pkg-dir/3.0.0: @@ -5279,13 +5319,13 @@ packages: find-up: 3.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== /pluralize/8.0.0: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== /prebuild-install/5.3.3: @@ -5307,20 +5347,20 @@ packages: which-pm-runs: 1.0.0 dev: false engines: - node: ">=6" + node: '>=6' hasBin: true resolution: integrity: sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g== /prelude-ls/1.1.2: dev: false engines: - node: ">= 0.8.0" + node: '>= 0.8.0' resolution: integrity: sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= /prettier/1.19.1: dev: false engines: - node: ">=4" + node: '>=4' hasBin: true resolution: integrity: sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== @@ -5339,13 +5379,13 @@ packages: /process/0.11.10: dev: false engines: - node: ">= 0.6.0" + node: '>= 0.6.0' resolution: integrity: sha1-czIwDoQBYb2j5podHZGn1LwW8YI= /progress/2.0.3: dev: false engines: - node: ">=0.4.0" + node: '>=0.4.0' resolution: integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== /promise/8.1.0: @@ -5357,7 +5397,7 @@ packages: /propagate/2.0.1: dev: false engines: - node: ">= 8" + node: '>= 8' resolution: integrity: sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== /proxy-addr/2.0.6: @@ -5366,7 +5406,7 @@ packages: ipaddr.js: 1.9.1 dev: false engines: - node: ">= 0.10" + node: '>= 0.10' resolution: integrity: sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== /proxy-agent/3.1.1: @@ -5381,7 +5421,7 @@ packages: socks-proxy-agent: 4.0.2 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-WudaR0eTsDx33O3EJE16PjBRZWcX8GqCEeERw1W3hZJgH/F2a46g7jty6UGty6NeJ4CKQy8ds2CJPMiyeqaTvw== /proxy-from-env/1.1.0: @@ -5410,7 +5450,7 @@ packages: /punycode/2.1.1: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== /pupa/2.0.1: @@ -5418,7 +5458,7 @@ packages: escape-goat: 2.1.1 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA== /puppeteer/3.3.0: @@ -5435,32 +5475,32 @@ packages: ws: 7.3.0 dev: false engines: - node: ">=10.18.1" + node: '>=10.18.1' requiresBuild: true resolution: integrity: sha512-23zNqRltZ1PPoK28uRefWJ/zKb5Jhnzbbwbpcna2o5+QMn17F0khq5s1bdH3vPlyj+J36pubccR8wiNA/VE0Vw== /qjobs/1.2.0: dev: false engines: - node: ">=0.9" + node: '>=0.9' resolution: integrity: sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== /qs/6.5.2: dev: false engines: - node: ">=0.6" + node: '>=0.6' resolution: integrity: sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== /qs/6.7.0: dev: false engines: - node: ">=0.6" + node: '>=0.6' resolution: integrity: sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== /qs/6.9.4: dev: false engines: - node: ">=0.6" + node: '>=0.6' resolution: integrity: sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== /query-string/5.1.1: @@ -5470,13 +5510,13 @@ packages: strict-uri-encode: 1.1.0 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== /querystring/0.2.0: dev: false engines: - node: ">=0.4.x" + node: '>=0.4.x' resolution: integrity: sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= /quote/0.4.0: @@ -5494,7 +5534,7 @@ packages: /range-parser/1.2.1: dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== /raw-body/2.4.0: @@ -5505,7 +5545,7 @@ packages: unpipe: 1.0.0 dev: false engines: - node: ">= 0.8" + node: '>= 0.8' resolution: integrity: sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== /raw-body/2.4.1: @@ -5516,7 +5556,7 @@ packages: unpipe: 1.0.0 dev: false engines: - node: ">= 0.8" + node: '>= 0.8' resolution: integrity: sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA== /rc/1.2.8: @@ -5535,7 +5575,7 @@ packages: read-pkg: 1.1.0 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= /read-pkg-up/4.0.0: @@ -5544,7 +5584,7 @@ packages: read-pkg: 3.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA== /read-pkg/1.1.0: @@ -5554,7 +5594,7 @@ packages: path-type: 1.1.0 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= /read-pkg/3.0.0: @@ -5564,7 +5604,7 @@ packages: path-type: 3.0.0 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= /readable-stream/1.1.14: @@ -5606,7 +5646,7 @@ packages: util-deprecate: 1.0.2 dev: false engines: - node: ">= 6" + node: '>= 6' resolution: integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== /readdirp/3.2.0: @@ -5614,7 +5654,7 @@ packages: picomatch: 2.2.2 dev: false engines: - node: ">= 8" + node: '>= 8' resolution: integrity: sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== /readdirp/3.4.0: @@ -5622,7 +5662,7 @@ packages: picomatch: 2.2.2 dev: false engines: - node: ">=8.10.0" + node: '>=8.10.0' resolution: integrity: sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== /rechoir/0.6.2: @@ -5630,7 +5670,7 @@ packages: resolve: 1.17.0 dev: false engines: - node: ">= 0.10" + node: '>= 0.10' resolution: integrity: sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= /redent/1.0.0: @@ -5639,7 +5679,7 @@ packages: strip-indent: 1.0.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= /regenerator-runtime/0.11.1: @@ -5653,13 +5693,13 @@ packages: /regexpp/2.0.1: dev: false engines: - node: ">=6.5.0" + node: '>=6.5.0' resolution: integrity: sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== /regexpp/3.1.0: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== /release-zalgo/1.0.0: @@ -5667,7 +5707,7 @@ packages: es6-error: 4.1.1 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA= /remap-istanbul/0.9.6: @@ -5691,13 +5731,13 @@ packages: is-finite: 1.1.0 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= /replace-ext/0.0.1: dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ= /request/2.88.2: @@ -5722,16 +5762,16 @@ packages: tough-cookie: 2.5.0 tunnel-agent: 0.6.0 uuid: 3.4.0 - deprecated: "request has been deprecated, see https://github.com/request/request/issues/3142" + deprecated: 'request has been deprecated, see https://github.com/request/request/issues/3142' dev: false engines: - node: ">= 6" + node: '>= 6' resolution: integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== /require-directory/2.1.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I= /require-main-filename/2.0.0: @@ -5741,7 +5781,7 @@ packages: /requirejs/2.3.6: dev: false engines: - node: ">=0.4.0" + node: '>=0.4.0' hasBin: true resolution: integrity: sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg== @@ -5755,17 +5795,17 @@ packages: global-modules: 1.0.0 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= /resolve-from/4.0.0: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== /resolve-url/0.2.1: - deprecated: "https://github.com/lydell/resolve-url#deprecated" + deprecated: 'https://github.com/lydell/resolve-url#deprecated' dev: false resolution: integrity: sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= @@ -5791,7 +5831,7 @@ packages: signal-exit: 3.0.3 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== /rfdc/1.1.4: @@ -5856,15 +5896,15 @@ packages: source-map-resolve: 0.5.3 dev: false engines: - node: ">=4.5.0" - npm: ">=2.15.9" + node: '>=4.5.0' + npm: '>=2.15.9' peerDependencies: - rollup: ">=0.31.2" + rollup: '>=0.31.2' resolution: integrity: sha1-YhJaqUCHqt97g+9N+vYptHMTXoc= /rollup-plugin-terser/5.3.0_rollup@1.32.1: dependencies: - "@babel/code-frame": 7.10.4 + '@babel/code-frame': 7.10.4 jest-worker: 24.9.0 rollup: 1.32.1 rollup-pluginutils: 2.8.2 @@ -5872,19 +5912,19 @@ packages: terser: 4.8.0 dev: false peerDependencies: - rollup: ">=0.66.0 <3" + rollup: '>=0.66.0 <3' resolution: integrity: sha512-XGMJihTIO3eIBsVGq7jiNYOdDMb3pVxuzY0uhOE/FM4x/u9nQgr3+McsjzqBn3QfHIpNSZmFnpoKAwHBEcsT7g== /rollup-plugin-uglify/6.0.4_rollup@1.32.1: dependencies: - "@babel/code-frame": 7.10.4 + '@babel/code-frame': 7.10.4 jest-worker: 24.9.0 rollup: 1.32.1 serialize-javascript: 2.1.2 uglify-js: 3.10.0 dev: false peerDependencies: - rollup: ">=0.66.0 <2" + rollup: '>=0.66.0 <2' resolution: integrity: sha512-ddgqkH02klveu34TF0JqygPwZnsbhHVI6t8+hGTcYHngPkQb5MIHI0XiztXIN/d6V9j+efwHAqEL7LspSxQXGw== /rollup-plugin-visualizer/4.0.4_rollup@1.32.1: @@ -5897,10 +5937,10 @@ packages: yargs: 15.3.1 dev: false engines: - node: ">=10" + node: '>=10' hasBin: true peerDependencies: - rollup: ">=1.20.0" + rollup: '>=1.20.0' resolution: integrity: sha512-odkyLiVxCEXh4AWFSl75+pbIapzhEZkOVww8pKUgraOHicSH67MYMnAOHWQVK/BYeD1cCiF/0kk8/XNX2+LM9A== /rollup-pluginutils/2.8.2: @@ -5911,8 +5951,8 @@ packages: integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== /rollup/1.32.1: dependencies: - "@types/estree": 0.0.45 - "@types/node": 8.10.61 + '@types/estree': 0.0.45 + '@types/node': 8.10.61 acorn: 7.3.1 dev: false hasBin: true @@ -5921,7 +5961,7 @@ packages: /run-async/2.4.1: dev: false engines: - node: ">=0.12.0" + node: '>=0.12.0' resolution: integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== /rxjs/6.5.5: @@ -5929,7 +5969,7 @@ packages: tslib: 1.13.0 dev: false engines: - npm: ">=2.0.0" + npm: '>=2.0.0' resolution: integrity: sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== /safe-buffer/5.1.2: @@ -5955,7 +5995,7 @@ packages: /semaphore/1.1.0: dev: false engines: - node: ">=0.8.0" + node: '>=0.8.0' resolution: integrity: sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA== /semver/5.3.0: @@ -5976,7 +6016,7 @@ packages: /semver/7.3.2: dev: false engines: - node: ">=10" + node: '>=10' hasBin: true resolution: integrity: sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== @@ -5997,7 +6037,7 @@ packages: statuses: 1.5.0 dev: false engines: - node: ">= 0.8.0" + node: '>= 0.8.0' resolution: integrity: sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== /serialize-javascript/2.1.2: @@ -6012,7 +6052,7 @@ packages: send: 0.17.1 dev: false engines: - node: ">= 0.8.0" + node: '>= 0.8.0' resolution: integrity: sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== /set-blocking/2.0.0: @@ -6028,7 +6068,7 @@ packages: shebang-regex: 1.0.0 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= /shebang-command/2.0.0: @@ -6036,19 +6076,19 @@ packages: shebang-regex: 3.0.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== /shebang-regex/1.0.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= /shebang-regex/3.0.0: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== /shell-quote/1.7.2: @@ -6062,7 +6102,7 @@ packages: rechoir: 0.6.2 dev: false engines: - node: ">=4" + node: '>=4' hasBin: true resolution: integrity: sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== @@ -6073,7 +6113,7 @@ packages: shelljs: 0.8.4 dev: false engines: - node: ">=4" + node: '>=4' hasBin: true resolution: integrity: sha512-aS0mWtW3T2sHAenrSrip2XGv39O9dXIFUqxAEWHEOS1ePtGIBavdPJY1kE2IHl14V/4iCbUiNDPGdyYTtmhSoA== @@ -6095,10 +6135,10 @@ packages: integrity: sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== /sinon/9.0.2: dependencies: - "@sinonjs/commons": 1.8.0 - "@sinonjs/fake-timers": 6.0.1 - "@sinonjs/formatio": 5.0.1 - "@sinonjs/samsam": 5.0.3 + '@sinonjs/commons': 1.8.0 + '@sinonjs/fake-timers': 6.0.1 + '@sinonjs/formatio': 5.0.1 + '@sinonjs/samsam': 5.0.3 diff: 4.0.2 nise: 4.0.4 supports-color: 7.1.0 @@ -6112,14 +6152,14 @@ packages: is-fullwidth-code-point: 2.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== /smart-buffer/4.1.0: dev: false engines: - node: ">= 6.0.0" - npm: ">= 3.0.0" + node: '>= 6.0.0' + npm: '>= 3.0.0' resolution: integrity: sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== /snap-shot-compare/3.0.0: @@ -6133,7 +6173,7 @@ packages: variable-diff: 1.1.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-bdwNOAGuKwPU+qsn0ASxTv+QfkXU+3VmkcDOkt965tes+JQQc8d6SfoLiEiRVhCey4v+ip2IjNUSbZm5nnkI9g== /snap-shot-core/10.2.0: @@ -6153,13 +6193,13 @@ packages: ramda: 0.26.1 dev: false engines: - node: ">=6" + node: '>=6' hasBin: true resolution: integrity: sha512-FsP+Wd4SCA4bLSm3vi6OVgfmGQcAQkUhwy45zDjZDm/6dZ5SDIgP40ORHg7z6MgMAK2+fj2DmhW7SXyvMU55Vw== /snap-shot-it/7.9.3: dependencies: - "@bahmutov/data-driven": 1.0.0 + '@bahmutov/data-driven': 1.0.0 check-more-types: 2.24.0 common-tags: 1.8.0 debug: 4.1.1 @@ -6172,7 +6212,7 @@ packages: snap-shot-core: 10.2.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-S5e59fbbc02AGA94LFWGVl/y/+jLMLr0/aykCtPYBE5rcyV9pSa63Aoik66/L8SkZ9piqqSmBmRAYm46+QvqBA== /socket.io-adapter/1.1.2: @@ -6223,7 +6263,7 @@ packages: socks: 2.3.3 dev: false engines: - node: ">= 6" + node: '>= 6' resolution: integrity: sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg== /socks/2.3.3: @@ -6232,8 +6272,8 @@ packages: smart-buffer: 4.1.0 dev: false engines: - node: ">= 6.0.0" - npm: ">= 3.0.0" + node: '>= 6.0.0' + npm: '>= 3.0.0' resolution: integrity: sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA== /source-map-resolve/0.5.3: @@ -6262,26 +6302,26 @@ packages: amdefine: 1.0.1 dev: false engines: - node: ">=0.8.0" + node: '>=0.8.0' optional: true resolution: integrity: sha1-2rc/vPwrqBm03gO9b26qSBZLP50= /source-map/0.5.7: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= /source-map/0.6.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== /source-map/0.7.3: dev: false engines: - node: ">= 8" + node: '>= 8' resolution: integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== /sourcemap-codec/1.4.8: @@ -6291,7 +6331,7 @@ packages: /sparkles/1.0.1: dev: false engines: - node: ">= 0.10" + node: '>= 0.10' resolution: integrity: sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw== /spawn-wrap/1.4.3: @@ -6344,14 +6384,14 @@ packages: tweetnacl: 0.14.5 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' hasBin: true resolution: integrity: sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== /statuses/1.5.0: dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= /stream-browserify/2.0.2: @@ -6377,13 +6417,13 @@ packages: lodash: 4.17.15 dev: false engines: - node: ">=6.0" + node: '>=6.0' resolution: integrity: sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg== /strict-uri-encode/1.1.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= /string-width/1.0.2: @@ -6393,7 +6433,7 @@ packages: strip-ansi: 3.0.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= /string-width/2.1.1: @@ -6402,7 +6442,7 @@ packages: strip-ansi: 4.0.0 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== /string-width/3.1.0: @@ -6412,7 +6452,7 @@ packages: strip-ansi: 5.2.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== /string-width/4.2.0: @@ -6422,7 +6462,7 @@ packages: strip-ansi: 6.0.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== /string.prototype.padend/3.1.0: @@ -6431,7 +6471,7 @@ packages: es-abstract: 1.17.6 dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA== /string.prototype.trimend/1.0.1: @@ -6469,7 +6509,7 @@ packages: ansi-regex: 2.1.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= /strip-ansi/4.0.0: @@ -6477,7 +6517,7 @@ packages: ansi-regex: 3.0.0 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-qEeQIusaw2iocTibY1JixQXuNo8= /strip-ansi/5.2.0: @@ -6485,7 +6525,7 @@ packages: ansi-regex: 4.1.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== /strip-ansi/6.0.0: @@ -6493,7 +6533,7 @@ packages: ansi-regex: 5.0.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== /strip-bom/2.0.0: @@ -6501,25 +6541,25 @@ packages: is-utf8: 0.2.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= /strip-bom/3.0.0: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= /strip-eof/1.0.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= /strip-final-newline/2.0.0: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== /strip-indent/1.0.1: @@ -6527,26 +6567,26 @@ packages: get-stdin: 4.0.1 dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' hasBin: true resolution: integrity: sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= /strip-json-comments/2.0.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo= /strip-json-comments/3.1.0: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== /supports-color/2.0.0: dev: false engines: - node: ">=0.8.0" + node: '>=0.8.0' resolution: integrity: sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= /supports-color/3.2.3: @@ -6554,7 +6594,7 @@ packages: has-flag: 1.0.0 dev: false engines: - node: ">=0.8.0" + node: '>=0.8.0' resolution: integrity: sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= /supports-color/5.5.0: @@ -6562,7 +6602,7 @@ packages: has-flag: 3.0.0 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== /supports-color/6.0.0: @@ -6570,7 +6610,7 @@ packages: has-flag: 3.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== /supports-color/6.1.0: @@ -6578,7 +6618,7 @@ packages: has-flag: 3.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== /supports-color/7.1.0: @@ -6586,7 +6626,7 @@ packages: has-flag: 4.0.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== /table/5.4.6: @@ -6597,7 +6637,7 @@ packages: string-width: 3.1.0 dev: false engines: - node: ">=6.0.0" + node: '>=6.0.0' resolution: integrity: sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== /tar-fs/2.1.0: @@ -6626,7 +6666,7 @@ packages: source-map-support: 0.5.19 dev: false engines: - node: ">=6.0.0" + node: '>=6.0.0' hasBin: true resolution: integrity: sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== @@ -6638,7 +6678,7 @@ packages: require-main-filename: 2.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g== /text-table/0.2.0: @@ -6663,7 +6703,7 @@ packages: /time-stamp/1.1.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= /timsort/0.3.0: @@ -6675,7 +6715,7 @@ packages: os-tmpdir: 1.0.2 dev: false engines: - node: ">=0.6.0" + node: '>=0.6.0' resolution: integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== /to-array/0.1.4: @@ -6685,7 +6725,7 @@ packages: /to-fast-properties/2.0.0: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= /to-regex-range/5.0.1: @@ -6693,13 +6733,13 @@ packages: is-number: 7.0.0 dev: false engines: - node: ">=8.0" + node: '>=8.0' resolution: integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== /toidentifier/1.0.0: dev: false engines: - node: ">=0.6" + node: '>=0.6' resolution: integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== /tough-cookie/2.5.0: @@ -6708,7 +6748,7 @@ packages: punycode: 2.1.1 dev: false engines: - node: ">=0.8" + node: '>=0.8' resolution: integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== /tough-cookie/4.0.0: @@ -6718,7 +6758,7 @@ packages: universalify: 0.1.2 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== /tr46/1.0.1: @@ -6730,7 +6770,7 @@ packages: /trim-newlines/1.0.0: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-WIeWa7WCpFA6QetST301ARgVphM= /ts-node/8.10.2_typescript@3.9.6: @@ -6743,10 +6783,10 @@ packages: yn: 3.1.1 dev: false engines: - node: ">=6.0.0" + node: '>=6.0.0' hasBin: true peerDependencies: - typescript: ">=2.7" + typescript: '>=2.7' resolution: integrity: sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== /tslib/1.13.0: @@ -6760,13 +6800,13 @@ packages: /tslint-config-prettier/1.18.0: dev: false engines: - node: ">=4.0.0" + node: '>=4.0.0' hasBin: true resolution: integrity: sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg== /tslint/5.20.1_typescript@3.9.6: dependencies: - "@babel/code-frame": 7.10.4 + '@babel/code-frame': 7.10.4 builtin-modules: 1.1.1 chalk: 2.4.2 commander: 2.20.3 @@ -6782,10 +6822,10 @@ packages: typescript: 3.9.6 dev: false engines: - node: ">=4.8.0" + node: '>=4.8.0' hasBin: true peerDependencies: - typescript: ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev" + typescript: '>=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev' resolution: integrity: sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg== /tsutils/2.29.0_typescript@3.9.6: @@ -6794,7 +6834,7 @@ packages: typescript: 3.9.6 dev: false peerDependencies: - typescript: ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + typescript: '>=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev' resolution: integrity: sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== /tsutils/3.17.1_typescript@3.9.6: @@ -6803,9 +6843,9 @@ packages: typescript: 3.9.6 dev: false engines: - node: ">= 6" + node: '>= 6' peerDependencies: - typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' resolution: integrity: sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== /tunnel-agent/0.6.0: @@ -6817,7 +6857,7 @@ packages: /tunnel/0.0.6: dev: false engines: - node: ">=0.6.11 <=0.7.0 || >=0.7.3" + node: '>=0.6.11 <=0.7.0 || >=0.7.3' resolution: integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== /tweetnacl/0.14.5: @@ -6829,25 +6869,25 @@ packages: prelude-ls: 1.1.2 dev: false engines: - node: ">= 0.8.0" + node: '>= 0.8.0' resolution: integrity: sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= /type-detect/4.0.8: dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== /type-fest/0.11.0: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== /type-fest/0.8.1: dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== /type-is/1.6.18: @@ -6856,7 +6896,7 @@ packages: mime-types: 2.1.27 dev: false engines: - node: ">= 0.6" + node: '>= 0.6' resolution: integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== /typedoc-default-themes/0.6.3: @@ -6867,12 +6907,12 @@ packages: underscore: 1.10.2 dev: false engines: - node: ">= 8" + node: '>= 8' resolution: integrity: sha512-rouf0TcIA4M2nOQFfC7Zp4NEwoYiEX4vX/ZtudJWU9IHA29MPC+PPgSXYLPESkUo7FuB//GxigO3mk9Qe1xp3Q== /typedoc/0.15.8: dependencies: - "@types/minimatch": 3.0.3 + '@types/minimatch': 3.0.3 fs-extra: 8.1.0 handlebars: 4.7.6 highlight.js: 9.18.1 @@ -6885,28 +6925,28 @@ packages: typescript: 3.7.5 dev: false engines: - node: ">= 6.0.0" + node: '>= 6.0.0' hasBin: true resolution: integrity: sha512-a0zypcvfIFsS7Gqpf2MkC1+jNND3K1Om38pbDdy/gYWX01NuJZhC5+O0HkIp0oRIZOo7PWrA5+fC24zkANY28Q== /typescript/3.7.5: dev: false engines: - node: ">=4.2.0" + node: '>=4.2.0' hasBin: true resolution: integrity: sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== /typescript/3.9.6: dev: false engines: - node: ">=4.2.0" + node: '>=4.2.0' hasBin: true resolution: integrity: sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw== /uglify-js/3.10.0: dev: false engines: - node: ">=0.8.0" + node: '>=0.8.0' hasBin: true resolution: integrity: sha512-Esj5HG5WAyrLIdYU74Z3JdG2PxdIusvj6IWHMtlyESxc7kcDz7zYlYjpnSokn1UbpV0d/QX9fan7gkCNd/9BQA== @@ -6932,13 +6972,13 @@ packages: /universalify/0.1.2: dev: false engines: - node: ">= 4.0.0" + node: '>= 4.0.0' resolution: integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== /unpipe/1.0.0: dev: false engines: - node: ">= 0.8" + node: '>= 0.8' resolution: integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= /uri-js/4.2.2: @@ -6948,7 +6988,7 @@ packages: resolution: integrity: sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== /urix/0.1.0: - deprecated: "Please see https://github.com/lydell/urix#deprecated" + deprecated: 'Please see https://github.com/lydell/urix#deprecated' dev: false resolution: integrity: sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= @@ -6996,7 +7036,7 @@ packages: /utils-merge/1.0.1: dev: false engines: - node: ">= 0.4.0" + node: '>= 0.4.0' resolution: integrity: sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= /uuid/3.4.0: @@ -7023,13 +7063,13 @@ packages: /validator/8.2.0: dev: false engines: - node: ">= 0.10" + node: '>= 0.10' resolution: integrity: sha512-Yw5wW34fSv5spzTXNkokD6S6/Oq92d8q/t14TqsS3fAiA1RYnxSFSIZ+CY3n6PGGRCq5HhJTSepQvFUS2QUDxA== /validator/9.4.1: dev: false engines: - node: ">= 0.10" + node: '>= 0.10' resolution: integrity: sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA== /variable-diff/1.1.0: @@ -7042,7 +7082,7 @@ packages: /vary/1.1.2: dev: false engines: - node: ">= 0.8" + node: '>= 0.8' resolution: integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= /verror/1.10.0: @@ -7052,7 +7092,7 @@ packages: extsprintf: 1.3.0 dev: false engines: - "0": node >=0.6.0 + '0': node >=0.6.0 resolution: integrity: sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= /vinyl/0.5.3: @@ -7062,13 +7102,13 @@ packages: replace-ext: 0.0.1 dev: false engines: - node: ">= 0.9" + node: '>= 0.9' resolution: integrity: sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4= /void-elements/2.0.1: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= /webidl-conversions/4.0.2: @@ -7101,7 +7141,7 @@ packages: is-typed-array: 1.1.3 dev: false engines: - node: ">= 0.4" + node: '>= 0.4' resolution: integrity: sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ== /which/1.3.1: @@ -7116,7 +7156,7 @@ packages: isexe: 2.0.0 dev: false engines: - node: ">= 8" + node: '>= 8' hasBin: true resolution: integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== @@ -7131,19 +7171,19 @@ packages: execa: 1.0.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-Pngk/RDCaI/DkuHPlGTdIkDiTAnAkyMjoQMZqRsxydNl1qGXNIoZrB7RK8g53F2tEgQBMqQJHQdYZuQEEAu54A== /word-wrap/1.2.3: dev: false engines: - node: ">=0.10.0" + node: '>=0.10.0' resolution: integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== /wordwrap/0.0.3: dev: false engines: - node: ">=0.4.0" + node: '>=0.4.0' resolution: integrity: sha1-o9XabNXAvAAI03I0u68b7WMFkQc= /wordwrap/1.0.0: @@ -7157,7 +7197,7 @@ packages: strip-ansi: 5.2.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== /wrap-ansi/6.2.0: @@ -7167,7 +7207,7 @@ packages: strip-ansi: 6.0.0 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== /wrappy/1.0.2: @@ -7187,7 +7227,7 @@ packages: mkdirp: 0.5.5 dev: false engines: - node: ">=4" + node: '>=4' resolution: integrity: sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== /ws/3.3.3: @@ -7201,7 +7241,7 @@ packages: /ws/7.3.0: dev: false engines: - node: ">=8.3.0" + node: '>=8.3.0' peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ^5.0.2 @@ -7235,43 +7275,43 @@ packages: xmlbuilder: 11.0.1 dev: false engines: - node: ">=4.0.0" + node: '>=4.0.0' resolution: integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== /xmlbuilder/11.0.1: dev: false engines: - node: ">=4.0" + node: '>=4.0' resolution: integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== /xmlbuilder/12.0.0: dev: false engines: - node: ">=6.0" + node: '>=6.0' resolution: integrity: sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ== /xmlbuilder/9.0.7: dev: false engines: - node: ">=4.0" + node: '>=4.0' resolution: integrity: sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= /xmldom/0.3.0: dev: false engines: - node: ">=10.0.0" + node: '>=10.0.0' resolution: integrity: sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g== /xmlhttprequest-ssl/1.5.5: dev: false engines: - node: ">=0.4.0" + node: '>=0.4.0' resolution: integrity: sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= /xpath.js/1.1.0: dev: false engines: - node: ">=0.4.0" + node: '>=0.4.0' resolution: integrity: sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ== /xregexp/2.0.0: @@ -7281,7 +7321,7 @@ packages: /xtend/4.0.2: dev: false engines: - node: ">=0.4" + node: '>=0.4' resolution: integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== /y18n/4.0.0: @@ -7309,7 +7349,7 @@ packages: decamelize: 1.2.0 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== /yargs-unparser/1.6.0: @@ -7319,7 +7359,7 @@ packages: yargs: 13.3.2 dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== /yargs/13.3.2: @@ -7352,7 +7392,7 @@ packages: yargs-parser: 18.1.3 dev: false engines: - node: ">=8" + node: '>=8' resolution: integrity: sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== /yauzl/2.10.0: @@ -7369,7 +7409,7 @@ packages: /yn/3.1.1: dev: false engines: - node: ">=6" + node: '>=6' resolution: integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== /z-schema/3.18.4: @@ -7383,17 +7423,17 @@ packages: commander: 2.20.3 resolution: integrity: sha512-DUOKC/IhbkdLKKiV89gw9DUauTV8U/8yJl1sjf6MtDmzevLKOF2duNJ495S3MFVjqZarr+qNGCPbkg4mu4PpLw== - "file:projects/abort-controller.tgz": - dependencies: - "@microsoft/api-extractor": 7.7.11 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/abort-controller.tgz': + dependencies: + '@microsoft/api-extractor': 7.7.11 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 cross-env: 7.0.2 delay: 4.3.0 @@ -7425,28 +7465,28 @@ packages: tslib: 2.0.0 typescript: 3.9.6 dev: false - name: "@rush-temp/abort-controller" + name: '@rush-temp/abort-controller' resolution: integrity: sha512-CP1NBDwa1NQHEdixtTsw4YFZpb9n5A1RZQZf6eFR73qUfOPHGJOj6TCFSypUSr7V2tHLzvfa9j4HG8ONyLNIfw== - tarball: "file:projects/abort-controller.tgz" + tarball: 'file:projects/abort-controller.tgz' version: 0.0.0 - "file:projects/ai-form-recognizer.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/fs-extra": 8.1.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/sinon": 9.0.4 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/ai-form-recognizer.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/fs-extra': 8.1.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/sinon': 9.0.4 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 chai: 4.2.0 chai-as-promised: 7.1.1_chai@4.2.0 cross-env: 7.0.2 @@ -7485,28 +7525,28 @@ packages: tslib: 2.0.0 typescript: 3.9.6 dev: false - name: "@rush-temp/ai-form-recognizer" + name: '@rush-temp/ai-form-recognizer' resolution: integrity: sha512-J6oJMlzTfy+mkGykMrSHXdI0Ge3dssflDYu6k4JrlYy87e19yJ+tgMNoEoUFYri95KO2ts34+BM9/sLaAIRV7w== - tarball: "file:projects/ai-form-recognizer.tgz" + tarball: 'file:projects/ai-form-recognizer.tgz' version: 0.0.0 - "file:projects/ai-text-analytics.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/chai-as-promised": 7.1.2 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/sinon": 9.0.4 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/ai-text-analytics.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/chai-as-promised': 7.1.2 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/sinon': 9.0.4 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 chai: 4.2.0 chai-as-promised: 7.1.1_chai@4.2.0 cross-env: 7.0.2 @@ -7544,26 +7584,26 @@ packages: tslib: 2.0.0 typescript: 3.9.6 dev: false - name: "@rush-temp/ai-text-analytics" + name: '@rush-temp/ai-text-analytics' resolution: integrity: sha512-0qtWNE3in6ZnLbonLVPQViYfV5ab8pF9+dZiqta/xszQziRU6maqTTXfHX3H92zfBFLJhkyXuUFp5RRxCRX+yg== - tarball: "file:projects/ai-text-analytics.tgz" + tarball: 'file:projects/ai-text-analytics.tgz' version: 0.0.0 - "file:projects/app-configuration.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-inject": 4.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/sinon": 9.0.4 + 'file:projects/app-configuration.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-inject': 4.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/sinon': 9.0.4 assert: 1.5.0 chai: 4.2.0 cross-env: 7.0.2 @@ -7601,30 +7641,30 @@ packages: typescript: 3.9.6 uglify-js: 3.10.0 dev: false - name: "@rush-temp/app-configuration" + name: '@rush-temp/app-configuration' resolution: integrity: sha512-AF3tZ0LP0kLHCPSFZKZiFK8I3YmzBe/dqo8UMcu31ZU6gLIZEt0aUinJ56cNpj5MEZgjBM6DRvCV0sDoXV+RJg== - tarball: "file:projects/app-configuration.tgz" + tarball: 'file:projects/app-configuration.tgz' version: 0.0.0 - "file:projects/core-amqp.tgz": - dependencies: - "@microsoft/api-extractor": 7.7.11 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-inject": 4.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/async-lock": 1.1.2 - "@types/chai": 4.2.11 - "@types/chai-as-promised": 7.1.2 - "@types/debug": 4.1.5 - "@types/is-buffer": 2.0.0 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/sinon": 9.0.4 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/core-amqp.tgz': + dependencies: + '@microsoft/api-extractor': 7.7.11 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-inject': 4.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/async-lock': 1.1.2 + '@types/chai': 4.2.11 + '@types/chai-as-promised': 7.1.2 + '@types/debug': 4.1.5 + '@types/is-buffer': 2.0.0 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/sinon': 9.0.4 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 async-lock: 1.2.4 buffer: 5.6.0 @@ -7667,19 +7707,19 @@ packages: util: 0.12.3 ws: 7.3.0 dev: false - name: "@rush-temp/core-amqp" + name: '@rush-temp/core-amqp' resolution: integrity: sha512-LsvJAYHJ5hn2n1Cv2+l5OinaagHK8Jo2b9hA3XeUG2+6KNy15LTqmYcbEHkYgfoDHSloa3jFKOUO4+9j01MHnw== - tarball: "file:projects/core-amqp.tgz" + tarball: 'file:projects/core-amqp.tgz' version: 0.0.0 - "file:projects/core-arm.tgz": - dependencies: - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/core-arm.tgz': + dependencies: + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 chai: 4.2.0 eslint: 6.8.0 eslint-config-prettier: 6.11.0_eslint@6.8.0 @@ -7700,16 +7740,16 @@ packages: typescript: 3.9.6 uglify-js: 3.10.0 dev: false - name: "@rush-temp/core-arm" + name: '@rush-temp/core-arm' resolution: integrity: sha512-DCrEbS5wWu+Yw/s6djt/DAHg+YclXiOQ9F8vC2PZ4sU2qpLIlcltNSgRKb1S+7mTiDcm6RbV0YvS1+cUdkiqyA== - tarball: "file:projects/core-arm.tgz" + tarball: 'file:projects/core-arm.tgz' version: 0.0.0 - "file:projects/core-asynciterator-polyfill.tgz": + 'file:projects/core-asynciterator-polyfill.tgz': dependencies: - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 eslint: 6.8.0 eslint-config-prettier: 6.11.0_eslint@6.8.0 eslint-plugin-no-null: 1.0.2_eslint@6.8.0 @@ -7718,25 +7758,25 @@ packages: prettier: 1.19.1 typescript: 3.9.6 dev: false - name: "@rush-temp/core-asynciterator-polyfill" + name: '@rush-temp/core-asynciterator-polyfill' resolution: integrity: sha512-H+PZImxHoD4ZcrJie2qstZmXk1/DHGyIq9b+eSSv4TjT9VsCbeaaEEaZ/iCsoUyzDuLWTj2MYqNEgU4L9ROTzA== - tarball: "file:projects/core-asynciterator-polyfill.tgz" + tarball: 'file:projects/core-asynciterator-polyfill.tgz' version: 0.0.0 - "file:projects/core-auth.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/core-auth.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 cross-env: 7.0.2 downlevel-dts: 0.4.0 @@ -7758,25 +7798,25 @@ packages: typescript: 3.9.6 util: 0.12.3 dev: false - name: "@rush-temp/core-auth" + name: '@rush-temp/core-auth' resolution: integrity: sha512-i2QFSjrS1hTQZZZNL5JoYk3cfdydWOs6eZNq6bzRBuhICjXfOHx5TARU8q0VsN4KCfvueMQzuocD6SE5ugVAEA== - tarball: "file:projects/core-auth.tgz" + tarball: 'file:projects/core-auth.tgz' version: 0.0.0 - "file:projects/core-client.tgz": - dependencies: - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/core-client.tgz': + dependencies: + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 chai: 4.2.0 cross-env: 7.0.2 downlevel-dts: 0.4.0 @@ -7809,34 +7849,34 @@ packages: typescript: 3.9.6 util: 0.12.3 dev: false - name: "@rush-temp/core-client" + name: '@rush-temp/core-client' resolution: integrity: sha512-YxuXJ37MWTcVyeknL1LwiaHLjoC0PFP2V/CTggVUT6b1LDpJlKnN6Owbw3f1FvBfbrZcniWWneli75KQjfh70w== - tarball: "file:projects/core-client.tgz" + tarball: 'file:projects/core-client.tgz' version: 0.0.0 - "file:projects/core-http.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@azure/logger-js": 1.3.2 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/express": 4.17.6 - "@types/glob": 7.1.2 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/node-fetch": 2.5.7 - "@types/sinon": 9.0.4 - "@types/tough-cookie": 4.0.0 - "@types/tunnel": 0.0.1 - "@types/uuid": 8.0.0 - "@types/xml2js": 0.4.5 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/core-http.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@azure/logger-js': 1.3.2 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/express': 4.17.6 + '@types/glob': 7.1.2 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/node-fetch': 2.5.7 + '@types/sinon': 9.0.4 + '@types/tough-cookie': 4.0.0 + '@types/tunnel': 0.0.1 + '@types/uuid': 8.0.0 + '@types/xml2js': 0.4.5 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 babel-runtime: 6.26.0 chai: 4.2.0 cross-env: 7.0.2 @@ -7883,28 +7923,28 @@ packages: xhr-mock: 2.5.1 xml2js: 0.4.23 dev: false - name: "@rush-temp/core-http" + name: '@rush-temp/core-http' resolution: integrity: sha512-yHkPDNijSvmUciUX6ZH6wkusnoZax+p7L/ulARDWd3S8xQ99J7RpzaVwXhY+FdLIAx3SHMq/Z+vugP532dVy7g== - tarball: "file:projects/core-http.tgz" + tarball: 'file:projects/core-http.tgz' version: 0.0.0 - "file:projects/core-https.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/sinon": 9.0.4 - "@types/uuid": 8.0.0 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/core-https.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/sinon': 9.0.4 + '@types/uuid': 8.0.0 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 chai: 4.2.0 cross-env: 7.0.2 downlevel-dts: 0.4.0 @@ -7942,24 +7982,24 @@ packages: util: 0.12.3 uuid: 8.2.0 dev: false - name: "@rush-temp/core-https" + name: '@rush-temp/core-https' resolution: integrity: sha512-bEkkjOgJgxKr5ZOi1FJTbApY36v1Xx50acbjFHH6sIBCLLqMDVNw0xV4sneRBHux43ZeBqrgieStp4Fsj2VNag== - tarball: "file:projects/core-https.tgz" + tarball: 'file:projects/core-https.tgz' version: 0.0.0 - "file:projects/core-lro.tgz": - dependencies: - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/core-lro.tgz': + dependencies: + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 chai: 4.2.0 eslint: 6.8.0 @@ -7995,16 +8035,16 @@ packages: typescript: 3.9.6 uglify-js: 3.10.0 dev: false - name: "@rush-temp/core-lro" + name: '@rush-temp/core-lro' resolution: integrity: sha512-Ap0icykIshfugWcAS/2+rNvrDKVkcfp2NtHQJEKYOfUBx1DHEVnB9V28u5lYmHvWfbmaAm1l6E6Bae02xOB48g== - tarball: "file:projects/core-lro.tgz" + tarball: 'file:projects/core-lro.tgz' version: 0.0.0 - "file:projects/core-paging.tgz": + 'file:projects/core-paging.tgz': dependencies: - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 eslint: 6.8.0 eslint-config-prettier: 6.11.0_eslint@6.8.0 eslint-plugin-no-null: 1.0.2_eslint@6.8.0 @@ -8013,25 +8053,25 @@ packages: prettier: 1.19.1 typescript: 3.9.6 dev: false - name: "@rush-temp/core-paging" + name: '@rush-temp/core-paging' resolution: integrity: sha512-jLeKpwyTeRLv1nD/QCwOzTvrQAceI3vZS3lGSxxR569SGdt4luWEKPxpql0GZ5jXLX8vg+t1dbnxeUcHikJWPw== - tarball: "file:projects/core-paging.tgz" + tarball: 'file:projects/core-paging.tgz' version: 0.0.0 - "file:projects/core-tracing.tgz": - dependencies: - "@microsoft/api-extractor": 7.7.11 - "@opencensus/web-types": 0.0.7 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/core-tracing.tgz': + dependencies: + '@microsoft/api-extractor': 7.7.11 + '@opencensus/web-types': 0.0.7 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 cross-env: 7.0.2 eslint: 6.8.0 @@ -8052,31 +8092,31 @@ packages: typescript: 3.9.6 util: 0.12.3 dev: false - name: "@rush-temp/core-tracing" + name: '@rush-temp/core-tracing' resolution: integrity: sha512-DXwjq+nIqEQLgnm1tiM6e+eyySwsWYp0Kdzli3RjPzmxAmD382fIrChGURS7SU8TbXu8YzPJSq5aCAAg+RLYIg== - tarball: "file:projects/core-tracing.tgz" + tarball: 'file:projects/core-tracing.tgz' version: 0.0.0 - "file:projects/cosmos.tgz": - dependencies: - "@azure/eslint-plugin-azure-sdk": 2.0.1_984cbb313f9ea271f36cadd8f9814e06 - "@microsoft/api-extractor": 7.7.11 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@types/debug": 4.1.5 - "@types/fast-json-stable-stringify": 2.0.0 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/node-fetch": 2.5.7 - "@types/priorityqueuejs": 1.0.1 - "@types/semaphore": 1.1.0 - "@types/sinon": 9.0.4 - "@types/tunnel": 0.0.1 - "@types/underscore": 1.10.2 - "@types/uuid": 8.0.0 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/eslint-plugin-tslint": 2.34.0_f8f62cb1f34b48259c049dd0f60912e9 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/cosmos.tgz': + dependencies: + '@azure/eslint-plugin-azure-sdk': 2.0.1_984cbb313f9ea271f36cadd8f9814e06 + '@microsoft/api-extractor': 7.7.11 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@types/debug': 4.1.5 + '@types/fast-json-stable-stringify': 2.0.0 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/node-fetch': 2.5.7 + '@types/priorityqueuejs': 1.0.1 + '@types/semaphore': 1.1.0 + '@types/sinon': 9.0.4 + '@types/tunnel': 0.0.1 + '@types/underscore': 1.10.2 + '@types/uuid': 8.0.0 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/eslint-plugin-tslint': 2.34.0_f8f62cb1f34b48259c049dd0f60912e9 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 cross-env: 7.0.2 debug: 4.1.1 dotenv: 8.2.0 @@ -8113,24 +8153,24 @@ packages: typescript: 3.9.6 uuid: 8.2.0 dev: false - name: "@rush-temp/cosmos" + name: '@rush-temp/cosmos' resolution: integrity: sha512-AEA+nq1Up+clYIiCU7RhTM+oZZ/EaPeJ44lZCT5K+9zIQhdVK7SnXEh7R1W7ySRlBc/wvUH2v5M2gZPa2pYcPA== - tarball: "file:projects/cosmos.tgz" + tarball: 'file:projects/cosmos.tgz' version: 0.0.0 - "file:projects/eslint-plugin-azure-sdk.tgz": - dependencies: - "@types/bluebird": 3.5.32 - "@types/chai": 4.2.11 - "@types/eslint": 4.16.8 - "@types/estree": 0.0.39 - "@types/glob": 7.1.2 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/experimental-utils": 2.34.0_eslint@6.8.0+typescript@3.9.6 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 - "@typescript-eslint/typescript-estree": 2.34.0_typescript@3.9.6 + 'file:projects/eslint-plugin-azure-sdk.tgz': + dependencies: + '@types/bluebird': 3.5.32 + '@types/chai': 4.2.11 + '@types/eslint': 4.16.8 + '@types/estree': 0.0.39 + '@types/glob': 7.1.2 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/experimental-utils': 2.34.0_eslint@6.8.0+typescript@3.9.6 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 + '@typescript-eslint/typescript-estree': 2.34.0_typescript@3.9.6 bluebird: 3.7.2 chai: 4.2.0 eslint: 6.8.0 @@ -8146,35 +8186,35 @@ packages: tslib: 2.0.0 typescript: 3.9.6 dev: false - name: "@rush-temp/eslint-plugin-azure-sdk" + name: '@rush-temp/eslint-plugin-azure-sdk' resolution: integrity: sha512-xuus5wsdkMICaNiqnO+aFLntroiwfATOf5ksUz0FabvdgHk73JqiD4K0k77lraWQxTo48ww+fsufinYd8Jp/cQ== - tarball: "file:projects/eslint-plugin-azure-sdk.tgz" + tarball: 'file:projects/eslint-plugin-azure-sdk.tgz' version: 0.0.0 - "file:projects/event-hubs.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-inject": 4.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/async-lock": 1.1.2 - "@types/chai": 4.2.11 - "@types/chai-as-promised": 7.1.2 - "@types/chai-string": 1.4.2 - "@types/debug": 4.1.5 - "@types/long": 4.0.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/sinon": 9.0.4 - "@types/uuid": 8.0.0 - "@types/ws": 7.2.6 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/event-hubs.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-inject': 4.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/async-lock': 1.1.2 + '@types/chai': 4.2.11 + '@types/chai-as-promised': 7.1.2 + '@types/chai-string': 1.4.2 + '@types/debug': 4.1.5 + '@types/long': 4.0.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/sinon': 9.0.4 + '@types/uuid': 8.0.0 + '@types/ws': 7.2.6 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 buffer: 5.6.0 chai: 4.2.0 @@ -8220,33 +8260,33 @@ packages: uuid: 8.2.0 ws: 7.3.0 dev: false - name: "@rush-temp/event-hubs" + name: '@rush-temp/event-hubs' resolution: integrity: sha512-mFcVc6WBPBac/+1cHV7l6S98uQlnMNotM1tqKe1kvefV7IMCIpBK+LgUvXGiU88Xqo2a0mjuaIJ5rhYqwavWjw== - tarball: "file:projects/event-hubs.tgz" + tarball: 'file:projects/event-hubs.tgz' version: 0.0.0 - "file:projects/event-processor-host.tgz": - dependencies: - "@azure/eslint-plugin-azure-sdk": 2.0.1_984cbb313f9ea271f36cadd8f9814e06 - "@azure/event-hubs": 2.1.4 - "@azure/ms-rest-nodeauth": 0.9.3 - "@microsoft/api-extractor": 7.7.11 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/async-lock": 1.1.2 - "@types/chai": 4.2.11 - "@types/chai-as-promised": 7.1.2 - "@types/chai-string": 1.4.2 - "@types/debug": 4.1.5 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/uuid": 8.0.0 - "@types/ws": 7.2.6 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/event-processor-host.tgz': + dependencies: + '@azure/eslint-plugin-azure-sdk': 2.0.1_984cbb313f9ea271f36cadd8f9814e06 + '@azure/event-hubs': 2.1.4 + '@azure/ms-rest-nodeauth': 0.9.3 + '@microsoft/api-extractor': 7.7.11 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/async-lock': 1.1.2 + '@types/chai': 4.2.11 + '@types/chai-as-promised': 7.1.2 + '@types/chai-string': 1.4.2 + '@types/debug': 4.1.5 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/uuid': 8.0.0 + '@types/ws': 7.2.6 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 async-lock: 1.2.4 azure-storage: 2.10.3 chai: 4.2.0 @@ -8277,29 +8317,30 @@ packages: uuid: 8.2.0 ws: 7.3.0 dev: false - name: "@rush-temp/event-processor-host" + name: '@rush-temp/event-processor-host' resolution: integrity: sha512-v8//igZBDMs+pWxrztd05w2tLFKpZA2XZVqaXKMVMUGVrBZOspFux/4jR2xRJYWgpgzu/u/cJ6B8Tj9XBkI5Pw== - tarball: "file:projects/event-processor-host.tgz" + tarball: 'file:projects/event-processor-host.tgz' version: 0.0.0 - "file:projects/eventhubs-checkpointstore-blob.tgz": - dependencies: - "@azure/storage-blob": 12.1.2 - "@microsoft/api-extractor": 7.7.11 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-inject": 4.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/chai-as-promised": 7.1.2 - "@types/chai-string": 1.4.2 - "@types/debug": 4.1.5 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/eventhubs-checkpointstore-blob.tgz': + dependencies: + '@azure/event-hubs': 5.2.2 + '@azure/storage-blob': 12.1.2 + '@microsoft/api-extractor': 7.7.11 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-inject': 4.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/chai-as-promised': 7.1.2 + '@types/chai-string': 1.4.2 + '@types/debug': 4.1.5 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 chai: 4.2.0 chai-as-promised: 7.1.1_chai@4.2.0 @@ -8342,29 +8383,29 @@ packages: typescript: 3.9.6 util: 0.12.3 dev: false - name: "@rush-temp/eventhubs-checkpointstore-blob" + name: '@rush-temp/eventhubs-checkpointstore-blob' resolution: - integrity: sha512-a2VkvB0VXuppKkDffruxfSgqSFzDSxaY0eIrKr6mLVXE2dhmttnQUqt/v8Rhfam0e9YQZ78STghOi9eyO3/oKg== - tarball: "file:projects/eventhubs-checkpointstore-blob.tgz" + integrity: sha512-yAtH+PkS66Vya1u6rFOuKprUZX+c8tVGk/jC6HFvK38X8ZmR+v1W6v9oKD0atccu4QVym87lhdEqr+u3D0Ogag== + tarball: 'file:projects/eventhubs-checkpointstore-blob.tgz' version: 0.0.0 - "file:projects/identity.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/express": 4.17.6 - "@types/jws": 3.2.2 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/qs": 6.9.3 - "@types/uuid": 8.0.0 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/identity.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/express': 4.17.6 + '@types/jws': 3.2.2 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/qs': 6.9.3 + '@types/uuid': 8.0.0 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 cross-env: 7.0.2 eslint: 6.8.0 @@ -8398,23 +8439,23 @@ packages: util: 0.12.3 uuid: 8.2.0 dev: false - name: "@rush-temp/identity" + name: '@rush-temp/identity' resolution: integrity: sha512-fMltFDFjWqi7FV7o0rpozKWYF4NYhnbH4Ii1GQmEGznUZqCWdtuf77Dh1DU8EwN1tkC22cNPHb02UG1KSO8yUw== - tarball: "file:projects/identity.tgz" + tarball: 'file:projects/identity.tgz' version: 0.0.0 - "file:projects/keyvault-admin.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/keyvault-admin.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 cross-env: 7.0.2 eslint: 6.8.0 eslint-config-prettier: 6.11.0_eslint@6.8.0 @@ -8433,29 +8474,29 @@ packages: tslib: 2.0.0 typescript: 3.9.6 dev: false - name: "@rush-temp/keyvault-admin" + name: '@rush-temp/keyvault-admin' resolution: integrity: sha512-VfS6x+BkW0cPqEmBaEDD50mQwBbEjnNm+oWNUmxpEIuGdjRYzM1Ko9l6tUOv4b5FkagtgmOrMNElZTqfLxpEMw== - tarball: "file:projects/keyvault-admin.tgz" + tarball: 'file:projects/keyvault-admin.tgz' version: 0.0.0 - "file:projects/keyvault-certificates.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/fs-extra": 8.1.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/query-string": 6.2.0 - "@types/sinon": 9.0.4 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/keyvault-certificates.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/fs-extra': 8.1.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/query-string': 6.2.0 + '@types/sinon': 9.0.4 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 chai: 4.2.0 cross-env: 7.0.2 @@ -8498,39 +8539,39 @@ packages: typescript: 3.9.6 url: 0.11.0 dev: false - name: "@rush-temp/keyvault-certificates" + name: '@rush-temp/keyvault-certificates' resolution: integrity: sha512-otmneyGWp0xCx4UwtBqDtU3ufO4PAaG5d6bEXAA4T76s3+cUlqLZtC5Zwqnd/DwnQiWVILhUxAI7OE1HM5EM/A== - tarball: "file:projects/keyvault-certificates.tgz" + tarball: 'file:projects/keyvault-certificates.tgz' version: 0.0.0 - "file:projects/keyvault-common.tgz": + 'file:projects/keyvault-common.tgz': dependencies: tslib: 2.0.0 typescript: 3.9.6 dev: false - name: "@rush-temp/keyvault-common" + name: '@rush-temp/keyvault-common' resolution: integrity: sha512-8HjWYduon5drbrpCcg6nCz4gPCBF8KnLh67y2Usfv1JxpWvTzKuGKZrUgt/tGTu6U469ykVfC2Ehj/rwTwjCJw== - tarball: "file:projects/keyvault-common.tgz" + tarball: 'file:projects/keyvault-common.tgz' version: 0.0.0 - "file:projects/keyvault-keys.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/fs-extra": 8.1.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/query-string": 6.2.0 - "@types/sinon": 9.0.4 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/keyvault-keys.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/fs-extra': 8.1.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/query-string': 6.2.0 + '@types/sinon': 9.0.4 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 chai: 4.2.0 cross-env: 7.0.2 @@ -8573,29 +8614,29 @@ packages: typescript: 3.9.6 url: 0.11.0 dev: false - name: "@rush-temp/keyvault-keys" + name: '@rush-temp/keyvault-keys' resolution: integrity: sha512-jzriUWCWYbtt/6Gqu1CLcaBoJ/v0kzeoW/JNV3riquZc7vgIhJsJzOW3FwIjiWyUUNePgNWxdATyW3e6NR/MEA== - tarball: "file:projects/keyvault-keys.tgz" + tarball: 'file:projects/keyvault-keys.tgz' version: 0.0.0 - "file:projects/keyvault-secrets.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/fs-extra": 8.1.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/query-string": 6.2.0 - "@types/sinon": 9.0.4 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/keyvault-secrets.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/fs-extra': 8.1.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/query-string': 6.2.0 + '@types/sinon': 9.0.4 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 chai: 4.2.0 cross-env: 7.0.2 @@ -8638,24 +8679,24 @@ packages: typescript: 3.9.6 url: 0.11.0 dev: false - name: "@rush-temp/keyvault-secrets" + name: '@rush-temp/keyvault-secrets' resolution: integrity: sha512-4QvZ/dB/AaMMJbqK7pKQcYfjcBWdhYgxZrRkta9Bd7Y3DZyTIUGCjxnWDZa4CyyV1BQSDY/rpAlnCneQQZZGjg== - tarball: "file:projects/keyvault-secrets.tgz" + tarball: 'file:projects/keyvault-secrets.tgz' version: 0.0.0 - "file:projects/logger.tgz": - dependencies: - "@microsoft/api-extractor": 7.7.11 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/sinon": 9.0.4 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/logger.tgz': + dependencies: + '@microsoft/api-extractor': 7.7.11 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/sinon': 9.0.4 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 chai: 4.2.0 cross-env: 7.0.2 @@ -8691,27 +8732,27 @@ packages: tslib: 2.0.0 typescript: 3.9.6 dev: false - name: "@rush-temp/logger" + name: '@rush-temp/logger' resolution: integrity: sha512-DeR/cH/+CueWgvrfb5NOhy4FZD6drOOP9/u7zHTT8UHDEOVrPfE3VHbz5ZgXY5HjbMrs46qRmy/UbZ/gQifGGg== - tarball: "file:projects/logger.tgz" + tarball: 'file:projects/logger.tgz' version: 0.0.0 - "file:projects/search-documents.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/sinon": 9.0.4 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/search-documents.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/sinon': 9.0.4 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 chai: 4.2.0 cross-env: 7.0.2 dotenv: 8.2.0 @@ -8750,33 +8791,33 @@ packages: typescript: 3.9.6 util: 0.12.3 dev: false - name: "@rush-temp/search-documents" + name: '@rush-temp/search-documents' resolution: integrity: sha512-d2Mn4FQL/FzuxABfvT9scg1+vRrfZ4c66sFtyYjrIybU4B37YLx3bryrj+Ufc8+m+kcZcb+kHzon2Pr41h47fQ== - tarball: "file:projects/search-documents.tgz" + tarball: 'file:projects/search-documents.tgz' version: 0.0.0 - "file:projects/service-bus.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-inject": 4.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/chai-as-promised": 7.1.2 - "@types/debug": 4.1.5 - "@types/glob": 7.1.2 - "@types/is-buffer": 2.0.0 - "@types/long": 4.0.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/ws": 7.2.6 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/service-bus.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-inject': 4.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/chai-as-promised': 7.1.2 + '@types/debug': 4.1.5 + '@types/glob': 7.1.2 + '@types/is-buffer': 2.0.0 + '@types/long': 4.0.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/ws': 7.2.6 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 buffer: 5.6.0 chai: 4.2.0 @@ -8826,25 +8867,25 @@ packages: typescript: 3.9.6 ws: 7.3.0 dev: false - name: "@rush-temp/service-bus" + name: '@rush-temp/service-bus' resolution: integrity: sha512-wSAmks1T5tOckF82dT10wTLXXNblVKFh9YPilOMKyWGKQ32c1A23vPmY2NgUDLA9AkIjRuH+diL1Hqam3/Wh2g== - tarball: "file:projects/service-bus.tgz" + tarball: 'file:projects/service-bus.tgz' version: 0.0.0 - "file:projects/storage-blob-changefeed.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/sinon": 9.0.4 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/storage-blob-changefeed.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/sinon': 9.0.4 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 cross-env: 7.0.2 dotenv: 8.2.0 @@ -8889,24 +8930,24 @@ packages: typescript: 3.9.6 util: 0.12.3 dev: false - name: "@rush-temp/storage-blob-changefeed" + name: '@rush-temp/storage-blob-changefeed' resolution: integrity: sha512-OEmmBjFrsZdj3dlB5T/i7ZL1pvM+P8m9rUFHOzmVtA11+T3WwGbJ9r61z0HMDUH1Fc2cuWdxlKJJYETv8vTWsg== - tarball: "file:projects/storage-blob-changefeed.tgz" + tarball: 'file:projects/storage-blob-changefeed.tgz' version: 0.0.0 - "file:projects/storage-blob.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/storage-blob.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 cross-env: 7.0.2 dotenv: 8.2.0 @@ -8950,26 +8991,26 @@ packages: typescript: 3.9.6 util: 0.12.3 dev: false - name: "@rush-temp/storage-blob" + name: '@rush-temp/storage-blob' resolution: integrity: sha512-X5SpvKqUvP2trk070l4e4dFQPQJmfl5dTbJsXHfr2/KWnjsi2h0HwbzrugwGrfcO5z3LntB5cXnkvPYCooLbkQ== - tarball: "file:projects/storage-blob.tgz" + tarball: 'file:projects/storage-blob.tgz' version: 0.0.0 - "file:projects/storage-file-datalake.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/fs-extra": 8.1.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@types/query-string": 6.2.0 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/storage-file-datalake.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/fs-extra': 8.1.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@types/query-string': 6.2.0 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 cross-env: 7.0.2 dotenv: 8.2.0 @@ -9016,24 +9057,24 @@ packages: typescript: 3.9.6 util: 0.12.3 dev: false - name: "@rush-temp/storage-file-datalake" + name: '@rush-temp/storage-file-datalake' resolution: integrity: sha512-1BJ5GBkoHlacpgfm3Xyv3ArI+BFipnFPfYOCyEUJhA9mGJMrG6eNDvWgWuLizBE1ZyvXXYaNRuwtmajGdCRDpQ== - tarball: "file:projects/storage-file-datalake.tgz" + tarball: 'file:projects/storage-file-datalake.tgz' version: 0.0.0 - "file:projects/storage-file-share.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/storage-file-share.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 cross-env: 7.0.2 dotenv: 8.2.0 @@ -9077,22 +9118,22 @@ packages: typescript: 3.9.6 util: 0.12.3 dev: false - name: "@rush-temp/storage-file-share" + name: '@rush-temp/storage-file-share' resolution: integrity: sha512-+grl8U9yaRX3UGYxkRfmey6oX4gRB/IiUff3B44r+s0ulNndZDPlBLj9zzDu5e7TEfl9kSZayeDdy4ufKwRFSg== - tarball: "file:projects/storage-file-share.tgz" + tarball: 'file:projects/storage-file-share.tgz' version: 0.0.0 - "file:projects/storage-internal-avro.tgz": - dependencies: - "@microsoft/api-extractor": 7.7.11 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/storage-internal-avro.tgz': + dependencies: + '@microsoft/api-extractor': 7.7.11 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 cross-env: 7.0.2 dotenv: 8.2.0 @@ -9135,24 +9176,24 @@ packages: typescript: 3.9.6 util: 0.12.3 dev: false - name: "@rush-temp/storage-internal-avro" + name: '@rush-temp/storage-internal-avro' resolution: integrity: sha512-b8cORQNpR2ljuDY3xEKtkpEbBQpJtMqHLcazaq2WneUNEF32aWQ8qS+j4hwyc8/S2LpYxXeJaEwWERTTpZYHSw== - tarball: "file:projects/storage-internal-avro.tgz" + tarball: 'file:projects/storage-internal-avro.tgz' version: 0.0.0 - "file:projects/storage-queue.tgz": - dependencies: - "@azure/core-tracing": 1.0.0-preview.8 - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/storage-queue.tgz': + dependencies: + '@azure/core-tracing': 1.0.0-preview.8 + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 cross-env: 7.0.2 dotenv: 8.2.0 @@ -9195,25 +9236,25 @@ packages: typescript: 3.9.6 util: 0.12.3 dev: false - name: "@rush-temp/storage-queue" + name: '@rush-temp/storage-queue' resolution: integrity: sha512-E6ImWiOuPf5pQGhAIZQazY04aR9GAiR0cFUblCT5s5f346DmloID1VfAsleww7nyzDtj1iRdUaWMzy/rpVYlMw== - tarball: "file:projects/storage-queue.tgz" + tarball: 'file:projects/storage-queue.tgz' version: 0.0.0 - "file:projects/tables.tgz": - dependencies: - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/tables.tgz': + dependencies: + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 chai: 4.2.0 cross-env: 7.0.2 downlevel-dts: 0.4.0 @@ -9246,24 +9287,24 @@ packages: typescript: 3.9.6 util: 0.12.3 dev: false - name: "@rush-temp/tables" + name: '@rush-temp/tables' resolution: integrity: sha512-RXIuE5quJ7ivUUn7oSwZOP7Vhc5TWDjMrYKhPaMhx0XHYfNJbR7VIfSwzdqchV16UAogmu8vFTegqIHzE7wLKQ== - tarball: "file:projects/tables.tgz" + tarball: 'file:projects/tables.tgz' version: 0.0.0 - "file:projects/template.tgz": - dependencies: - "@microsoft/api-extractor": 7.7.11 - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-json": 4.1.0_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/mocha": 7.0.2 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/template.tgz': + dependencies: + '@microsoft/api-extractor': 7.7.11 + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-json': 4.1.0_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/mocha': 7.0.2 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 cross-env: 7.0.2 eslint: 6.8.0 @@ -9296,19 +9337,19 @@ packages: typescript: 3.9.6 util: 0.12.3 dev: false - name: "@rush-temp/template" + name: '@rush-temp/template' resolution: integrity: sha512-+dvbgqLz+ED05Nx6LQICU3EP+ftI7QEnEKovzKqf4IQgpyf2BKDgaeOv8fPJ4sI3b6/yHAorjffanNoFxf1HrQ== - tarball: "file:projects/template.tgz" + tarball: 'file:projects/template.tgz' version: 0.0.0 - "file:projects/test-utils-perfstress.tgz": - dependencies: - "@opentelemetry/api": 0.6.1 - "@types/minimist": 1.2.0 - "@types/node": 8.10.61 - "@types/node-fetch": 2.5.7 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/test-utils-perfstress.tgz': + dependencies: + '@opentelemetry/api': 0.6.1 + '@types/minimist': 1.2.0 + '@types/node': 8.10.61 + '@types/node-fetch': 2.5.7 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 eslint: 6.8.0 eslint-plugin-no-only-tests: 2.4.0 eslint-plugin-promise: 4.2.1 @@ -9323,28 +9364,28 @@ packages: tslib: 2.0.0 typescript: 3.9.6 dev: false - name: "@rush-temp/test-utils-perfstress" + name: '@rush-temp/test-utils-perfstress' resolution: integrity: sha512-5hNzD8rPaOLuqwqEhVNpa3SMZMnNSBcGqf26N4gxzspZoMl4xpPIKh+CafaVLXBgZWnJxiwOnPZmO4f45yITzw== - tarball: "file:projects/test-utils-perfstress.tgz" + tarball: 'file:projects/test-utils-perfstress.tgz' version: 0.0.0 - "file:projects/test-utils-recorder.tgz": - dependencies: - "@opentelemetry/api": 0.6.1 - "@rollup/plugin-commonjs": 11.0.2_rollup@1.32.1 - "@rollup/plugin-multi-entry": 3.0.1_rollup@1.32.1 - "@rollup/plugin-node-resolve": 8.1.0_rollup@1.32.1 - "@rollup/plugin-replace": 2.3.3_rollup@1.32.1 - "@types/chai": 4.2.11 - "@types/fs-extra": 8.1.1 - "@types/md5": 2.2.0 - "@types/mocha": 7.0.2 - "@types/mock-fs": 4.10.0 - "@types/mock-require": 2.0.0 - "@types/nise": 1.4.0 - "@types/node": 8.10.61 - "@typescript-eslint/eslint-plugin": 2.34.0_3787943315ebc5ea524d5c102dc9e452 - "@typescript-eslint/parser": 2.34.0_eslint@6.8.0+typescript@3.9.6 + 'file:projects/test-utils-recorder.tgz': + dependencies: + '@opentelemetry/api': 0.6.1 + '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 + '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 + '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1 + '@rollup/plugin-replace': 2.3.3_rollup@1.32.1 + '@types/chai': 4.2.11 + '@types/fs-extra': 8.1.1 + '@types/md5': 2.2.0 + '@types/mocha': 7.0.2 + '@types/mock-fs': 4.10.0 + '@types/mock-require': 2.0.0 + '@types/nise': 1.4.0 + '@types/node': 8.10.61 + '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 + '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 chai: 4.2.0 eslint: 6.8.0 eslint-plugin-no-only-tests: 2.4.0 @@ -9383,17 +9424,17 @@ packages: typescript: 3.9.6 xhr-mock: 2.5.1 dev: false - name: "@rush-temp/test-utils-recorder" + name: '@rush-temp/test-utils-recorder' resolution: integrity: sha512-10CtIMD4P7n8J6FMHFvOz2ayYDLOq7/AAjEy+bcTBBbuReAgOqBqUEsno31YZZrqxjav+V3CIjoa7AiIo1CDLw== - tarball: "file:projects/test-utils-recorder.tgz" + tarball: 'file:projects/test-utils-recorder.tgz' version: 0.0.0 - "file:projects/testhub.tgz": + 'file:projects/testhub.tgz': dependencies: - "@azure/event-hubs": 2.1.4 - "@types/node": 8.10.61 - "@types/uuid": 8.0.0 - "@types/yargs": 15.0.5 + '@azure/event-hubs': 2.1.4 + '@types/node': 8.10.61 + '@types/uuid': 8.0.0 + '@types/yargs': 15.0.5 async-lock: 1.2.4 death: 1.1.0 debug: 4.1.1 @@ -9404,49 +9445,49 @@ packages: uuid: 8.2.0 yargs: 15.3.1 dev: false - name: "@rush-temp/testhub" + name: '@rush-temp/testhub' resolution: integrity: sha512-wWCeWZLGFaZKJOdG2zTpsvZxIdzw5SSDZfCYo/gcI3i0/IK+ukijKncP1wy1SpQG7pnc0CRY3IaJ6UdaMI93bg== - tarball: "file:projects/testhub.tgz" + tarball: 'file:projects/testhub.tgz' version: 0.0.0 -registry: "" +registry: '' specifiers: - "@rush-temp/abort-controller": "file:./projects/abort-controller.tgz" - "@rush-temp/ai-form-recognizer": "file:./projects/ai-form-recognizer.tgz" - "@rush-temp/ai-text-analytics": "file:./projects/ai-text-analytics.tgz" - "@rush-temp/app-configuration": "file:./projects/app-configuration.tgz" - "@rush-temp/core-amqp": "file:./projects/core-amqp.tgz" - "@rush-temp/core-arm": "file:./projects/core-arm.tgz" - "@rush-temp/core-asynciterator-polyfill": "file:./projects/core-asynciterator-polyfill.tgz" - "@rush-temp/core-auth": "file:./projects/core-auth.tgz" - "@rush-temp/core-client": "file:./projects/core-client.tgz" - "@rush-temp/core-http": "file:./projects/core-http.tgz" - "@rush-temp/core-https": "file:./projects/core-https.tgz" - "@rush-temp/core-lro": "file:./projects/core-lro.tgz" - "@rush-temp/core-paging": "file:./projects/core-paging.tgz" - "@rush-temp/core-tracing": "file:./projects/core-tracing.tgz" - "@rush-temp/cosmos": "file:./projects/cosmos.tgz" - "@rush-temp/eslint-plugin-azure-sdk": "file:./projects/eslint-plugin-azure-sdk.tgz" - "@rush-temp/event-hubs": "file:./projects/event-hubs.tgz" - "@rush-temp/event-processor-host": "file:./projects/event-processor-host.tgz" - "@rush-temp/eventhubs-checkpointstore-blob": "file:./projects/eventhubs-checkpointstore-blob.tgz" - "@rush-temp/identity": "file:./projects/identity.tgz" - "@rush-temp/keyvault-admin": "file:./projects/keyvault-admin.tgz" - "@rush-temp/keyvault-certificates": "file:./projects/keyvault-certificates.tgz" - "@rush-temp/keyvault-common": "file:./projects/keyvault-common.tgz" - "@rush-temp/keyvault-keys": "file:./projects/keyvault-keys.tgz" - "@rush-temp/keyvault-secrets": "file:./projects/keyvault-secrets.tgz" - "@rush-temp/logger": "file:./projects/logger.tgz" - "@rush-temp/search-documents": "file:./projects/search-documents.tgz" - "@rush-temp/service-bus": "file:./projects/service-bus.tgz" - "@rush-temp/storage-blob": "file:./projects/storage-blob.tgz" - "@rush-temp/storage-blob-changefeed": "file:./projects/storage-blob-changefeed.tgz" - "@rush-temp/storage-file-datalake": "file:./projects/storage-file-datalake.tgz" - "@rush-temp/storage-file-share": "file:./projects/storage-file-share.tgz" - "@rush-temp/storage-internal-avro": "file:./projects/storage-internal-avro.tgz" - "@rush-temp/storage-queue": "file:./projects/storage-queue.tgz" - "@rush-temp/tables": "file:./projects/tables.tgz" - "@rush-temp/template": "file:./projects/template.tgz" - "@rush-temp/test-utils-perfstress": "file:./projects/test-utils-perfstress.tgz" - "@rush-temp/test-utils-recorder": "file:./projects/test-utils-recorder.tgz" - "@rush-temp/testhub": "file:./projects/testhub.tgz" + '@rush-temp/abort-controller': 'file:./projects/abort-controller.tgz' + '@rush-temp/ai-form-recognizer': 'file:./projects/ai-form-recognizer.tgz' + '@rush-temp/ai-text-analytics': 'file:./projects/ai-text-analytics.tgz' + '@rush-temp/app-configuration': 'file:./projects/app-configuration.tgz' + '@rush-temp/core-amqp': 'file:./projects/core-amqp.tgz' + '@rush-temp/core-arm': 'file:./projects/core-arm.tgz' + '@rush-temp/core-asynciterator-polyfill': 'file:./projects/core-asynciterator-polyfill.tgz' + '@rush-temp/core-auth': 'file:./projects/core-auth.tgz' + '@rush-temp/core-client': 'file:./projects/core-client.tgz' + '@rush-temp/core-http': 'file:./projects/core-http.tgz' + '@rush-temp/core-https': 'file:./projects/core-https.tgz' + '@rush-temp/core-lro': 'file:./projects/core-lro.tgz' + '@rush-temp/core-paging': 'file:./projects/core-paging.tgz' + '@rush-temp/core-tracing': 'file:./projects/core-tracing.tgz' + '@rush-temp/cosmos': 'file:./projects/cosmos.tgz' + '@rush-temp/eslint-plugin-azure-sdk': 'file:./projects/eslint-plugin-azure-sdk.tgz' + '@rush-temp/event-hubs': 'file:./projects/event-hubs.tgz' + '@rush-temp/event-processor-host': 'file:./projects/event-processor-host.tgz' + '@rush-temp/eventhubs-checkpointstore-blob': 'file:./projects/eventhubs-checkpointstore-blob.tgz' + '@rush-temp/identity': 'file:./projects/identity.tgz' + '@rush-temp/keyvault-admin': 'file:./projects/keyvault-admin.tgz' + '@rush-temp/keyvault-certificates': 'file:./projects/keyvault-certificates.tgz' + '@rush-temp/keyvault-common': 'file:./projects/keyvault-common.tgz' + '@rush-temp/keyvault-keys': 'file:./projects/keyvault-keys.tgz' + '@rush-temp/keyvault-secrets': 'file:./projects/keyvault-secrets.tgz' + '@rush-temp/logger': 'file:./projects/logger.tgz' + '@rush-temp/search-documents': 'file:./projects/search-documents.tgz' + '@rush-temp/service-bus': 'file:./projects/service-bus.tgz' + '@rush-temp/storage-blob': 'file:./projects/storage-blob.tgz' + '@rush-temp/storage-blob-changefeed': 'file:./projects/storage-blob-changefeed.tgz' + '@rush-temp/storage-file-datalake': 'file:./projects/storage-file-datalake.tgz' + '@rush-temp/storage-file-share': 'file:./projects/storage-file-share.tgz' + '@rush-temp/storage-internal-avro': 'file:./projects/storage-internal-avro.tgz' + '@rush-temp/storage-queue': 'file:./projects/storage-queue.tgz' + '@rush-temp/tables': 'file:./projects/tables.tgz' + '@rush-temp/template': 'file:./projects/template.tgz' + '@rush-temp/test-utils-perfstress': 'file:./projects/test-utils-perfstress.tgz' + '@rush-temp/test-utils-recorder': 'file:./projects/test-utils-recorder.tgz' + '@rush-temp/testhub': 'file:./projects/testhub.tgz'