{"version":3,"file":"RequestBatcher.mjs","names":[],"sources":["../../src/util/RequestBatcher.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n\n/**\n * @internal\n * A data structure that represents a possible process and how to handle it.\n */\nexport interface WaitingProcess<Args extends any[], Return> {\n  name: string;\n  args: Args;\n  perform: (...args: Args) => Promise<Return>;\n  awaitingResolutions: ((returnValue: Return) => void)[];\n  awaitingRejections: ((err: any) => void)[];\n  after?: (result: Return) => void;\n}\n\nexport const ANY_KEY = \"any\";\n\n/**\n * Options for processes that are waiting to execute\n */\nexport interface WaitingProcessOptions<Args extends any[], Return> {\n  /**\n   * The name of the process like \"read\" or \"delete\"\n   */\n  name: string;\n  /**\n   * The arguements supplied to the process\n   */\n  args: Args;\n  /**\n   * A function that will be triggered when it's time to execute this process\n   * @param args - arguments supplied to the process\n   * @returns a return type\n   */\n  perform: (...args: Args) => Promise<Return>;\n  /**\n   * A custom function to modify the queue based on the current state of the\n   * queue\n   * @param processQueue - The current process queue\n   * @param currentlyProcessing - The Process that is currently executing\n   * @param args - provided args\n   * @returns A WaitingProcess that this request should listen to, or undefined\n   * if it should create its own\n   */\n  modifyQueue: (\n    processQueue: WaitingProcess<any[], any>[],\n    currentlyProcessing: WaitingProcess<any[], any> | undefined,\n    args: Args,\n  ) => WaitingProcess<any[], any> | undefined;\n  after?: (result: Return) => void;\n}\n\n/**\n * @internal\n * A utility for batching a request\n */\nexport class RequestBatcher {\n  /**\n   * A mapping between a process key and the last time in UTC a process of that\n   * key was executed.\n   */\n  private lastRequestTimestampMap: Record<string, number> = {};\n\n  /**\n   * A pointer to the current process the batcher is working on\n   */\n  private currentlyProcessing: WaitingProcess<any[], any> | undefined =\n    undefined;\n\n  /**\n   * A queue of upcoming processes\n   */\n  private processQueue: WaitingProcess<any[], any>[] = [];\n\n  /**\n   * The amount of time (in milliseconds) between requests of the same key\n   */\n  public batchMillis: number;\n\n  /**\n   * @param options - options, including the value for batchMillis\n   */\n  constructor(\n    options?: Partial<{\n      batchMillis: number;\n    }>,\n  ) {\n    this.batchMillis = options?.batchMillis || 1000;\n  }\n\n  /**\n   * Check if the request batcher is currently working on a process\n   * @param key - the key of the process to check\n   * @returns true if the batcher is currently working on the provided process\n   */\n  public isLoading(key: string): boolean {\n    if (key === ANY_KEY) return !!this.currentlyProcessing;\n    return this.currentlyProcessing?.name === key;\n  }\n\n  /**\n   * Triggers the next process in the queue or triggers a timeout to wait to\n   * execute the next process in the queue if not enough time has passed since\n   * the last process was triggered.\n   */\n  private triggerOrWaitProcess() {\n    if (!this.processQueue[0] || this.currentlyProcessing) {\n      return;\n    }\n    this.currentlyProcessing = this.processQueue.shift();\n    const processName = this.currentlyProcessing!.name;\n\n    // Set last request timestamp if not available\n    if (!this.lastRequestTimestampMap[processName]) {\n      this.lastRequestTimestampMap[processName] = Date.UTC(0, 0, 0, 0, 0, 0, 0);\n    }\n\n    const lastRequestTimestamp = this.lastRequestTimestampMap[processName];\n    const timeSinceLastTrigger = Date.now() - lastRequestTimestamp;\n\n    const triggerProcess = async () => {\n      this.lastRequestTimestampMap[processName] = Date.now();\n      this.lastRequestTimestampMap[ANY_KEY] = Date.now();\n      // Remove the process from the queue\n      const processToTrigger = this.currentlyProcessing;\n      if (processToTrigger) {\n        this.currentlyProcessing = processToTrigger;\n        try {\n          const returnValue = await processToTrigger.perform(\n            ...processToTrigger.args,\n          );\n          if (processToTrigger.after) {\n            processToTrigger.after(returnValue);\n          }\n          processToTrigger.awaitingResolutions.forEach((callback) => {\n            callback(returnValue);\n          });\n        } catch (err) {\n          processToTrigger.awaitingRejections.forEach((callback) => {\n            callback(err);\n          });\n        }\n        this.currentlyProcessing = undefined;\n\n        this.triggerOrWaitProcess();\n      }\n    };\n\n    if (timeSinceLastTrigger < this.batchMillis) {\n      setTimeout(triggerProcess, this.batchMillis - timeSinceLastTrigger);\n    } else {\n      triggerProcess();\n    }\n  }\n\n  /**\n   * Adds a process to the queue and waits for the process to be complete\n   * @param options - WaitingProcessOptions\n   * @returns A promise that resolves when the process resolves\n   */\n  public async queueProcess<Args extends any[], ReturnType>(\n    options: WaitingProcessOptions<Args, ReturnType>,\n  ): Promise<ReturnType> {\n    return new Promise((resolve, reject) => {\n      const shouldAwait = options.modifyQueue(\n        this.processQueue,\n        this.currentlyProcessing,\n        options.args,\n      );\n\n      if (shouldAwait) {\n        shouldAwait.awaitingResolutions.push(resolve);\n        shouldAwait.awaitingRejections.push(reject);\n        return;\n      }\n\n      const waitingProcess: WaitingProcess<Args, ReturnType> = {\n        name: options.name,\n        args: options.args,\n        perform: options.perform,\n        awaitingResolutions: [resolve],\n        awaitingRejections: [reject],\n        after: options.after,\n      };\n      // HACK: Ugly cast\n      this.processQueue.push(\n        waitingProcess as unknown as WaitingProcess<any[], any>,\n      );\n      this.triggerOrWaitProcess();\n    });\n  }\n}\n"],"mappings":";AAeA,MAAa,UAAU;;;;;AAyCvB,IAAa,iBAAb,MAA4B;;;;CA0B1B,YACE,SAGA;AAzBF,OAAQ,0BAAkD,EAAE;AAK5D,OAAQ,sBACN,KAAA;AAKF,OAAQ,eAA6C,EAAE;AAerD,OAAK,cAAc,SAAS,eAAe;;;;;;;CAQ7C,UAAiB,KAAsB;AACrC,MAAI,QAAA,MAAiB,QAAO,CAAC,CAAC,KAAK;AACnC,SAAO,KAAK,qBAAqB,SAAS;;;;;;;CAQ5C,uBAA+B;AAC7B,MAAI,CAAC,KAAK,aAAa,MAAM,KAAK,oBAChC;AAEF,OAAK,sBAAsB,KAAK,aAAa,OAAO;EACpD,MAAM,cAAc,KAAK,oBAAqB;AAG9C,MAAI,CAAC,KAAK,wBAAwB,aAChC,MAAK,wBAAwB,eAAe,KAAK,IAAI,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;EAG3E,MAAM,uBAAuB,KAAK,wBAAwB;EAC1D,MAAM,uBAAuB,KAAK,KAAK,GAAG;EAE1C,MAAM,iBAAiB,YAAY;AACjC,QAAK,wBAAwB,eAAe,KAAK,KAAK;AACtD,QAAK,wBAAA,SAAmC,KAAK,KAAK;GAElD,MAAM,mBAAmB,KAAK;AAC9B,OAAI,kBAAkB;AACpB,SAAK,sBAAsB;AAC3B,QAAI;KACF,MAAM,cAAc,MAAM,iBAAiB,QACzC,GAAG,iBAAiB,KACrB;AACD,SAAI,iBAAiB,MACnB,kBAAiB,MAAM,YAAY;AAErC,sBAAiB,oBAAoB,SAAS,aAAa;AACzD,eAAS,YAAY;OACrB;aACK,KAAK;AACZ,sBAAiB,mBAAmB,SAAS,aAAa;AACxD,eAAS,IAAI;OACb;;AAEJ,SAAK,sBAAsB,KAAA;AAE3B,SAAK,sBAAsB;;;AAI/B,MAAI,uBAAuB,KAAK,YAC9B,YAAW,gBAAgB,KAAK,cAAc,qBAAqB;MAEnE,iBAAgB;;;;;;;CASpB,MAAa,aACX,SACqB;AACrB,SAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,cAAc,QAAQ,YAC1B,KAAK,cACL,KAAK,qBACL,QAAQ,KACT;AAED,OAAI,aAAa;AACf,gBAAY,oBAAoB,KAAK,QAAQ;AAC7C,gBAAY,mBAAmB,KAAK,OAAO;AAC3C;;GAGF,MAAM,iBAAmD;IACvD,MAAM,QAAQ;IACd,MAAM,QAAQ;IACd,SAAS,QAAQ;IACjB,qBAAqB,CAAC,QAAQ;IAC9B,oBAAoB,CAAC,OAAO;IAC5B,OAAO,QAAQ;IAChB;AAED,QAAK,aAAa,KAChB,eACD;AACD,QAAK,sBAAsB;IAC3B"}