{"version":3,"file":"batching.js","sourceRoot":"","sources":["../../../src/link/batch/batching.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AA0BtD,qFAAqF;AACrF,2FAA2F;AAC3F,kBAAkB;AAClB;IAgBE,0BAAY,EAYX;YAXC,aAAa,mBAAA,EACb,aAAa,mBAAA,EACb,QAAQ,cAAA,EACR,YAAY,kBAAA,EACZ,QAAQ,cAAA;QApBV,oEAAoE;QAC5D,iBAAY,GAAG,IAAI,GAAG,EAAwB,CAAC;QAE/C,6BAAwB,GAAG,IAAI,GAAG,EAGvC,CAAC;QAsBF,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,QAAQ,GAAG,QAAQ,IAAI,CAAC,CAAC;QAC9B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,QAAQ,IAAI,CAAC,cAAM,OAAA,EAAE,EAAF,CAAE,CAAC,CAAC;IACzC,CAAC;IAEM,yCAAc,GAArB,UAAsB,OAAyB;QAA/C,iBAmEC;QAlEC,IAAM,WAAW,yBACZ,OAAO,KACV,IAAI,EAAE,EAAE,EACR,KAAK,EAAE,EAAE,EACT,QAAQ,EAAE,EAAE,EACZ,WAAW,EAAE,IAAI,GAAG,EAAE,GACvB,CAAC;QAEF,IAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAE7C,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC;YAC5B,WAAW,CAAC,UAAU,GAAG,IAAI,UAAU,CAAc,UAAC,QAAQ;gBAC5D,IAAI,KAAK,GAAG,KAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;gBACxC,IAAI,CAAC,KAAK;oBAAE,KAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC;gBAE5D,qEAAqE;gBACrE,sEAAsE;gBACtE,mBAAmB;gBACnB,IAAM,sBAAsB,GAAG,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC;gBAChD,IAAM,iBAAiB,GAAG,WAAW,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,CAAC;gBAC7D,WAAW,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACtC,IAAI,iBAAiB,EAAE,CAAC;oBACtB,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;gBACzB,CAAC;gBAED,oFAAoF;gBACpF,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;oBAClB,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACtD,CAAC;gBAED,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;oBACnB,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACxD,CAAC;gBAED,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;oBACtB,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC9D,CAAC;gBAED,gGAAgG;gBAChG,IAAI,sBAAsB,IAAI,KAAI,CAAC,aAAa,EAAE,CAAC;oBACjD,KAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,CAAC;gBACrC,CAAC;gBAED,oHAAoH;gBACpH,IAAI,KAAK,CAAC,IAAI,KAAK,KAAI,CAAC,QAAQ,EAAE,CAAC;oBACjC,KAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;gBACzB,CAAC;gBAED,OAAO;;oBACL,yEAAyE;oBACzE,IACE,WAAW,CAAC,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC;wBACxC,WAAW,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,EAChC,CAAC;wBACD,4DAA4D;wBAC5D,IAAI,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;4BAChD,KAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;4BACvB,oCAAoC;4BACpC,MAAA,KAAK,CAAC,YAAY,0CAAE,WAAW,EAAE,CAAC;wBACpC,CAAC;oBACH,CAAC;gBACH,CAAC,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;QAED,OAAO,WAAW,CAAC,UAAU,CAAC;IAChC,CAAC;IAED,sBAAsB;IACtB,mDAAmD;IAC5C,uCAAY,GAAnB,UACE,GAAgB;QAAhB,oBAAA,EAAA,QAAgB;QAEhB,IAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzC,0CAA0C;QAC1C,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YAC1B,+BAA+B;YAC/B,OAAO;QACT,CAAC;QAED,IAAM,UAAU,GAAiC,EAAE,CAAC;QACpD,IAAM,QAAQ,GAA+B,EAAE,CAAC;QAChD,IAAM,WAAW,GAAkC,EAAE,CAAC;QACtD,IAAM,KAAK,GAA4B,EAAE,CAAC;QAC1C,IAAM,MAAM,GAA6B,EAAE,CAAC;QAC5C,IAAM,SAAS,GAAgC,EAAE,CAAC;QAElD,wEAAwE;QACxE,2EAA2E;QAC3E,oEAAoE;QACpE,KAAK,CAAC,OAAO,CAAC,UAAC,OAAO;YACpB,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YACnC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC/B,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC3B,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,IAAM,iBAAiB,GACrB,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,QAAQ,CAAC,IAAI,UAAU,CAAC,EAAE,EAAE,CAAC;QAE7D,IAAM,OAAO,GAAG,UAAC,KAAY;YAC3B,6BAA6B;YAC7B,MAAM,CAAC,OAAO,CAAC,UAAC,SAAS;gBACvB,IAAI,SAAS,EAAE,CAAC;oBACd,4BAA4B;oBAC5B,SAAS,CAAC,OAAO,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,CAAC,KAAK,CAAC,EAAR,CAAQ,CAAC,CAAC;gBACrC,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,KAAK,CAAC,YAAY,GAAG,iBAAiB,CAAC,SAAS,CAAC;YAC/C,IAAI,EAAE,UAAC,OAAO;gBACZ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC5B,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC;gBACtB,CAAC;gBAED,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;oBACpC,IAAM,KAAK,GAAG,IAAI,KAAK,CACrB,8CAAuC,OAAO,CAAC,MAAM,kCAAwB,KAAK,CAAC,MAAM,CAAE,CAC5F,CAAC;oBACD,KAAa,CAAC,MAAM,GAAG,OAAO,CAAC;oBAEhC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC;gBACxB,CAAC;gBAED,OAAO,CAAC,OAAO,CAAC,UAAC,MAAM,EAAE,KAAK;oBAC5B,IAAI,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;wBACjB,KAAK,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,UAAC,IAAI,IAAK,OAAA,IAAI,CAAC,MAAM,CAAC,EAAZ,CAAY,CAAC,CAAC;oBAC/C,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC;YACD,KAAK,EAAE,OAAO;YACd,QAAQ,EAAE;gBACR,SAAS,CAAC,OAAO,CAAC,UAAC,QAAQ;oBACzB,IAAI,QAAQ,EAAE,CAAC;wBACb,4BAA4B;wBAC5B,QAAQ,CAAC,OAAO,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,EAAE,EAAH,CAAG,CAAC,CAAC;oBAC/B,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC;SACF,CAAC,CAAC;QAEH,OAAO,WAAW,CAAC;IACrB,CAAC;IAEO,mDAAwB,GAAhC,UAAiC,GAAW;QAA5C,iBASC;QARC,YAAY,CAAC,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QACrD,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAC/B,GAAG,EACH,UAAU,CAAC;YACT,KAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;YACvB,KAAI,CAAC,wBAAwB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5C,CAAC,EAAE,IAAI,CAAC,aAAa,CAAC,CACvB,CAAC;IACJ,CAAC;IACH,uBAAC;AAAD,CAAC,AAnMD,IAmMC","sourcesContent":["import type { FetchResult, NextLink, Operation } from \"../core/index.js\";\nimport type { ObservableSubscription } from \"../../utilities/index.js\";\nimport { Observable } from \"../../utilities/index.js\";\n\nexport type BatchHandler = (\n operations: Operation[],\n forward?: (NextLink | undefined)[]\n) => Observable | null;\n\nexport interface BatchableRequest {\n operation: Operation;\n forward?: NextLink;\n}\n\ntype QueuedRequest = BatchableRequest & {\n observable?: Observable;\n next: Array<(result: FetchResult) => void>;\n error: Array<(error: Error) => void>;\n complete: Array<() => void>;\n subscribers: Set;\n};\n\n// Batches are primarily a Set, but may have other optional\n// properties, such as batch.subscription.\ntype RequestBatch = Set & {\n subscription?: ObservableSubscription;\n};\n\n// QueryBatcher doesn't fire requests immediately. Requests that were enqueued within\n// a certain amount of time (configurable through `batchInterval`) will be batched together\n// into one query.\nexport class OperationBatcher {\n // Queue on which the QueryBatcher will operate on a per-tick basis.\n private batchesByKey = new Map();\n\n private scheduledBatchTimerByKey = new Map<\n string,\n ReturnType\n >();\n private batchDebounce?: boolean;\n private batchInterval?: number;\n private batchMax: number;\n\n //This function is called to the queries in the queue to the server.\n private batchHandler: BatchHandler;\n private batchKey: (operation: Operation) => string;\n\n constructor({\n batchDebounce,\n batchInterval,\n batchMax,\n batchHandler,\n batchKey,\n }: {\n batchDebounce?: boolean;\n batchInterval?: number;\n batchMax?: number;\n batchHandler: BatchHandler;\n batchKey?: (operation: Operation) => string;\n }) {\n this.batchDebounce = batchDebounce;\n this.batchInterval = batchInterval;\n this.batchMax = batchMax || 0;\n this.batchHandler = batchHandler;\n this.batchKey = batchKey || (() => \"\");\n }\n\n public enqueueRequest(request: BatchableRequest): Observable {\n const requestCopy: QueuedRequest = {\n ...request,\n next: [],\n error: [],\n complete: [],\n subscribers: new Set(),\n };\n\n const key = this.batchKey(request.operation);\n\n if (!requestCopy.observable) {\n requestCopy.observable = new Observable((observer) => {\n let batch = this.batchesByKey.get(key)!;\n if (!batch) this.batchesByKey.set(key, (batch = new Set()));\n\n // These booleans seem to me (@benjamn) like they might always be the\n // same (and thus we could do with only one of them), but I'm not 100%\n // sure about that.\n const isFirstEnqueuedRequest = batch.size === 0;\n const isFirstSubscriber = requestCopy.subscribers.size === 0;\n requestCopy.subscribers.add(observer);\n if (isFirstSubscriber) {\n batch.add(requestCopy);\n }\n\n // called for each subscriber, so need to save all listeners (next, error, complete)\n if (observer.next) {\n requestCopy.next.push(observer.next.bind(observer));\n }\n\n if (observer.error) {\n requestCopy.error.push(observer.error.bind(observer));\n }\n\n if (observer.complete) {\n requestCopy.complete.push(observer.complete.bind(observer));\n }\n\n // The first enqueued request triggers the queue consumption after `batchInterval` milliseconds.\n if (isFirstEnqueuedRequest || this.batchDebounce) {\n this.scheduleQueueConsumption(key);\n }\n\n // When amount of requests reaches `batchMax`, trigger the queue consumption without waiting on the `batchInterval`.\n if (batch.size === this.batchMax) {\n this.consumeQueue(key);\n }\n\n return () => {\n // If this is last subscriber for this request, remove request from queue\n if (\n requestCopy.subscribers.delete(observer) &&\n requestCopy.subscribers.size < 1\n ) {\n // If this is last request from queue, remove queue entirely\n if (batch.delete(requestCopy) && batch.size < 1) {\n this.consumeQueue(key);\n // If queue was in flight, cancel it\n batch.subscription?.unsubscribe();\n }\n }\n };\n });\n }\n\n return requestCopy.observable;\n }\n\n // Consumes the queue.\n // Returns a list of promises (one for each query).\n public consumeQueue(\n key: string = \"\"\n ): (Observable | undefined)[] | undefined {\n const batch = this.batchesByKey.get(key);\n // Delete this batch and process it below.\n this.batchesByKey.delete(key);\n if (!batch || !batch.size) {\n // No requests to be processed.\n return;\n }\n\n const operations: QueuedRequest[\"operation\"][] = [];\n const forwards: QueuedRequest[\"forward\"][] = [];\n const observables: QueuedRequest[\"observable\"][] = [];\n const nexts: QueuedRequest[\"next\"][] = [];\n const errors: QueuedRequest[\"error\"][] = [];\n const completes: QueuedRequest[\"complete\"][] = [];\n\n // Even though batch is a Set, it preserves the order of first insertion\n // when iterating (per ECMAScript specification), so these requests will be\n // handled in the order they were enqueued (minus any deleted ones).\n batch.forEach((request) => {\n operations.push(request.operation);\n forwards.push(request.forward);\n observables.push(request.observable);\n nexts.push(request.next);\n errors.push(request.error);\n completes.push(request.complete);\n });\n\n const batchedObservable =\n this.batchHandler(operations, forwards) || Observable.of();\n\n const onError = (error: Error) => {\n //each callback list in batch\n errors.forEach((rejecters) => {\n if (rejecters) {\n //each subscriber to request\n rejecters.forEach((e) => e(error));\n }\n });\n };\n\n batch.subscription = batchedObservable.subscribe({\n next: (results) => {\n if (!Array.isArray(results)) {\n results = [results];\n }\n\n if (nexts.length !== results.length) {\n const error = new Error(\n `server returned results with length ${results.length}, expected length of ${nexts.length}`\n );\n (error as any).result = results;\n\n return onError(error);\n }\n\n results.forEach((result, index) => {\n if (nexts[index]) {\n nexts[index].forEach((next) => next(result));\n }\n });\n },\n error: onError,\n complete: () => {\n completes.forEach((complete) => {\n if (complete) {\n //each subscriber to request\n complete.forEach((c) => c());\n }\n });\n },\n });\n\n return observables;\n }\n\n private scheduleQueueConsumption(key: string): void {\n clearTimeout(this.scheduledBatchTimerByKey.get(key));\n this.scheduledBatchTimerByKey.set(\n key,\n setTimeout(() => {\n this.consumeQueue(key);\n this.scheduledBatchTimerByKey.delete(key);\n }, this.batchInterval)\n );\n }\n}\n"]}