@bitrix24/b24jssdk
Version:
Bitrix24 REST API JavaScript SDK
177 lines (173 loc) • 7.73 kB
JavaScript
/**
* @package @bitrix24/b24jssdk
* @version 3.0.0
* @copyright (c) 2026 Bitrix24
* @license MIT
* @see https://github.com/bitrix24/b24jssdk
* @see https://bitrix24.github.io/b24jssdk/
*/
;
const _walkBounds = require('../_walk-bounds.cjs');
const abstractAction = require('../abstract-action.cjs');
const result = require('../../result.cjs');
const _cursorStalled = require('../_cursor-stalled.cjs');
const _cursorProgress = require('../_cursor-progress.cjs');
const _uppercaseListParams = require('./_uppercase-list-params.cjs');
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
class CallListV2 extends abstractAction.AbstractAction {
static {
__name(this, "CallListV2");
}
/**
* Fast data retrieval without counting the total number of records.
*
* **Every option is documented on the [callList v2 page](https://bitrix24.github.io/b24jssdk/docs/working-with-the-rest-api/call-list-rest-api-ver2/),
* and each one on {@link ActionCallListV2}.** Not repeated here: nothing
* watches a sentence in a comment, while the page is link-checked and its
* code compiled on every CI run.
*
* What matters while editing this file:
*
* - The cursor only advances if rows arrive sorted by `cursorIdKey` ascending,
* so the walk writes its own `order` and strips a caller's with a `warning`.
* - `idKey` reads the RESPONSE, `cursorIdKey` writes the REQUEST, and the two
* fail differently. A wrong `cursorIdKey` means the page condition never
* matches, the same page keeps arriving, and the walk stops with
* {@link cursorStalledError}. A wrong `idKey` is quieter: if the value
* cannot be read as a number the walk warns and stops short, and if it
* names a *different numeric* field it advances a cursor the request never
* sorts by — which skips rows rather than reporting anything.
* - Page size on `restApi:v2` is a fixed 50 — there is no `limit` to ask with,
* so a page shorter than that ends the walk, but only once the cursor read
* from it has been vouched for.
* - The portal keeps only the later of two top-level keys differing by case,
* so a caller's uppercase `FILTER` can drop either their conditions or this
* walk's cursor. Both are warned about, neither can be fixed from in here —
* see {@link warnOnShadowedUppercaseParams}.
* - `maxPages` never ends a walk silently: the rows already read come back
* with `JSSDK_ACTION_MAX_PAGES_EXCEEDED` attached, so a short list that
* looks complete is never what a caller gets.
*
* @template T - The type of the elements of the returned array (default is `unknown`).
* @param {ActionCallListV2} options - every field is documented on the type.
* @returns {Promise<Result<T[]>>} A promise that resolves to the result of an REST API call.
*
* @example
* import { EnumCrmEntityTypeId, Text } from '@bitrix24/b24jssdk'
*
* interface CrmItem { id: number, title: string }
* const sixMonthAgo = new Date()
* sixMonthAgo.setMonth((new Date()).getMonth() - 6)
* sixMonthAgo.setHours(0, 0, 0)
* const response = await b24.actions.v2.callList.make<CrmItem>({
* method: 'crm.item.list',
* params: {
* entityTypeId: EnumCrmEntityTypeId.company,
* filter: {
* '=%title': 'A%',
* '>=createdTime': Text.toB24Format(sixMonthAgo) // created at least 6 months ago
* },
* select: ['id', 'title']
* },
* idKey: 'id',
* customKeyForResult: 'items',
* requestId: 'list-123'
* })
* if (!response.isSuccess) {
* throw new Error(`Problem: ${response.getErrorMessages().join('; ')}`)
* }
* const list = response.getData()
* console.log(`Result: ${list?.length}`) // Number of items received
*/
async make(options) {
const batchSize = 50;
const result$1 = new result.Result();
const idKey = options?.idKey ?? "ID";
const cursorIdKey = options?.cursorIdKey ?? idKey;
const customKeyForResult = options?.customKeyForResult ?? null;
const params = options?.params ?? {};
if ("order" in params && params["order"]) {
this._logger.warning("callList.make: user-provided `order` parameter is ignored because cursor-based pagination requires ordering by cursorIdKey. Use `filter` to narrow results instead.").catch(() => {
});
}
const moreIdKey = `>${cursorIdKey}`;
const { order: _ignoredOrder, ...restParams } = params;
const requestParams = {
...restParams,
order: { [cursorIdKey]: "ASC" },
filter: { ...params["filter"] || {}, [moreIdKey]: 0 },
start: -1
};
_uppercaseListParams.warnOnShadowedUppercaseParams("callList.make", requestParams, this._logger);
let allItems = [];
let pages = 0;
const maxPages = _walkBounds.resolveMaxPages("callList.make", options?.maxPages);
try {
while (true) {
_walkBounds.assertNotAborted(options?.signal, "callList.make", options.method);
const response = await this._b24.actions.v2.call.make({
method: options.method,
params: requestParams,
requestId: options.requestId
});
if (!response.isSuccess) {
this._logger.error("callFastListMethod", {
method: options.method,
requestId: options.requestId,
messages: response.getErrorMessages()
}).catch(() => {
});
for (const [index, error] of response.errors) {
result$1.addError(error, index);
}
break;
}
const responseData = response.getData();
if (!responseData) {
break;
}
const resultData = null === customKeyForResult ? responseData.result : responseData.result[customKeyForResult];
if (resultData.length === 0) {
break;
}
allItems = [...allItems, ...resultData];
pages += 1;
options.progress?.({ pages, rows: allItems.length });
const isShortPage = resultData.length < batchSize;
const lastItem = resultData[resultData.length - 1];
const cursorValue = lastItem ? Number.parseInt(lastItem[idKey], 10) : Number.NaN;
if (Number.isFinite(cursorValue)) {
if (cursorValue === requestParams.filter[moreIdKey]) {
throw _cursorStalled.cursorStalledError("callList.make", _cursorStalled.CURSOR_STALLED_HINT_LIST);
}
if (!_cursorProgress.cursorProgressed(cursorValue, requestParams.filter[moreIdKey], "ASC")) {
throw _cursorStalled.cursorWentBackwardsError("callList.make", _cursorStalled.CURSOR_STALLED_HINT_LIST);
}
if (isShortPage) {
break;
}
requestParams.filter[moreIdKey] = cursorValue;
if (pages >= maxPages) {
throw _walkBounds.maxPagesExceededError("callList.make", options.method, maxPages);
}
} else {
if (!isShortPage) {
this._logger.warning(`callList.make: pagination stops here \u2014 no numeric id could be read from the returned items via idKey "${idKey}". Make sure idKey matches the id field in the response; if the sortable field name differs from it, also set cursorIdKey (e.g. idKey: 'id', cursorIdKey: 'ID').`).catch(() => {
});
}
break;
}
}
} catch (error) {
if (_walkBounds.isWalkBoundsError(error)) {
result$1.addError(error);
} else {
throw error;
}
}
return result$1.setData(allItems);
}
}
exports.CallListV2 = CallListV2;
//# sourceMappingURL=call-list.cjs.map