@bitrix24/b24jssdk
Version:
Bitrix24 REST API JavaScript SDK
1 lines • 7.36 kB
Source Map (JSON)
{"version":3,"file":"_cursor-stalled.mjs","sources":["../../../../src/core/actions/_cursor-stalled.ts"],"sourcesContent":["import { SdkError } from '../sdk-error'\n\n/**\n * The error every keyset walk raises when its cursor stops advancing.\n *\n * A keyset walk reads a cursor out of the page it just received and sends it\n * back on the next request. If the value that comes back is the one already\n * sent, the walk cannot make progress — the server is answering with the same\n * page — and **nothing else in any of these loops notices**: the page is full,\n * so the end-of-data checks stay silent, and the walk runs until the eager\n * helpers exhaust memory or the streaming ones yield the same rows for ever.\n *\n * Shared by both API versions on purpose. The v3 helpers drive a common\n * generator (`actions/v3/_keyset-paginate.ts`); the v2 ones each carry their own\n * inline loop. Three insertion points, one failure, one code — so a caller\n * matching on the string does not have to know which loop they were in, and the\n * error page documents it once.\n *\n * **`status: 500`, not 400.** The neighbouring client-side guards use 400\n * because they decide from the arguments alone, before a request is sent. This\n * one cannot: an identical cursor means the page condition was not applied, and\n * from here there is no way to tell a caller who named the wrong field from a\n * portal that ignores the condition on this method. Reporting a caller error\n * would be a guess.\n *\n * **It throws rather than folding into the `Result`.** The eager helpers catch\n * their soft-error wrapper and return what they collected; an `SdkError` goes\n * past that, so `callList` / `callTail` reject instead of resolving. What makes\n * that right is what the collected rows are worth: for the list walkers the page\n * condition is either honoured from the first request or dropped from the first\n * request, so every row held at that point is page one repeated, and returning\n * it would hand back duplicates that read as data. For the tail walkers a stall\n * can begin after real pages, and those are lost — the honest cost of refusing a\n * result that is both incomplete and duplicated with no way to tell which rows\n * are which. A caller who wants the pages that did arrive should walk with\n * `fetchList` / `fetchTail`, which yield each page before this throws.\n *\n * @param action - Caller-facing label, e.g. `callList.make` — the same wording\n * the filter guards use, so both errors from one action read alike.\n * @param hint - What to check, in the vocabulary of *that* action. The list\n * walkers expose `idKey` / `cursorIdKey`; the tail walkers expose\n * `cursorField`, and telling a `callTail` caller to set `cursorIdKey` would\n * send them after an option their action does not have.\n */\nexport function cursorStalledError(action: string, hint: string): SdkError {\n // Static text plus the two labels, both literals chosen by the action. No\n // cursor value, filter, field value or method name is interpolated: unlike\n // AjaxError, SdkError does NOT run its description through\n // `redactSensitiveParams`, and a cursor is a field value read off the\n // response.\n return new SdkError({\n code: 'JSSDK_ACTION_CURSOR_STALLED',\n description: `${action}: the cursor did not move — the server answered with the same page again, so this walk can never finish. ${hint} `\n + `Stopping instead of looping for ever. Note that a streaming helper (fetchList / fetchTail) has already yielded every page it read, including the repeated one, so a consumer that persisted them has to undo that.`,\n status: 500\n })\n}\n\n/** What to check when an emulated-keyset **list** walk stalls. */\nexport const CURSOR_STALLED_HINT_LIST\n = 'Check `idKey` — the id field as the response spells it — against `cursorIdKey`, the field name the request sorts and filters by. When the two differ, the page condition is written with a name the server does not match, and it is dropped: on `restApi:v2` `tasks.task.list` the response carries a lowercase `id` while the filter accepts an uppercase `ID`, so that walk needs `idKey: \\'id\\', cursorIdKey: \\'ID\\'`. The v3 method spells it lowercase both ways and needs no override.'\n\n/** What to check when a native **tail** walk stalls. */\nexport const CURSOR_STALLED_HINT_TAIL\n = 'Check `cursorField`: it must name the field the server actually pages by, it must be readable in the response (include it in `select`), and its values must advance from page to page — a block of rows sharing one value is enough to stall the walk, so prefer a unique field. With `order: \\'DESC\\'`, check `initialValue` too.'\n\n/**\n * The error a keyset walk raises when its cursor moves the **wrong way**.\n *\n * Separate from {@link cursorStalledError} because the name of a code is a\n * promise: `STALLED` says the cursor did not move, and here it did — backwards,\n * or into a value it had already passed. A caller matching on the string should\n * not have to read the description to find out which of the two happened, and a\n * code that covers both would make `STALLED` untrue of half its uses.\n *\n * What it catches that the stall check cannot: a server alternating between two\n * pages — `A, B, A, B` — never repeats the *immediately preceding* cursor, so\n * the stall check never fires, and the walk runs for ever. Every cycle has to\n * step backwards somewhere; this is that step. It also catches a cursor that\n * moves backwards without cycling at all, which loses rows rather than looping\n * and which nothing looked for before (#495).\n *\n * Same `status: 500` and the same throw-rather-than-fold reasoning as its\n * sibling — see the note there, which applies unchanged.\n */\nexport function cursorWentBackwardsError(action: string, hint: string): SdkError {\n // Static text plus the two action-chosen labels. No cursor value: `SdkError`\n // does not run its description through `redactSensitiveParams`, and a cursor\n // is a field value read off the response.\n return new SdkError({\n code: 'JSSDK_ACTION_CURSOR_WENT_BACKWARDS',\n description: `${action}: the cursor moved backwards — the server answered with a page it had already passed, so this walk can never finish. ${hint} `\n + `A server that alternates between two pages produces exactly this, and the \"did not move\" check cannot see it: the value differs from the one just sent, it is simply one the walk had already used. `\n + `Stopping instead of looping for ever. Note that a streaming helper (fetchList / fetchTail) has already yielded every page it read, so a consumer that persisted them has to undo that.`,\n status: 500\n })\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AA4CO,SAAS,kBAAA,CAAmB,QAAgB,IAAA,EAAwB;AAMzE,EAAA,OAAO,IAAI,QAAA,CAAS;AAAA,IAClB,IAAA,EAAM,6BAAA;AAAA,IACN,WAAA,EAAa,CAAA,EAAG,MAAM,CAAA,8GAAA,EAA4G,IAAI,CAAA,mNAAA,CAAA;AAAA,IAEtI,MAAA,EAAQ;AAAA,GACT,CAAA;AACH;AAZgB,MAAA,CAAA,kBAAA,EAAA,oBAAA,CAAA;AAeT,MAAM,wBAAA,GACT;AAGG,MAAM,wBAAA,GACT;AAqBG,SAAS,wBAAA,CAAyB,QAAgB,IAAA,EAAwB;AAI/E,EAAA,OAAO,IAAI,QAAA,CAAS;AAAA,IAClB,IAAA,EAAM,oCAAA;AAAA,IACN,WAAA,EAAa,CAAA,EAAG,MAAM,CAAA,0HAAA,EAAwH,IAAI,CAAA,2XAAA,CAAA;AAAA,IAGlJ,MAAA,EAAQ;AAAA,GACT,CAAA;AACH;AAXgB,MAAA,CAAA,wBAAA,EAAA,0BAAA,CAAA;;;;"}