@bitrix24/b24jssdk
Version:
Bitrix24 REST API JavaScript SDK
154 lines (150 loc) • 8.02 kB
JavaScript
/**
* @package @bitrix24/b24jssdk
* @version 3.0.0
* @copyright (c) 2026 Bitrix24
* @license MIT
* @see https://github.com/bitrix24/b24jssdk
* @see https://bitrix24.github.io/b24jssdk/
*/
;
const _walkBounds = require('../_walk-bounds.cjs');
const abstractAction = require('../abstract-action.cjs');
const result = require('../../result.cjs');
const sdkError = require('../../sdk-error.cjs');
const _keysetPaginate = require('./_keyset-paginate.cjs');
const _cursorStalled = require('../_cursor-stalled.cjs');
const _cursorProgress = require('../_cursor-progress.cjs');
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
class CallTailV3 extends abstractAction.AbstractAction {
static {
__name(this, "CallTailV3");
}
/**
* Returns every record of a `tail` method as one array.
*
* @template T - The type of the elements of the returned array (default is `unknown`).
*
* @param {ActionCallTailV3} options - parameters for executing the request.
* - `method: string` - A REST API `tail` method name (for example: `main.eventlog.tail`).
* - `params?: Omit<TypeCallParamsV3, 'pagination' | 'order' | 'cursor'>` - Request parameters
* (`filter`, `select`). `pagination`, `order` and `cursor` are managed by this helper.
* The cursor field must NOT be used in `filter`.
* - `cursorField?: string` - The DTO field that drives the cursor. Default is `id`.
* - `order?: 'ASC' | 'DESC'` - Cursor direction. Default is `ASC`. For `DESC` you MUST pass
* `initialValue` (the server pages by `field < value`, so the default `0` returns nothing).
* - `customKeyForResult?: string` - The key the response groups rows under. Default is `items`.
* - `requestId?: string` - Unique request identifier for tracking.
* - `maxPages?: number` - Stop after this many pages and throw
* `JSSDK_ACTION_MAX_PAGES_EXCEEDED` naming the method. Defaults to 10 000 — a
* backstop, not a policy. The rows already collected come back with the
* error attached rather than discarded — check `isSuccess`.
* - `signal?: AbortSignal` - Stop the walk. Checked at the top of each iteration, so an
* already-aborted signal costs no request. Throws `JSSDK_ACTION_ABORTED`.
* - `progress?: (p: { pages: number, rows: number }) => void` - Called after each
* collected page. Counts, not a percentage — cursor paging reads no total.
* - `limit?: number` - How many records to retrieve at a time. Default is `50`.
* **A request, not a guarantee.** Each method applies its own maximum and a page
* shorter than `limit` is not the end of the data — `tasks.task.list` answers 50
* however much you ask for, measured with 60 rows available. This walker is
* cap-tolerant; hand-rolled paging on `call.make` is not. On the build measured, a
* `limit` of `0` or a non-numeric one was refused with
* `INVALIDPAGINATIONEXCEPTION` and a negative one answered a bare 500 — one
* method on one on-premise build, so treat the codes as what to expect rather
* than a contract.
* - `initialValue?: number | string` - Cursor start value for the first page. Default is `0`
* (valid for ascending numeric fields); required for `DESC` and for non-numeric fields.
*
* @returns {Promise<Result<T[]>>} A promise that resolves to the result of an REST API call.
*
* @example
* const response = await b24.actions.v3.callTail.make<{ id: string }>({
* method: 'main.eventlog.tail',
* params: { select: ['id', 'auditTypeId'] },
* cursorField: 'id',
* customKeyForResult: 'items'
* })
* if (!response.isSuccess) {
* throw new Error(`Problem: ${response.getErrorMessages().join('; ')}`)
* }
* console.log(`Result: ${response.getData()?.length}`)
*/
async make(options) {
const batchSize = options?.limit ?? 50;
const result$1 = new result.Result();
const cursorField = options?.cursorField ?? "id";
const order = options?.order ?? "ASC";
const customKeyForResult = options?.customKeyForResult ?? "items";
const params = options?.params ?? {};
_keysetPaginate.assertTailFilter(params["filter"], "callTail.make");
if (_cursorProgress.DESC_ORDER.test(order) && options?.initialValue === void 0) {
throw new sdkError.SdkError({
code: "JSSDK_CORE_B24_CALL_TAIL_DESC_REQUIRES_INITIAL_VALUE",
description: 'callTail.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.',
status: 500
});
}
if (_keysetPaginate.filterMentionsField(params["filter"], cursorField)) {
this._logger.warning(`callTail.make: the cursor field "${cursorField}" must not appear in \`filter\` \u2014 the server orders and pages by it and will reject a filter on the same field (INVALIDFILTEREXCEPTION). Remove it from \`filter\`.`).catch(() => {
});
}
let select = params["select"];
if (Array.isArray(select)) {
if (!select.includes(cursorField)) {
select = [...select, cursorField];
}
} else if (cursorField !== "id") {
this._logger.warning(`callTail.make: no \`select\` provided with a non-default cursorField "${cursorField}" \u2014 make sure it is in the server's default field set, otherwise pass \`select\` including "${cursorField}" so the cursor can advance.`).catch(() => {
});
}
const { select: _ignoredSelect, ...restParams } = params;
const allItems = [];
let pages = 0;
try {
for await (const page of _keysetPaginate.keysetPaginate(this._b24, this._logger, {
method: options.method,
requestId: options.requestId,
customKeyForResult,
initialCursor: options?.initialValue ?? 0,
// Native keyset: drive the server's `cursor: { field, value, order, limit }`.
buildParams: /* @__PURE__ */ __name((cursor) => ({
...restParams,
...select ? { select } : {},
cursor: { field: cursorField, value: cursor, order, limit: batchSize }
}), "buildParams"),
// Advance by the raw cursor-field value from the last item; a missing
// value (cursorField not selected / wrong name) stops the walk.
readNextCursor: /* @__PURE__ */ __name((lastItem) => lastItem[cursorField] ?? null, "readNextCursor"),
noCursorWarning: `callTail.make: pagination stops here \u2014 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\`).`,
errorLabel: "callTailMethod",
actionLabel: "callTail.make",
stalledCursorHint: _cursorStalled.CURSOR_STALLED_HINT_TAIL,
// The caller's own `order`, normalised: the server pages by
// `field > value` for ASC and `field < value` for DESC, so which way the
// cursor must move is their choice rather than a constant here.
cursorDirection: _cursorProgress.resolveCursorDirection(order),
maxPages: options?.maxPages,
signal: options?.signal
})) {
for (const item of page) {
allItems.push(item);
}
pages += 1;
options.progress?.({ pages, rows: allItems.length });
}
} catch (error) {
if (error instanceof _keysetPaginate.KeysetPaginationError) {
for (const [index, err] of error.errors) {
result$1.addError(err, index);
}
} else if (_walkBounds.isWalkBoundsError(error)) {
result$1.addError(error);
} else {
throw error;
}
}
return result$1.setData(allItems);
}
}
exports.CallTailV3 = CallTailV3;
//# sourceMappingURL=call-tail.cjs.map