{"version":3,"file":"cleanup-queue.cjs","sources":["../../../src/collection/cleanup-queue.ts"],"sourcesContent":["import { codedMessage, devBuild } from '../error-message'\n\n// Share one elapsed clock for appointments and startup handoff deadlines.\n// Some runtimes lack `performance` although the DOM types declare it.\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\nexport const cleanupNow = () => globalThis.performance?.now() ?? Date.now()\n\ntype CleanupTask = {\n  executeAt: number\n  callback: () => void\n}\n\n/**\n * Batches many GC registrations behind a single shared timeout.\n */\nexport class CleanupQueue {\n  private static instance: CleanupQueue | null = null\n\n  private tasks: Map<unknown, CleanupTask> = new Map()\n\n  private timeoutId: ReturnType<typeof setTimeout> | null = null\n  private microtaskScheduled = false\n\n  private constructor() {}\n\n  public static getInstance(): CleanupQueue {\n    return (CleanupQueue.instance ??= new CleanupQueue())\n  }\n\n  /**\n   * Queues a cleanup task and defers timeout selection to a microtask so\n   * multiple synchronous registrations can share one root timer.\n   */\n  public schedule(key: unknown, gcTime: number, callback: () => void): void {\n    const executeAt = cleanupNow() + gcTime\n    this.tasks.set(key, { executeAt, callback })\n\n    if (!this.microtaskScheduled) {\n      this.microtaskScheduled = true\n      Promise.resolve().then(() => {\n        this.microtaskScheduled = false\n        this.updateTimeout()\n      })\n    }\n  }\n\n  public cancel(key: unknown): void {\n    this.tasks.delete(key)\n\n    // Retire the root timer with the last task. A non-empty queue keeps its\n    // timer even when the cancelled task was the earliest: it wakes early,\n    // finds nothing due and reschedules, which costs less than rescanning\n    // every task on each cancellation.\n    if (this.tasks.size === 0 && this.timeoutId !== null) {\n      clearTimeout(this.timeoutId)\n      this.timeoutId = null\n    }\n  }\n\n  /**\n   * Keeps only one active timeout: whichever task is due next.\n   */\n  private updateTimeout(): void {\n    if (this.timeoutId !== null) {\n      clearTimeout(this.timeoutId)\n      this.timeoutId = null\n    }\n\n    if (this.tasks.size === 0) {\n      return\n    }\n\n    let earliestTime = Infinity\n    for (const task of this.tasks.values()) {\n      if (task.executeAt < earliestTime) {\n        earliestTime = task.executeAt\n      }\n    }\n\n    const delay = Math.max(0, earliestTime - cleanupNow())\n    // Run every due task, then select the next shared wakeup.\n    this.timeoutId = setTimeout(() => {\n      this.timeoutId = null\n      const now = cleanupNow()\n      for (const [key, task] of this.tasks.entries()) {\n        if (now >= task.executeAt) {\n          this.tasks.delete(key)\n          try {\n            task.callback()\n          } catch (error) {\n            console.error(\n              devBuild() && process.env.NODE_ENV !== `production`\n                ? 'Error in CleanupQueue task:'\n                : codedMessage(214),\n              error,\n            )\n          }\n        }\n      }\n\n      this.updateTimeout()\n    }, delay)\n    // Background collection GC must not keep an otherwise finished Node\n    // process alive. Browsers return a numeric timer handle.\n    if (typeof this.timeoutId === `object`) this.timeoutId.unref()\n  }\n}\n"],"names":["microtaskScheduled","instance","updateTimeout","devBuild","codedMessage"],"mappings":";;;AAKO,MAAM,aAAa,MAAM,WAAW,aAAa,IAAA,KAAS,KAAK,IAAA;AAU/D,MAAM,gBAAN,MAAM,cAAa;AAAA,EAQhB,cAAc;AALtB,SAAQ,4BAAuC,IAAA;AAE/C,SAAQ,YAAkD;AAC1D,SAAQA,KAAqB;AAAA,EAEN;AAAA,EAEvB,OAAc,cAA4B;AACxC,WAAQ,cAAaC,OAAa,IAAI,cAAA;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,SAAS,KAAc,QAAgB,UAA4B;AACxE,UAAM,YAAY,eAAe;AACjC,SAAK,MAAM,IAAI,KAAK,EAAE,WAAW,UAAU;AAE3C,QAAI,CAAC,KAAKD,IAAoB;AAC5B,WAAKA,KAAqB;AAC1B,cAAQ,UAAU,KAAK,MAAM;AAC3B,aAAKA,KAAqB;AAC1B,aAAKE,GAAA;AAAA,MACP,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEO,OAAO,KAAoB;AAChC,SAAK,MAAM,OAAO,GAAG;AAMrB,QAAI,KAAK,MAAM,SAAS,KAAK,KAAK,cAAc,MAAM;AACpD,mBAAa,KAAK,SAAS;AAC3B,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQA,KAAsB;AAC5B,QAAI,KAAK,cAAc,MAAM;AAC3B,mBAAa,KAAK,SAAS;AAC3B,WAAK,YAAY;AAAA,IACnB;AAEA,QAAI,KAAK,MAAM,SAAS,GAAG;AACzB;AAAA,IACF;AAEA,QAAI,eAAe;AACnB,eAAW,QAAQ,KAAK,MAAM,OAAA,GAAU;AACtC,UAAI,KAAK,YAAY,cAAc;AACjC,uBAAe,KAAK;AAAA,MACtB;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,IAAI,GAAG,eAAe,YAAY;AAErD,SAAK,YAAY,WAAW,MAAM;AAChC,WAAK,YAAY;AACjB,YAAM,MAAM,WAAA;AACZ,iBAAW,CAAC,KAAK,IAAI,KAAK,KAAK,MAAM,WAAW;AAC9C,YAAI,OAAO,KAAK,WAAW;AACzB,eAAK,MAAM,OAAO,GAAG;AACrB,cAAI;AACF,iBAAK,SAAA;AAAA,UACP,SAAS,OAAO;AACd,oBAAQ;AAAA,cACNC,sBAAA,KAAc,QAAQ,IAAI,aAAa,eACnC,gCACAC,aAAAA,aAAa,GAAG;AAAA,cACpB;AAAA,YAAA;AAAA,UAEJ;AAAA,QACF;AAAA,MACF;AAEA,WAAKF,GAAA;AAAA,IACP,GAAG,KAAK;AAGR,QAAI,OAAO,KAAK,cAAc,SAAU,MAAK,UAAU,MAAA;AAAA,EACzD;AACF;AA1FE,cAAeD,KAAgC;AAD1C,IAAM,eAAN;;;"}