@itwin/presentation-frontend
Version:
Frontend of iModel.js Presentation library
118 lines • 5.53 kB
JavaScript
;
/*---------------------------------------------------------------------------------------------
* 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