@bitrix24/b24jssdk
Version:
Bitrix24 REST API JavaScript SDK
1 lines • 28.3 kB
Source Map (JSON)
{"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'\nimport { assertNotAborted, maxPagesExceededError, resolveMaxPages } from '../_walk-bounds'\nimport { cursorStalledError, cursorWentBackwardsError } from '../_cursor-stalled'\nimport { cursorProgressed } from '../_cursor-progress'\nimport type { CursorDirection } from '../_cursor-progress'\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** use this one — see {@link assertTailFilter}.\n * They forward `filter` untouched, so the shapes they can accept are decided by\n * the portal rather than by this mechanism.\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: isPortalFilterGroup(filter)\n ? `${action}: \\`filter\\` must be an array here. A logic group is a valid v3 filter — the portal accepts one on its own — but this action appends its page condition to \\`filter\\` on every request, and only an array can be extended. Wrap it: filter: [FilterV3.or(...)].`\n : `${action}: \\`filter\\` must be the restApi:v3 array form, e.g. [['id', '>', 100]] or FilterV3.build(...). `\n + `The restApi:v2 object dialect ({ '>id': 100 }) is not accepted by the portal: a filter element carrying none of type/logic/conditions/negative is read positionally, so a map keyed by an operator prefix matches no condition it knows.`,\n status: 500\n })\n}\n\n/**\n * The keys `FilterStructure::fillStructure()` looks for before it falls through\n * to positional parsing.\n */\nconst PORTAL_GROUP_KEYS = ['type', 'logic', 'conditions', 'negative'] as const\n\n/**\n * Would the portal read this object as a **group** rather than positionally?\n *\n * Deliberately not `isGroup` from `tools/filter-v3.ts`, which asks a different\n * question: that one validates what the *builder* produces and so requires\n * `conditions`. This one predicts what the *portal* will do, and the portal\n * checks all four of `type` / `logic` / `conditions` / `negative` — any one of\n * them takes the group branch. Testing only `conditions` would make the guard\n * stricter than the server it is standing in for, and reject `{ type: … }`,\n * which the portal accepts. A false rejection is worse than the round trip this\n * guard saves, because there is no way for the caller to get past it.\n */\nfunction isPortalFilterGroup(value: unknown): boolean {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false\n }\n\n return PORTAL_GROUP_KEYS.some(key => key in value)\n}\n\n/**\n * Does `filter` mention `field` anywhere — including inside a logic group?\n *\n * The tail walkers warn when the cursor field also appears in `filter`, because\n * the server orders and pages by it and rejects a filter on the same field.\n * That check used to be written inline as \"a top-level array containing a\n * triple whose first element is the field\", which stopped seeing anything the\n * moment a bare group became a legal filter — and had never seen a group nested\n * inside the array form either. Both are exactly where a caller puts a\n * condition once they reach for `FilterV3.or()`.\n *\n * Two bounds, because a filter is ordinary JavaScript rather than parsed JSON\n * and so can be shaped in ways `JSON.parse` could never produce. They answer\n * different questions and neither replaces the other:\n *\n * - **`depth`** stops a legitimately deep nesting. The limit is far past any\n * filter anyone writes; reaching it means the shape is pathological, and\n * answering \"no\" there is right — the caller loses a warning, not a request.\n * - **`seen`** stops the same node being explored twice over. Depth alone bounds\n * the distance the walk travels, not the work it does, and those come apart as\n * soon as one node is reachable by two edges: `const a = []; a.push(a, a)`\n * gives `T(d) = 1 + 2·T(d − 1)`, and a plain DAG built without any cycle at all\n * — `let n = [['x', '=', 1]]; for (let i = 0; i < 32; i++) n = [n, n]` — does\n * the same. Measured at the shipped depth of 32, the two-edge cycle took\n * **85 seconds** of blocked event loop inside a check whose only output is a\n * warning. With `seen` it returns immediately.\n *\n * `seen` records **the depth each node was explored with**, not merely that it\n * was seen, and a cached `false` is trusted only when that visit had at least as\n * much budget as this one. The distinction is the whole correctness of the memo,\n * because a `false` means one of two different things: \"this subtree does not\n * mention the field\", or \"the budget ran out before we could tell\". Storing them\n * as one answer loses a real `true`, and not only in a corner:\n *\n * ```js\n * const a = [['x', '=', 1]]\n * let chain = a\n * for (let i = 0; i < 30; i++) { chain = [chain] }\n * filterMentionsField([chain, a], 'x') // must be true\n * ```\n *\n * The long path reaches `a` with one level of budget left, cannot look inside it\n * and answers `false`; the short path meets `a` again with 31 levels to spare. A\n * memo that recorded only \"visited\" would hand back that truncated `false` — and\n * `[a, chain]`, the same objects in the other order, would answer `true`. The\n * depth-keyed memo answers `true` either way, and still collapses the shapes\n * above, where every revisit happens at the same depth or shallower.\n */\nconst MAX_FILTER_DEPTH = 32\n\nexport function filterMentionsField(filter: unknown, field: string, depth = MAX_FILTER_DEPTH): boolean {\n return walkFilterForField(filter, field, depth, new WeakMap())\n}\n\nfunction walkFilterForField(\n filter: unknown,\n field: string,\n depth: number,\n seen: WeakMap<object, number>\n): boolean {\n if (depth <= 0) {\n return false\n }\n\n if (typeof filter === 'object' && filter !== null) {\n // Trust a cached `false` only if that visit had at least this much budget.\n // A node explored with one level left would otherwise poison a later visit\n // that had thirty — see the worked example above.\n const exploredWith = seen.get(filter)\n if (exploredWith !== undefined && exploredWith >= depth) {\n return false\n }\n seen.set(filter, depth)\n }\n\n if (Array.isArray(filter)) {\n // A triple is an array too — `['id', '>', 100]` — and it is the only array\n // whose first element is a string, because every element of a filter array\n // is itself a triple or a group. So this branch fires only once we are\n // already inside a triple, and stopping here is deliberate: the remaining\n // two elements are the operator and the value, and matching a field name\n // against those would report `['severity', '=', 'id']` as a filter on `id`.\n if (typeof filter[0] === 'string') {\n return filter[0] === field\n }\n return filter.some(node => walkFilterForField(node, field, depth - 1, seen))\n }\n\n if (typeof filter === 'object' && filter !== null && 'conditions' in filter) {\n return walkFilterForField((filter as { conditions: unknown }).conditions, field, depth - 1, seen)\n }\n\n return false\n}\n\n/**\n * Reject the `restApi:v2` object dialect for the **native tail** walkers.\n *\n * Narrower than {@link assertArrayFilter} on purpose, because the mechanism is\n * different: `callTail` / `fetchTail` paginate through the separate `cursor`\n * parameter and forward `filter` verbatim, so nothing here needs to extend it.\n * What the portal accepts is therefore the only constraint, and it accepts more\n * than an array.\n *\n * Measured on an on-premise build (`SM_VERSION 26.700.0`), `main.eventlog.list`:\n *\n * | `filter` | portal |\n * | --- | --- |\n * | `[['auditTypeId', '=', 'USER_LOGIN']]` | accepted |\n * | `[{ logic: 'or', conditions: [...] }]` | accepted |\n * | `{ logic: 'or', conditions: [...] }` — bare group | **accepted** |\n * | `{ '>id': 1 }` — the v2 dialect | rejected, \"Unknown filter condition\" |\n *\n * On the same method and the same build, `select` turned out **not** to be\n * required: an empty body answered 200 with 44 items, with or without\n * `pagination`. #465 reported that shape answering HTTP 500 and asked for it to\n * be documented as a rule; it did not reproduce, so it is recorded here rather\n * than written into the docs as one. If it is ever seen again, this note is the\n * thing to correct.\n *\n * The grammar explains the split: `FilterStructure::fillStructure()` reads\n * `type` / `logic` / `conditions` / `negative` off a map and only falls through\n * to the positional `handleSimpleCondition()` — which dispatches on `count()`\n * and reads `[0]`, `[1]`, `[2]` — when none of them is present. So a group is a\n * shape the portal knows, and an operator-prefix map is not: there the operator\n * is a position, not a prefix on the field name as it was in v2.\n *\n * A bare group is thus left alone. Guarding it would reject what\n * {@link FilterV3.or} produces and what the portal answers to, which is a worse\n * outcome than the round trip this guard saves.\n *\n * @throws {SdkError} `JSSDK_ACTION_V3_TAIL_FILTER_INVALID`\n */\nexport function assertTailFilter(filter: unknown, action: string): void {\n if (filter === undefined || Array.isArray(filter) || isPortalFilterGroup(filter)) {\n return\n }\n\n // A code of its own rather than the list one: there `NOT_ARRAY` states the\n // condition exactly, here a non-array is valid and only the v2 dialect is\n // refused. Codes are documented as stable strings to match on, so one whose\n // name asserts something untrue of half its call sites is a cost paid by\n // whoever reads it and not by us.\n throw new SdkError({\n code: 'JSSDK_ACTION_V3_TAIL_FILTER_INVALID',\n description: `${action}: \\`filter\\` must be the restApi:v3 array form, e.g. [['id', '>', 100]] or FilterV3.build(...), or a logic group from FilterV3.or() / and() / not(). `\n + `The restApi:v2 object dialect ({ '>id': 100 }) is not accepted by the portal: a filter element carrying none of type/logic/conditions/negative is read positionally, so a map keyed by an operator prefix matches no condition it knows. `\n + `Rejected here rather than one round trip later, where the server reports it in wording that never mentions the dialect.`,\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 * Caller-facing name of the action, e.g. `callList.make` — the wording the\n * filter guards already use, so both errors from one action read alike.\n * Separate from {@link KeysetPaginateStrategy.errorLabel}, which is an\n * internal log label (`callFastListMethod`) and does not belong in a message\n * the caller reads.\n */\n actionLabel: string\n /**\n * What to check when the cursor stops advancing, in the vocabulary of *this*\n * action: the list walkers expose `idKey` / `cursorIdKey`, the tail walkers\n * expose `cursorField`. See `actions/_cursor-stalled.ts` for the two texts.\n */\n stalledCursorHint: string\n /**\n * Which way this walk paginates, so the loop can tell a cursor that advanced\n * from one that went backwards.\n *\n * The list walkers are always `ASC` — they append `[cursorIdKey, '>', cursor]`\n * — while the tail walkers take the caller's `order`. See\n * `actions/_cursor-progress.ts` for what the direction buys.\n */\n cursorDirection: CursorDirection\n /** Page ceiling; see `_walk-bounds.ts`. */\n maxPages?: number\n /** Cancellation; see `_walk-bounds.ts`. */\n signal?: AbortSignal\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 *\n * A cursor that does not carry the walk forward is a different case and throws\n * `SdkError` directly, past that fold — see the guards below for why.\n *\n * @throws {KeysetPaginationError} when the underlying `call` reports a soft error\n * @throws {SdkError} `JSSDK_ACTION_CURSOR_STALLED` when the cursor comes back\n * equal to the one just sent\n * @throws {SdkError} `JSSDK_ACTION_CURSOR_WENT_BACKWARDS` when it moves against\n * the walk's own direction\n * @throws {SdkError} `JSSDK_ACTION_MAX_PAGES_EXCEEDED` when the walk reaches its\n * page ceiling, and `JSSDK_ACTION_ABORTED` when its `signal` fires\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 let pages = 0\n const maxPages = resolveMaxPages(strategy.actionLabel, strategy.maxPages)\n\n while (true) {\n // Before the request, so an already-aborted signal costs no round trip.\n assertNotAborted(strategy.signal, strategy.actionLabel, strategy.method)\n\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 pages += 1\n yield resultData\n // Again after the page has been handed over. The gap between `yield` and\n // the top of the loop is not empty - the stall guard and the ceiling both\n // sit in it - so a consumer that aborts while holding a page would\n // otherwise be told the read is too large, or that the cursor stalled,\n // when what actually happened is that they cancelled. No request is saved\n // by this check; the correct diagnosis is.\n assertNotAborted(strategy.signal, strategy.actionLabel, strategy.method)\n\n maxPageSize = Math.max(maxPageSize, resultData.length)\n // Read rather than acted on: the cursor is inspected first, and only then\n // does a short page end the walk. See the note below the cursor checks.\n const isShortPage = resultData.length < maxPageSize\n\n const lastItem = resultData[resultData.length - 1] as Record<string, any>\n const next = lastItem ? strategy.readNextCursor(lastItem) : null\n\n // `readNextCursor` is declared to return `number | string | null`, but the\n // tail walkers read the value straight off the response — `lastItem[\n // cursorField]`, typed `any` — so nothing narrows it at runtime. A\n // `cursorField` naming an object- or array-valued field (ordinary in a v3\n // response) then yields a **fresh reference on every page**, which the stall\n // check below cannot see: `===` between two distinct objects is never true,\n // so the walk this guard exists to stop would run forever anyway.\n //\n // A non-primitive is therefore treated as no cursor at all and takes the\n // same warning-and-stop path as an unreadable one — in both cases\n // `cursorField` does not name a scalar, which is exactly what the warning\n // already tells the caller. This subsumes the `null` / `undefined` check it\n // replaces; the list walkers hand back `null` for an unparsable id and keep\n // the behaviour they had.\n if (typeof next !== 'number' && typeof next !== 'string') {\n // Silent when the page is short: the walk is ending here anyway, and a\n // warning about an unreadable cursor on the page that proves end-of-data\n // would fire at the close of every successful walk whose last page is\n // shaped differently. Only a full page needs the cursor.\n if (!isShortPage) {\n logger.warning(strategy.noCursorWarning).catch(() => {})\n }\n break\n }\n\n // Both cursor checks run **before** the short-page stop, and that ordering is\n // the fix for #496 rather than an accident. A page shorter than the largest\n // seen used to end the walk as \"end of data\" whatever the cursor did — so a\n // stalled page that happened to be short returned a truncated, overlapping\n // result and reported success. The two cases are separable after all: the\n // page condition asks for rows strictly past the cursor, so a row at or\n // before it cannot be in an answer that honoured the condition, however\n // short the page. A short page whose cursor did not move is a server\n // ignoring the condition, not the data running out.\n //\n // `===` rather than `==`, so no genuinely different value is read as a stall\n // — `0 == ''` and `0 == false` are true, and stopping on one of those would\n // be stopping on a lie. The cost is one extra request when a server changes\n // only the type of an identical cursor (`100` → `'100'`): that reads as\n // movement, and the guard fires on the next page once the type settles.\n if (next === cursor) {\n throw cursorStalledError(strategy.actionLabel, strategy.stalledCursorHint)\n }\n\n // …and a cursor that moved, but the wrong way. A server alternating between\n // two pages never repeats the immediately preceding value, so the check\n // above never fires and the walk runs for ever (#495). Every cycle steps\n // backwards somewhere, and this is that step — for the price of a comparison\n // rather than the set of every cursor seen that deferred the issue.\n if (!cursorProgressed(next, cursor, strategy.cursorDirection)) {\n throw cursorWentBackwardsError(strategy.actionLabel, strategy.stalledCursorHint)\n }\n\n // End of data, now that the cursor has been vouched for.\n if (isShortPage) {\n break\n }\n\n // Last, so every cheaper stop wins: a stalled cursor is still reported\n // as a stall - the more specific diagnosis - instead of surfacing as \"too\n // many pages\" 10 000 requests later.\n //\n // A walk that ends exactly on its ceiling finishes only when its final page\n // is *short*: that is what proves end-of-data. When the row count is an\n // exact multiple of the page size the last page is full, nothing has proved\n // the data ended, and the ceiling fires - so `maxPages: 10` over exactly 500\n // rows at 50 a page reports the ceiling. It cannot be otherwise without\n // spending a request the caller's ceiling did not allow. The eager walkers\n // hand back every row they read, so nothing is lost to it.\n if (pages >= maxPages) {\n throw maxPagesExceededError(strategy.actionLabel, strategy.method, maxPages)\n }\n\n cursor = next\n }\n}\n"],"names":[],"mappings":"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"}