{"id":48455486,"url":"https://github.com/wmakeev/moysklad","last_synced_at":"2026-04-06T22:31:18.949Z","repository":{"id":57303385,"uuid":"48270606","full_name":"wmakeev/moysklad","owner":"wmakeev","description":"Библиотека для работы с API сервиса МойСклад","archived":false,"fork":false,"pushed_at":"2025-04-05T05:49:36.000Z","size":1030,"stargazers_count":39,"open_issues_count":0,"forks_count":7,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-05T06:25:11.510Z","etag":null,"topics":["client","crm","javascript","moysklad","nodejs","rest","warehouse","wms"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wmakeev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-12-19T06:04:01.000Z","updated_at":"2025-04-05T05:49:40.000Z","dependencies_parsed_at":"2022-09-17T21:21:55.824Z","dependency_job_id":"6b0d8094-d1ca-42dc-92e9-a30f6732fbde","html_url":"https://github.com/wmakeev/moysklad","commit_stats":{"total_commits":206,"total_committers":1,"mean_commits":206.0,"dds":0.0,"last_synced_commit":"33fe94af868fb3e634eab08c783c71ddfb16ded5"},"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"purl":"pkg:github/wmakeev/moysklad","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wmakeev%2Fmoysklad","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wmakeev%2Fmoysklad/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wmakeev%2Fmoysklad/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wmakeev%2Fmoysklad/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wmakeev","download_url":"https://codeload.github.com/wmakeev/moysklad/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wmakeev%2Fmoysklad/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31492751,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-06T17:22:55.647Z","status":"ssl_error","status_checked_at":"2026-04-06T17:22:54.741Z","response_time":112,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client","crm","javascript","moysklad","nodejs","rest","warehouse","wms"],"created_at":"2026-04-06T22:31:16.706Z","updated_at":"2026-04-06T22:31:18.941Z","avatar_url":"https://github.com/wmakeev.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![moysklad](https://wmakeev-public-files.s3-eu-west-1.amazonaws.com/images/logos/logoMS500x350.png)\n\n# moysklad \u003c!-- omit in toc --\u003e\n\n[![npm](https://img.shields.io/npm/v/moysklad.svg?cacheSeconds=1800\u0026style=flat-square)](https://www.npmjs.com/package/moysklad)\n[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/wmakeev/moysklad/main.yml?style=flat-square)](https://github.com/wmakeev/moysklad/actions/workflows/main.yml)\n\u003c!-- [![Codecov](https://img.shields.io/codecov/c/github/wmakeev/moysklad?style=flat-square)](https://app.codecov.io/gh/wmakeev/moysklad/tree/master/) --\u003e\n\nБиблиотека для взаимодействия с [JSON API сервиса МойСклад](https://dev.moysklad.ru/) для node.js.\n\n\u003e **ВНИМАНИЕ!** Библиотека находится в стадии становления. API может незначительно меняться. Перед обновлением минорной версии смотрите [историю изменений](https://github.com/wmakeev/moysklad/blob/master/CHANGELOG.md).\n\nБиблиотека представляет максимально простой и прозрачный интерфейс к существующим методам [API МойСклад](https://api.moysklad.ru/api/remap/1.2/doc), не абстрагирует разработчика от API и не выполняет никаких внутренних преобразований отправляемых и получаемых данных.\n\nОсновная задача библиотеки - упростить ряд рутинных задач:\n\n- формирование строки запроса (передача параметров, заголовков и фильтрация)\n- обработка ошибок\n- методы для преобразования даты в формат МойСклад и обратно в `Date`\n- базовые типы TypeScript для подсказок по API библиотеки (но не для API МойСклад)\n\nВажно отметить, что библиотека не поможет вам разобраться с API МойСклад, но лишь упростит работу с ним.\n\n## Содержание \u003c!-- omit in toc --\u003e\n\n- [Установка](#установка)\n- [Использование](#использование)\n- [Параметры инициализации](#параметры-инициализации)\n- [Аутентификация](#аутентификация)\n- [Статические методы](#статические-методы)\n  - [getTimeString](#gettimestring)\n  - [parseTimeString](#parsetimestring)\n  - [parseUrl (статический метод)](#parseurl-статический-метод)\n  - [buildFilter](#buildfilter)\n  - [buildQuery](#buildquery)\n  - [getVersion](#getversion)\n- [Методы экземпляра](#методы-экземпляра)\n  - [GET](#get)\n  - [POST](#post)\n  - [PUT](#put)\n  - [DELETE](#delete)\n  - [getOptions](#getoptions)\n  - [getVersion - метод экземпляра](#getversion---метод-экземпляра)\n  - [buildUrl](#buildurl)\n  - [parseUrl](#parseurl)\n  - [fetchUrl](#fetchurl)\n  - [Основные аргументы](#основные-аргументы)\n    - [path](#path)\n    - [query](#query)\n      - [querystring](#querystring)\n      - [filter](#filter)\n      - [order](#order)\n      - [expand и limit](#expand-и-limit)\n    - [options (параметры запроса)](#options-параметры-запроса)\n- [Управление потоком запросов](#управление-потоком-запросов)\n- [Обработка ошибок](#обработка-ошибок)\n  - [Повтор запроса при ошибке](#повтор-запроса-при-ошибке)\n  - [Виды ошибок](#виды-ошибок)\n    - [MoyskladError](#moyskladerror)\n    - [MoyskladRequestError](#moyskladrequesterror)\n    - [MoyskladApiError](#moyskladapierror)\n    - [MoyskladCollectionError](#moyskladcollectionerror)\n    - [MoyskladUnexpectedRedirectError](#moyskladunexpectedredirecterror)\n- [События](#события)\n- [История изменений](#история-изменений)\n- [Планы развития](#планы-развития)\n- [TODO](#todo)\n\n## Установка\n\n\u003e Поддерживаются (тестируются) версии Node.js \u003e=16.8\n\n```bash\nnpm install moysklad\n```\n\nДля Node.js до 18 версии, дополнительно нужно установить библиотеку для\n[Fetch API](https://developer.mozilla.org/en/docs/Web/API/Fetch_API) и явно указать модуль с соответствующим интерфейсом при создании экземпляра библиотеки\n\n```bash\nnpm install undici\n```\n\n[undici.fetch](https://github.com/nodejs/undici#undicifetchinput-init-promise)\n\n```js\nimport { fetch } from 'undici'\nimport Moysklad from 'moysklad'\n\nconst moysklad = Moysklad({ fetch })\n```\n\n## Использование\n\n```js\nimport Moysklad from 'moysklad'\n\n// Для инициализации экземпляра библиотеки указывать ключевое слово new не нужно\nconst ms = Moysklad({ login, password })\n\nms.GET('entity/customerorder', {\n  filter: {\n    applicable: true,\n    state: {\n      name: 'Отгружен'\n    },\n    sum: { $gt: 1000000, $lt: 2000000 }\n  },\n  limit: 10,\n  order: 'moment,desc',\n  expand: 'agent'\n}).then(({ meta, rows }) =\u003e {\n  console.log(\n    `Последние ${meta.limit} из ${meta.size} проведенных заказов ` +\n      `в статусе \"Отгружен\" на сумму от 10000 до 20000 руб`\n  )\n\n  // Выводим имя заказа, имя контрагента и сумму заказа для всех позиций\n  rows.forEach(row =\u003e {\n    console.log(`${row.name} ${row.agent.name} ${row.sum / 100}`)\n  })\n})\n```\n\n\u003e Совместно с библиотекой рекомендуется использовать [планировщик запросов](#управление-потоком-запросов)\n\n\u003e С другими примерами использования можно ознакомиться в папке [examples](https://github.com/wmakeev/moysklad/tree/master/examples)\n\n## Параметры инициализации\n\nВсе параметры опциональные (имеют значения по умолчанию)\n\n| Параметр     | Значение по умолчанию                                                                            | Описание                                                                                                                                                                                                                                                                                                                           |\n| ------------ | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `fetch`      | глобальный fetch                                                                                 | Функция с интерфейсом [Fetch API](https://developer.mozilla.org/ru/docs/Web/API/Fetch_API). Если глобальный fetch не найден, то будет выброшена ошибка при попытке осуществить http запрос. Начиная с Node.js 18 [fetch](https://nodejs.org/dist/latest-v18.x/docs/api/globals.html#fetch) является частью стандартной библиотеки. |\n| `retry`      | функция вида `(thunk) =\u003e thunk()`                                                                | Функция для управления поведением при возникновении ошибок (см. [Повтор запроса при ошибке](#повтор-запроса-при-ошибке)).                                                                                                                                                                                                          |\n| `endpoint`   | `\"https://api.moysklad.ru/api\"`                                                                  | Точка доступа к API (хост точки доступа можно указать через переменную окружения `MOYSKLAD_HOST`, по умолчанию `api.moysklad.ru`)                                                                                                                                                                                                  |\n| `api`        | `\"remap\"`                                                                                        | Раздел API (можно задать через переменную окружения `MOYSKLAD_API`)                                                                                                                                                                                                                                                                |\n| `apiVersion` | `\"1.2\"`                                                                                          | Версия API (можно задать через переменную окружения `MOYSKLAD_{NAME}_API_VERSION`, где `{NAME}` - название API в верхнем регистре, напр. `MOYSKLAD_REMAP_API_VERSION`)                                                                                                                                                             |\n| `token`      | `undefined`                                                                                      | Токен доступа к API (см. [Аутентификация](#аутентификация))                                                                                                                                                                                                                                                                        |\n| `login`      | `undefined`                                                                                      | Логин для доступа к API (см. [Аутентификация](#аутентификация))                                                                                                                                                                                                                                                                    |\n| `password`   | `undefined`                                                                                      | Пароль для доступа к API (см. [Аутентификация](#аутентификация))                                                                                                                                                                                                                                                                   |\n| `emitter`    | `undefined`                                                                                      | экземпляр [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) для передачи [событий библиотеки](#события)                                                                                                                                                                                                 |\n| `userAgent`  | `moysklad/{ver} (+https://github.com/wmakeev/moysklad)`, где `{ver}` - текущая версия библиотеки | Содержимое заголовка \"User-Agent\" при выполнении запроса. Удобно использовать для контроля изменений через API на вкладке \"Аудит\". Можно задать через переменную окружения `MOYSKLAD_USER_AGENT`.                                                                                                                                  |\n\nЯвное задание параметра переопределяет значение заданное в соотв. переменной окружения.\n\n**Пример использования:**\n\n```js\nimport Moysklad from 'moysklad'\n\n// Явное указание используемой версии API\nconst moysklad = Moysklad({ apiVersion: '1.2' })\n```\n\n## Аутентификация\n\nЕсть несколько способов передачи параметров аутентификации:\n\n1. Напрямую при инициализации экземпляра\n\n   ```js\n   // Аутентификация по логину и паролю\n   const moysklad = Moysklad({ login, password })\n   ```\n\n   ```js\n   // Аутентификация по токену\n   const moysklad = Moysklad({ token })\n   ```\n\n2. Через глобальные переменные или переменные окружения\n\n   Если параметры аутентификации не указаны при инициализации клиента,\n\n   ```js\n   const moysklad = Moysklad()\n   ```\n\n   то будет проведен поиск параметров в следующем порядке:\n\n   1. Переменная окружения `process.env.MOYSKLAD_TOKEN`\n   2. Переменные окружения `process.env.MOYSKLAD_LOGIN` и `process.env.MOYSKLAD_PASSWORD`\n   3. Глобальная переменная `window.MOYSKLAD_TOKEN`\n   4. Глобальные переменные `window.MOYSKLAD_LOGIN` и `window.MOYSKLAD_PASSWORD`\n   5. Глобальная переменная `global.MOYSKLAD_TOKEN`\n   6. Глобальные переменные `global.MOYSKLAD_LOGIN` и `global.MOYSKLAD_PASSWORD`\n\n## Статические методы\n\n### getTimeString\n\n\u003e Преобразует локальную дату в строку в формате API МойСклад в часовом поясе Москвы\n\n```ts\nMoysklad.getTimeString(date: Date, includeMs?: boolean): string\n```\n\n**Параметры:**\n\n`date` - дата\n\n`includeMs` - если `true`, то в дату будут включены миллисекунды\n\n**Пример использования:**\n\n```js\nconst date = new Date('2017-02-01T07:10:11.123Z')\nconst timeString = Moysklad.getTimeString(date, true)\n\nassert.equal(timeString, '2017-02-01 10:10:11.123')\n```\n\n### parseTimeString\n\n\u003e Преобразует строку с датой в формате API МойСклад в объект даты (с учетом локального часового пояса и часового пояса API МойСклад)\n\n```ts\nMoysklad.parseTimeString(date: string) : Date\n```\n\n**Параметры:**\n\n`date` - дата в формате МойСклад (напр. `2017-04-08 13:33:00.123`)\n\n**Пример использования:**\n\n```js\nconst parsedDate = Moysklad.parseTimeString('2017-04-08 13:33:00.123')\n\nassert.equal(parsedDate.toISOString(), '2017-04-08T10:33:00.123Z')\n```\n\n### parseUrl (статический метод)\n\n\u003e Разбор url на составные компоненты\n\nАналогичен [parseUrl](#parseurl) методу экземпляра, за тем исключением, что\nна вход принимает только строку в формате href МойСклад.\n\n### buildFilter\n\n\u003e Возвращает строку фильтра по объекту `QueryFilter` (см. [filter](#filter))\n\n```js\nMoysklad.buildFilter({ name: { $st: 'foo' } })\n// 'code=123;name~=foo'\n```\n\n### buildQuery\n\n\u003e Формирует строку с параметрами запроса по объекту `Query` (см. [query](#query))\n\n```js\nMoysklad.buildQuery({\n  filter: { name: 'foo' },\n  limit: 100,\n  foo: 'bar'\n})\n\n// 'filter=name%3Dfoo\u0026limit=100\u0026foo=bar'\n```\n\n### getVersion\n\n\u003e Возвращает текущую версию библиотеки. Версия из package.json (поле `version`)\n\n## Методы экземпляра\n\n### GET\n\n\u003e GET запрос\n\n```ts\nms.GET(path: string, query?: object, options?: object): Promise\n```\n\n**Параметры:**\n\n`path` - [url ресурса](#path)\n\n`query` - [параметры запроса](#query)\n\n`options` - [опции запроса](#options-параметры-запроса)\n\n**Пример использования:**\n\n```js\nconst productsCollection = await ms.GET('entity/product', { limit: 50 })\n\nconst order = await ms.GET(`entity/customerorder/${orderId}`, {\n  expand: 'positions'\n})\n```\n\n### POST\n\n\u003e POST запрос\n\n```ts\nms.POST(\n  path: string,\n  payload?: object | Array\u003cobject\u003e,\n  query?: object,\n  options?: object\n): Promise\n```\n\n**Параметры:**\n\n`path` - [url ресурса](#path)\n\n`payload` - объект или коллекция объектов (будет преобразовано в строку методом `JSON.stringify`)\n\n`query` - [параметры запроса](#query)\n\n`options` - [опции запроса](#options-параметры-запроса)\n\n**Пример использования:**\n\n```js\nconst newProduct = await ms.POST('entity/product', { name: 'Новый товар' })\n```\n\nПо умолчанию, при массовом обновлении сущностей, если _хотя бы один_ из элементов в ответе содержит ошибку, то метод выбросит ошибку\n[MoyskladCollectionError](#moyskladcollectionerror) .\n\nЕсли такое поведение не является предпочтительным, то можно обрабатывать ошибки при массовом обновлении/создании объектов вручную (см. `muteCollectionErrors` в [параметрах запроса](#options-параметры-запроса)):\n\n```js\nconst updated = await ms.POST('entity/supply', supplyList, null, {\n  muteCollectionErrors: true\n})\n\nconst errors = updated\n  .filter(item =\u003e item.errors)\n  .map(item =\u003e item.errors[0].error)\n\nif (errors.length) {\n  console.log('Есть ошибки:', errors.join(', '))\n}\n\nconst supplyHrefList = updated\n  .filter(item =\u003e !item.errors)\n  .map(item =\u003e item.meta.href)\n```\n\n### PUT\n\n\u003e PUT запрос\n\n```ts\nms.PUT(\n  path: string | string[],\n  payload?: object,\n  query?: object,\n  options?: object\n) : Promise\n```\n\n**Параметры:**\n\n`path` - [url ресурса](#path)\n\n`payload` - обновляемый объект (будет преобразован в строку методом `JSON.stringify`)\n\n`query` - [параметры запроса](#query)\n\n`options` - [опции запроса](#options-параметры-запроса)\n\n**Пример использования:**\n\n```js\nconst updatedProduct = await ms.PUT(`entity/product/${id}`, product)\n```\n\n### DELETE\n\n\u003e DELETE запрос\n\n```ts\nms.DELETE(path: string, options?: object): Promise\n```\n\n**Параметры:**\n\n`path` - [url ресурса](#path)\n\n`options` - [опции запроса](#options-параметры-запроса)\n\nМетод `DELETE` возвращает `undefined` при успешном запросе.\n\n**Пример использования:**\n\n```js\nawait ms.DELETE(`entity/product/${product.id}`)\n```\n\n### getOptions\n\n\u003e Возвращает опции переданные в момент инициализации экземпляра библиотеки\n\n**Пример использования:**\n\n```js\nconst options = {\n  login: 'login',\n  password: 'password'\n}\n\nconst ms = Moysklad(options)\n\nconst msOptions = ms.getOptions()\n\nassert.ok(msOptions !== options)\nassert.equal(msOptions.login, 'login')\nassert.equal(msOptions.password, 'password')\n```\n\n### getVersion - метод экземпляра\n\n\u003e Аналогичен статическому методу [getVersion](#getversion)\n\n### buildUrl\n\n\u003e Формирует url запроса\n\n```ts\nms.buildUrl(url: string, query?: object): string\n```\n\n**Параметры:**\n\n`url` - полный url (должен соответствовать настройкам)\n\n`path` - [url ресурса](#path)\n\n`query` - [параметры запроса](#query)\n\n**Пример использования:**\n\n```js\nconst url = ms.buildUrl(\n  'https://api.moysklad.ru/api/remap/1.2/entity/customerorder?expand=positions',\n  { limit: 100 }\n)\n\nassert.equal(\n  url,\n  'https://api.moysklad.ru/api/remap/1.2/entity/customerorder?expand=positions\u0026limit=100'\n)\n```\n\n```js\nconst url = ms.buildUrl('entity/customerorder', { expand: 'positions' })\n\nassert.equal(\n  url,\n  'https://api.moysklad.ru/api/remap/1.2/entity/customerorder?expand=positions'\n)\n```\n\nМожно безопасно дублировать символы `/`, лишние знаки будут исключены из\nрезультирующего url\n\n```js\nconst positionUrl = `/positions/${posId}/`\n\nconst url = ms.buildUrl(`entity/customerorder/` + positionUrl)\n\nassert.equal(\n  url,\n  `https://api.moysklad.ru/api/remap/1.2/entity/customerorder/positions/${posId}`\n)\n```\n\n### parseUrl\n\n\u003e Разбор url на составные компоненты\n\n```ts\nms.parseUrl(url: string): {\n  endpoint: string\n  api: string\n  apiVersion: string\n  path: Array\u003cstring\u003e\n  query: object\n}\n```\n\n**Параметры:**\n\n`url` - url ресурса\n\n**Пример использования:**\n\n```js\nconst parsedUri = ms.parseUrl('https://api.moysklad.ru/api/remap/1.2/entity/customerorder?expand=positions')\n\nassert.deepEqual(parsedUri, {\n  endpoint: 'https://api.moysklad.ru/api',\n  api: 'remap'\n  apiVersion: '1.2',\n  path: ['entity', 'customerorder'],\n  query: {\n    expand: 'positions'\n  }\n})\n```\n\n### fetchUrl\n\n\u003e Выполнить запрос по указанному url\n\n```ts\nms.fetchUrl(url: string, options?: object): Promise\n```\n\n**Параметры:**\n\n`url` - url ресурса\n\n`options` - [опции запроса](#options-параметры-запроса)\n\n**Пример использования:**\n\n```js\nconst url = `https://api.moysklad.ru/api/remap/1.2/entity/customerorder/eb7bcc22-ae8d-11e3-9e32-002590a28eca`\n\nconst patch = { applicable: false }\n\nconst updatedOrder = await ms.fetchUrl(url, {\n  method: 'PUT',\n  body: JSON.stringify(patch)\n})\n```\n\n### Основные аргументы\n\n#### path\n\nСтрока.\n\n**Примеры:**\n\nUrl запроса можно указать полностью\n\n```js\nms.GET(\n  `https://api.moysklad.ru/api/remap/1.2/entity/customerorder/${ORDER_ID}/positions/${POSITION_ID}?expand=assortment`\n)\n```\n\nНо гораздо удобнее указывать путь только после версии API и выносить\nпараметры запроса в параметры метода. Полный url будет сгенерирован автоматически, согласно [настройкам экземпляра](#параметры-инициализации).\n\nНиже пример аналогичного запроса:\n\n```js\nms.GET(`entity/customerorder/${ORDER_ID}/positions/${POSITION_ID}`, {\n  expand: 'assortment'\n})\n```\n\nМожно безопасно дублировать символы `/`, лишние знаки будут исключены из\nрезультирующего url\n\n```js\nconst positionUrl = `/positions/${posId}`\n\nms.GET(`entity/customerorder/` + positionUrl)\n```\n\n#### query\n\n##### querystring\n\nВсе поля объекта запроса преобразуются в соответствующую строку запроса url. Некоторые поля могут подвергаться преобразованию (напр. поля [`filter`](#filter) и [`order`](#order)).\n\nПоле объекта запроса должно иметь тип: `string`, `number`, `boolean`, `null` или `undefined`, любое другое значение вызовет ошибку.\n\n```js\nconst query = {\n  str: 'some string',\n  num: 1,\n  bool: true,\n  nil: null, // будет добавлено в строку запроса с пустым значением\n  nothing: undefined, // поле будет пропущено\n  arr: ['str', 1, true, null, undefined]\n}\n\n// https://api.moysklad.ru/api/remap/1.2/entity/demand?str=some%20string\u0026num=1\u0026bool=true\u0026nil=\u0026arr=str\u0026arr=1\u0026arr=true\u0026arr=\nms.GET('entity/demand', query)\n```\n\n##### filter\n\nЕсли поле `filter` объект, то вложенные поля `filter` преобразуются в параметры фильтра в строке запроса в соответствии со следующими правилами:\n\n- `string`, `number`, `boolean` не проходят дополнительных преобразований (`key=value`)\n- `null` преобразуется в пустую строку (`key=`)\n- `Date` преобразуется в строку методом [`getTimeString`](#gettimestring) (`key=YYYY-MM-DD HH:mm:ss`)\n- `object` интерпретируется как набор селекторов или вложенных полей (см. пример ниже)\n\n**Пример фильтра:**\n\n```js\nconst query = {\n  filter: {\n    name: '00001',\n    code: [1, 2, '03'],\n    foo: new Date(2000, 0, 1),\n    state: {\n      name: 'Оформлен'\n    },\n    moment: {\n      $gt: new Date(2000, 0, 1),\n      $lte: new Date(2001, 0, 2, 10, 0, 15, 123)\n    },\n    bar: {\n      baz: 1,\n      $exists: true\n    }\n  }\n}\n```\n\nсоответствует следующему значению поля `filter` в запросе (даты переданы в часовом поясе +5):\n\n```txt\nbar!=;bar.baz=1;code=03;code=1;code=2;foo=1999-12-31 22:00:00;moment\u003c=2001-01-02 08:00:15.123;moment\u003e1999-12-31 22:00:00;name=00001;state.name=Оформлен\n```\n\nДля построения фильтра можно использовать селекторы в стиле Mongo (как в примере выше).\n\nПодробное описание всех возможных селекторов:\n\n| Селектор                             | Фильтр МойСклад               | Описание                   |\n| ------------------------------------ | ----------------------------- | -------------------------- |\n| `key: { $eq: value }`                | `key=value`                   | равно                      |\n| `key: { $ne: value }`                | `key!=value`                  | не равно                   |\n| `key: { $gt: value }`                | `key\u003evalue`                   | больше                     |\n| `key: { $gte: value }`               | `key\u003e=value`                  | больше или равно           |\n| `key: { $lt: value }`                | `key\u003cvalue`                   | меньше                     |\n| `key: { $lte: value }`               | `key\u003c=value`                  | меньше или равно           |\n| `key: { $st: value }`                | `key~=value`                  | начинается со строки       |\n| `key: { $et: value }`                | `key=~value`                  | заканчивается строкой      |\n| `key: { $contains: value }`          | `key~value`                   | содержит строку            |\n| `key: { $in: [..] }` или `key: [..]` | `key=value1;key=value2;...`   | входит в                   |\n| `key: { $nin: [..] }`                | `key!=value1;key!=value2;...` | не входит в                |\n| `key: { $exists: true }`             | `key!=`                       | наличие значения (не null) |\n| `key: { $exists: false }`            | `key=`                        | пустое значение (null)     |\n| `key: { $all: [{..}, ..] }`          |                               | объединение условий        |\n| `key: { $not: {..} }`                |                               | отрицание условия          |\n\nНа один ключ можно использовать несколько селекторов.\n\nПодробнее с правилами фильтрации можно ознакомится в документации МойСклад:\n\n- [Фильтрация выборки с помощью параметра filter](https://dev.moysklad.ru/doc/api/remap/1.2/#mojsklad-json-api-obschie-swedeniq-fil-traciq-wyborki-s-pomosch-u-parametra-filter)\n- [Оператор фильтрации \"подобие\"](https://dev.moysklad.ru/doc/api/remap/1.2/#mojsklad-json-api-obschie-swedeniq-operator-fil-tracii-quot-podobie-quot)\n- [Фильтрация](https://dev.moysklad.ru/doc/api/remap/1.2/workbook/#workbook-fil-traciq-listanie-poisk-i-sortirowka-fil-traciq)\n\n##### order\n\nЕсли поле `order` массив, то произойдет преобразование записи из формы массива в строку.\n\n**Примеры:**\n\n- `['name']` → `'name'`\n- `[['code','desc']]` → `'code,desc'`\n- `['name', ['code','desc']]` → `'name;code,desc'`\n- `['name,desc', ['code','asc'], ['moment']]` → `'name,desc;code,asc;moment'`\n\n👉 [examples/query.js](https://github.com/wmakeev/moysklad/blob/master/examples/query.js)\n\n##### expand и limit\n\nОбратите внимание на то, что если указано значение expand, то необходимо явно указать значение для limit меньше или равное 100, иначе expand [будет проигнорирован](https://dev.moysklad.ru/doc/api/remap/1.2/workbook/#workbook-chto-takoe-expand).\n\n#### options (параметры запроса)\n\nВсе поля указанные в объекте `options`, за исключением описанных в этом разделе, передаются напрямую в опции fetch ([fetch options](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch#options)).\n\nПоля описанные ниже обрабатываются только библиотекой moysklad и не передаются в fetch:\n\n| Поле                        | Тип       | Описание                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |\n| --------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `rawResponse`               | `boolean` | Если `true`, то метод вернет исходный объект [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response). Код и содержимое ответа не проверяется на ошибки. Тело ответа нужно [прочитать самостоятельно](https://github.com/nodejs/undici?tab=readme-ov-file#garbage-collection).                                                                                                                                                                                                                                                                                            |\n| `includeResponse`           | `boolean` | Если `true`, то метод вернет массив из двух элементов - результат и объект [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response). Ошибки будут обработаны как при обычном запросе.                                                                                                                                                                                                                                                                                                                                                                                     |\n| `rawRedirect`               | `boolean` | Если ответ сервера с кодом в диапазоне 300-399 (редирект), то будет выброшена ошибка [MoyskladUnexpectedRedirectError](#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`. |\n| `muteApiErrors`             | `boolean` | Если `true` и запрос завершился ошибкой API, то метод вернет объект с описанием ошибки из тела ответа как результат. Такое поведение уместно если вы хотите вручную обработать ошибку. Прочие ошибки, которые не содержат JSON ответа (напр. ошибки соединения), продолжат выбрасываться в штатном режиме. Для игнорирования ошибок только внутри коллекций, используйте опцию `muteCollectionErrors`.                                                                                                                                                                                 |\n| `muteCollectionErrors`      | `boolean` | Если `true`, то все ошибки внутри коллекций при массовом обновлении сущностей будут проигнорированы. В этом случае ошибки нужно будет отфильтровать и обработать вручную.                                                                                                                                                                                                                                                                                                                                                                                                              |\n| `precision`                 | `boolean` | Если `true`, то в запрос будет включен заголовок `X-Lognex-Precision` со значением `true` (отключение округления цен и себестоимости до копеек).                                                                                                                                                                                                                                                                                                                                                                                                                                       |\n| ~~`webHookDisable`~~        | `boolean` | (deprecated) Если `true`, то в запрос будет включен заголовок `X-Lognex-WebHook-Disable` со значением `true` (отключить уведомления вебхуков в контексте данного запроса). Не рекомендуется использовать данную опцию, применяйте `webHookDisableByPrefix`.                                                                                                                                                                                                                                                                                                                            |\n| `webHookDisableByPrefix`    | `string`  | Префикс url для выборочного отключения вебхуков, будет добавлен в качестве значения заголовка `X-Lognex-WebHook-DisableByPrefix`.                                                                                                                                                                                                                                                                                                                                                                                                                                                      |\n| `downloadExpirationSeconds` | `number`  | Устанавливает значение для заголовка `X-Lognex-Download-Expiration-Seconds` (подробнее см. [Ссылки на файлы](https://dev.moysklad.ru/doc/api/remap/1.2/#mojsklad-json-api-obschie-swedeniq-ssylki-na-fajly))                                                                                                                                                                                                                                                                                                                                                                           |\n\n\u003cdetails\u003e\n  \u003csummary\u003eПримеры\u003c/summary\u003e\n\n- Формирование заполненного шаблона печатной формы и получение ссылки для загрузки ([examples/download-print-form.js](https://github.com/wmakeev/moysklad/blob/master/examples/download-print-form.js)):\n\n  ```js\n  import path from 'node:path'\n  import { writeFile } from 'node:fs/promises'\n  import { fetch } from 'undici'\n  import Moysklad from 'moysklad'\n\n  const TEMPLATE_ID = '8a686b8a-9e4a-11e5-7a69-97110004af3e'\n  const DEMAND_ID = '13abf361-e9c6-45ea-a940-df70289a7f95'\n\n  async function downloadPrintForm() {\n    const ms = Moysklad({ fetch })\n\n    const body = {\n      template: {\n        meta: {\n          href: ms.buildUrl(\n            `entity/demand/metadata/customtemplate/${TEMPLATE_ID}`\n          ),\n          type: 'customtemplate',\n          mediaType: 'application/json'\n        }\n      },\n      extension: 'pdf'\n    }\n\n    /** @type {import('undici').Response} */\n    const response = await ms.POST(\n      `entity/demand/${DEMAND_ID}/export`,\n      body,\n      null,\n      // вернуть результат запроса с редиректом без предварительного разбора\n      { rawRedirect: true }\n    )\n\n    const location = response.headers.get('location')\n\n    console.log(location)\n    // 'https://print-prod.moysklad.ru/temp/.../00123.pdf'\n\n    const formResponse = await fetch(location)\n\n    const blob = await formResponse.blob()\n\n    const buffer = Buffer.from(await blob.arrayBuffer())\n\n    await writeFile(path.join(process.cwd(), '__temp/form.pdf'), buffer)\n  }\n\n  downloadPrintForm()\n  ```\n\n- Указание HTTP заголовка\n\n  ```js\n  const ms = Moysklad()\n\n  const folder = {\n    meta: {\n      type: 'productfolder',\n      href: ms.buildUrl(`entity/productfolder/${FOLDER_ID}`)\n    },\n    description: 'Новое описание группы товаров'\n  }\n\n  // Указываем кастомный заголовок X-Lognex-WebHook-Disable для PUT запроса\n  const updatedFolder = await ms.PUT(\n    `entity/productfolder/${FOLDER_ID}`,\n    folder,\n    null,\n    {\n      // вместо этого можно использовать webHookDisable: true\n      headers: {\n        'X-Lognex-WebHook-Disable': true\n      }\n    }\n  )\n\n  assert.equal(updatedFolder.description, folder.description)\n  ```\n\n- Автоматический редирект\n\n  Идентификаторы товаров в приложении МойСклад отличаются от идентификаторов в API. Поэтому, при запросе товара по id из приложения, будет выполнен редирект на другой href.\n\n  ```js\n  const ms = Moysklad({ fetch })\n\n  // https://api.moysklad.ru/app/#good/edit?id=cb277549-34f4-4029-b9de-7b37e8e25a54\n  const PRODUCT_UI_ID = 'cb277549-34f4-4029-b9de-7b37e8e25a54'\n\n  // Error: 308 Permanent Redirect\n  await ms.fetchUrl(ms.buildUrl(`entity/product/${PRODUCT_UI_ID}`))\n\n  // Указана опция redirect\n  const product = await ms.fetchUrl(\n    ms.buildUrl(`entity/product/${PRODUCT_UI_ID}`),\n    { redirect: 'follow' }\n  )\n\n  assert.ok(product) // OK\n  ```\n\n\u003c/details\u003e\n\n## Управление потоком запросов\n\nДля управления потоком запросов с целью уложиться в [ограничения](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).\n\nПланировщик считывает информацию о текущих лимитах из заголовков ответов API МойСклад и ограничивает скорость выполнения запросов, предотвращая появление ошибок `429 Too Many Requests`.\n\nВ случае если ошибки 429 избежать не удалось, запрос будет повторен при восстановлении доступного лимита.\n\n**Пример использования:**\n\n```ts\nimport Moysklad from 'moysklad'\nimport { fetch } from 'undici'\nimport { wrapFetchApi } from 'moysklad-fetch-planner'\n\nconst ms = Moysklad({ fetch: wrapFetchApi(fetch) })\n```\n\n## Обработка ошибок\n\n### Повтор запроса при ошибке\n\nПри инициализации клиента есть возможность задать свою логику обработки ошибочных запросов. В примере ниже код для автоматического повтора запроса при получении ошибки.\n\n\u003cdetails\u003e\n  \u003csummary\u003eПример\u003c/summary\u003e\n\n```js\nimport Moysklad from 'moysklad'\nimport { wrapFetch } from 'moysklad-fetch-planner'\nimport pRetry from 'p-retry'\nimport { fetch } from 'undici'\n\n/**\n * Пример настройки клиента для API МойСклад.\n *\n * 1. Подключается планировщик запросов `moysklad-fetch-planner` для автоматического\n * контроля за лимитами для предотвращения возникновения ошибки `429 Too Many Request`.\n *\n * 2. Подключается механизм повтора ошибочных запросов для случаев когда ошибка\n * могла быть вызвана временными неполадками в процессе выполнения запроса (для\n * примера используется npm библиотека `p-retry`).\n */\nconst ms = Moysklad({\n  fetch: wrapFetch(fetch),\n  retry: (thunk, signal) =\u003e {\n    return pRetry(thunk, {\n      retries: 2,\n      shouldRetry: Moysklad.shouldRetryError,\n      onFailedAttempt: error =\u003e {\n        console.log(\n          `Attempt ${error.attemptNumber} failed. There are ${error.retriesLeft} retries left.`\n        )\n      },\n      signal\n    })\n  }\n})\n\ntry {\n  // Запрос с ошибкой в url-запроса повторяться не будет, если API МойСклад\n  // вернул об этом сообщение.\n  await ms.GET('foo')\n  // ↳ Attempt 1 failed. There are 2 retries left.\n} catch (err) {\n  console.log(err)\n  // ↳ MoyskladApiError: Неопознанный путь: https://api.moysklad.ru/api/remap/1.2/foo (https://dev.moysklad.ru/doc/api/remap/1.2/#error_1002)\n}\n\ntry {\n  // Запрос с ошибкой которая имеет HTTP код `503` (в том числе, и другие коды\n  // `5xx`) будет повторяться. Т.к. подобная ошибка иногда может быть вызвана\n  // временными сбоями на стороне сервера API МойСклад.\n  await ms.fetchUrl(\n    'https://api.moysklad.ru/api/remap/1.0/entity/customerorder'\n  )\n  // ↳ Attempt 1 failed. There are 2 retries left.\n  // ↳ Attempt 2 failed. There are 1 retries left.\n  // ↳ Attempt 3 failed. There are 0 retries left.\n} catch (err) {\n  console.log(err)\n  // ↳ MoyskladRequestError: 503 Service Unavailable\n}\n\ntry {\n  // Запрос с ошибкой которая имеет код `ENOTFOUND` (и ряд других) будет\n  // повторяться. Т.к. такая ошибка иногда может быть вызвана сбоями в процессе\n  // HTTP соединения.\n  await ms.fetchUrl('https://example')\n  // ↳ Attempt 1 failed. There are 2 retries left.\n  // ↳ Attempt 2 failed. There are 1 retries left.\n  // ↳ Attempt 3 failed. There are 0 retries left.\n} catch (err) {\n  console.log(err)\n  // ↳ TypeError: fetch failed\n}\n\n// Запросы вызвавшие ошибки с кодами 429 обрабатываются и повторяются внутри\n// планировщика. При подключении планировщика обрабатывать в `retry` такие\n// ошибки не нужно.\n```\n\n\u003c/details\u003e\n\n### Виды ошибок\n\nВ рамках работы с библиотекой выделены следующие виды ошибок:\n\n| №   | Название ошибки         | Класс ошибки                                                        | Наследует                                     | Описание                                                                                                                                            |\n| --- | ----------------------- | ------------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 1   | **Ошибка библиотеки**   | [MoyskladError](#moyskladerror)                                     | Error                                         | Ошибка библиотеки (например не верно указаны параметры одного из методов).                                                                          |\n| 2   | **Ошибка запроса**      | [MoyskladRequestError](#moyskladrequesterror)                       | [MoyskladError](#moyskladerror)               | Ответ получен с кодом ошибки, тело ответа НЕ содержит JSON с описанием ошибки в формате МойСклад.                                                   |\n| 3   | **Ошибка API МойСклад** | [MoyskladApiError](#moyskladapierror)                               | [MoyskladRequestError](#moyskladrequesterror) | Ответ получен с кодом ошибки, тело ответа содержит JSON с описанием ошибки в формате МойСклад.                                                      |\n| 4   | **Ошибка в коллекции**  | [MoyskladCollectionError](#moyskladcollectionerror)                 | [MoyskladApiError](#moyskladapierror)         | Ошибка в одном из элементов внутри коллекции.                                                                                                       |\n| 5   | **Неявный редирект**    | [MoyskladUnexpectedRedirectError](#moyskladunexpectedredirecterror) | [MoyskladRequestError](#moyskladrequesterror) | Ошибка возникает когда запрос вернул перенаправление (код `3xx`) и явно не указана опция запроса `rawRedirect` (опция `redirect` не равна `follow`) |\n\nБиблиотека дает возможность указать параметры запроса `muteApiErrors` и `muteCollectionErrors` для игнорирования ошибок API п.3 и п.4 соответственно.\n\nОшибки глобального fetch модуля или переданного при инициализации экземпляра не перехватываются внутри библиотеки. Т.е. все описанные выше ошибки, связанные с выполнением запроса, формируются уже после анализа полученного ответа.\n\n#### MoyskladError\n\n\u003e Внутренняя ошибка библиотеки не связанная с выполнением запроса к API\n\nНаследует класс `Error`\n\n\u003cdetails\u003e\n  \u003csummary\u003eПримеры\u003c/summary\u003e\n\nКод с ошибкой:\n\n```js\nawait ms.GET('entity/product', {\n  filter: 123\n})\n```\n\nСтруктура ошибки:\n\n```json\n{\n  \"name\": \"MoyskladError\",\n  \"message\": \"Поле filter запроса должно быть строкой или объектом\"\n}\n```\n\n\u003c/details\u003e\n\n#### MoyskladRequestError\n\n\u003e Ошибка при выполнении запроса\n\nНаследует класс [MoyskladError](#moyskladerror)\n\n\u003cdetails\u003e\n  \u003csummary\u003eПримеры\u003c/summary\u003e\n\nКод с ошибкой:\n\n```js\nconst ms = Moysklad({ fetch, api: 'foo', apiVersion: '0' })\n\nawait ms.GET('foo/bar')\n```\n\nСтруктура ошибки:\n\n```json\n{\n  \"name\": \"MoyskladRequestError\",\n  \"message\": \"404 Not Found\",\n  \"url\": \"https://api.moysklad.ru/api/foo/0/foo/bar\",\n  \"status\": 404,\n  \"statusText\": \"Not Found\"\n}\n```\n\n\u003c/details\u003e\n\n#### MoyskladApiError\n\n\u003e Ошибка API МойСклад\n\nНаследует класс [MoyskladRequestError](#moyskladrequesterror)\n\nОшибка формируется в случае, если API помимо HTTP кода ошибки, так же вернуло стандартное описание ошибки МойСклад в формате JSON. В обратном случае (ответ не содержит JSON с ошибкой) будет выброшена ошибка [MoyskladRequestError](#moyskladrequesterror)\n\n\u003cdetails\u003e\n  \u003csummary\u003eПримеры\u003c/summary\u003e\n\nКод с ошибкой:\n\n```js\nawait ms.GET('entity/product2')\n```\n\nСтруктура ошибки:\n\n```json\n{\n  \"name\": \"MoyskladApiError\",\n  \"message\": \"Неизвестный тип: 'product2' (https://dev.moysklad.ru/doc/api/remap/1.2/#error_1005)\",\n  \"url\": \"https://api.moysklad.ru/api/remap/1.2/entity/product2\",\n  \"status\": 412,\n  \"statusText\": \"Precondition Failed\",\n  \"code\": 1005,\n  \"moreInfo\": \"https://dev.moysklad.ru/doc/api/remap/1.2/#error_1005\",\n  \"errors\": [\n    {\n      \"error\": \"Неизвестный тип: 'product2'\",\n      \"code\": 1005,\n      \"moreInfo\": \"https://dev.moysklad.ru/doc/api/remap/1.2/#error_1005\"\n    }\n  ]\n}\n```\n\nМожно игнорировать ошибку API, указав `muteApiErrors:true` в опциях запроса.\n\n```js\nconst rawError1 = await ms.GET('entity/product2', null, {\n  muteApiErrors: true\n})\n\nconsole.log(rawError1.errors[0].error)\n// Неизвестный тип: 'product2'\n```\n\n\u003c/details\u003e\n\n#### MoyskladCollectionError\n\n\u003e Ошибка в коллекции при массовом создании/изменении сущностей\n\nНаследует класс [MoyskladApiError](#moyskladapierror)\n\nОшибка выбрасывается когда возвращаемая коллекция содержит хотя бы одну ошибку.\n\nНапример, когда при массовом обновлении нескольких объектов часть из них не были обновлены, то API вернет массив с результатами в части которых будет указана ошибка.\n\n\u003cdetails\u003e\n  \u003csummary\u003eПримеры\u003c/summary\u003e\nКод с ошибкой:\n\n```js\nawait ms.POST('entity/product', [\n  { foo: 'bar' },\n  {\n    meta: {\n      type: 'product',\n      href: ms.buildUrl(`entity/product/${uuidFromApi}`)\n    },\n    weight: 42\n  },\n  { name: 123 }\n])\n```\n\nСтруктура ошибки:\n\n```json\n{\n  \"name\": \"MoyskladCollectionError\",\n  \"message\": \"Ошибка сохранения объекта: поле 'name' не может быть пустым или отсутствовать (https://dev.moysklad.ru/doc/api/remap/1.2/#error_3000)\",\n  \"url\": \"https://api.moysklad.ru/api/remap/1.2/entity/product\",\n  \"status\": 400,\n  \"statusText\": \"Bad Request\",\n  \"code\": 3000,\n  \"moreInfo\": \"https://dev.moysklad.ru/doc/api/remap/1.2/#error_3000\",\n  \"line\": 1,\n  \"column\": 3,\n  \"errors\": [\n    {\n      \"error\": \"Ошибка сохранения объекта: поле 'name' не может быть пустым или отсутствовать\",\n      \"code\": 3000,\n      \"parameter\": \"name\",\n      \"moreInfo\": \"https://dev.moysklad.ru/doc/api/remap/1.2/#error_3000\",\n      \"line\": 1,\n      \"column\": 3\n    },\n    {\n      \"error\": \"Ошибка формата: значение поля 'name' не соответствует типу строка\",\n      \"code\": 2016,\n      \"moreInfo\": \"https://dev.moysklad.ru/doc/api/remap/1.2/#error_2016\",\n      \"line\": 1,\n      \"column\": 169\n    }\n  ],\n  \"errorsIndexes\": [\n    [\n      0,\n      [\n        {\n          \"error\": \"Ошибка сохранения объекта: поле 'name' не может быть пустым или отсутствовать\",\n          \"code\": 3000,\n          \"parameter\": \"name\",\n          \"moreInfo\": \"https://dev.moysklad.ru/doc/api/remap/1.2/#error_3000\",\n          \"line\": 1,\n          \"column\": 3\n        }\n      ]\n    ],\n    [\n      2,\n      [\n        {\n          \"error\": \"Ошибка формата: значение поля 'name' не соответствует типу строка\",\n          \"code\": 2016,\n          \"moreInfo\": \"https://dev.moysklad.ru/doc/api/remap/1.2/#error_2016\",\n          \"line\": 1,\n          \"column\": 169\n        }\n      ]\n    ]\n  ]\n}\n```\n\nМожно игнорировать ошибки в коллекции, указав `muteCollectionErrors:true`\nв опциях запроса.\n\n```js\nconst result2 = await ms.POST(\n  'entity/product',\n  [\n    { foo: 'bar' },\n    {\n      meta: {\n        type: 'product',\n        href: ms.buildUrl(`entity/product/${uuidFromApi}`)\n      },\n      weight: 42\n    },\n    { name: 123 }\n  ],\n  null,\n  {\n    muteCollectionErrors: true\n  }\n)\n\nconst collItemError = result2.find(it =\u003e it.errors)\n\nif (collItemError) {\n  console.log(collItemError.errors[0].error)\n  // Ошибка сохранения объекта: поле 'name' не может быть пустым или отсутствовать\n}\n```\n\n\u003c/details\u003e\n\n#### MoyskladUnexpectedRedirectError\n\n\u003e Ошибка если запрос вернул перенаправление (код `3xx`), когда явно не указана опция запроса `rawRedirect` и опция `redirect` не равна `follow`\n\nНаследует класс [MoyskladRequestError](#moyskladrequesterror)\n\n\u003cdetails\u003e\n  \u003csummary\u003eПримеры\u003c/summary\u003e\n\n```js\n/** id товара из приложения МойСклад */\nconst uuidFromApp = 'cb277549-34f4-4029-b9de-7b37e8e25a54'\n\n/** id товара из API (отличается от id из приложения) */\nlet uuidFromApi\n\nconst getProduct = id =\u003e ms.GET(`entity/product/${id}`)\n\ntry {\n  await getProduct(uuidFromApp)\n} catch (err) {\n  if (err instanceof Moysklad.MoyskladUnexpectedRedirectError) {\n    uuidFromApi = ms.parseUrl(err.location).path.pop()\n    await getProduct(uuidFromApi)\n  } else {\n    throw err\n  }\n}\n```\n\nМожно обработать перенаправление без перехвата ошибки:\n\n```js\nlet product = await ms.GET(`entity/product/${uuidFromApp}`, null, {\n  rawRedirect: true\n})\n\nif (product instanceof Response) {\n  uuidFromApi = ms.parseUrl(product.headers.get('location')).path.pop()\n\n  product = await ms.GET(`entity/product/${uuidFromApi}`)\n}\n\nconsole.log(product.id === uuidFromApp) // false\n```\n\nИли использовать автоматическое перенаправление, указав значение `follow` в опции `redirect`:\n\n```js\nconst product = await ms.GET(`entity/product/${uuidFromApp}`, null, {\n  redirect: 'follow'\n})\n\nconsole.log(product.id === uuidFromApp) // false\n```\n\n\u003c/details\u003e\n\n## События\n\n| Событие         | Передаваемый объект                           | Момент наступления            |\n| --------------- | --------------------------------------------- | ----------------------------- |\n| `request`       | `{ requestId, url, options }`                 | Отправлен http запрос         |\n| `response`      | `{ requestId, url, options, response }`       | Получен ответ на запрос       |\n| `response:body` | `{ requestId, url, options, response, body }` | Загружено тело ответа         |\n| `error`         | `Error`, `{ requestId }`                      | Ошибка при выполнении запроса |\n\n\u003cdetails\u003e\n  \u003csummary\u003eПримеры\u003c/summary\u003e\n\n```js\nimport { fetch } from 'undici'\nimport { EventEmitter } from 'events'\nimport Moysklad from 'moysklad'\n\n/** @type {Moysklad.MoyskladEmitter} */\nconst emitter = new EventEmitter()\n\nconst ms = Moysklad({ fetch, emitter })\n\nemitter\n  .on('request', ({ requestId, url, options }) =\u003e {\n    console.log(`${requestId} ${options.method} ${url}`)\n  })\n  .on('error', (err, { requestId }) =\u003e {\n    console.log(requestId, err)\n  })\n\nms.GET('entity/customerorder', { limit: 1 }).then(res =\u003e {\n  console.log('Order name: ' + res.rows[0].name)\n})\n```\n\nБолее подробный пример смотрите в [examples/events.js](https://github.com/wmakeev/moysklad/blob/master/examples/events.js).\n\n\u003c/details\u003e\n\n## История изменений\n\n[CHANGELOG.md](https://github.com/wmakeev/moysklad/blob/master/CHANGELOG.md)\n\n## Планы развития\n\nПланируется немного переработанная версия библиотеки в другом репозитории и npm пакете. Без концептуальных изменений, но с убранным легаси кодом.\n\n- Переписать на TypeScript\n- Добавить новый метод для формирования объекта запроса\n- Убрать всё легаси (в том числе то, что тянет лишние зависимости - \"have2\" и \"stampit\")\n- Более развернутая документация с автогенерацией части описаний методов\n\n## TODO\n\nСвалка мыслей по развитию библиотеки - [TODO.md](https://github.com/wmakeev/moysklad/blob/master/TODO.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwmakeev%2Fmoysklad","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwmakeev%2Fmoysklad","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwmakeev%2Fmoysklad/lists"}