UNPKG

@bitrix24/b24jssdk

Version:

Bitrix24 REST API JavaScript SDK

1 lines • 16.7 kB
{"version":3,"file":"abstract-interaction-batch.cjs","sources":["../../../../../src/core/interaction/batch/abstract-interaction-batch.ts"],"sourcesContent":["import type { IProcessingStrategy } from './processing/interface-strategy'\nimport type {\n BatchCommandsArrayUniversal,\n BatchCommandsObjectUniversal,\n BatchCommandV3, BatchNamedCommandsUniversal,\n ICallBatchOptions, ICallBatchResult\n} from '../../../types/http'\nimport type { RestrictionManager } from '../../http/limiters/manager'\nimport type { PayloadTime } from '../../../types/payloads'\nimport type { Result } from '../../result'\nimport type { AjaxResult } from '../../http/ajax-result'\nimport type { NumberString } from '../../../types/common'\nimport type { TypeDescriptionError } from '../../../types/auth'\nimport type { LoggerInterface } from '../../../types/logger'\nimport { SdkError } from '../../sdk-error'\nimport { LoggerFactory } from '../../../logger'\n\n/**\n * The keys a batch command object is read for. Anything else a caller put there\n * is ignored, and the one that matters is `query`: on `restApi:v3` that is the\n * portal's own wire name for a command's arguments, so a caller reading the\n * portal reference — or translating a `curl` example — reaches for it naturally.\n */\nconst READ_COMMAND_KEYS: readonly string[] = ['method', 'params', 'as', 'parallel']\n\nexport interface BatchResponseData<T = unknown> {\n readonly result?: T[] | Record<string | number, T>\n readonly result_error?: (string | TypeDescriptionError)[] | Record<string | number, string | TypeDescriptionError>\n readonly result_total?: NumberString[] | Record<string | number, NumberString>\n readonly result_next?: NumberString[] | Record<string | number, NumberString>\n readonly result_time?: PayloadTime[] | Record<string | number, PayloadTime>\n}\n\n/**\n * What a `batch` call's `result` field actually holds, i.e. what\n * `AjaxResult.getData()!.result` returns for a batch response.\n *\n * `AjaxResult<X>` already means \"the payload is `{ result: X, time }`\", so the\n * type argument is the INNER value, not the whole envelope. This used to be\n * written `AjaxResult<BatchPayload<T>>`, which described one envelope too many\n * (`{ result: { result: …, time }, time }`) — every consumer then had to launder\n * the difference through `as unknown as`, and those casts were load-bearing\n * rather than cosmetic: they silenced a real mismatch.\n *\n * The two arms are the two REST versions, which genuinely differ:\n * - **v2** splits the response into `result` / `result_error` / `result_time` /\n * `result_total` / `result_next` — {@link BatchResponseData}.\n * - **v3** puts the per-command results directly in `result`, with no\n * per-command error or time split.\n *\n * Each version's strategy narrows the union with a plain `as`, which is a\n * narrowing the runtime really does make (the transport knows its own version)\n * rather than an unchecked reinterpretation.\n *\n * The union carries no discriminant, so nothing in the type system enforces\n * that a v2 strategy only ever sees a v2 response — that coupling is held by\n * `HttpV2`/`HttpV3` each constructing their own `InteractionBatch`. Do not read\n * `getData()!.result` generically outside the paired processing strategy: there\n * is no tag to branch on, and picking the wrong arm compiles.\n */\nexport type BatchResponsePayload<T = unknown>\n = BatchResponseData<T>\n | T[]\n | Record<string | number, T>\n\nexport type InteractionBatchOptions = Required<Omit<ICallBatchOptions, 'isHaltOnError' | 'isObjectMode'>> & {\n /**\n * @memo this regeneration is `isHaltOnError` and it is currently `!isHaltOnError`\n */\n parallelDefaultValue: boolean\n restrictionManager: RestrictionManager\n processingStrategy?: IProcessingStrategy\n /** The transport's logger, used by {@link AbstractInteractionBatch._warnUnreadCommandKeys}. */\n logger?: LoggerInterface\n}\n\nexport type ResponseHelper = {\n requestId: string\n status: number\n time: PayloadTime\n restrictionManager: RestrictionManager\n}\n\n/**\n * Working with batch requests\n */\nexport abstract class AbstractInteractionBatch {\n protected parallelDefaultValue: boolean\n protected requestId: string\n protected restrictionManager: RestrictionManager\n protected logger?: LoggerInterface\n // @memo this regeneration -> isObjectMode\n protected processingStrategy?: IProcessingStrategy\n\n protected _commands: BatchCommandV3[] = []\n\n constructor(options: InteractionBatchOptions) {\n this.parallelDefaultValue = options.parallelDefaultValue\n this.requestId = options.requestId\n this.restrictionManager = options.restrictionManager\n this.processingStrategy = options.processingStrategy\n this.logger = options.logger\n }\n\n // region Setter Strategy ////\n public setProcessingStrategy(processingStrategy: IProcessingStrategy) {\n this.processingStrategy = processingStrategy\n }\n // endregion ////\n\n // region Getter ////\n get size(): number {\n return this._commands.length\n }\n\n get maxSize(): number {\n return 0\n }\n // endregion ////\n\n // region Request ////\n public addCommands(\n calls: BatchCommandsArrayUniversal | BatchCommandsObjectUniversal | BatchNamedCommandsUniversal\n ): void {\n if (!this.processingStrategy) {\n throw new SdkError({\n code: 'JSSDK_INTERACTION_BATCH_EMPTY_PROCESSING_STRATEGY',\n description: 'ProcessingStrategy not set',\n status: 500\n })\n }\n\n try {\n this._warnUnreadCommandKeys(calls)\n } catch {\n // A diagnostic must not be able to fail a call that would have worked.\n // Reading a caller's object runs their code: a `Proxy` whose `ownKeys`\n // throws, or a getter that does, would otherwise escape `addCommands` —\n // measured, before this guard.\n }\n\n this._commands = this.processingStrategy.prepareCommands(calls, {\n parallelDefaultValue: this.parallelDefaultValue\n })\n }\n\n /**\n * Warns, once per `addCommands`, when a batch command lost its arguments to a\n * key the parser does not read — a command with no `params`, or one naming\n * `query`. A key beside a populated `params` is left alone.\n *\n * `query` is the reason this exists. On `restApi:v3` the portal's own\n * reference calls a command's arguments `query`, and so does every `curl`\n * example; the SDK's key is `params`, and it writes the wire name for you.\n * Write `query` yourself and the arguments are read by nobody — the command\n * goes out with an empty `query`, which the portal **accepts**. Measured on\n * `main.eventlog.list` with `select: ['id']` and `pagination: { limit: 2 }`:\n * under `params` the portal answered 2 rows of 1 field, the same request\n * spelled `query` answered 50 rows of 13 fields — the whole default page, with\n * the `select` and the limit both gone. HTTP 200 either way, no error\n * anywhere.\n *\n * `restApi:v2` loses them just as quietly by a different route: there the\n * arguments are serialised into the `cmd` querystring from `params`, so the\n * command goes out as `method?` with nothing after it. Measured on `user.get`\n * with `filter: { ACTIVE: 'N' }`, against a portal whose only user is active:\n * under `params` the portal answered 0 rows — the filter worked — and the same\n * request spelled `query` answered 1 row, the user the filter was meant to\n * exclude.\n *\n * So there is nothing to notice on either version: no error, no empty result,\n * just an answer to a question nobody asked.\n *\n * TypeScript catches a fresh object literal and nothing more — assign it to a\n * variable first, or build the commands from a config object, a `JSON.parse`,\n * or plain JavaScript, and the compiler never sees it. Same hole\n * `_warnMisplacedOptions` was written for (#426), and the same trade: warn\n * rather than throw, because the call still does something. Through\n * `forcedLog`, because the default logger is silent and a caller who has not\n * wired one up is exactly who this is for (#483).\n *\n * Once per `addCommands` — i.e. once per batch **request** — not once per\n * command: 50 commands built from one bad template would otherwise be 50\n * identical console lines. The keys are collected across the batch and\n * reported together, with the positions that carried them. A `batchByChunk`\n * walk still warns once per chunk, since each chunk is its own request.\n *\n * Own enumerable string keys only, so a key on a prototype, a symbol key, or a\n * non-enumerable one is not seen — and a tuple carrying extra elements is not\n * checked at all. All are the quiet direction: this exists to catch the\n * ordinary mistake, not to validate every shape a caller can build.\n *\n * `forcedLog` reaches the console only while the app has wired no logger of\n * its own. One that filters by level may drop this, like every other SDK\n * warning.\n */\n protected _warnUnreadCommandKeys(\n calls: BatchCommandsArrayUniversal | BatchCommandsObjectUniversal | BatchNamedCommandsUniversal\n ): void {\n const unread = new Set<string>()\n const positions: string[] = []\n\n for (const [index, row] of Object.entries(calls)) {\n if (!row || typeof row !== 'object' || Array.isArray(row)) {\n continue\n }\n\n // `Object.keys`, never `Object.values`, and that is load-bearing: what\n // reaches the message is key NAMES, which cannot carry a credential the\n // way a value can. `local/no-credential-in-logger` cannot see through the\n // string building below, so a later \"show the value too, it's friendlier\"\n // edit would pass lint — it is a security change and should read as one.\n // A key whose value is `undefined` carries nothing and costs nothing — it\n // is what a spread of a partly-filled template leaves behind.\n const rowUnread = Object.keys(row).filter(key => (\n !READ_COMMAND_KEYS.includes(key)\n && undefined !== (row as unknown as Record<string, unknown>)[key]\n ))\n\n if (0 === rowUnread.length) {\n continue\n }\n\n // Narrow on purpose: an unread key is only worth saying something about\n // when the arguments are actually missing, or when it is `query` — the one\n // name that is never right here. A caller who carries their own `id`,\n // `label` or `_meta` beside a populated `params` has lost nothing, and\n // warning them on every command of every batch, with advice to move it\n // into `params`, would be both noise and wrong. A command carrying neither\n // `params` nor `query` but some other key does still warn: nothing here\n // tells an argument-less command apart from one whose arguments went\n // astray under a name nobody reads.\n // \"Has arguments\" means a non-empty object, not merely \"not undefined\".\n // `params: null`, `params: {}` and `params: false` are what a config-driven\n // builder leaves behind when it initialises the key and then merges the\n // wrong one — treating those as arguments silenced the very typo this\n // exists to catch. A **function** is worse than absent: `ParseRow` puts it\n // in `query`, `JSON.stringify` drops it, and the item reaches the portal\n // with no `query` at all — which is rejected, taking every sibling command\n // with it.\n const params = (row as { params?: unknown }).params\n const hasParams = null !== params\n && 'object' === typeof params\n && Object.keys(params as object).length > 0\n\n // Case-folded: `Query` and `QUERY` lose the arguments exactly as `query`\n // does, and a caller translating a reference is as likely to write either.\n const namesTheWireKey = rowUnread.some(key => 'query' === key.toLowerCase())\n\n if (hasParams && !namesTheWireKey) {\n continue\n }\n\n rowUnread.forEach(key => unread.add(key))\n positions.push(index)\n }\n\n if (0 === unread.size) {\n return\n }\n\n const keys = [...unread]\n\n LoggerFactory.forcedLog(\n this.logger ?? LoggerFactory.createNullLogger(),\n 'warning',\n `[b24jssdk] batch command: ${keys.join(', ')} `\n + `${1 === keys.length ? 'is' : 'are'} ignored — `\n + 'a command\\'s arguments go in `params`. Write `params: { … }`. '\n + '(`query` is the portal\\'s own wire spelling on `restApi:v3`, which the SDK writes for you; '\n + 'on `restApi:v2` the arguments are serialised into the `cmd` querystring instead.)',\n {\n code: 'JSSDK_BATCH_UNREAD_COMMAND_KEY',\n unread: keys.join(', '),\n read: READ_COMMAND_KEYS.join(', '),\n commands: positions.join(', ')\n }\n ).catch(() => {})\n }\n\n public getCommandsForCall(): unknown {\n if (!this.processingStrategy) {\n throw new SdkError({\n code: 'JSSDK_INTERACTION_BATCH_EMPTY_PROCESSING_STRATEGY',\n description: 'ProcessingStrategy not set',\n status: 500\n })\n }\n\n return this.processingStrategy.buildCommands(this._commands)\n }\n // endregion ////\n\n // region Response ////\n public abstract prepareResponse<T>(response: AjaxResult<BatchResponsePayload<T>>): Promise<Result<ICallBatchResult<T>>>\n // endregion ////\n}\n"],"names":["SdkError","LoggerFactory"],"mappings":"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"}