{"version":3,"sources":["../../src/lib/batcher.ts"],"sourcesContent":["import type { SchedulerFn } from './scheduler'\n\nimport { DetachedPromise } from './detached-promise'\n\ntype CacheKeyFn = (\n key: K\n) => PromiseLike | C\n\ntype BatcherOptions = {\n cacheKeyFn?: CacheKeyFn\n schedulerFn?: SchedulerFn\n}\n\ntype WorkFnContext = {\n resolve: (value: V | PromiseLike) => void\n key: K\n}\n\ntype WorkFn = (context: WorkFnContext) => Promise\n\n/**\n * A wrapper for a function that will only allow one call to the function to\n * execute at a time.\n */\nexport class Batcher {\n private readonly pending = new Map>()\n\n protected constructor(\n private readonly cacheKeyFn?: CacheKeyFn,\n /**\n * A function that will be called to schedule the wrapped function to be\n * executed. This defaults to a function that will execute the function\n * immediately.\n */\n private readonly schedulerFn: SchedulerFn = (fn) => fn()\n ) {}\n\n /**\n * Creates a new instance of PendingWrapper. If the key extends a string or\n * number, the key will be used as the cache key. If the key is an object, a\n * cache key function must be provided.\n */\n public static create(\n options?: BatcherOptions\n ): Batcher\n public static create(\n options: BatcherOptions &\n Required, 'cacheKeyFn'>>\n ): Batcher\n public static create(\n options?: BatcherOptions\n ): Batcher {\n return new Batcher(options?.cacheKeyFn, options?.schedulerFn)\n }\n\n /**\n * Wraps a function in a promise that will be resolved or rejected only once\n * for a given key. This will allow multiple calls to the function to be\n * made, but only one will be executed at a time. The result of the first\n * call will be returned to all callers.\n *\n * @param key the key to use for the cache\n * @param fn the function to wrap\n * @returns a promise that resolves to the result of the function\n */\n public async batch(key: K, fn: WorkFn): Promise {\n const cacheKey = (this.cacheKeyFn ? await this.cacheKeyFn(key) : key) as C\n if (cacheKey === null) {\n return fn({ resolve: (value) => Promise.resolve(value), key })\n }\n\n const pending = this.pending.get(cacheKey)\n if (pending) return pending\n\n const { promise, resolve, reject } = new DetachedPromise()\n this.pending.set(cacheKey, promise)\n\n this.schedulerFn(async () => {\n try {\n const result = await fn({ resolve, key })\n\n // Resolving a promise multiple times is a no-op, so we can safely\n // resolve all pending promises with the same result.\n resolve(result)\n } catch (err) {\n reject(err)\n } finally {\n this.pending.delete(cacheKey)\n }\n })\n\n return promise\n }\n}\n"],"names":["DetachedPromise","Batcher","cacheKeyFn","schedulerFn","fn","pending","Map","create","options","batch","key","cacheKey","resolve","value","Promise","get","promise","reject","set","result","err","delete"],"mappings":"AAEA,SAASA,eAAe,QAAQ,qBAAoB;AAkBpD;;;CAGC,GACD,OAAO,MAAMC;IAGX,YACE,AAAiBC,UAA6B,EAC9C;;;;KAIC,GACD,AAAiBC,cAAiC,CAACC,KAAOA,IAAI,CAC9D;aAPiBF,aAAAA;aAMAC,cAAAA;aATFE,UAAU,IAAIC;IAU5B;IAcH,OAAcC,OACZC,OAA8B,EACZ;QAClB,OAAO,IAAIP,QAAiBO,2BAAAA,QAASN,UAAU,EAAEM,2BAAAA,QAASL,WAAW;IACvE;IAEA;;;;;;;;;GASC,GACD,MAAaM,MAAMC,GAAM,EAAEN,EAAgB,EAAc;QACvD,MAAMO,WAAY,IAAI,CAACT,UAAU,GAAG,MAAM,IAAI,CAACA,UAAU,CAACQ,OAAOA;QACjE,IAAIC,aAAa,MAAM;YACrB,OAAOP,GAAG;gBAAEQ,SAAS,CAACC,QAAUC,QAAQF,OAAO,CAACC;gBAAQH;YAAI;QAC9D;QAEA,MAAML,UAAU,IAAI,CAACA,OAAO,CAACU,GAAG,CAACJ;QACjC,IAAIN,SAAS,OAAOA;QAEpB,MAAM,EAAEW,OAAO,EAAEJ,OAAO,EAAEK,MAAM,EAAE,GAAG,IAAIjB;QACzC,IAAI,CAACK,OAAO,CAACa,GAAG,CAACP,UAAUK;QAE3B,IAAI,CAACb,WAAW,CAAC;YACf,IAAI;gBACF,MAAMgB,SAAS,MAAMf,GAAG;oBAAEQ;oBAASF;gBAAI;gBAEvC,kEAAkE;gBAClE,qDAAqD;gBACrDE,QAAQO;YACV,EAAE,OAAOC,KAAK;gBACZH,OAAOG;YACT,SAAU;gBACR,IAAI,CAACf,OAAO,CAACgB,MAAM,CAACV;YACtB;QACF;QAEA,OAAOK;IACT;AACF","ignoreList":[0]}