UNPKG

@segment/analytics-core

Version:

This package represents core 'shared' functionality that is shared by analytics packages. This is not designed to be used directly, but internal to analytics-node and analytics-browser.

108 lines (86 loc) 2.57 kB
import { Emitter } from '@segment/analytics-generic-utils' import { backoff } from './backoff' /** * @internal */ export const ON_REMOVE_FROM_FUTURE = 'onRemoveFromFuture' interface QueueItem { id: string } export class PriorityQueue<Item extends QueueItem = QueueItem> extends Emitter { protected future: Item[] = [] protected queue: Item[] protected seen: Record<string, number> public maxAttempts: number constructor( maxAttempts: number, queue: Item[], seen?: Record<string, number> ) { super() this.maxAttempts = maxAttempts this.queue = queue this.seen = seen ?? {} } push(...items: Item[]): boolean[] { const accepted = items.map((operation) => { const attempts = this.updateAttempts(operation) if (attempts > this.maxAttempts || this.includes(operation)) { return false } this.queue.push(operation) return true }) this.queue = this.queue.sort( (a, b) => this.getAttempts(a) - this.getAttempts(b) ) return accepted } pushWithBackoff(item: Item, minTimeout = 0): boolean { // One immediate retry unless we have a minimum timeout (e.g. for rate limiting) if (minTimeout == 0 && this.getAttempts(item) === 0) { return this.push(item)[0] } const attempt = this.updateAttempts(item) if (attempt > this.maxAttempts || this.includes(item)) { return false } let timeout = backoff({ attempt: attempt - 1 }) if (minTimeout > 0 && timeout < minTimeout) { timeout = minTimeout } setTimeout(() => { this.queue.push(item) // remove from future list this.future = this.future.filter((f) => f.id !== item.id) // Lets listeners know that a 'future' message is now available in the queue this.emit(ON_REMOVE_FROM_FUTURE) }, timeout) this.future.push(item) return true } public getAttempts(item: Item): number { return this.seen[item.id] ?? 0 } public updateAttempts(item: Item): number { this.seen[item.id] = this.getAttempts(item) + 1 return this.getAttempts(item) } includes(item: Item): boolean { return ( this.queue.includes(item) || this.future.includes(item) || Boolean(this.queue.find((i) => i.id === item.id)) || Boolean(this.future.find((i) => i.id === item.id)) ) } pop(): Item | undefined { return this.queue.shift() } public get length(): number { return this.queue.length } public get todo(): number { return this.queue.length + this.future.length } }