UNPKG

@bitrix24/b24jssdk

Version:

Bitrix24 REST API JavaScript SDK

1 lines • 12.4 kB
{"version":3,"file":"call-list.mjs","sources":["../../../../../src/core/actions/v3/call-list.ts"],"sourcesContent":["import type { WalkBoundsOptions, WalkProgress } from '../_walk-bounds'\nimport { isWalkBoundsError } from '../_walk-bounds'\nimport type { TypeCallParams, TypeCallParamsV3, TypeFilterV3 } from '../../../types/http'\nimport { AbstractAction } from '../abstract-action'\nimport { Result } from '../../result'\nimport { assertArrayFilter, keysetPaginate, KeysetPaginationError } from './_keyset-paginate'\nimport { CURSOR_STALLED_HINT_LIST } from '../_cursor-stalled'\n\nexport type ActionCallListV3 = WalkBoundsOptions & {\n /** Called after each collected page; see `WalkProgress`. */\n progress?: WalkProgress\n method: string\n /**\n * `filter` is narrowed to the v3 array form here, unlike {@link TypeCallParamsV3},\n * which also accepts the v2 object dialect for backward compatibility.\n *\n * Keyset pagination is emulated by appending `[cursorIdKey, '>', cursor]` to\n * this filter on every page, so an array is not a preference — it is the only\n * shape the mechanism can extend. The object form used to be accepted here and\n * then threw `filter is not iterable` at runtime, one page into the walk.\n */\n params?: Omit<TypeCallParamsV3, 'pagination' | 'order' | 'filter'> & { filter?: TypeFilterV3 }\n /**\n * Name of the id field **as it appears in each response item**; its value\n * drives the cursor. Default `'id'`.\n */\n idKey?: string\n /**\n * Field name used in the **request**, for `order` and the `[field, '>', n]`\n * page filter. Defaults to `idKey`, which is usually right on `restApi:v3`\n * where names are camelCase in both directions.\n */\n cursorIdKey?: string\n /** Key the rows are nested under in the response, e.g. `items` for CRM items. */\n customKeyForResult: string\n /**\n * Sent as the `bx24_request_id` query parameter, for tracing. It does **not**\n * deduplicate anything — for that see `idempotencyKey`.\n */\n requestId?: string\n /**\n * Rows per page. Default `50`, and **a request rather than a guarantee**:\n * every method applies its own maximum, so a page shorter than `limit` is not\n * the end of the data. The walk allows for that; hand-rolled paging on\n * `call.make` does not.\n */\n limit?: number\n}\n\n/**\n * Fast data retrieval without counting the total number of records. `restApi:v3`\n *\n * Iterates through all pages of a v3 list method using keyset (cursor) pagination and collects\n * every item into a single array returned as a `Result`. Unlike the v2 counterpart `CallListV2`,\n * it uses v3-style array filter syntax and supports the `limit` option — a requested page size,\n * since each method enforces its own maximum. No number here is a rule: `tasks.task.list` was\n * measured at 50 whatever is asked, and 1000 is the figure the reference quotes rather than one\n * observed on any method.\n * Unlike `FetchListV3`, which streams pages via an async generator, this class returns the\n * complete dataset in one awaited call.\n */\nexport class CallListV3 extends AbstractAction {\n /**\n * Fast data retrieval without counting the total number of records.\n *\n * **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/),\n * and each one on {@link ActionCallListV3}.** Not repeated here: nothing\n * watches a sentence in a comment, while the page is link-checked and its\n * code compiled on every CI run.\n *\n * What matters while editing this file:\n *\n * - The cursor only advances if rows arrive sorted by `cursorIdKey` ascending,\n * so the walk writes its own `order` and strips a caller's with a `warning`.\n * - `idKey` reads the RESPONSE, `cursorIdKey` writes the REQUEST, and the two\n * fail differently. A wrong `cursorIdKey` means the page condition never\n * matches, the same page keeps arriving, and the walk stops with\n * `JSSDK_ACTION_CURSOR_STALLED`\n * ({@link CURSOR_STALLED_HINT_LIST} names the usual causes). A wrong `idKey` is quieter: if the value\n * cannot be read as a number the walk warns and stops short, and if it\n * names a *different numeric* field it advances a cursor the request never\n * sorts by — which skips rows rather than reporting anything.\n * - End of data is decided against the largest page seen, never against\n * `limit`, which methods are free to cap below the ask. The rule lives in\n * {@link keysetPaginate}, which this delegates to.\n * - `maxPages` never ends a walk silently: the rows already read come back\n * with `JSSDK_ACTION_MAX_PAGES_EXCEEDED` attached, so a short list that\n * looks complete is never what a caller gets.\n *\n * @template T - The type of the elements of the returned array (default is `unknown`).\n * @param {ActionCallListV3} options - every field is documented on the type.\n * @returns {Promise<Result<T[]>>} A promise that resolves to the result of an REST API call.\n *\n * @example\n * import { Text } from '@bitrix24/b24jssdk'\n *\n * interface MainEventLogItem { id: number, userId: number }\n * const sixMonthAgo = new Date()\n * sixMonthAgo.setMonth((new Date()).getMonth() - 6)\n * sixMonthAgo.setHours(0, 0, 0)\n * const response = await b24.actions.v3.callList.make<MainEventLogItem>({\n * method: 'main.eventlog.list',\n * params: {\n * filter: [\n * ['timestampX', '>=', Text.toB24Format(sixMonthAgo)] // created at least 6 months ago\n * ],\n * select: ['id', 'userId']\n * },\n * idKey: 'id',\n * customKeyForResult: 'items',\n * requestId: 'eventlog-123',\n * limit: 60\n * })\n * if (!response.isSuccess) {\n * throw new Error(`Problem: ${response.getErrorMessages().join('; ')}`)\n * }\n * const list = response.getData()\n * console.log(`Result: ${list?.length}`) // Number of items received\n */\n public override async make<T = unknown>(options: ActionCallListV3): Promise<Result<T[]>> {\n const batchSize = options?.limit ?? 50\n const result: Result<T[]> = new Result()\n\n const idKey = options?.idKey ?? 'id'\n const cursorIdKey = options?.cursorIdKey ?? idKey\n const customKeyForResult = options?.customKeyForResult ?? null\n const params = options?.params ?? {}\n\n // Warn and strip user-provided `order` — cursor pagination requires ordering by cursorIdKey only\n if ('order' in params && params['order']) {\n 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(() => {})\n }\n\n assertArrayFilter(params['filter'], 'callList.make')\n\n const { order: _ignoredOrder, ...restParams } = params as TypeCallParams\n const requestParams: TypeCallParamsV3 & { filter: TypeFilterV3 } = {\n ...restParams,\n order: { [cursorIdKey]: 'ASC' },\n filter: [...(params['filter'] ?? [])],\n pagination: { page: 0, limit: batchSize }\n }\n\n const allItems: T[] = []\n let pages = 0\n try {\n for await (const page of keysetPaginate<T>(this._b24, this._logger, {\n method: options.method,\n requestId: options.requestId,\n customKeyForResult,\n initialCursor: 0,\n // Emulated keyset: append the `[cursorIdKey, '>', cursor]` page filter.\n buildParams: cursor => ({ ...requestParams, filter: [...requestParams.filter, [cursorIdKey, '>', cursor]] }),\n // Advance by the numeric id read from the last item via `idKey`. A\n // non-numeric value (almost always an `idKey` that doesn't match the\n // response field — e.g. sorting by `ID` while the response carries a\n // lowercase `id`) stops the walk instead of silently truncating.\n readNextCursor: (lastItem) => {\n const value = Number.parseInt(lastItem[idKey], 10)\n return Number.isFinite(value) ? value : null\n },\n noCursorWarning: `callList.make: pagination stops here — 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').`,\n errorLabel: 'callFastListMethod',\n actionLabel: 'callList.make',\n stalledCursorHint: CURSOR_STALLED_HINT_LIST,\n // Always ascending: the page condition is `[cursorIdKey, '>', cursor]`\n // and the request sorts by the same field, so the cursor read off each\n // page is strictly greater than the one it was requested with.\n cursorDirection: 'ASC',\n maxPages: options?.maxPages,\n signal: options?.signal\n })) {\n for (const item of page) {\n allItems.push(item)\n }\n pages += 1\n options.progress?.({ pages, rows: allItems.length })\n }\n } catch (error) {\n if (error instanceof KeysetPaginationError) {\n for (const [index, err] of error.errors) {\n result.addError(err, index)\n }\n } else if (isWalkBoundsError(error)) {\n // A bound the caller set, not a fault in the data: the pages already\n // collected are correct, so they are returned with the error attached\n // rather than discarded — the same shape this walker already produces\n // for a soft error from the portal.\n result.addError(error)\n } else {\n throw error\n }\n }\n\n return result.setData(allItems)\n }\n}\n"],"names":[],"mappings":"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"}