UNPKG

@itwin/presentation-frontend

Version:

Frontend of iModel.js Presentation library

118 lines 5.53 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.StreamedResponseGenerator = void 0; const rxjs_1 = require("rxjs"); const rxjs_for_await_1 = require("rxjs-for-await"); const core_bentley_1 = require("@itwin/core-bentley"); /** * This class allows loading values in multiple parallel batches and return them either as an array or an async iterator. * Pages are prefetched in advanced according to the `parallelism` argument. * @internal */ class StreamedResponseGenerator { _props; constructor(_props) { this._props = _props; } /** Creates a response with the total item count and an async iterator. */ async createAsyncIteratorResponse() { const firstPage = await this.fetchFirstPage(); return { total: firstPage.total, items: (0, rxjs_for_await_1.eachValueFrom)(this.getRemainingPages(firstPage).pipe((0, rxjs_1.concatAll)())), }; } /** * Fetches the first page. * This function has to be called in order to retrieve the total items count. */ async fetchFirstPage() { const start = this._props.paging?.start ?? 0; const batchSize = this._props.batchSize ?? this._props.paging?.size ?? 0; return this._props.getBatch({ start, size: batchSize }, 0); } getRemainingPages(firstPage) { const pageStart = this._props.paging?.start ?? 0; const maxParallelRequests = this._props.maxParallelRequests; const pageSize = this._props.paging?.size; const { total, items: firstPageItems } = firstPage; // If there are no items, return a single empty page. if (total === 0) { return (0, rxjs_1.of)([]); } // If the response is empty, something went wrong. const receivedItemsLength = firstPage.items.length; if (!receivedItemsLength) { handleEmptyPageResult(pageStart, total); } const totalItemsToFetch = total - pageStart; if (receivedItemsLength === totalItemsToFetch) { return (0, rxjs_1.of)(firstPageItems); } let itemsToFetch; let batchSize; if (pageSize) { itemsToFetch = Math.min(totalItemsToFetch, pageSize) - receivedItemsLength; batchSize = Math.min(this._props.batchSize ?? Number.MAX_SAFE_INTEGER, pageSize, receivedItemsLength); } else { itemsToFetch = totalItemsToFetch - receivedItemsLength; batchSize = Math.min(this._props.batchSize ?? Number.MAX_SAFE_INTEGER, receivedItemsLength); } const remainingBatches = Math.ceil(itemsToFetch / batchSize); // Return the first page and then stream the remaining ones. return (0, rxjs_1.concat)((0, rxjs_1.of)(firstPage.items), (0, rxjs_1.range)(1, remainingBatches).pipe((0, rxjs_1.mergeMap)(async (idx) => { const start = pageStart + idx * batchSize; const size = Math.min(total - start, batchSize); const page = await this._props.getBatch({ start, size }, idx); if (!page.items.length) { handleEmptyPageResult(start, total); } // Pass along the index, so that the items could be sorted. return { idx, items: page.items }; }, maxParallelRequests), (0, rxjs_1.scan)( // Collect the emitted pages an emit them in the correct order. (acc, value) => { let { lastEmitted } = acc; const { accumulatedBatches } = acc; const { idx } = value; // If current batch is not in order, put it in the accumulator if (idx - 1 !== lastEmitted) { accumulatedBatches.insert(value); return { lastEmitted, accumulatedBatches, itemsToEmit: [] }; } // Collect all batches to emit in order. lastEmitted = idx; const batchesToEmit = [value]; for (const batch of accumulatedBatches) { if (batch.idx - 1 !== lastEmitted) { break; } lastEmitted = batch.idx; batchesToEmit.push(batch); } // Remove batches to emit from the accumulator. for (const batch of batchesToEmit) { accumulatedBatches.remove(batch); } const itemsToEmit = batchesToEmit.flatMap((x) => x.items); return { lastEmitted, accumulatedBatches, itemsToEmit }; }, { lastEmitted: 0, accumulatedBatches: new core_bentley_1.SortedArray((a, b) => a.idx - b.idx), itemsToEmit: new Array(), }), (0, rxjs_1.map)(({ itemsToEmit }) => itemsToEmit))); } } exports.StreamedResponseGenerator = StreamedResponseGenerator; function handleEmptyPageResult(pageStart, total) { if (pageStart >= total) { throw new Error(`Requested page with start index ${pageStart} is out of bounds. Total number of items: ${total}`); } throw new Error("Paged request returned non zero total count but no items"); } //# sourceMappingURL=StreamedResponseGenerator.js.map