{"version":3,"file":"fetch-tail.mjs","sources":["../../../../../src/core/actions/v3/fetch-tail.ts"],"sourcesContent":["import type { WalkBoundsOptions } from '../_walk-bounds'\nimport type { TypeCallParams, TypeCallParamsV3, TypeFilterV3 } from '../../../types/http'\nimport { AbstractAction } from '../abstract-action'\nimport { SdkError } from '../../sdk-error'\nimport type { FilterV3Group } from '../../../tools/filter-v3'\nimport { assertTailFilter, filterMentionsField, keysetPaginate, KeysetPaginationError } from './_keyset-paginate'\nimport { CURSOR_STALLED_HINT_TAIL } from '../_cursor-stalled'\nimport { DESC_ORDER, resolveCursorDirection } from '../_cursor-progress'\n\nexport type ActionFetchTailV3 = WalkBoundsOptions & {\n  method: string\n  /**\n   * `filter` is narrowed away from the `restApi:v2` object dialect, which the\n   * portal rejects everywhere in v3. A **logic group** stays allowed: this\n   * walker forwards `filter` untouched, and the portal accepts a bare group as\n   * the whole filter — measured.\n   */\n  params?: Omit<TypeCallParamsV3, 'pagination' | 'order' | 'cursor' | 'filter'> & { filter?: TypeFilterV3 | FilterV3Group }\n  cursorField?: string\n  order?: 'ASC' | 'DESC' | 'asc' | 'desc' | string\n  customKeyForResult?: string\n  requestId?: string\n  limit?: number\n  initialValue?: number | string\n}\n\n/**\n * Calls a REST API `tail` method (native keyset cursor) and returns an async\n * generator for efficient large data retrieval. `restApi:v3`\n *\n * Unlike `fetchList`, which emulates keyset pagination on top of the `list`\n * action by injecting a `[field, '>', n]` filter, this helper drives the server\n * `tail` action with its native `cursor: { field, value, order, limit }`\n * parameter (see the v3 reference §6.2). The server itself adds `field > value`\n * (asc) / `field < value` (desc) and sorts by `field`, so the cursor field\n * MUST NOT appear in `filter` (the server rejects it with\n * `INVALIDFILTEREXCEPTION`).\n */\nexport class FetchTailV3 extends AbstractAction {\n  /**\n   * Streams every record of a `tail` method as chunks, advancing the keyset\n   * cursor between requests.\n   *\n   * @template T - The type of items in the returned arrays (default is `unknown`).\n   *\n   * @param {ActionFetchTailV3} options - parameters for executing the request.\n   *     - `method: string` - A REST API `tail` method name (for example: `main.eventlog.tail`).\n   *     - `params?: Omit<TypeCallParamsV3, 'pagination' | 'order' | 'cursor'>` - Request parameters.\n   *         Use `filter` and `select` to control the selection. `pagination`, `order` and `cursor`\n   *         are managed by this helper and must not be passed. The cursor field must NOT be used in `filter`.\n   *     - `cursorField?: string` - The DTO field that drives the cursor. Must be monotonic and\n   *         preferably unique, and present in `select`. Default is `id`.\n   *     - `order?: 'ASC' | 'DESC'` - Cursor direction. Default is `ASC`. For `DESC` you MUST pass\n   *         `initialValue` (the server pages by `field < value`, so the default `0` returns nothing).\n   *     - `customKeyForResult?: string` - The key the response groups rows under. Default is `items`.\n   *     - `requestId?: string` - Unique request identifier for tracking.\n   *     - `maxPages?: number` - Stop after this many pages and throw\n   *         `JSSDK_ACTION_MAX_PAGES_EXCEEDED` naming the method. Defaults to 10 000 — a\n   *         backstop, not a policy. Every page yielded before it fires has already\n   *         been delivered to the consumer; only the walk stops.\n   *     - `signal?: AbortSignal` - Stop the walk. Checked at the top of each iteration, so an\n   *         already-aborted signal costs no request. Throws `JSSDK_ACTION_ABORTED`.\n   *     - `limit?: number` - How many records to retrieve at a time. Default is `50`.\n   *         **A request, not a guarantee.** Each method applies its own maximum and a page\n   *         shorter than `limit` is not the end of the data — `tasks.task.list` answers 50\n   *         however much you ask for, measured with 60 rows available. This walker is\n   *         cap-tolerant; hand-rolled paging on `call.make` is not. On the build measured, a\n   *         `limit` of `0` or a non-numeric one was refused with\n   *         `INVALIDPAGINATIONEXCEPTION` and a negative one answered a bare 500 — one\n   *         method on one on-premise build, so treat the codes as what to expect rather\n   *         than a contract.\n   *     - `initialValue?: number | string` - Cursor start value for the first page. Default is `0`\n   *         (valid for ascending numeric fields); required for `DESC` and for non-numeric fields.\n   *\n   * @returns {AsyncGenerator<T[]>} An async generator that yields chunks of data as arrays of type `T`.\n   *\n   * @example\n   * const generator = b24.actions.v3.fetchTail.make<{ id: string }>({\n   *   method: 'main.eventlog.tail',\n   *   params: { select: ['id', 'auditTypeId'] },\n   *   cursorField: 'id',\n   *   customKeyForResult: 'items'\n   * })\n   * for await (const chunk of generator) {\n   *   console.log(`Processing ${chunk.length} items`)\n   * }\n   */\n  public override async* make<T = unknown>(options: ActionFetchTailV3): AsyncGenerator<T[]> {\n    const batchSize = options?.limit ?? 50\n    const cursorField = options?.cursorField ?? 'id'\n    const order = options?.order ?? 'ASC'\n    const customKeyForResult = options?.customKeyForResult ?? 'items'\n    const params = options?.params ?? {}\n\n    assertTailFilter(params['filter'], 'fetchTail.make')\n\n    // DESC keyset needs an explicit start: the server pages by `field < value`,\n    // so the default first-page value 0 would match nothing for a non-negative\n    // field. Require `initialValue` (the type maximum / newest value) for DESC.\n    if (DESC_ORDER.test(order) && options?.initialValue === undefined) {\n      throw new SdkError({\n        code: 'JSSDK_CORE_B24_FETCH_TAIL_DESC_REQUIRES_INITIAL_VALUE',\n        description: 'fetchTail.make: order \"DESC\" requires an explicit `initialValue` (the server pages by `field < value`, so the default 0 returns nothing). Pass `initialValue` set to the type maximum / newest value.',\n        status: 500\n      })\n    }\n\n    // The cursor field cannot also live in `filter` — the server forces ordering\n    // and the `> value` condition on it and rejects a duplicate with\n    // INVALIDFILTEREXCEPTION. Warn instead of letting the server 400. The scan\n    // descends into logic groups: it used to look only at a top-level array of\n    // triples, which stopped seeing anything at all once a bare group became a\n    // legal filter here, and had never seen a group nested inside the array\n    // form.\n    if (filterMentionsField(params['filter'], cursorField)) {\n      this._logger.warning(`fetchTail.make: the cursor field \"${cursorField}\" must not appear in \\`filter\\` — the server orders and pages by it and will reject a filter on the same field (INVALIDFILTEREXCEPTION). Remove it from \\`filter\\`.`).catch(() => {})\n    }\n\n    // The cursor field must be readable in the response to advance. Append it to\n    // an explicit `select`; when `select` is omitted we rely on the server's\n    // default field set — warn for a non-default cursorField, which may not be\n    // in those defaults (the per-page guard below would otherwise stop silently).\n    let select = params['select'] as string[] | undefined\n    if (Array.isArray(select)) {\n      if (!select.includes(cursorField)) {\n        select = [...select, cursorField]\n      }\n    } else if (cursorField !== 'id') {\n      this._logger.warning(`fetchTail.make: no \\`select\\` provided with a non-default cursorField \"${cursorField}\" — make sure it is in the server's default field set, otherwise pass \\`select\\` including \"${cursorField}\" so the cursor can advance.`).catch(() => {})\n    }\n\n    const { select: _ignoredSelect, ...restParams } = params as TypeCallParams\n\n    try {\n      yield* keysetPaginate<T>(this._b24, this._logger, {\n        method: options.method,\n        requestId: options.requestId,\n        customKeyForResult,\n        initialCursor: options?.initialValue ?? 0,\n        // Native keyset: drive the server's `cursor: { field, value, order, limit }`.\n        buildParams: cursor => ({\n          ...restParams,\n          ...(select ? { select } : {}),\n          cursor: { field: cursorField, value: cursor, order, limit: batchSize }\n        }),\n        // Advance by the raw cursor-field value from the last item; a missing\n        // value (cursorField not selected / wrong name) stops the walk.\n        readNextCursor: lastItem => lastItem[cursorField] ?? null,\n        noCursorWarning: `fetchTail.make: pagination stops here — no value could be read from the returned items via cursorField \"${cursorField}\". Make sure cursorField matches a field present in the response (and in \\`select\\`).`,\n        errorLabel: 'fetchTailMethod',\n        actionLabel: 'fetchTail.make',\n        stalledCursorHint: CURSOR_STALLED_HINT_TAIL,\n        // The caller's own `order`, normalised: the server pages by\n        // `field > value` for ASC and `field < value` for DESC, so which way the\n        // cursor must move is their choice rather than a constant here.\n        cursorDirection: resolveCursorDirection(order),\n        maxPages: options?.maxPages,\n        signal: options?.signal\n      })\n    } catch (error) {\n      if (error instanceof KeysetPaginationError) {\n        throw new SdkError({\n          code: 'JSSDK_CORE_B24_FETCH_TAIL_METHOD_API_V3',\n          description: `API Error: ${error.messages.join('; ')}`,\n          status: 500\n        })\n      }\n      throw error\n    }\n  }\n}\n"],"names":[],"mappings":"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"}