{"version":3,"file":"_keyset-paginate.mjs","sources":["../../../../../src/core/actions/v3/_keyset-paginate.ts"],"sourcesContent":["import type { TypeB24 } from '../../../types/b24'\nimport type { LoggerInterface } from '../../../types/logger'\nimport type { TypeCallParams, TypeFilterV3 } from '../../../types/http'\nimport type { AjaxResult } from '../../http/ajax-result'\nimport { SdkError } from '../../sdk-error'\n\n/**\n * Reject a non-array `filter` for the emulated-keyset list actions.\n *\n * `TypeCallParamsV3.filter` accepts the v3 array of triples AND the v2 object\n * dialect, kept for backward compatibility, and that union is right for a plain\n * `call`, which forwards the filter untouched. It is wrong for `callList` /\n * `fetchList`: those append `[cursorIdKey, '>', cursor]` to the same filter on\n * every page, so an array is not a preference, it is the only shape the\n * mechanism can extend. Passing the object form used to throw `filter is not\n * iterable` from a spread, one page into the walk.\n *\n * Both action option types already narrow `filter` to {@link TypeFilterV3}, so\n * for a TypeScript caller the `asserts` signature adds nothing the parameter\n * type has not already done. It exists for the callers the types cannot reach:\n * JavaScript, and anyone who wrote `params as any`.\n *\n * `callTail` / `fetchTail` do NOT need this. They paginate through the separate\n * `cursor` parameter and forward `filter` untouched, so the object dialect is\n * harmless there — which is why the fix stops at two of the four v3 walkers.\n *\n * @throws {SdkError} `JSSDK_ACTION_V3_LIST_FILTER_NOT_ARRAY`\n */\nexport function assertArrayFilter(\n  filter: unknown,\n  action: string\n): asserts filter is undefined | TypeFilterV3 {\n  if (filter === undefined || Array.isArray(filter)) {\n    return\n  }\n\n  // Static text plus the caller-supplied `action` label only. Never interpolate\n  // the filter, or any other caller value, into an SdkError description: unlike\n  // AjaxError, SdkError does NOT run its message through\n  // `redactSensitiveParams`, and a filter legitimately carries user data — the\n  // email or phone number being searched for.\n  throw new SdkError({\n    code: 'JSSDK_ACTION_V3_LIST_FILTER_NOT_ARRAY',\n    description: `${action}: \\`filter\\` must be the restApi:v3 array form, e.g. [['id', '>', 100]] or FilterV3.build(...). `\n      + `The restApi:v2 object dialect ({ '>id': 100 }) cannot be used here, because keyset pagination extends the filter with a cursor condition.`,\n    status: 500\n  })\n}\n\n/**\n * Thrown by {@link keysetPaginate} when the underlying v3 `call` reports a soft\n * error part-way through a walk. It carries both the raw `[key, Error]` entries\n * (so the eager `call*` helpers can fold them into a `Result` via `addError`)\n * and the flat messages (so the streaming `fetch*` helpers can rethrow as their\n * own action-specific `SdkError`).\n */\nexport class KeysetPaginationError extends Error {\n  public readonly errors: Iterable<[string, Error]>\n  public readonly messages: string[]\n\n  constructor(errors: Iterable<[string, Error]>, messages: string[]) {\n    super(messages.join('; '))\n    this.name = 'KeysetPaginationError'\n    this.errors = errors\n    this.messages = messages\n  }\n}\n\n/**\n * Per-helper plug-ins that adapt the shared keyset loop to either the emulated\n * `list` cursor (a `[field, '>', n]` filter) or the native `tail` cursor.\n */\nexport type KeysetPaginateStrategy = {\n  /** REST API method name (e.g. `tasks.task.list`, `main.eventlog.tail`). */\n  method: string\n  /** Optional request id forwarded to `call`. */\n  requestId?: string\n  /** Key under `result` that holds the row array (e.g. `items`). */\n  customKeyForResult: string | null\n  /** Cursor value used for the very first page. */\n  initialCursor: number | string\n  /** Build the per-page request params for a given cursor value. */\n  buildParams: (cursor: number | string) => TypeCallParams\n  /**\n   * Read the next cursor value from the last item of a full page. Return `null`\n   * to stop pagination — used when the cursor field cannot be read from the\n   * response (the loop logs {@link KeysetPaginateStrategy.noCursorWarning}).\n   */\n  readNextCursor: (lastItem: Record<string, any>) => number | string | null\n  /** Logged at `warning` level when `readNextCursor` returns `null`. */\n  noCursorWarning: string\n  /** Label logged at `error` level when the underlying `call` fails. */\n  errorLabel: string\n}\n\n/**\n * Shared keyset-pagination driver for the v3 list/tail helpers\n * (`callList`/`fetchList` and `callTail`/`fetchTail`).\n *\n * It repeatedly calls `actions.v3.call`, yields each page, and advances the\n * cursor until the data runs out. End-of-data is detected by the size of the\n * page the **server actually returns**, not the requested `limit`: the loop\n * tracks the largest page seen so far (`maxPageSize`) and stops only when a page\n * is shorter than that (or empty). This matters because some v3 methods (e.g.\n * `tasks.task.list`) silently cap the page below the requested `limit`; keying\n * the stop on `limit` would end the walk right after the first capped page.\n *\n * On a soft error it throws {@link KeysetPaginationError}; the calling helper\n * decides whether to fold it into a `Result` (eager) or rethrow as an\n * `SdkError` (streaming).\n */\nexport async function* keysetPaginate<T = unknown>(\n  b24: TypeB24,\n  logger: LoggerInterface,\n  strategy: KeysetPaginateStrategy\n): AsyncGenerator<T[]> {\n  let cursor = strategy.initialCursor\n  let maxPageSize = 0\n\n  while (true) {\n    const response: AjaxResult<T> = await b24.actions.v3.call.make<T>({\n      method: strategy.method,\n      params: strategy.buildParams(cursor),\n      requestId: strategy.requestId\n    })\n\n    if (!response.isSuccess) {\n      logger.error(strategy.errorLabel, {\n        method: strategy.method,\n        requestId: strategy.requestId,\n        messages: response.getErrorMessages()\n      }).catch(() => {})\n      throw new KeysetPaginationError(response.errors, response.getErrorMessages())\n    }\n\n    const responseData = response.getData()\n    if (!responseData) {\n      break\n    }\n\n    const resultData: T[] = (responseData.result as any)[strategy.customKeyForResult as any] as T[]\n    // Guard against a wrong `customKeyForResult` (key absent → undefined): treat\n    // a missing/non-array bucket as \"no data\" instead of throwing on `.length`.\n    if (!Array.isArray(resultData) || resultData.length === 0) {\n      break\n    }\n\n    yield resultData\n\n    maxPageSize = Math.max(maxPageSize, resultData.length)\n    if (resultData.length < maxPageSize) {\n      break\n    }\n\n    const lastItem = resultData[resultData.length - 1] as Record<string, any>\n    const next = lastItem ? strategy.readNextCursor(lastItem) : null\n    if (next === null || next === undefined) {\n      logger.warning(strategy.noCursorWarning).catch(() => {})\n      break\n    }\n    cursor = next\n  }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AA4BO,SAAS,iBAAA,CACd,QACA,MAAA,EAC4C;AAC5C,EAAA,IAAI,MAAA,KAAW,MAAA,IAAa,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,EAAG;AACjD,IAAA;AAAA,EACF;AAOA,EAAA,MAAM,IAAI,QAAA,CAAS;AAAA,IACjB,IAAA,EAAM,uCAAA;AAAA,IACN,WAAA,EAAa,GAAG,MAAM,CAAA,yOAAA,CAAA;AAAA,IAEtB,MAAA,EAAQ;AAAA,GACT,CAAA;AACH;AAnBgB,MAAA,CAAA,iBAAA,EAAA,mBAAA,CAAA;AA4BT,MAAM,8BAA8B,KAAA,CAAM;AAAA,EAxDjD;AAwDiD,IAAA,MAAA,CAAA,IAAA,EAAA,uBAAA,CAAA;AAAA;AAAA,EAC/B,MAAA;AAAA,EACA,QAAA;AAAA,EAEhB,WAAA,CAAY,QAAmC,QAAA,EAAoB;AACjE,IAAA,KAAA,CAAM,QAAA,CAAS,IAAA,CAAK,IAAI,CAAC,CAAA;AACzB,IAAA,IAAA,CAAK,IAAA,GAAO,uBAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAAA,EAClB;AACF;AA6CA,gBAAuB,cAAA,CACrB,GAAA,EACA,MAAA,EACA,QAAA,EACqB;AACrB,EAAA,IAAI,SAAS,QAAA,CAAS,aAAA;AACtB,EAAA,IAAI,WAAA,GAAc,CAAA;AAElB,EAAA,OAAO,IAAA,EAAM;AACX,IAAA,MAAM,WAA0B,MAAM,GAAA,CAAI,OAAA,CAAQ,EAAA,CAAG,KAAK,IAAA,CAAQ;AAAA,MAChE,QAAQ,QAAA,CAAS,MAAA;AAAA,MACjB,MAAA,EAAQ,QAAA,CAAS,WAAA,CAAY,MAAM,CAAA;AAAA,MACnC,WAAW,QAAA,CAAS;AAAA,KACrB,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,SAAA,EAAW;AACvB,MAAA,MAAA,CAAO,KAAA,CAAM,SAAS,UAAA,EAAY;AAAA,QAChC,QAAQ,QAAA,CAAS,MAAA;AAAA,QACjB,WAAW,QAAA,CAAS,SAAA;AAAA,QACpB,QAAA,EAAU,SAAS,gBAAA;AAAiB,OACrC,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AACjB,MAAA,MAAM,IAAI,qBAAA,CAAsB,QAAA,CAAS,MAAA,EAAQ,QAAA,CAAS,kBAAkB,CAAA;AAAA,IAC9E;AAEA,IAAA,MAAM,YAAA,GAAe,SAAS,OAAA,EAAQ;AACtC,IAAA,IAAI,CAAC,YAAA,EAAc;AACjB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,UAAA,GAAmB,YAAA,CAAa,MAAA,CAAe,QAAA,CAAS,kBAAyB,CAAA;AAGvF,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,UAAU,CAAA,IAAK,UAAA,CAAW,WAAW,CAAA,EAAG;AACzD,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,UAAA;AAEN,IAAA,WAAA,GAAc,IAAA,CAAK,GAAA,CAAI,WAAA,EAAa,UAAA,CAAW,MAAM,CAAA;AACrD,IAAA,IAAI,UAAA,CAAW,SAAS,WAAA,EAAa;AACnC,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,QAAA,GAAW,UAAA,CAAW,UAAA,CAAW,MAAA,GAAS,CAAC,CAAA;AACjD,IAAA,MAAM,IAAA,GAAO,QAAA,GAAW,QAAA,CAAS,cAAA,CAAe,QAAQ,CAAA,GAAI,IAAA;AAC5D,IAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,MAAA,EAAW;AACvC,MAAA,MAAA,CAAO,OAAA,CAAQ,QAAA,CAAS,eAAe,CAAA,CAAE,MAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AACvD,MAAA;AAAA,IACF;AACA,IAAA,MAAA,GAAS,IAAA;AAAA,EACX;AACF;AAnDuB,MAAA,CAAA,cAAA,EAAA,gBAAA,CAAA;;;;"}