npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@opsregistry/adapter-saby

v0.5.0

Published

Saby adapter for universal electronic business document exchange operations.

Readme

@opsregistry/adapter-saby

Адаптер Saby (СБИС) для универсальных операций электронного обмена деловыми документами.

Пакет преобразует контракты @opsregistry/contracts в команды API ЭДО Saby. Потребитель работает с нормализованными идентификаторами, сторонами, статусами, вложениями и событиями, не распространяя русские имена полей Saby по своему приложению.

Реализовано

  • it.identity.authenticate — сервисная авторизация приложения или готовый access token;
  • documents.exchangeDocument.listСБИС.СписокДокументов;
  • documents.exchangeDocument.getСБИС.ПрочитатьДокумент;
  • documents.exchangeDocument.listChangesСБИС.СписокИзменений.
  • documents.exchangeDocument.create — создать черновик через СБИС.ЗаписатьДокумент;
  • documents.exchangeDocument.send — отправить черновик через СБИС.ВыполнитьДействие.
  • documents.exchangeDocument.accept — принять входящий документ или выбранные вложения;
  • documents.exchangeDocument.reject — отклонить входящий документ или выбранные вложения.
  • documents.exchangeDocument.requestCancellation — запросить аннулирование;
  • documents.exchangeDocument.acceptCancellation — принять запрос на аннулирование;
  • documents.exchangeDocument.rejectCancellation — отклонить запрос на аннулирование.
  • documents.exchangeDocument.prepareSigning — получить подготовленные хеши вложений;
  • documents.exchangeDocument.completeSigning — загрузить внешние подписи и завершить действие.

Адаптер не формирует электронную подпись сам: это отдельная операция trust.digitalSignature.create, которую выполняет локальный криптографический провайдер.

Настройка приложения Saby

В Saby создайте внешнее приложение с сервисной авторизацией и минимальными правами на ЭДО. Адаптеру нужны:

  • app_client_id — ID подключения;
  • app_secret — защищённый ключ;
  • secret_key — сервисный ключ.

Передавайте секреты только через окружение или защищённое хранилище приложения:

import { createAdapter } from '@opsregistry/adapter-saby';

const saby = createAdapter({
	appClientId: process.env.SABY_APP_CLIENT_ID!,
	appSecret: process.env.SABY_APP_SECRET!,
	serviceKey: process.env.SABY_SERVICE_KEY!
});

Можно передать уже полученный accessToken вместо трёх параметров приложения. Адаптер получает и кэширует токен сам, а в команды ЭДО отправляет его только в заголовке X-SBISAccessToken.

Получение документов

Saby требует тип документа для СБИС.СписокДокументов. Это ограничение конкретного провайдера: универсальный контракт оставляет documentType необязательным, но этот адаптер проверяет его перед вызовом.

Фильтры по нормализованному status, documentSubtype и внутреннему направлению пока отклоняются явной ошибкой: соответствующие коды состояний и поддержка полей должны быть подтверждены на живом кабинете, а молча игнорировать запрошенный фильтр нельзя.

const incoming = await saby.listExchangeDocuments({
	documentType: 'ДокОтгрВх',
	direction: 'incoming',
	dateFrom: '2026-08-01',
	dateTo: '2026-08-31',
	pageSize: 50
});

const document = await saby.getExchangeDocument({
	documentId: incoming.items[0]!.documentId
});

nextPageToken непрозрачен: сохраняйте и передавайте его без разбора. Ссылки Saby на вложения и подписи временные; приложение не должно считать их постоянным архивным адресом.

Синхронизация изменений

const changes = await saby.listExchangeDocumentChanges({
	changedAfter: '2026-08-01T00:00:00Z',
	pageSize: 50
});

// Для следующего опроса передайте changes.nextCursor без изменения.

Внутри cursor сохраняются координаты, необходимые Saby: событие, документ, редакция и время. Публичный контракт не раскрывает и не стандартизует внутреннее устройство курсора.

Создание и отправка

Создание не запускает документооборот. Оно записывает черновик, а clientReference сохраняется в Редакция.ПримечаниеИС, чтобы связать документ с объектом исходной системы.

const draft = await saby.createExchangeDocument({
	clientReference: 'invoice:42',
	documentType: 'ДокОтгрИсх',
	attachments: [
		{
			name: 'invoice.pdf',
			content: { kind: 'base64', data: pdfBase64 }
		}
	]
});

Отправка отделена намеренно и требует confirmLegalAction: true. Saby сам определяет требования регламента к подписи. Адаптер поддерживает управляемую Saby подпись и два режима отложенной подписи, но отклоняет withoutSignature, потому что не может гарантировать отсутствие подписи на переходе.

await saby.sendExchangeDocument({
	documentId: draft.document.documentId,
	signingMode: 'deferredWithConfirmation',
	confirmLegalAction: true
});

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

Для формализованного вложения Saby требует одновременно передать documentType, documentSubtype, formatVersion и formatSubversion; частичный набор адаптер отклоняет до запроса. content.kind=url означает ссылку на объект файлового хранилища Saby, а не произвольный публичный URL.

Обработка входящих документов

getExchangeDocument возвращает availableActions: нормализованный смысл действия, название Saby, идентификатор этапа и признаки обязательной подписи или комментария. Для accept и reject адаптер повторно читает актуальный документ непосредственно перед изменением и выбирает действие нужного смысла. Если подходящих действий несколько, нужно передать выбранный элемент как action; адаптер не делает юридически значимый выбор сам.

const incoming = await saby.getExchangeDocument({ documentId });
const acceptAction = incoming.document.availableActions.find(
	(action) => action.kind === 'accept'
);

await saby.acceptExchangeDocument({
	documentId,
	action: acceptAction,
	attachmentIds: ['attachment-id'],
	signingMode: 'deferredWithConfirmation',
	confirmLegalAction: true
});

Без attachmentIds обрабатывается весь пакет. При их наличии адаптер включает СложноеУтверждение и передаёт Saby только выбранные вложения. Отклонение всегда требует reason:

await saby.rejectExchangeDocument({
	documentId,
	signingMode: 'providerManaged',
	reason: 'The document amount does not match the contract',
	confirmLegalAction: true
});

Режим withoutSignature допускается только когда актуальное действие Saby явно сообщает, что подпись не требуется. accept и reject, как и send, не поддерживают выдуманную адаптером идемпотентность и отклоняют idempotencyKey.

Аннулирование

Аннулирование отделено от удаления: это подписываемое соглашение двух сторон. Отправка запроса переводит документ в cancellationPending, но ещё не делает его аннулированным.

await saby.requestExchangeDocumentCancellation({
	documentId,
	reason: 'The document was issued by mistake',
	signingMode: 'deferredWithConfirmation',
	confirmLegalAction: true
});

Получатель запроса вызывает acceptExchangeDocumentCancellation или rejectExchangeDocumentCancellation. Перед ответом адаптер перечитывает актуальный регламент Saby и выбирает действие Документ аннулирован либо Аннулирование отклонено. Можно передать action из availableActions, если в регламенте найдено несколько подходящих переходов. Для частичного аннулирования во всех трёх операциях поддерживается attachmentIds.

Подпись на физическом носителе

Для локальной подписи адаптер не обращается к носителю и никогда не получает его PIN. Сначала prepareExchangeDocumentSigning вызывает СБИС.ПодготовитьДействие с точной редакцией, этапом, действием и отпечатком сертификата. Saby формирует служебные вложения и возвращает их хеши.

Приложение передаёт эти хеши локальному провайдеру операции trust.digitalSignature.create. Провайдер работает с Рутокеном через PKCS#11 или системный криптопровайдер, показывает пользователю сертификат и запрашивает PIN локально. В адаптер Saby возвращаются только готовые подписи Base64. completeExchangeDocumentSigning прикладывает их к соответствующим вложениям и вызывает СБИС.ВыполнитьДействие после confirmLegalAction: true.

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

Стенды и документация

  • production API: https://online.sbis.ru/service/?srv=1;
  • production OAuth: https://online.sbis.ru/oauth/service/;
  • тестовый API доступен на fix-online.sbis.ru и задаётся через baseUrl/authUrl.

Официальная документация:

  • https://saby.ru/help/integration/api/edo
  • https://saby.ru/help/integration/api/auth/service/
  • https://saby.ru/help/integration/api/all_methods/list_doc/
  • https://saby.ru/help/integration/api/all_methods/read_doc/
  • https://saby.ru/help/integration/api/all_methods/changeslist/
  • https://saby.ru/help/integration/api/all_methods/doc
  • https://saby.ru/help/integration/api/all_methods/make_doc/
  • https://saby.ru/help/integration/api/sequence/ep
  • https://saby.ru/help/integration/api/sequence/incom_doc
  • https://saby.ru/help/integration/api/sequence/cancellation
  • https://saby.ru/help/integration/api/all_methods/develop_doc

Живая проверка production API выполнена 3 сентября 2026 года для сервисной OAuth-авторизации, documents.exchangeDocument.list и создания неотправленного черновика. Saby требует явно указывать UTF-8 в Content-Type; транспорт адаптера делает это автоматически. При создании документа ООО без формализованного XML передавайте в ourOrganization как taxId, так и registrationReasonCode. Отправка, принятие и отклонение в боевом кабинете намеренно не проверялись, поскольку это юридически значимые действия.

Ограничения первого релиза

  • соответствие редких статусов нормализованной модели уточняется по живым ответам;
  • dateTimeOffset по умолчанию равен +03:00 и должен совпадать с часовым поясом кабинета;
  • временные download URL возвращаются как есть и не скачиваются автоматически;
  • адаптер не хранит документы, подписи и секреты;
  • боевую отправку следует выполнять только после отдельного подтверждения пользователя.