UNPKG

@bitrix24/b24jssdk

Version:

Bitrix24 REST API JavaScript SDK

1 lines 14.8 kB
{"version":3,"file":"ajax-result.mjs","sources":["../../../../src/core/http/ajax-result.ts"],"sourcesContent":["import type { IResult } from '../result'\nimport type { Payload, SuccessPayload } from '../../types/payloads'\nimport type { ValidationDetail } from './parse-error-payload'\nimport { parseErrorPayload } from './parse-error-payload'\nimport type { TypeCallParams, TypeHttp } from '../../types/http'\nimport { Type } from '../../tools/type'\nimport { Text } from '../../tools/text'\nimport { Result } from '../result'\nimport { AjaxError } from './ajax-error'\nimport { ApiVersion } from '../../types/b24'\nimport { SdkError } from '../sdk-error'\n\nexport type AjaxQuery = Readonly<{\n method: string\n params: TypeCallParams\n requestId: string\n}>\n\ntype AjaxResultOptions<T> = Readonly<{\n answer: Payload<T>\n query: AjaxQuery\n status: number\n /**\n * An error the caller has already built, used instead of deriving one from\n * `answer`.\n *\n * For the soft-error path in `AbstractHttp`, which has parsed the portal's\n * body, built an `AjaxError` from it, and then needs a `Result` to hand back.\n * Re-deriving there parses the same body twice — and the second pass folds the\n * validation messages onto a description that already contains them, so the\n * text came out doubled (#423). Carrying the error is also simply more honest:\n * it is the error, not a reconstruction of one.\n */\n error?: AjaxError\n}>\n\ntype ErrorData = {\n code: string\n description: string\n status: number\n validation?: readonly ValidationDetail[]\n}\n\n/**\n * Typed result wrapper for a single Bitrix24 REST API response.\n *\n * Extends {@link Result} with the raw HTTP status, the originating query\n * (method, params, requestId), and the deserialized payload. On construction\n * it inspects the payload for API-level error fields and populates the\n * inherited error collection, so callers can branch on {@link isSuccess}\n * without inspecting raw HTTP status codes.\n */\nexport class AjaxResult<T = unknown> extends Result<Payload<T>> implements IResult<Payload<T>> {\n private readonly _status: number\n private readonly _query: AjaxQuery\n protected override _data: Payload<T> | null | undefined\n\n constructor(options: AjaxResultOptions<T>) {\n super()\n\n this._data = options.answer ? Object.freeze(options.answer) : undefined\n this._query = Object.freeze(structuredClone(options.query))\n this._status = options.status\n\n if (options.error) {\n this.addError(options.error, 'base-error')\n } else {\n this.#processErrors()\n }\n }\n\n override get isSuccess(): boolean {\n return this.#getIsSuccess()\n }\n\n /**\n * @todo test this predicate\n */\n #getIsSuccess(): this is { getData: () => SuccessPayload<T> } {\n return this._errors.size === 0\n }\n\n override getData(): undefined | SuccessPayload<T> {\n if (!this.isSuccess) {\n return undefined\n }\n\n const payload = this._data as SuccessPayload<T>\n\n return Object.freeze({\n result: payload.result,\n time: payload.time\n }) as SuccessPayload<T>\n }\n\n /**\n * If the response contains error data, we'll restore it to an error.\n *\n * The parsing lives in {@link parseErrorPayload}, shared with\n * `AbstractHttp._convertAxiosErrorToAjaxError()`. It used to be written out\n * here as well, and the two copies had drifted into agreeing on everything\n * except that neither read `validation[].field` (#423).\n */\n #processErrors(): void {\n const parsed = parseErrorPayload(this._data, 'JSSDK_RESPONSE_ERROR', 'Some error in response')\n if (parsed === undefined) {\n return\n }\n\n this.addError(this.#createAjaxError({\n code: parsed.code,\n description: parsed.description,\n status: this._status,\n validation: parsed.validation\n }), 'base-error')\n }\n\n #createAjaxError(errorData: ErrorData): AjaxError {\n return new AjaxError({\n code: errorData.code,\n description: errorData.description,\n status: errorData.status,\n validation: errorData.validation,\n requestInfo: {\n method: this._query.method,\n params: this._query.params,\n requestId: this._query.requestId\n }\n })\n }\n\n /**\n * Alias for {@link AjaxResult.isMore}.\n *\n * `restApi:v2` only — see {@link AjaxResult.isMore} for what this returns on\n * a `restApi:v3` response.\n */\n hasMore(): boolean {\n return this.isMore()\n }\n\n /**\n * Whether the `restApi:v2` envelope carries a `next` offset — i.e. the portal\n * has more rows for this query.\n *\n * **`restApi:v2` only.** `restApi:v3` returns no `next` field, so this returns\n * `false` on a v3 response — which is not the same statement as \"there are no\n * more rows\". Do not branch on it for v3; there is nothing to read.\n *\n * This is a reader for a protocol field, not a deprecated API: it stays for as\n * long as `restApi:v2` does, and so does its counterpart\n * {@link AjaxResult.getNext} — the two together are the manual `restApi:v2`\n * paging loop, and neither is going away. For new code prefer the list\n * helpers, which hide the offset bookkeeping and work under both protocol\n * versions:\n * - `restApi:v2`: `b24.actions.v2.callList.make` or `b24.actions.v2.fetchList.make`\n * - `restApi:v3`: `b24.actions.v3.callList.make` or `b24.actions.v3.fetchList.make`\n */\n isMore(): boolean {\n if (!this.isSuccess) {\n return false\n }\n const payload = this._data as { next?: number }\n const nextValue = 'next' in payload ? payload.next : undefined\n\n return Type.isNumber(nextValue)\n }\n\n /**\n * The row count the `restApi:v2` envelope reports in its `total` field.\n *\n * **`restApi:v2` only.** `restApi:v3` returns no `total`, so this returns `0`\n * on a v3 response — which is not the same statement as \"no rows matched\".\n * Do not read it for v3; use\n * `b24.actions.v3.aggregate.make` with `count` / `countDistinct` instead,\n * bearing in mind that action is `@experimental` and unverified against a live\n * portal.\n *\n * This is a reader for a protocol field, not a deprecated API. It is the only\n * way to obtain a count under `restApi:v2` — the list helpers iterate without\n * exposing `total`, {@link SuccessPayload} deliberately omits it, and the\n * `aggregate` action exists for `restApi:v3` only. It therefore stays for as\n * long as `restApi:v2` does, and is not part of the `3.0.0` removal set.\n *\n * That is a decision with a trigger, not an open-ended promise. Revisit it\n * when either holds: `b24.actions.v3.aggregate` is verified against a live\n * portal and loses its `@experimental` tag across the common modules (a v3\n * count then exists, and `getTotal()` has a replacement for the first time),\n * or Bitrix24 announces a `restApi:v2` sunset date (the field it reads goes\n * away regardless). Until one of those happens there is nothing to migrate\n * callers to, which is the whole reason it is still here.\n *\n * Note this trigger is specific to the readers. {@link AjaxResult.getNext} and\n * {@link AjaxResult.fetchNext} already have a working replacement, so nothing\n * about `aggregate` maturing changes anything for them — a `restApi:v2` sunset\n * is their only exit condition.\n */\n getTotal(): number {\n if (!this.isSuccess) {\n return 0\n }\n const payload = this._data as { total?: number }\n const totalValue = 'total' in payload ? payload.total : undefined\n\n return Text.toInteger(totalValue)\n }\n\n getStatus(): number {\n return this._status\n }\n\n getQuery(): Readonly<AjaxQuery> {\n return this._query\n }\n\n /**\n * Alias for {@link AjaxResult.getNext}, returning `null` where that returns\n * `false`.\n *\n * **`restApi:v2` only** — see {@link AjaxResult.getNext}, including the throw\n * on a `restApi:v3` client, which this inherits.\n */\n async fetchNext(http: TypeHttp): Promise<AjaxResult<T> | null> {\n const data = await this.getNext(http)\n if (data === false) {\n return null\n }\n\n return data\n }\n\n /**\n * Re-runs this result's own query with `params.start` set to the `next` offset\n * the `restApi:v2` envelope reported, and resolves to the following page.\n * Returns `false` when this result is unsuccessful or has no `next`.\n *\n * `restApi:v2` only, and permanently so. Unlike the readers above, this one\n * acts on the envelope, and `restApi:v3` has no `next` to act on — so it\n * throws rather than silently returning `false`, which would be\n * indistinguishable from \"last page\". That throw is not a transitional\n * measure; it is the honest answer for a protocol that does not have this\n * operation.\n *\n * For new code prefer `b24.actions.v{2,3}.callList.make` (collect everything)\n * or `b24.actions.v{2,3}.fetchList.make` (async generator, one page per\n * iteration — the same page-by-page control this gives, without the manual\n * offset bookkeeping, and it works under both protocol versions). This method\n * is kept because it works under `restApi:v2` and deleting it would break\n * running code for no gain, not because it is the better tool.\n *\n * @throws {SdkError} `JSSDK_CORE_METHOD_NOT_SUPPORT_IN_API_V3` when called against a `restApi:v3` HTTP client.\n */\n async getNext(http: TypeHttp): Promise<AjaxResult<T> | false> {\n if (http.apiVersion === ApiVersion.v3) {\n throw new SdkError({\n code: 'JSSDK_CORE_METHOD_NOT_SUPPORT_IN_API_V3',\n description: `restApi:v3 not support method getNext`,\n status: 500\n })\n }\n if (\n !this.isSuccess\n || !this.isMore()\n ) {\n return false\n }\n\n const nextPageQuery = this.#buildNextPageQuery()\n return http.call<T>(\n nextPageQuery.method,\n nextPageQuery.params\n )\n }\n\n #buildNextPageQuery(): AjaxQuery {\n const payload = this._data as { next?: number }\n const nextValue = 'next' in payload ? payload.next : undefined\n\n // Fresh params object — the previous shallow `{ ...this._query }` shared the\n // params reference and wrote `start` back into the frozen _query, so the\n // previous result's getQuery().params silently changed after getNext() (#144).\n //\n // `requestId` comes along with the spread but is never used: getNext() reads\n // only `.method` and `.params`, and the http client mints a fresh id per\n // request. Reusing this page's id for the next page would be wrong anyway.\n return {\n ...this._query,\n params: { ...this._query.params, start: Text.toInteger(nextValue) }\n }\n }\n\n // Immutable API\n override setData(): never {\n throw new ReferenceError('AjaxResult does not allow data modification')\n }\n}\n"],"names":[],"mappings":"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"}