UNPKG

@bitrix24/b24jssdk

Version:

Bitrix24 REST API JavaScript SDK

149 lines (146 loc) • 6.82 kB
/** * @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/ */ import { isWalkBoundsError } from '../_walk-bounds.mjs'; import { AbstractAction } from '../abstract-action.mjs'; import { Result } from '../../result.mjs'; import { assertArrayFilter, keysetPaginate, KeysetPaginationError } from './_keyset-paginate.mjs'; import { CURSOR_STALLED_HINT_LIST } from '../_cursor-stalled.mjs'; var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); class CallListV3 extends AbstractAction { static { __name(this, "CallListV3"); } /** * Fast data retrieval without counting the total number of records. * * **Every option is documented on the [callList v3 page](https://bitrix24.github.io/b24jssdk/docs/working-with-the-rest-api/call-list-rest-api-ver3/), * and each one on {@link ActionCallListV3}.** 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 * `JSSDK_ACTION_CURSOR_STALLED` * ({@link CURSOR_STALLED_HINT_LIST} names the usual causes). 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. * - End of data is decided against the largest page seen, never against * `limit`, which methods are free to cap below the ask. The rule lives in * {@link keysetPaginate}, which this delegates to. * - `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 {ActionCallListV3} 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 { Text } from '@bitrix24/b24jssdk' * * interface MainEventLogItem { id: number, userId: number } * const sixMonthAgo = new Date() * sixMonthAgo.setMonth((new Date()).getMonth() - 6) * sixMonthAgo.setHours(0, 0, 0) * const response = await b24.actions.v3.callList.make<MainEventLogItem>({ * method: 'main.eventlog.list', * params: { * filter: [ * ['timestampX', '>=', Text.toB24Format(sixMonthAgo)] // created at least 6 months ago * ], * select: ['id', 'userId'] * }, * idKey: 'id', * customKeyForResult: 'items', * requestId: 'eventlog-123', * limit: 60 * }) * 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 = options?.limit ?? 50; const result = new 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(() => { }); } assertArrayFilter(params["filter"], "callList.make"); const { order: _ignoredOrder, ...restParams } = params; const requestParams = { ...restParams, order: { [cursorIdKey]: "ASC" }, filter: [...params["filter"] ?? []], pagination: { page: 0, limit: batchSize } }; const allItems = []; let pages = 0; try { for await (const page of keysetPaginate(this._b24, this._logger, { method: options.method, requestId: options.requestId, customKeyForResult, initialCursor: 0, // Emulated keyset: append the `[cursorIdKey, '>', cursor]` page filter. buildParams: /* @__PURE__ */ __name((cursor) => ({ ...requestParams, filter: [...requestParams.filter, [cursorIdKey, ">", cursor]] }), "buildParams"), // Advance by the numeric id read from the last item via `idKey`. A // non-numeric value (almost always an `idKey` that doesn't match the // response field — e.g. sorting by `ID` while the response carries a // lowercase `id`) stops the walk instead of silently truncating. readNextCursor: /* @__PURE__ */ __name((lastItem) => { const value = Number.parseInt(lastItem[idKey], 10); return Number.isFinite(value) ? value : null; }, "readNextCursor"), noCursorWarning: `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').`, errorLabel: "callFastListMethod", actionLabel: "callList.make", stalledCursorHint: CURSOR_STALLED_HINT_LIST, // Always ascending: the page condition is `[cursorIdKey, '>', cursor]` // and the request sorts by the same field, so the cursor read off each // page is strictly greater than the one it was requested with. cursorDirection: "ASC", maxPages: options?.maxPages, signal: options?.signal })) { for (const item of page) { allItems.push(item); } pages += 1; options.progress?.({ pages, rows: allItems.length }); } } catch (error) { if (error instanceof KeysetPaginationError) { for (const [index, err] of error.errors) { result.addError(err, index); } } else if (isWalkBoundsError(error)) { result.addError(error); } else { throw error; } } return result.setData(allItems); } } export { CallListV3 }; //# sourceMappingURL=call-list.mjs.map