UNPKG

@bitrix24/b24jssdk

Version:

Bitrix24 REST API JavaScript SDK

1 lines • 12.5 kB
{"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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"}