{"version":3,"file":"call-list.cjs","sources":["../../../../../src/core/actions/v2/call-list.ts"],"sourcesContent":["import type { WalkBoundsOptions, WalkProgress } from '../_walk-bounds'\nimport { assertNotAborted, isWalkBoundsError, maxPagesExceededError, resolveMaxPages } from '../_walk-bounds'\nimport type { TypeCallParams, TypeCallParamsV2, TypeFilterV2 } from '../../../types/http'\nimport type { AjaxResult } from '../../http/ajax-result'\nimport { AbstractAction } from '../abstract-action'\nimport { Result } from '../../result'\nimport { cursorStalledError, cursorWentBackwardsError, CURSOR_STALLED_HINT_LIST } from '../_cursor-stalled'\nimport { cursorProgressed } from '../_cursor-progress'\nimport { warnOnShadowedUppercaseParams } from './_uppercase-list-params'\n\nexport type ActionCallListV2 = WalkBoundsOptions & {\n  /** Called after each collected page; see `WalkProgress`. */\n  progress?: WalkProgress\n  /** REST list method that returns rows, e.g. `crm.item.list`, `tasks.task.list`. */\n  method: string\n  /**\n   * Request parameters. `start` and `order` are excluded: the walk writes both\n   * itself on every page. Use `filter` and `select` to narrow the selection.\n   *\n   * Conditions must go in the **lowercase** `filter`, with any uppercase\n   * `FILTER` removed — see {@link warnOnShadowedUppercaseParams} for what the\n   * portal does with two keys differing only by case, and why it is silent.\n   */\n  params?: Omit<TypeCallParamsV2, 'start' | 'order'>\n  /**\n   * Name of the id field **as it appears in each response item**; its value\n   * drives the cursor. Default `'ID'` — `crm.item.list` and other camelCase\n   * methods return `id`, so they need `idKey: 'id'`.\n   */\n  idKey?: string\n  /**\n   * Field name used in the **request**, for `order` and the `>` page filter.\n   * Defaults to `idKey`. Set it only when a method spells the id differently in\n   * the two: `tasks.task.list` sorts and filters by `ID` but returns `id`, so it\n   * needs `idKey: 'id', cursorIdKey: 'ID'`.\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` on `restApi:v3`.\n   */\n  requestId?: string\n}\n\n/**\n * Fast data retrieval without counting the total number of records. `restApi:v2`\n *\n * Iterates through all pages of a v2 list method using cursor-based pagination (ordering and\n * filtering by the item id) and collects every item into a single array returned as a `Result`.\n * Unlike `FetchListV2`, which yields pages one by one via an async generator, this class waits\n * for all pages to finish and returns the complete dataset in one call.\n */\nexport class CallListV2 extends AbstractAction {\n  /**\n   * Fast data retrieval without counting the total number of records.\n   *\n   * **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/),\n   * and each one on {@link ActionCallListV2}.** 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   *   {@link cursorStalledError}. 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   * - Page size on `restApi:v2` is a fixed 50 — there is no `limit` to ask with,\n   *   so a page shorter than that ends the walk, but only once the cursor read\n   *   from it has been vouched for.\n   * - The portal keeps only the later of two top-level keys differing by case,\n   *   so a caller's uppercase `FILTER` can drop either their conditions or this\n   *   walk's cursor. Both are warned about, neither can be fixed from in here —\n   *   see {@link warnOnShadowedUppercaseParams}.\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 {ActionCallListV2} 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 { EnumCrmEntityTypeId, Text } from '@bitrix24/b24jssdk'\n   *\n   * interface CrmItem { id: number, title: string }\n   * const sixMonthAgo = new Date()\n   * sixMonthAgo.setMonth((new Date()).getMonth() - 6)\n   * sixMonthAgo.setHours(0, 0, 0)\n   * const response = await b24.actions.v2.callList.make<CrmItem>({\n   *   method: 'crm.item.list',\n   *   params: {\n   *     entityTypeId:  EnumCrmEntityTypeId.company,\n   *     filter: {\n   *       '=%title': 'A%',\n   *       '>=createdTime': Text.toB24Format(sixMonthAgo) // created at least 6 months ago\n   *     },\n   *     select: ['id', 'title']\n   *   },\n   *   idKey: 'id',\n   *   customKeyForResult: 'items',\n   *   requestId: 'list-123'\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: ActionCallListV2): Promise<Result<T[]>> {\n    const batchSize = 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    const moreIdKey = `>${cursorIdKey}`\n    const { order: _ignoredOrder, ...restParams } = params as TypeCallParams\n    const requestParams: TypeCallParamsV2 & { filter: TypeFilterV2 } = {\n      ...restParams,\n      order: { [cursorIdKey]: 'ASC' },\n      filter: { ...(params['filter'] || {}), [moreIdKey]: 0 },\n      start: -1\n    }\n\n    warnOnShadowedUppercaseParams('callList.make', requestParams as Record<string, unknown>, this._logger)\n\n    let allItems: T[] = []\n    let pages = 0\n    const maxPages = resolveMaxPages('callList.make', options?.maxPages)\n\n    try {\n      while (true) {\n        assertNotAborted(options?.signal, 'callList.make', options.method)\n\n        const response: AjaxResult<T> = await this._b24.actions.v2.call.make<T>({\n          method: options.method,\n          params: requestParams,\n          requestId: options.requestId\n        })\n\n        if (!response.isSuccess) {\n          this._logger.error('callFastListMethod', {\n            method: options.method,\n            requestId: options.requestId,\n            messages: response.getErrorMessages()\n          }).catch(() => {})\n          for (const [index, error] of response.errors) {\n            result.addError(error, index)\n          }\n          break\n        }\n        const responseData = response.getData()\n        if (!responseData) {\n          break\n        }\n\n        const resultData: T[] = null === customKeyForResult\n          ? responseData.result as T[]\n          : (responseData.result as any)[customKeyForResult] as T[]\n\n        if (resultData.length === 0) {\n          break\n        }\n\n        allItems = [...allItems, ...resultData]\n        pages += 1\n        options.progress?.({ pages, rows: allItems.length })\n\n        // Read rather than acted on: the cursor is checked first, and only\n        // then does a short page end the walk. A page shorter than the one\n        // asked for used to end it as \"end of data\" whatever the cursor did, so\n        // a stalled page that happened to be capped returned a truncated,\n        // overlapping result and reported success (#496). The two are\n        // separable: `>idKey` asks for rows strictly past the cursor, so a row\n        // at or before it cannot be in an answer that honoured the condition,\n        // however short the page.\n        const isShortPage = resultData.length < batchSize\n\n        // Update the filter for the next iteration\n        const lastItem = resultData[resultData.length - 1] as Record<string, any>\n        const cursorValue = lastItem ? Number.parseInt(lastItem[idKey], 10) : Number.NaN\n        if (Number.isFinite(cursorValue)) {\n          // A full page whose last id is the one already filtered on means the\n          // `>idKey` condition was dropped and this page will keep arriving. The\n          // check above cannot see it — a repeated page is full, so\n          // `resultData.length < batchSize` stays false however long the walk runs\n          // — and `allItems` grows by the same 50 rows for ever.\n          //\n          // Measured on a live portal, `tasks.task.list` with `idKey: 'id'` and no\n          // `cursorIdKey`: the walk was capped at three pages and collected 150\n          // rows of which 50 were unique, the cursor reading 3 every time. The\n          // response spells the id lowercase, the filter accepts it uppercase, so\n          // `>id` matches nothing the server knows and is ignored. That is the\n          // configuration #185 added `cursorIdKey` for; this is the signal that it\n          // is missing, instead of a hang.\n          if (cursorValue === requestParams.filter[moreIdKey]) {\n            throw cursorStalledError('callList.make', CURSOR_STALLED_HINT_LIST)\n          }\n\n          // …and a cursor that moved the wrong way. A server alternating between\n          // two pages never repeats the immediately preceding value, so the\n          // check above never fires and the walk runs for ever (#495). Every\n          // cycle steps backwards somewhere, and this is that step.\n          if (!cursorProgressed(cursorValue, requestParams.filter[moreIdKey] as number, 'ASC')) {\n            throw cursorWentBackwardsError('callList.make', CURSOR_STALLED_HINT_LIST)\n          }\n\n          // End of data, now that the cursor has been vouched for.\n          if (isShortPage) {\n            break\n          }\n\n          requestParams.filter[moreIdKey] = cursorValue\n\n          // Last, so every cheaper stop wins: a stalled cursor is still\n          // reported as a stall, the more specific diagnosis. A walk that ends\n          // exactly on its ceiling finishes only when its final page is short —\n          // that is what proves end-of-data. On an exact multiple of the page\n          // size the last page is full, nothing has proved the data ended, and\n          // this fires; the rows read are returned with the error attached,\n          // not discarded.\n          if (pages >= maxPages) {\n            throw maxPagesExceededError('callList.make', options.method, maxPages)\n          }\n        } else {\n          // No usable numeric cursor id could be read from the page's items via\n          // `idKey` — almost always an `idKey` that doesn't match the\n          // response field (e.g. a request that sorts by `ID` while the response\n          // carries a lowercase `id`). Without a cursor we can't advance, so stop and\n          // tell the caller how to fix it instead of silently truncating.\n          // A short page is simply the end of the data, so say nothing about a\n          // cursor the walk never needed.\n          if (!isShortPage) {\n            this._logger.warning(`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').`).catch(() => {})\n          }\n          break\n        }\n      }\n    } catch (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 the soft-error exit above\n      // already produces.\n      if (isWalkBoundsError(error)) {\n        result.addError(error)\n      } else {\n        throw error\n      }\n    }\n\n    return result.setData(allItems)\n  }\n}\n"],"names":["AbstractAction","result","Result","warnOnShadowedUppercaseParams","resolveMaxPages","assertNotAborted","cursorStalledError","CURSOR_STALLED_HINT_LIST","cursorProgressed","cursorWentBackwardsError","maxPagesExceededError","isWalkBoundsError"],"mappings":";;;;;;;;;;;;;;;;;;;AAsDO,MAAM,mBAAmBA,6BAAA,CAAe;AAAA,EAtD/C;AAsD+C,IAAA,MAAA,CAAA,IAAA,EAAA,YAAA,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8D7C,MAAsB,KAAkB,OAAA,EAAiD;AACvF,IAAA,MAAM,SAAA,GAAY,EAAA;AAClB,IAAA,MAAMC,QAAA,GAAsB,IAAIC,aAAA,EAAO;AAEvC,IAAA,MAAM,KAAA,GAAQ,SAAS,KAAA,IAAS,IAAA;AAChC,IAAA,MAAM,WAAA,GAAc,SAAS,WAAA,IAAe,KAAA;AAC5C,IAAA,MAAM,kBAAA,GAAqB,SAAS,kBAAA,IAAsB,IAAA;AAC1D,IAAA,MAAM,MAAA,GAAS,OAAA,EAAS,MAAA,IAAU,EAAC;AAGnC,IAAA,IAAI,OAAA,IAAW,MAAA,IAAU,MAAA,CAAO,OAAO,CAAA,EAAG;AACxC,MAAA,IAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,qKAAqK,CAAA,CAAE,MAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AAAA,IAC5M;AAEA,IAAA,MAAM,SAAA,GAAY,IAAI,WAAW,CAAA,CAAA;AACjC,IAAA,MAAM,EAAE,KAAA,EAAO,aAAA,EAAe,GAAG,YAAW,GAAI,MAAA;AAChD,IAAA,MAAM,aAAA,GAA6D;AAAA,MACjE,GAAG,UAAA;AAAA,MACH,KAAA,EAAO,EAAE,CAAC,WAAW,GAAG,KAAA,EAAM;AAAA,MAC9B,MAAA,EAAQ,EAAE,GAAI,MAAA,CAAO,QAAQ,CAAA,IAAK,EAAC,EAAI,CAAC,SAAS,GAAG,CAAA,EAAE;AAAA,MACtD,KAAA,EAAO;AAAA,KACT;AAEA,IAAAC,kDAAA,CAA8B,eAAA,EAAiB,aAAA,EAA0C,IAAA,CAAK,OAAO,CAAA;AAErG,IAAA,IAAI,WAAgB,EAAC;AACrB,IAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,IAAA,MAAM,QAAA,GAAWC,2BAAA,CAAgB,eAAA,EAAiB,OAAA,EAAS,QAAQ,CAAA;AAEnE,IAAA,IAAI;AACF,MAAA,OAAO,IAAA,EAAM;AACX,QAAAC,4BAAA,CAAiB,OAAA,EAAS,MAAA,EAAQ,eAAA,EAAiB,OAAA,CAAQ,MAAM,CAAA;AAEjE,QAAA,MAAM,WAA0B,MAAM,IAAA,CAAK,KAAK,OAAA,CAAQ,EAAA,CAAG,KAAK,IAAA,CAAQ;AAAA,UACtE,QAAQ,OAAA,CAAQ,MAAA;AAAA,UAChB,MAAA,EAAQ,aAAA;AAAA,UACR,WAAW,OAAA,CAAQ;AAAA,SACpB,CAAA;AAED,QAAA,IAAI,CAAC,SAAS,SAAA,EAAW;AACvB,UAAA,IAAA,CAAK,OAAA,CAAQ,MAAM,oBAAA,EAAsB;AAAA,YACvC,QAAQ,OAAA,CAAQ,MAAA;AAAA,YAChB,WAAW,OAAA,CAAQ,SAAA;AAAA,YACnB,QAAA,EAAU,SAAS,gBAAA;AAAiB,WACrC,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,UAAC,CAAC,CAAA;AACjB,UAAA,KAAA,MAAW,CAAC,KAAA,EAAO,KAAK,CAAA,IAAK,SAAS,MAAA,EAAQ;AAC5C,YAAAJ,QAAA,CAAO,QAAA,CAAS,OAAO,KAAK,CAAA;AAAA,UAC9B;AACA,UAAA;AAAA,QACF;AACA,QAAA,MAAM,YAAA,GAAe,SAAS,OAAA,EAAQ;AACtC,QAAA,IAAI,CAAC,YAAA,EAAc;AACjB,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,aAAkB,IAAA,KAAS,kBAAA,GAC7B,aAAa,MAAA,GACZ,YAAA,CAAa,OAAe,kBAAkB,CAAA;AAEnD,QAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,UAAA;AAAA,QACF;AAEA,QAAA,QAAA,GAAW,CAAC,GAAG,QAAA,EAAU,GAAG,UAAU,CAAA;AACtC,QAAA,KAAA,IAAS,CAAA;AACT,QAAA,OAAA,CAAQ,WAAW,EAAE,KAAA,EAAO,IAAA,EAAM,QAAA,CAAS,QAAQ,CAAA;AAUnD,QAAA,MAAM,WAAA,GAAc,WAAW,MAAA,GAAS,SAAA;AAGxC,QAAA,MAAM,QAAA,GAAW,UAAA,CAAW,UAAA,CAAW,MAAA,GAAS,CAAC,CAAA;AACjD,QAAA,MAAM,WAAA,GAAc,WAAW,MAAA,CAAO,QAAA,CAAS,SAAS,KAAK,CAAA,EAAG,EAAE,CAAA,GAAI,MAAA,CAAO,GAAA;AAC7E,QAAA,IAAI,MAAA,CAAO,QAAA,CAAS,WAAW,CAAA,EAAG;AAchC,UAAA,IAAI,WAAA,KAAgB,aAAA,CAAc,MAAA,CAAO,SAAS,CAAA,EAAG;AACnD,YAAA,MAAMK,iCAAA,CAAmB,iBAAiBC,uCAAwB,CAAA;AAAA,UACpE;AAMA,UAAA,IAAI,CAACC,iCAAiB,WAAA,EAAa,aAAA,CAAc,OAAO,SAAS,CAAA,EAAa,KAAK,CAAA,EAAG;AACpF,YAAA,MAAMC,uCAAA,CAAyB,iBAAiBF,uCAAwB,CAAA;AAAA,UAC1E;AAGA,UAAA,IAAI,WAAA,EAAa;AACf,YAAA;AAAA,UACF;AAEA,UAAA,aAAA,CAAc,MAAA,CAAO,SAAS,CAAA,GAAI,WAAA;AASlC,UAAA,IAAI,SAAS,QAAA,EAAU;AACrB,YAAA,MAAMG,iCAAA,CAAsB,eAAA,EAAiB,OAAA,CAAQ,MAAA,EAAQ,QAAQ,CAAA;AAAA,UACvE;AAAA,QACF,CAAA,MAAO;AAQL,UAAA,IAAI,CAAC,WAAA,EAAa;AAChB,YAAA,IAAA,CAAK,QAAQ,OAAA,CAAQ,CAAA,2GAAA,EAAyG,KAAK,CAAA,gKAAA,CAAkK,CAAA,CAAE,MAAM,MAAM;AAAA,YAAC,CAAC,CAAA;AAAA,UACvT;AACA,UAAA;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,KAAA,EAAO;AAKd,MAAA,IAAIC,6BAAA,CAAkB,KAAK,CAAA,EAAG;AAC5B,QAAAV,QAAA,CAAO,SAAS,KAAK,CAAA;AAAA,MACvB,CAAA,MAAO;AACL,QAAA,MAAM,KAAA;AAAA,MACR;AAAA,IACF;AAEA,IAAA,OAAOA,QAAA,CAAO,QAAQ,QAAQ,CAAA;AAAA,EAChC;AACF;;;;"}