@bitrix24/b24jssdk
Version:
Bitrix24 REST API JavaScript SDK
234 lines (231 loc) • 8.39 kB
JavaScript
/**
* @package @bitrix24/b24jssdk
* @version 2.2.0
* @copyright (c) 2026 Bitrix24
* @license MIT
* @see https://github.com/bitrix24/b24jssdk
* @see https://bitrix24.github.io/b24jssdk/
*/
import { parseErrorPayload } from './parse-error-payload.mjs';
import { Type } from '../../tools/type.mjs';
import { Text } from '../../tools/text.mjs';
import { Result } from '../result.mjs';
import { AjaxError } from './ajax-error.mjs';
import { ApiVersion } from '../../types/b24.mjs';
import { SdkError } from '../sdk-error.mjs';
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
class AjaxResult extends Result {
static {
__name(this, "AjaxResult");
}
_status;
_query;
_data;
constructor(options) {
super();
this._data = options.answer ? Object.freeze(options.answer) : void 0;
this._query = Object.freeze(structuredClone(options.query));
this._status = options.status;
if (options.error) {
this.addError(options.error, "base-error");
} else {
this.#processErrors();
}
}
get isSuccess() {
return this.#getIsSuccess();
}
/**
* @todo test this predicate
*/
#getIsSuccess() {
return this._errors.size === 0;
}
getData() {
if (!this.isSuccess) {
return void 0;
}
const payload = this._data;
return Object.freeze({
result: payload.result,
time: payload.time
});
}
/**
* If the response contains error data, we'll restore it to an error.
*
* The parsing lives in {@link parseErrorPayload}, shared with
* `AbstractHttp._convertAxiosErrorToAjaxError()`. It used to be written out
* here as well, and the two copies had drifted into agreeing on everything
* except that neither read `validation[].field` (#423).
*/
#processErrors() {
const parsed = parseErrorPayload(this._data, "JSSDK_RESPONSE_ERROR", "Some error in response");
if (parsed === void 0) {
return;
}
this.addError(this.#createAjaxError({
code: parsed.code,
description: parsed.description,
status: this._status,
validation: parsed.validation
}), "base-error");
}
#createAjaxError(errorData) {
return new AjaxError({
code: errorData.code,
description: errorData.description,
status: errorData.status,
validation: errorData.validation,
requestInfo: {
method: this._query.method,
params: this._query.params,
requestId: this._query.requestId
}
});
}
/**
* Alias for {@link AjaxResult.isMore}.
*
* `restApi:v2` only — see {@link AjaxResult.isMore} for what this returns on
* a `restApi:v3` response.
*/
hasMore() {
return this.isMore();
}
/**
* Whether the `restApi:v2` envelope carries a `next` offset — i.e. the portal
* has more rows for this query.
*
* **`restApi:v2` only.** `restApi:v3` returns no `next` field, so this returns
* `false` on a v3 response — which is not the same statement as "there are no
* more rows". Do not branch on it for v3; there is nothing to read.
*
* This is a reader for a protocol field, not a deprecated API: it stays for as
* long as `restApi:v2` does, and so does its counterpart
* {@link AjaxResult.getNext} — the two together are the manual `restApi:v2`
* paging loop, and neither is going away. For new code prefer the list
* helpers, which hide the offset bookkeeping and work under both protocol
* versions:
* - `restApi:v2`: `b24.actions.v2.callList.make` or `b24.actions.v2.fetchList.make`
* - `restApi:v3`: `b24.actions.v3.callList.make` or `b24.actions.v3.fetchList.make`
*/
isMore() {
if (!this.isSuccess) {
return false;
}
const payload = this._data;
const nextValue = "next" in payload ? payload.next : void 0;
return Type.isNumber(nextValue);
}
/**
* The row count the `restApi:v2` envelope reports in its `total` field.
*
* **`restApi:v2` only.** `restApi:v3` returns no `total`, so this returns `0`
* on a v3 response — which is not the same statement as "no rows matched".
* Do not read it for v3; use
* `b24.actions.v3.aggregate.make` with `count` / `countDistinct` instead,
* bearing in mind that action is `@experimental` and unverified against a live
* portal.
*
* This is a reader for a protocol field, not a deprecated API. It is the only
* way to obtain a count under `restApi:v2` — the list helpers iterate without
* exposing `total`, {@link SuccessPayload} deliberately omits it, and the
* `aggregate` action exists for `restApi:v3` only. It therefore stays for as
* long as `restApi:v2` does, and is not part of the `3.0.0` removal set.
*
* That is a decision with a trigger, not an open-ended promise. Revisit it
* when either holds: `b24.actions.v3.aggregate` is verified against a live
* portal and loses its `@experimental` tag across the common modules (a v3
* count then exists, and `getTotal()` has a replacement for the first time),
* or Bitrix24 announces a `restApi:v2` sunset date (the field it reads goes
* away regardless). Until one of those happens there is nothing to migrate
* callers to, which is the whole reason it is still here.
*
* Note this trigger is specific to the readers. {@link AjaxResult.getNext} and
* {@link AjaxResult.fetchNext} already have a working replacement, so nothing
* about `aggregate` maturing changes anything for them — a `restApi:v2` sunset
* is their only exit condition.
*/
getTotal() {
if (!this.isSuccess) {
return 0;
}
const payload = this._data;
const totalValue = "total" in payload ? payload.total : void 0;
return Text.toInteger(totalValue);
}
getStatus() {
return this._status;
}
getQuery() {
return this._query;
}
/**
* Alias for {@link AjaxResult.getNext}, returning `null` where that returns
* `false`.
*
* **`restApi:v2` only** — see {@link AjaxResult.getNext}, including the throw
* on a `restApi:v3` client, which this inherits.
*/
async fetchNext(http) {
const data = await this.getNext(http);
if (data === false) {
return null;
}
return data;
}
/**
* Re-runs this result's own query with `params.start` set to the `next` offset
* the `restApi:v2` envelope reported, and resolves to the following page.
* Returns `false` when this result is unsuccessful or has no `next`.
*
* `restApi:v2` only, and permanently so. Unlike the readers above, this one
* acts on the envelope, and `restApi:v3` has no `next` to act on — so it
* throws rather than silently returning `false`, which would be
* indistinguishable from "last page". That throw is not a transitional
* measure; it is the honest answer for a protocol that does not have this
* operation.
*
* For new code prefer `b24.actions.v{2,3}.callList.make` (collect everything)
* or `b24.actions.v{2,3}.fetchList.make` (async generator, one page per
* iteration — the same page-by-page control this gives, without the manual
* offset bookkeeping, and it works under both protocol versions). This method
* is kept because it works under `restApi:v2` and deleting it would break
* running code for no gain, not because it is the better tool.
*
* @throws {SdkError} `JSSDK_CORE_METHOD_NOT_SUPPORT_IN_API_V3` when called against a `restApi:v3` HTTP client.
*/
async getNext(http) {
if (http.apiVersion === ApiVersion.v3) {
throw new SdkError({
code: "JSSDK_CORE_METHOD_NOT_SUPPORT_IN_API_V3",
description: `restApi:v3 not support method getNext`,
status: 500
});
}
if (!this.isSuccess || !this.isMore()) {
return false;
}
const nextPageQuery = this.#buildNextPageQuery();
return http.call(
nextPageQuery.method,
nextPageQuery.params
);
}
#buildNextPageQuery() {
const payload = this._data;
const nextValue = "next" in payload ? payload.next : void 0;
return {
...this._query,
params: { ...this._query.params, start: Text.toInteger(nextValue) }
};
}
// Immutable API
setData() {
throw new ReferenceError("AjaxResult does not allow data modification");
}
}
export { AjaxResult };
//# sourceMappingURL=ajax-result.mjs.map