moysklad
    TypeScript icon, indicating that this package has built-in type declarations

    0.10.1 • Public • Published

    moysklad

    moysklad

    npm Travis Coveralls Code Climate Code Climate JavaScript Style Guide

    Библиотека для взаимодействия с JSON API сервиса МойСклад для node.js и браузера.

    ВНИМАНИЕ! Библиотека находится в стадии разработки и становления. API к релизной версии может быть изменен. Перед обновлением версии смотрите историю изменений.

    Содержание

    Особенности

    Библиотека представляет максимально простой и прозрачный интерфейс к существующим методам API МойСклад и не выполняет никаких внутренних преобразований отправляемых и получаемых данных.

    При необходимости, можно расширить возможности библиотеки внешними модулями.

    Установка

    Поддерживаются версии node.js >=12

    $ npm install moysklad
    

    для работы библиотеки в node.js, дополнительно нужно установить полифил для Fetch API или явно указать модуль с соответствующим интерфейсом при создании экземпляра библиотеки

    Полифил:

    $ npm install isomorphic-fetch
    
    require('isomorphic-fetch') // polyfill
    
    const Moysklad = require('moysklad')
    
    const moysklad = Moysklad()

    Модуль:

    $ npm install node-fetch
    
    const fetch = require('node-fetch')
    
    const Moysklad = require('moysklad')
    
    const moysklad = Moysklad({ fetch })

    В примерах выше приведены две наиболее популярные библиотеки реализующие Fetch API в node.js

    Для работы с библиотекой в браузере установка полифила или отдельного модуля не требуется.

    Использование

    const Moysklad = require('moysklad')
    
    // Для инициализации экземпляра библиотеки указывать ключевое слово new не нужно
    const ms = Moysklad({ login, password })
    
    ms.GET('entity/customerorder', {
      filter: {
        applicable: true,
        state: {
          name: 'Отгружен'
        },
        sum: { $gt: 1000000, $lt: 2000000 }
      },
      limit: 10,
      order: 'moment,desc',
      expand: 'agent'
    }).then(({ meta, rows }) => {
      console.log(
        `Последние ${meta.limit} из ${meta.size} проведенных заказов ` +
          `в статусе "Отгружен" на сумму от 10000 до 20000 руб`
      )
    
      // Выводим имя заказа, имя контрагента и сумму заказа для всех позиций
      rows.forEach(row => {
        console.log(`${row.name} ${row.agent.name} ${row.sum / 100}`)
      })
    })

    С другими примерами использования можно ознакомиться в папке examples

    Параметры инициализации

    Все параметры опциональные (имеют значения по умолчанию)

    Параметр Значение по умолчанию Описание
    fetch глобальный fetch Функция с интерфейсом Fetch API. Если глобальный fetch не найден, то будет выброшена ошибка при попытке осуществить http запрос.
    endpoint "https://online.moysklad.ru/api" Точка досупа к API
    api "remap" Раздел API
    apiVersion "1.2" Версия API
    token undefined Токен доступа к API (см. Аутентификация)
    login undefined Логин для доступа к API (см. Аутентификация)
    password undefined Пароль для доступа к API (см. Аутентификация)
    emitter undefined экземляр EventEmitter для передачи событий библиотеки
    userAgent moysklad/{version} (+https://github.com/wmakeev/moysklad) Содержимое заголовка "User-Agent" при выполнении запроса. Удобно использовать для контроля изменений через API на вкладке "Аудит".

    Некоторые внешние расширения могут добавлять свои дополнительные параметры.

    Пример использования:

    const Moysklad = require('moysklad')
    
    // Явное указание используемой версии API
    const moysklad = Moysklad({ apiVersion: '1.2' })

    Аутентификация

    Есть несколько способов передачи параметров аутентификации:

    1. Напрямую при инициализации экземпляра

      // Аутентификация по логину и паролю
      const moysklad = Moysklad({ login, password })
      // Аутентификация по токену
      const moysklad = Moysklad({ token })
    2. Через глобальные переменные или переменные окружения

      Если параметры аутентификации не указаны при инициализации клиента,

      const moysklad = Moysklad()

      то будет проведен поиск параметров в следующем порядке:

      1. Переменная окружения process.env.MOYSKLAD_TOKEN
      2. Переменные окружения process.env.MOYSKLAD_LOGIN и process.env.MOYSKLAD_PASSWORD
      3. Глобальная переменная global.MOYSKLAD_TOKEN
      4. Глобальные переменные global.MOYSKLAD_LOGIN и global.MOYSKLAD_PASSWORD

    Расширения

    Библиотеку можно расширять дополнительными возможностями, подключая внешние модули расширений.

    Пример подключения расширения для очереди запросов:

    const MoyskladCore = require('moysklad')
    const MoyskladQueueExtension = require('moysklad-extension-queue')
    
    const Moysklad = MoyskladCore.compose(MoyskladQueueExtension)
    
    const moysklad = Moysklad({
      queue: true // включение очереди запросов
    })

    API

    Статические методы

    getTimeString

    Преобразует локальную дату в строку в формате API МойСклад в часовом поясе Москвы

    Moysklad.getTimeString(date: Date, includeMs?: boolean): string

    Параметры:

    date - дата

    includeMs - если true, то в дату будут включены миллисекунды

    Пример использования:

    const date = new Date('2017-02-01T07:10:11.123Z')
    const timeString = Moysklad.getTimeString(date, true)
    
    assert.equal(timeString, '2017-02-01 10:10:11.123')

    Если необходимо работать с датами в часовом поясе отличном от часового пояса машины, где используется библиотека, то часовой пояс можно задать через глобальную переменную MOYSKLAD_TIMEZONE.

    Например, предположим, что локальный часовой пояс +5, но дату и время необходимо интерпретировать, как будто вы находитесь в часовом поясе +6.

    В этом случае, необходимо указать в переменной MOYSKLAD_TIMEZONE часовой пояс +6 в формате минут 300 (+5 ч * 60 мин).

    // Локальный часовой пояс +5
    // process.env.MOYSKLAD_TIMEZONE === 360 // +6 (Омск)
    
    const date = new Date('2017-02-01T10:10:11')
    const timeString = Moysklad.getTimeString(date)
    
    assert.equal(timeString, '2017-02-01 07:10:11')

    parseTimeString

    Преобразует строку с датой в формате API МойСклад в объект даты (с учетом локального часового пояса и часового пояса API МойСклад)

    Moysklad.parseTimeString(date: string) : Date

    Параметры:

    date - дата в формате МойСклад (напр. 2017-04-08 13:33:00.123)

    Пример использования:

    const parsedDate = Moysklad.parseTimeString('2017-04-08 13:33:00.123')
    
    assert.equal(parsedDate.toISOString(), '2017-04-08T10:33:00.123Z')

    Если необходимо работать с датами в часовом поясе отличном от часового пояса машины, где используется библиотека, то часовой пояс можно задать через глобальную переменную MOYSKLAD_TIMEZONE.

    Например, предположим, что локальный часовой пояс +5, но дату и время необходимо интерпретировать, как будто вы находитесь в часовом поясе +6.

    В этом случае, необходимо указать в переменной MOYSKLAD_TIMEZONE часовой пояс +6 в формате минут 300 (+5 ч * 60 мин).

    // Локальный часовой пояс +5 (Екатеринбург)
    // process.env.MOYSKLAD_TIMEZONE === 360 // +6 (Омск)
    
    const parsedDate = Moysklad.parseTimeString('2017-04-08 10:33:00')
    
    assert.equal(
      parsedDate.toTimeString(),
      // Время как в Омске в часовом поясе +6 (+3 часа к часовому поясу Москвы)
      '13:33:00 GMT+0500 (Yekaterinburg Standard Time)'
    )

    Методы экземпляра

    GET

    GET запрос

    ms.GET(path: string, query?: object, options?: object): Promise

    Параметры:

    path - url ресурса

    query - параметры запроса

    options - опции запроса

    Пример использования:

    const productsCollection = await ms.GET('entity/product', { limit: 50 })
    
    const order = await ms.GET(`entity/customerorder/${orderId}`, {
      expand: 'positions'
    })

    POST

    POST запрос

    ms.POST(
      path: string,
      payload?: object | Array<object>,
      query?: object,
      options?: object
    ): Promise

    Параметры:

    path - url ресурса

    payload - объект или коллекция объектов (будет преобразовано в строку методом JSON.stringify)

    query - параметры запроса

    options - опции запроса

    Пример использования:

    const newProduct = await ms.POST('entity/product', { name: 'Новый товар' })

    По умолчанию, при массовом обновлении сущностей, если хотябы один из элементов в ответе содержит ошибку, то метод вызовет ошибку MoyskladApiError .

    Если такое поведение не является предпочтительным, то можно обрабатывать ошибки при массовом обновлении/создании объектов вручную (см. muteErrors в параметрах запроса):

    const updated = await ms.POST('entity/supply', supplyList, null, {
      muteErrors: true
    })
    
    const errors = updated
      .filter(item => item.errors)
      .map(item => item.errors[0].error)
    
    if (errors.length) {
      console.log('Есть ошибки:', errors.join(', '))
    }
    
    const supplyHrefs = updated
      .filter(item => !item.errors)
      .map(item => item.meta.href)

    TODO С другой стороны, в примере выше можно получить другой вид ошибки. Например timeout и метод вернет undefined. Выходит, нужно конкретизировать тип ошибки в muteErrors.

    PUT

    PUT запрос

    ms.PUT(
      path: string | string[],
      payload?: object,
      query?: object,
      options?: object
    ) : Promise

    Параметры:

    path - url ресурса

    payload - обнвляемый объект (будет преобразован в строку методом JSON.stringify)

    query - параметры запроса

    options - опции запроса

    Пример использования:

    const updatedProduct = await ms.PUT(`entity/product/${id}`, product)

    DELETE

    DELETE запрос

    ms.DELETE(path: string, options?: object): Promise

    Параметры:

    path - url ресурса

    options - опции запроса

    Метод DELETE возвращает undefined при успешном запросе.

    Пример использования:

    await ms.DELETE(`entity/product/${product.id}`)

    getOptions

    Возвращает опции переданные в момент инициализации экземпляра библиотеки

    Пример использования:

    const options = {
      login: 'login',
      password: 'password'
    }
    
    const ms = Moysklad(options)
    
    const msOptions = ms.getOptions()
    
    assert.ok(msOptions !== options)
    assert.equal(msOptions.login, 'login')
    assert.equal(msOptions.password, 'password')

    buildUrl

    Формирует url запроса

    ms.buildUrl(url: string, query?: object): string

    Параметры:

    url - полный url (должен соответствовать настройкам)

    path - url ресурса

    query - параметры запроса

    Пример использования:

    const url = ms.buildUrl(
      'https://online.moysklad.ru/api/remap/1.2/entity/customerorder?expand=positions',
      { limit: 100 }
    )
    
    assert.equal(
      url,
      'https://online.moysklad.ru/api/remap/1.2/entity/customerorder?expand=positions&limit=100'
    )
    const url = ms.buildUrl('entity/customerorder', { expand: 'positions' })
    
    assert.equal(
      url,
      'https://online.moysklad.ru/api/remap/1.2/entity/customerorder?expand=positions'
    )

    Можно безопасно дублировать символы /, лишние знаки будут исключены из результирующего url

    const positionUrl = `/positions/${posId}/`
    
    const url = ms.buildUrl(`entity/customerorder/` + positionUrl)
    
    assert.equal(url, `entity/customerorder/positions/${posId}`)

    parseUrl

    Разбор url на составные компоненты

    ms.parseUrl(url: string): {
      endpoint: string
      api: string
      apiVersion: string
      path: Array<string>
      query: object
    }

    Параметры:

    url - url ресурса

    Пример использования:

    const parsedUri = ms.parseUrl('https://online.moysklad.ru/api/remap/1.2/entity/customerorder?expand=positions')
    
    assert.deepEqual(parsedUri, {
      endpoint: 'https://online.moysklad.ru/api',
      api: 'remap'
      apiVersion: '1.2',
      path: ['entity', 'customerorder'],
      query: {
        expand: 'positions'
      }
    })

    fetchUrl

    Выполнить запрос по указанному url

    ms.fetchUrl(url: string, options?: object): Promise

    Параметры:

    url - url ресурса

    options - опции запроса

    Пример использования:

    const url = `https://online.moysklad.ru/api/remap/1.2/entity/customerorder/eb7bcc22-ae8d-11e3-9e32-002590a28eca`
    
    const patch = { applicable: false }
    
    const updatedOrder = await ms.fetchUrl(url, {
      method: 'PUT',
      body: JSON.stringify(patch)
    })

    Основные аргументы

    path

    Строка.

    Примеры:

    Url запроса можно указать полностью

    ms.GET(
      `https://online.moysklad.ru/api/remap/1.2/entity/customerorder/${ORDER_ID}/positions/${POSITION_ID}?expand=assortment`
    )

    Но гораздо удобнее указывать путь только после версии API и выносить параметры запроса в параметры метода. Полный url будет сгенерирован автоматически, согласно настройкам экземпляра.

    Ниже пример аналогичного запроса:

    ms.GET(`entity/customerorder/${ORDER_ID}/positions/${POSITION_ID}`, {
      expand: 'assortment'
    })

    Можно безопасно дублировать символы /, лишние знаки будут сключены из результирующего url

    const positionUrl = `/positions/${posId}`
    
    ms.GET(`entity/customerorder/` + positionUrl)
    query
    querystring

    Все поля объекта запроса преобразуются в соответствующую строку запроса url. Некоторые поля могут подвергаться преобразованию (напр. поля filter и order).

    Поле объекта запроса должно иметь тип: string, number, boolean, null или undefined, любое другое значение вызовет ошибку.

    const query = {
      str: 'some string',
      num: 1,
      bool: true,
      nil: null, // будет добавлено в строку запроса с пустым значением
      nothing: undefined, // поле будет пропущено
      arr: ['str', 1, true, null, undefined]
    }
    
    // https://online.moysklad.ru/api/remap/1.2/entity/demand?str=some%20string&num=1&bool=true&nil=&arr=str&arr=1&arr=true&arr=
    ms.GET('entity/demand', query)
    filter

    Если поле filter объект, то вложенные поля filter преобразуются в параметры фильтра в строке запроса в соответствии со следующими правилами:

    • string, number, boolean не проходят дополнительных преобразований (key=value)
    • null преобразуется в пустую строку (key=)
    • Date преобразуется в строку методом getTimeString (key=YYYY-MM-DD HH:mm:ss)
    • object интерпретируется как набор селекторов или вложенных полей (см. пример ниже)

    Пример фильтра:

    const query = {
      filter: {
        name: '00001',
        code: [1, 2, '03'],
        foo: new Date(2000, 0, 1),
        state: {
          name: 'Оформлен'
        },
        moment: {
          $gt: new Date(2000, 0, 1),
          $lte: new Date(2001, 0, 2, 10, 0, 15, 123)
        },
        bar: {
          baz: 1,
          $exists: true
        }
      }
    }

    соответствует следующему значению поля filter в запросе (даты переданы в часовом поясе +5):

    bar!=;bar.baz=1;code=03;code=1;code=2;foo=1999-12-31 22:00:00;moment<=2001-01-02 08:00:15.123;moment>1999-12-31 22:00:00;name=00001;state.name=Оформлен
    

    Для построения фильтра можно использовать селекторы в стиле Mongo (как в примере выше).

    Подробное описание всех возможных селекторов:

    Селектор Фильтр МойСклад Описание
    key: { $eq: value } key=value равно
    key: { $ne: value } key!=value не равно
    key: { $gt: value } key>value больше
    key: { $gte: value } key>=value больше или равно
    key: { $lt: value } key<value меньше
    key: { $lte: value } key<=value меньше или равно
    key: { $st: value } key~=value начинается со строки
    key: { $et: value } key=~value заканчивается строкой
    key: { $contains: value } key~value содержит строку
    key: { $in: [..] } или key: [..] key=value1;key=value2;... входит в
    key: { $nin: [..] } key!=value1;key!=value2;... не входит в
    key: { $exists: true } key!= наличие значения (не null)
    key: { $exists: false } key= пустое значение (null)
    key: { $and: [{..}, ..] } объединение условий
    key: { $not: {..} } отрицание условия

    На один ключ можно использовать несколько селекторов

    order

    Если поле order массив, то произойдет преобразование записи из формы массива в строку.

    Примеры:

    • ['name']'name'
    • [['code','desc']]'code,desc'
    • ['name', ['code','desc']]'name;code,desc'
    • ['name,desc', ['code','asc'], ['moment']]'name,desc;code,asc;moment'

    👉 examples/query.js

    expand и limit

    Если указано значение expand, но не указан limit, то в поле limit по умолчанию будет подставлено значение 100. Это важно, т.к. в версии API remap 1.2 expand не работает, если не указан limit.

    options (параметры запроса)

    Все опции переданные в объекте options (за исключением описанных ниже) передаются напрямую в опции метода fetch (Fetch API) при осуществлении запроса.

    С опциями fetch API можно ознакомиться по этой ссылке

    Опции специфичные для библиотеки moysklad (не передаются в fetch):

    Поле Тип Описание
    rawResponse boolean Если true, то метод вернет результат в виде объекта Response
    muteErrors boolean Если true, то все ошибки будут проигнорированы (метод не будет генерировать ошибку если код ответа сервера не в диапазоне 200-299 и/или тело ответа содержит описание ошибки МойСклад).
    millisecond boolean (не используется начиная с Remap API 1.2) Если true, то в запрос будет включен заголовок X-Lognex-Format-Millisecond со значением true (все даты объекта будут возвращены с учетом миллисекунд).
    precision boolean Если true, то в запрос будет включен заголовок X-Lognex-Precision со значением true (отключение округления цен и себестоимости до копеек).
    webHookDisable boolean Если true, то в запрос будет включен заголовок X-Lognex-WebHook-Disable со значением true (отключить уведомления вебхуков в контексте данного запроса).

    Примеры:

    • Формирование заполненного шаблона печатной формы и получение ссылки для загрузки:

      const ms = Moysklad({ fetch: require('node-fetch') })
      
      const body = {
        template: {
          meta: {
            href: ms.buildUrl(
              `entity/demand/metadata/customtemplate/${TEMPLATE_ID}`
            ),
            type: 'customtemplate',
            mediaType: 'application/json'
          }
        },
        extension: 'pdf'
      }
      
      const { headers, status } = await ms.POST(
        `entity/demand/${DEMAND_ID}/export`,
        body,
        null,
        {
          rawResponse: true, // вернуть результат запроса без предварительного разбора
          muteErrors: true // не обрабатывать ошибки, если код ответа сервера не в диапазоне 200-299
        }
      )
      
      assert.equal(status, 307)
      
      const location = headers.get('location')
      assert.true(/https:\/\/120708.selcdn.ru\/prod-files/.test(location))
    • Указание кастомного заголовка

      const ms = Moysklad({ fetch: require('node-fetch') })
      
      const folder = {
        meta: {
          type: 'productfolder',
          href: ms.buildUrl(`entity/productfolder/${FOLDER_ID}`)
        },
        description: 'Новое описание группы товаров'
      }
      
      // Указываем кастомный заголовок X-Lognex-WebHook-Disable для PUT запроса
      const updatedFolder = await ms.PUT(
        `entity/productfolder/${FOLDER_ID}`,
        folder,
        null,
        {
          // вместо этого можно использовать webHookDisable: true
          headers: {
            'X-Lognex-WebHook-Disable': true
          }
        }
      )
      
      assert.equal(updatedFolder.description, folder.description)
    • Автоматический редирект

      Идентификаторы товаров в приложении МойСклад отличаются от идентификаторов в API. Поэтому, при запросе товара по id из приложения, будет выполнен редирект на другой href.

      const ms = Moysklad({ fetch })
      
      // https://online.moysklad.ru/app/#good/edit?id=cb277549-34f4-4029-b9de-7b37e8e25a54
      const PRODUCT_UI_ID = 'cb277549-34f4-4029-b9de-7b37e8e25a54'
      
      // Error: 308 Permanent Redirect
      await ms.fetchUrl(ms.buildUrl(`entity/product/${PRODUCT_UI_ID}`))
      
      // Указана опция redirect
      const product = await ms.fetchUrl(
        ms.buildUrl(`entity/product/${PRODUCT_UI_ID}`),
        { redirect: 'follow' }
      )
      
      assert.ok(product) // OK

    События

    Событие Передаваемый объект Момент наступления
    request { url, options } Отправлен http запрос
    response { url, options, response } Получен ответ на запрос
    response:body { url, options, response, body } Загружено тело ответа
    error Error Ошибка при выполнении запроса

    Пример использования:

    const fetch = require('node-fetch')
    const Moysklad = require('moysklad')
    const { EventEmitter } = require('events')
    
    const emitter = new EventEmitter()
    const ms = Moysklad({ fetch, emitter })
    
    emitter.on('request', ({ url, options }) => {
      console.log(`${options.method} ${url}`)
    })
    
    ms.GET('entity/customerorder', { limit: 1 }).then(res => {
      console.log('Order name: ' + res.rows[0].name)
    })

    Более подробный пример смотрите в examples/events.js.

    Работа с ошибками

    Библиотека генерирует отдельные классы ошибок

    MoyskladError

    Наследует класс Error

    Внутренняя ошибка библиотеки не связанная с выполнением запроса к API

    const Moysklad = require('moysklad')
    
    const ms = Moysklad()
    
    try {
      await ms.GET('entity/product', {
        filter: 123
      })
    } catch (err) {
      assert.ok(err instanceof Moysklad.MoyskladError)
      assert.strictEqual(
        err.message,
        'Поле filter запроса должно быть строкой или объектом`
      )
    }

    MoyskladRequestError

    Ошибка при выполнении запроса

    Наследует класс MoyskladError

    const Moysklad = require('moysklad')
    
    const ms = Moysklad({ fetch, api: 'foo', apiVersion: '1.0' })
    
    try {
      await ms.GET('foo/bar')
    } catch (err) {
      assert.ok(err instanceof Moysklad.MoyskladRequestError)
      assert.strictEqual(err.name, 'MoyskladRequestError')
      assert.strictEqual(err.message, '404 Not Found')
      assert.strictEqual(err.status, 404)
      assert.strictEqual(err.statusText, 'Not Found')
      assert.strictEqual(err.url, 'https://online.moysklad.ru/api/foo/0/foo/bar')
    }

    MoyskladApiError

    Ошибка API МойСклад

    Наследует класс MoyskladRequestError

    const assert = require('assert')
    const Moysklad = require('moysklad')
    
    const ms = Moysklad({ fetch, api: 'foo', apiVersion: '1.0' })
    
    try {
      await ms.PUT('entity/product', {
        foo: 'bar'
      })
    } catch (err) {
      assert.ok(err instanceof Moysklad.MoyskladApiError)
      assert.strictEqual(err.name, 'MoyskladApiError')
      assert.strictEqual(
        err.message,
        'Не указан идентификатор объекта (https://dev.moysklad.ru/doc/api/remap/1.2/#error_1012)'
      )
      assert.strictEqual(err.code, 1012)
      assert.strictEqual(
        err.moreInfo,
        'https://dev.moysklad.ru/doc/api/remap/1.2/#error_1012'
      )
      assert.strictEqual(err.status, 400)
      assert.strictEqual(err.statusText, 'Bad Request')
      assert.strictEqual(
        err.url,
        'https://online.moysklad.ru/api/remap/1.2/entity/product'
      )
      assert.strictEqual(err.errors[0].code, err.code)
      assert.strictEqual(err.errors[0].error, 'Не указан идентификатор объекта')
      assert.strictEqual(err.errors[0].moreInfo, err.moreInfo)
    }

    TODO

    Мысли по различным дополнительным возможностям, которые могут быть включены в следующие версии, описаны в TODO.md

    История изменений

    CHANGELOG.md

    Install

    npm i moysklad

    DownloadsWeekly Downloads

    96

    Version

    0.10.1

    License

    MIT

    Unpacked Size

    87.6 kB

    Total Files

    34

    Last publish

    Collaborators

    • wmakeev