UNPKG

moysklad

Version:

Библиотека для работы с API сервиса МойСклад

1,073 lines (966 loc) 36.4 kB
export = Moysklad /** * Создает экземпляр клиента для работы с API МойСклад * @param options Параметры инициализации экземпляра */ declare function Moysklad(options?: Moysklad.Options): Moysklad.Instance declare namespace Moysklad { export type RetryFunction = <U, T extends (...args: any[]) => Promise<U>>( thunk: T, signal?: AbortSignal | undefined ) => Promise<U> export interface Instance { /** * Выполняет GET запрос по указанному ресурсу * * @param path Путь к ресурсу * @param query Строка запроса * @param options Опции запроса */ GET( path: string, query?: Query | null | undefined, options?: RequestOptions | undefined ): Promise<any> /** @deprecated */ GET( path: string[], query?: Query | null | undefined, options?: RequestOptions | undefined ): Promise<any> /** @deprecated */ GET(params: { /** Путь к ресурсу */ path: string | string[] /** Строка запроса */ query?: Query | null | undefined /** Опции запроса */ options?: RequestOptions | undefined }): Promise<any> /** * Выполняет POST запрос по указанному ресурсу * * @param path Путь к ресурсу * @param payload Тело запроса * @param query Строка запроса * @param options Опции запроса */ POST( path: string, payload: any, query?: Query | null | undefined, options?: RequestOptions | undefined ): Promise<any> /** @deprecated */ POST( path: string[], payload: any, query?: Query | null | undefined, options?: RequestOptions | undefined ): Promise<any> /** @deprecated */ POST(params: { /** Путь к ресурсу */ path: string | string[] /** Тело запроса */ payload: any /** Строка запроса */ query?: Query | null | undefined /** Опции запроса */ options?: RequestOptions | undefined }): Promise<any> /** * Выполняет PUT запрос по указанному ресурсу * * @param path Путь к ресурсу * @param payload Тело запроса * @param query Строка запроса * @param options Опции запроса */ PUT( path: string, payload: any, query?: Query | null | undefined, options?: RequestOptions | undefined ): Promise<any> /** @deprecated */ PUT( path: string[], payload: any, query?: Query | null | undefined, options?: RequestOptions | undefined ): Promise<any> /** @deprecated */ PUT(params: { /** Путь к ресурсу */ path: string | string[] /** Тело запроса */ payload: any /** Строка запроса */ query?: Query | null | undefined /** Опции запроса */ options?: RequestOptions | undefined }): Promise<any> /** * Выполняет DELETE запрос по указанному ресурсу * * @param path Путь к ресурсу * @param options Опции запроса */ DELETE(path: string, options?: RequestOptions): Promise<any> /** @deprecated */ DELETE(path: string[], options?: RequestOptions): Promise<any> /** @deprecated */ DELETE(params: { /** Путь к ресурсу */ path: string | string[] /** Опции запроса */ options?: RequestOptions | undefined }): Promise<any> /** * Возвращает параметры с которыми был инициализирован текущий клиент * * Пример: * ```js * const ms = Moysklad({ apiVersion: '1.2' }) * * const options = ms.getOptions() * * console.log(options) * // { endpoint: "https://api.moysklad.ru/api", api: "remap", apiVersion: "1.2", fetch } * * ``` */ getOptions(): Options /** * Возвращает текущую версию библиотеки. * * Версия из package.json (поле `version`) */ getVersion(): string /** * Возвращает полный url для указанных параметров * * @param path Путь к ресурсу или href * @param query Параметры строки запроса * * Пример: * * ```js * ms.buildUrl('entity/customerorder', { expand: 'agent' }) * // https://api.moysklad.ru/api/remap/1.2/entity/customerorder?expand=agent * ``` */ buildUrl(path: string, query?: Query): string /** * @deprecated Для передачи параметра `path` используйте строку */ buildUrl(path: string[], query?: Query): string /** * Разбирает url ресурса API МойСклад на составные части * * @param url url, path или ref ресурса API МойСклад * * - url `https://...` * - ref `"path/to"` * - (deprecated) path `["path", "to"]` */ parseUrl(url: string): { /** * Точка доступа к API * * Пример: `https://api.moysklad.ru/api` */ endpoint: string /** * Раздел API * * Пример: `remap` */ api: string /** * Версия API * * Пример: `1.2` */ apiVersion: string /** * Составные части пути к ресурсу * * Пример: `["entity", "customerorder"]` */ path: string[] /** * Параметры строки запроса * * Пример: `{"expand": "agent"}` */ query: Query } /** * Выполняет запрос по указанному url и возвращает результат * * @param url url ресурса * @param options Параметры запроса * * Пример: * ```js * const url = `https://api.moysklad.ru/api/remap/1.2/entity/customerorder/eb7bc422-ae8d-11e3-9e32-002590a28eca` * * const patch = { applicable: false } * * const updatedOrder = await ms.fetchUrl(url, { * method: 'PUT', * body: JSON.stringify(patch) * }) * ``` */ fetchUrl(url: string, options?: RequestOptions): Promise<any> /** * Возвращает значение HTTP заголовка Authorization */ getAuthHeader(): string } /** * Параметры инициализации экземпляра клиента */ export interface Options { /** * Функция с интерфейсом [Fetch API](https://developer.mozilla.org/ru/docs/Web/API/Fetch_API). * * По умолчанию используется глобальный fetch (если глобальный fetch не * найден, то при первом запросе будет выброшена ошибка). * * Для управления потоком запросов с целью уложиться в [ограничения](https://dev.moysklad.ru/doc/api/remap/1.2/#mojsklad-json-api-ogranicheniq) * API МойСклад можно использовать модуль [moysklad-fetch-planner](https://www.npmjs.com/package/moysklad-fetch-planner). * Модуль считывает информацию о текущих лимитах из заголовков ответов API * МойСклад и ограничивает скорость выполнения запросов, предотвращая появления * ошибок 429 Too Many Requests. В случае если ошибки 429 избежать не удалось, * запрос будет повторен при восстановлении доступного лимита. * * Пример использования: * * ```ts * import Moysklad from 'moysklad' * import { fetch } from 'undici' * import { wrapFetchApi } from 'moysklad-fetch-planner' * * const ms = Moysklad({ fetch: wrapFetchApi(fetch) }) * ``` */ fetch?: any /** * Функция для управления поведением при возникновении ошибок. * * В функцию `retry` передается аргумент - `thunk`. `thunk` - асинхронная * функция без аргументов, при выполнении которой выполняется текущий * HTTP-запрос. * * Логика внутри функции `retry` должна возвращать результат выполнения * функции `thunk`, там же обрабатываются ошибки и при необходимости * повторяется выполнение запроса через повторный вызов `thunk`. * * Можно использовать готовые модули в которых уже реализована стратегия * повторения запросов. * * Пример с применением модуля [p-retry](https://github.com/sindresorhus/p-retry): * * ```ts * import Moysklad from 'moysklad' * import pRetry from 'p-retry' * * const ms = Moysklad({ * retry: (thunk, signal) => { * return pRetry(thunk, { * retries: 2, * shouldRetry: Moysklad.shouldRetryError, * onFailedAttempt: error => { * console.log( * `Attempt ${error.attemptNumber} failed. There are ${error.retriesLeft} retries left.` * ) * }, * signal * }) * } * }) * ``` * * Для обработки ошибок `429 Too Many Requests` рекомендуется использовать модуль * планировщик запросов [moysklad-fetch-planner](https://www.npmjs.com/package/moysklad-fetch-planner) * (см. описание опции `fetch`). Использование планировщика можно совмещать с * опцией `retry`. * * [Пример настройки](https://github.com/wmakeev/moysklad/blob/master/examples/retry.js) клиента с retry и планировщиком. */ retry?: RetryFunction /** * Точка доступа к API * * по умолчанию `https://api.moysklad.ru/api` */ endpoint?: string /** * Раздел API * * по умолчанию `remap` */ api?: string /** * Версия API * * по умолчанию `1.2` */ apiVersion?: string /** * Токен для доступа к API * * Можно передать через глобальную переменную или переменную окружения `MOYSKLAD_TOKEN` * (см. [Аутентификация](https://github.com/wmakeev/moysklad#аутентификация)) */ token?: string /** * Логин * * Можно передать через глобальную переменную или переменную окружения `MOYSKLAD_LOGIN` * (см. [Аутентификация](https://github.com/wmakeev/moysklad#аутентификация)) */ login?: string /** * Пароль * * Можно передать через глобальную переменную или переменную окружения `MOYSKLAD_PASSWORD` * (см. [Аутентификация](https://github.com/wmakeev/moysklad#аутентификация)) */ password?: string /** * Экземпляр [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) для получения событий * * Пример использования: * * ```js * const { fetch } = require('undici') * const { EventEmitter } = require('events') * * const Moysklad = require('..') * * const emitter = new EventEmitter() * * const ms = Moysklad({ fetch, emitter }) * * const startTime = Date.now() * * emitter * .on('request', ({ requestId, url, options }) => { * console.log(`${options.method} ${url} (+${Date.now() - startTime}ms)`) * }) * .on('response', ({ requestId, url, options: { method }, response: { statusText, status } }) => { * console.log(`${method} ${statusText} ${status} ${url} (+${Date.now() - startTime}ms)`) * }) * .on('response:body', ({ requestId, url, options: { method }, response, body }) => { * console.log(`${method} BODY ${url} (+${Date.now() - startTime}ms)`) * }) * .on('error', (error, { requestId }) => { * console.log(error, requestId) * }) * * ms.GET('entity/customerorder', { limit: 1 }).then(res => { * console.log('Order name: ' + res.rows[0].name) * }) * ``` * * Вывод в консоли: * * ```text * GET https://api.moysklad.ru/api/remap/1.2/entity/customerorder?limit=1 (+4ms) * GET OK 200 https://api.moysklad.ru/api/remap/1.2/entity/customerorder?limit=1 (+575ms) * GET BODY https://api.moysklad.ru/api/remap/1.2/entity/customerorder?limit=1 (+580ms) * Order name: 00600 * ``` * */ emitter?: any /** * Содержимое заголовка "User-Agent" при выполнении запроса. * * Удобно использовать для контроля изменений через API на вкладке "Аудит". * * По умолчанию: `moysklad/{version} (+https://github.com/wmakeev/moysklad)` */ userAgent?: string [option: string]: any } /** * Все опции переданные в объекте `options` (за исключением вспомогательных) передаются напрямую * в опции метода `fetch` ([Fetch API](http://github.github.io/fetch/)) при осуществлении запроса. */ export interface RequestOptions { /** * Если `true`, то метод вернет результат запроса в виде объекта * [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) * * Если ответ содержит ошибку, то вам её нужно обработать вручную. * * Если `true`, то опции `muteApiErrors` и `muteCollectionErrors` не учитываются. */ rawResponse?: boolean /** * Если ответ сервера с кодом в диапазоне 300-399 (редирект), то будет * выброшена ошибка [MoyskladUnexpectedRedirectError](https://github.com/wmakeev/moysklad?tab=readme-ov-file#moyskladunexpectedredirecterror), * поэтому, для явной обработки редиректа необходимо указать опцию `rawRedirect` со * значением `true`. В этом случае метод вернет объект * [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response), * из которого можно получить Location заголовок. * * Такое поведение сработает, * только если явно не указана опция [redirect](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit#redirect) * со значением `follow`. */ rawRedirect?: boolean /** * Если `true`, то метод вернет массив из двух элементов - результат и * исходный объект [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response). * Ошибки будут обработаны как при обычном запросе. */ includeResponse?: boolean /** * Если `true`, то все ошибки API (тип ошибки `MoyskladApiError`) будут проигнорированы. * * Ошибка `MoyskladApiError` генерируется в случаях: * - если код ответа сервера не 2xx и тело ответа содержит поле `errors` * - если ответ коллекция и хотя бы один элемент коллекции содержит поле `errors` * * Результат будет содержать ответ сервера с ошибкой или коллекцию. * * Ошибка сервера или протокола проигнорированы не будут. * * Опция полезна, если вы хотите самостоятельно обрабатывать ошибки API МойСклад. * * Пример (одна ошибка): * * ```js * const result = await ms.GET('foo', null, { muteApiErrors: true }) * * if (result.errors) { * console.log(result.errors[0].error) * } * ``` * * Пример (ошибка в коллекции): * * ```js * const result = await ms.POST('entity/demand', [demands], { * muteApiErrors: true * }) * * if (Array.isArray(result)) { * const errors = result.filter(it => it.errors) * * if (errors.length) { * throw new Error('В коллекции есть ошибки') * } * } else if (result.errors) { * throw new Error(result.errors[0].error) * } * ``` * * @see `muteCollectionErrors` */ muteApiErrors?: boolean /** * @deprecated используйте `muteApiErrors` */ muteErrors?: boolean /** * Если `true`, то будут проигнорированы ошибки внутри коллекций. * * Если ответ коллекция и хотя бы один элемент коллекции содержит поле `errors`, * то ошибка `MoyskladApiError` выброшена не будет. * * Пример (ошибка в коллекции): * * ```js * const result = await ms.POST('entity/demand', [demands], { * muteCollectionErrors: true * }) * * const errors = result.filter(it => it.errors) * * if (errors.length) { * throw new Error('В коллекции есть ошибки') * } * ``` */ muteCollectionErrors?: boolean /** * Если `true`, то в запрос будет включен заголовок `X-Lognex-Precision` со значением `true` * (отключение округления цен и себестоимости до копеек). */ precision?: boolean /** * Если `true`, то в запрос будет включен заголовок `X-Lognex-WebHook-Disable` * со значением `true` (отключить уведомления вебхуков в контексте данного запроса). * * @deprecated Не рекомендуется использовать данную опцию, применяйте `webHookDisableByPrefix`. */ webHookDisable?: boolean /** * Префикс url для выборочного отключения вебхуков, будет добавлен в * качестве значения заголовка `X-Lognex-WebHook-DisableByPrefix`. */ webHookDisableByPrefix?: string /** * В JSON API для скачивания файла формируется редирект на временный URL * файла. Для корректной работы необходимо поддерживать обработку * перенаправлений запросов. * Запрос на временный URL не требует авторизации. По умолчанию время жизни * временного URL равно одной минуте. * * C помощью параметра можно изменить время жизни URL в секундах. Текущее * максимально возможное значение - 86400 секунд (24 часа). Долгоживущие * ссылки предназначены для экспорта. Использование этих ссылок на публично * доступных web-страницах запрещено. * * [Ссылки на файлы](https://dev.moysklad.ru/doc/api/remap/1.2/#mojsklad-json-api-obschie-swedeniq-ssylki-na-fajly) */ downloadExpirationSeconds?: number /** * Можно добавить дополнительные заголовки запроса */ headers?: { [header: string]: string | number } /** * Обработка редиректа * * Установите `follow`, если нужно автоматически обрабатывать редирект `3xx`. * Например при запросе товара по Id из приложения МойСклад * * default: `manual` */ redirect?: 'manual' | 'follow' | 'error' [option: string]: any } export type QueryValue = string | number | boolean | Date export interface QueryObject { /** * Равно `key=value` */ $eq?: QueryValue | undefined /** * Не равно `key!=value` */ $ne?: QueryValue | undefined /** * Больше `key>value` */ $gt?: QueryValue | undefined /** * Больше или равно `key>=value` */ $gte?: QueryValue | undefined /** * Меньше `key<value` */ $lt?: QueryValue | undefined /** * Меньше или равно `key<=value` */ $lte?: QueryValue | undefined /** * Начинается со строки `key~=value` */ $st?: QueryValue | undefined /** * Заканчивается строкой `key=~value` */ $et?: QueryValue | undefined /** * Содержит строку `key~value` */ $contains?: QueryValue | undefined /** * Входит в `key=value1;key=value2;...` */ $in?: QueryValue[] | undefined /** * Не входит `key!=value1;key!=value2;...` */ $nin?: QueryValue[] | undefined /** * Наличие значения (не null) * * - `true` - `key!=` * - `false` - `key=` */ $exists?: boolean | undefined /** * Объединение нескольких условий * * Оба фильтра ниже идентичны: * * ```js * const filter1 = { * name: { * $all: [ * { $eq: 'foo' }, * { * $not: { * $eq: 10, * $in: [5, 6] * } * } * ] * } * } * * const filter2 = { * name: { * $eq: 'foo', * $not: { * $eq: 10, * $in: [5, 6] * } * } * } * ``` */ $all?: QueryObject[] | undefined /** * Отрицание условия */ $not?: QueryObject | undefined /** * Равно `key=value` */ [key: string]: | QueryValue | QueryValue[] | QueryObject | QueryObject[] | undefined } export type QueryFilter = { [key: string]: QueryValue | QueryValue[] | QueryObject | undefined } export type QueryOrder = Array<string | [string] | [string, string]> /** * Параметры запроса * * Все поля объекта преобразуются в соответствующую строку запроса url. Некоторые поля (поле `filter`) подвергаются преобразованию. */ export interface Query { /** * Используется для фильтрации элементов коллекции * * ```js * const filter = { * applicable: true, * moment: { * $gt: '2019-08-10 11:00' * } * } * ``` */ filter?: QueryFilter | string | undefined /** TODO */ search?: string | undefined /** * Используется для раскрытия ссылок на связанные объекты * * Пример: `agent,positions.assortment` * * Если указан `expand` и не указан `limit`, то `limit` будет автоматически установлен как `100` */ expand?: string | undefined /** * Задает ограничение на кол-во возвращаемых элементов в коллекции * * Если указан `expand` и не указан `limit`, то `limit` будет автоматически установлен как `100` */ limit?: number | undefined /** Задает смещение для первого элемента в коллекции */ offset?: number | undefined /** * Сортировка выборки * * Примеры: * - `name` или `['name']` * - `code,desc` или `[['code','desc']]` * - `name;code,desc` или `['name', ['code','desc']]` * - `name,desc;code,asc` или `['name,desc', ['code','asc']]` */ order?: QueryOrder | string | undefined /** * Запустить запрос в [асинхронном режиме](https://dev.moysklad.ru/doc/api/remap/1.2/workbook/#workbook-rabota-s-asinhronnym-obmenom). * Асинхронный обмен предоставляет возможность получать результаты длительных запросов асинхронно, не используя многократные запросы с листанием. * * Параметры запроса limit и offset указывать не нужно, так как отчет будет построен полностью. * * [Список запросов](https://dev.moysklad.ru/doc/api/remap/1.2/#mojsklad-json-api-asinhronnyj-obmen), которые могут быть выполнены асинхронно * * Пример: * * ```js * const resp = await ms.GET( * 'entity/assortment', * { * filter: { * "stockStore": { * "$in": [ * "https://api.moysklad.ru/api/remap/1.2/entity/store/b63b4250-ad44-11e9-912f-f3d4000759d7" * ] * }, * "stockMode": { * "$eq": "positiveOnly" * } * }, * async: true * }, * { rawResponse: true } * ) * * if (resp.status !== 202) { * throw new Error('Ожидался ответ асинхронной задачи с кодом 202') * } * * const statusUrl = resp.headers.get('Content-Location') * const resultUrl = resp.headers.get('Location') * * // ... проверка статуса запроса по ссылке statusUrl и получение результата по ссылке resultUrl * * ``` */ async?: boolean [key: string]: any } /** * Возвращает текущую версию библиотеки. * * Версия из package.json (поле `version`) */ export function getVersion(): string /** * Преобразует дату в строку в формате API МойСклад в часовом поясе Москвы * @param date дата * @param includeMs если `true`, то в результирующую дату будут включены миллисекунды */ export function getTimeString( date: Date | number, includeMs?: boolean ): string /** * Преобразует строку с датой в формате API МойСклад в объект даты (с учетом часового пояса исходной даты) * @param date дата в формате МойСклад (напр. `2017-04-08 13:33:00.123`) */ export function parseTimeString(date: string): Date /** * Разбирает url ресурса API МойСклад на составные части * * @param url url, path или ref ресурса API МойСклад * * - url `https://...` */ export function parseUrl(url: string): { /** * Точка доступа к API * * Пример: `https://api.moysklad.ru/api` */ endpoint: string /** * Раздел API * * Пример: `remap` */ api: string /** * Версия API * * Пример: `1.2` */ apiVersion: string /** * Составные части пути к ресурсу * * Пример: `["entity", "customerorder"]` */ path: string[] /** * Параметры строки запроса * * Пример: `{"expand": "agent"}` */ query: Query } /** * Формирует не закодированную строку фильтра * * ```js * Moysklad.buildFilter({ name: { $st: 'foo' } }) * // 'code=123;name~=foo' * ``` * * @param filter Объект фильтра */ export function buildFilter(filter: QueryFilter): string /** * Формирует строку с параметрами запроса * * ```js * Moysklad.buildQuery({ * filter: { name: 'foo' }, * limit: 100, * foo: 'bar' * }) * * // 'filter=name%3Dfoo&limit=100&foo=bar' * ``` * * @param query Параметры запроса */ export function buildQuery(query: Query): string /** * Вспомогательная функция, которую можно использовать в реализации логики * повторения запросов при возникновении ошибки. * * Возвращает: * * - `true` - причина ошибка, скорее всего, вызвана временными неполадками, * поэтому запрос можно повторить. * * - `false` - запрос содержит ошибку, повторять запрос нецелесообразно. * * @param {Error} error Объект ошибки * @returns {boolean} `true` - если следует сделать повторную попытку, иначе `false` */ export function shouldRetryError(error: Error): boolean /** * Метод используется для расширения библиотеки внешними модулями * @param extension Модуль расширения */ export function compose(extension: Function): typeof Moysklad /** Описание конкретной ошибки в API МойСклад */ export interface ApiErrorInfo { error: string code: number moreInfo: string column?: number line?: number error_message?: string dependencies?: { href: string type: string mediaType: string metadataHref?: string uuidHref?: string }[] } /** * Объект с полем `errors` в котором перечислены все ошибки. * * Используется для сообщения об ошибках в API МойСклад. */ export interface ApiError { errors: ApiErrorInfo[] } /** * Внутренняя ошибка библиотеки не связанная с выполнением запроса к API */ export class MoyskladError extends Error {} /** * Ошибка при выполнении запроса */ export class MoyskladRequestError extends MoyskladError { /** url http запроса */ url?: string /** Код статуса http запроса */ status?: number /** Текст статуса http запроса */ statusText?: string } /** * Ошибка если запрос вернул перенаправление (код `3xx`), когда явно не * указана опция запроса `rawRedirect` и опция `redirect` не равна `follow` */ export class MoyskladUnexpectedRedirectError extends MoyskladRequestError { /** Location заголовок редиректа */ location?: string } /** * Ошибка API МойСклад */ export class MoyskladApiError extends MoyskladRequestError { /** Код первой ошибки */ code: number /** Подробное описание из первой ошибки */ moreInfo: string /** Список ошибок запроса */ errors: ApiErrorInfo[] /** Тело запроса */ requestBody?: string /** * Место нахождения первой ошибки в теле запроса. * * Пример форматированного вывода ошибки с указанием места проблемы: * * ```ts * import { codeFrameColumns } from 'awesome-code-frame' * * try { * // some code ... * } catch(err) { * if (err instanceof MoyskladApiError && err.requestBody && err.location) { * const codeFrame = codeFrameColumns(err.requestBody, err.location) * console.log(codeFrame) * } * throw err * } * ``` */ location?: { start: { line: number column?: number } } } /** * Ошибка в коллекции при массовом создании/изменении сущностей */ export class MoyskladCollectionError extends MoyskladApiError { /** * Ошибки в соответствии с индексами переданных сущностей в исходной коллекции. * * Позволяет точно сопоставить ошибки с конкретной сущностью из коллекции. */ errorsIndexes: Array<[number, ApiErrorInfo[]]> } /** Событие `request` отправки запроса */ export interface RequestEvent { /** Уникальный номер запроса в рамках модуля библиотеки */ requestId: number /** URL запроса */ url: string /** Параметры запроса */ options: any } /** Событие `response` получения ответа на запрос */ export interface ResponseEvent extends RequestEvent { /** Ответ на запрос */ response: Response } /** Событие `response:body` получения ответа на запрос */ export interface ResponseBodyEvent extends ResponseEvent { body: any } export interface MoyskladEmitter { on(name: 'request', handler: (ev: RequestEvent) => void): this on(name: 'response', handler: (ev: ResponseEvent) => void): this on(name: 'response:body', handler: (ev: ResponseBodyEvent) => void): this on( name: 'error', handler: (error: Error, options: { requestId: number }) => void ): this on(name: string, handler: (...args: any[]) => void): this } }