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

@hlf-core/transport

v3.2.10

Published

TypeScript library for Hyperledger Fabric client transport: command sending, block and event parsing, batch delivery and chaincode error handling

Readme

@hlf-core/transport

TypeScript библиотека клиентского транспорта Hyperledger Fabric: отправка команд, разбор блоков и получение событий

npm version License: ISC

Клиентская сторона транспорта: подключение к сети, отправка команд в chaincode, получение ответов, разбор блоков и событий. Реализует модель команд @ts-core/common поверх fabric-network, поэтому прикладной код работает с транспортом одинаково, независимо от того, ходит он в блокчейн или в обычный сервис.

Парный пакет на стороне chaincode — @hlf-core/transport-chaincode, общий протокол — @hlf-core/transport-common.

Содержание

Описание

Транспорт наследует TransportImpl из @ts-core/common, то есть предоставляет привычные send и sendListen для команд. Разница с обычным транспортом в том, что доставка идёт через Hyperledger Fabric: команда упаковывается в конверт, отправляется единственным методом контракта и, в зависимости от признака isReadonly, выполняется как запрос без записи либо как транзакция с подтверждением.

Обратное направление устроено иначе. Chaincode не может «ответить» вне транзакции, поэтому события приходят не через транспорт, а из блоков: транспорт подписывается на блоки сети и разбирает их содержимое.

Основные возможности

  • Отправка команд — с автоматическим выбором между запросом и транзакцией по признаку isReadonly
  • Подключение с переподключением — настраиваемые задержка и число попыток
  • Разбор ошибок chaincode — доменная ошибка извлекается из ответа Fabric и восстанавливается как ExtendedError с кодом и деталями
  • Разбор блоков — транзакции, события, коды валидации, связь событий с породившими их транзакциями
  • Пакетный режим — команды накапливаются chaincode и исполняются одной транзакцией, ответы разбираются из блока
  • Фабрика транспортов — переиспользование подключений по идентификатору

Установка

npm install @hlf-core/transport

Зависимости

{
    "@hlf-core/api": "~3.1.4",
    "@hlf-core/transport-common": "~3.0.15"
}

Транзитивно подтягиваются @ts-core/common, fabric-network и fabric-common (через @hlf-core/api).

Быстрый старт

import { TransportFabric } from '@hlf-core/transport';

let transport = new TransportFabric(logger, {
    fabricIdentity: 'user',
    fabricNetworkName: 'mychannel',
    fabricChaincodeName: 'mychaincode',
    fabricConnectionSettingsPath: './connection.json',
    fabricIdentityPrivateKey: privateKey,
    fabricIdentityCertificate: certificate,

    reconnectDelay: 1000,
    reconnectMaxAttempts: 5,
    isExitApplicationOnDisconnect: false
});

await transport.connect();

// команда с ответом
let user = await transport.sendListen(new UserGetCommand(uid));

// команда без ожидания ответа
transport.send(new UserAddCommand({ name: 'Ivan', publicKey }));

Как это работает

Подключение и переподключение

connect() проставляет значения по умолчанию и запускает цикл подключения:

if (!_.isNumber(this.settings.reconnectDelay)) { this.settings.reconnectDelay = DateUtil.MILISECONDS_SECOND; }
if (!_.isNumber(this.settings.reconnectMaxAttempts)) { this.settings.reconnectMaxAttempts = 0; }
if (!_.isBoolean(this.settings.isExitApplicationOnDisconnect)) { this.settings.isExitApplicationOnDisconnect = true; }

Попытки повторяются с задержкой reconnectDelay, пока их число не превысит reconnectMaxAttempts. По умолчанию повторов нет: reconnectMaxAttempts равен нулю, поэтому первая же неудача приводит к разрыву.

При успешном подключении транспорт подписывается на блоки сети и события контракта, после чего разрешается промис connect().

Важно: isExitApplicationOnDisconnect по умолчанию включён, и при разрыве транспорт завершает процесс через process.exit(1). Это удобно для сервисов под управлением оркестратора, который перезапустит контейнер, но неожиданно в других сценариях. Подробнее — в разделе Важные особенности.

Отправка команды

protected async commandRequestExecute(command, options, isNeedReply) {
    let payload = this.createRequestPayload(command, options, isNeedReply);
    TransportFabricRequestPayload.clear(payload);

    let response = await this.transactionSend(this.api.contract.createTransaction(TRANSPORT_FABRIC_METHOD), command, payload);
    if (isNeedReply && this.isCommandAsync(command)) {
        this.responseMessageReceived(command.id, response);
    }
}

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

Способ отправки выбирается по признаку команды:

let method = payload.isReadonly ? transaction.evaluate : transaction.submit;

evaluate выполняет команду на peer без создания транзакции — ничего не попадает в леджер. submit проходит полный цикл подтверждения и записи.

Признак берётся из самой команды, причём поддерживаются оба имени — историческое isQuery и текущее isReadonly:

protected isCommandReadonly<U>(command: ITransportCommand<U>): boolean {
    if (ObjectUtil.hasOwnProperty(command, 'isQuery')) { return command['isQuery'] === true; }
    if (ObjectUtil.hasOwnProperty(command, 'isReadonly')) { return command['isReadonly'] === true; }
    return false;
}

Ошибки chaincode

Fabric возвращает ошибку chaincode обёрнутой в собственную структуру и с текстовым префиксом. Транспорт разбирает её и восстанавливает исходную доменную ошибку:

protected parseChaincodeError(error: any): ExtendedError {
    let message = error.message.replace('error in simulation: transaction returned with failure:', '').trim();
    if (!ObjectUtil.isJSON(message)) { return null; }

    let response = TransformUtil.toClass(TransportFabricResponsePayload, TransformUtil.toJSON(message));
    if (!ExtendedError.instanceOf(response.response)) { return null; }

    let item = ExtendedError.create(response.response);
    item.stack = null;
    return item;
}

Благодаря этому прикладной код ловит ту же ошибку с тем же кодом, что была брошена внутри chaincode, а не строку с текстом транзакции. Стек обнуляется — он относился бы к клиенту и вводил в заблуждение.

Источник ошибки ищется последовательно в response, responses[0].response, endorsements[0] и в самой ошибке при статусе 500 — форма ответа зависит от того, на каком этапе Fabric прервал выполнение.

Блоки и события

Транспорт односторонний: команды идут только от клиента к chaincode. Попытка отправить событие или ответ в обратную сторону явно запрещена:

protected eventRequestExecute<U>(event: ITransportEvent<U>, options?: void): Promise<void> {
    throw new ExtendedError(`Method doesn't supported`);
}

События приходят из блоков. TransportFabricBlockParser разбирает блок в структуру с транзакциями и событиями, попутно проставляя коды валидации из метаданных блока и связывая события с транзакциями по хэшу:

public static checkEventsTransaction(events, transactions): void {
    let map = new Map();
    transactions.forEach(item => map.set(item.hash, item));

    for (let event of events) {
        let item = map.get(event.transactionHash);
        event.requestId = item.requestId;
        event.transactionValidationCode = item.validationCode;
    }
}

Так по событию можно узнать, какая команда его породила (requestId) и была ли транзакция принята сетью (transactionValidationCode) — событие в блоке присутствует даже у отклонённой транзакции.

События транспорта отличаются от прочих по имени: всё, что опубликовано под transportFabricEvent, разбирается как карта «хэш транзакции → список событий», остальное — как одиночное событие chaincode.

Для наследников предусмотрены пустые хуки:

protected async blockEventCallback(event: IFabricBlock): Promise<void> { }
protected async contractEventCallback(event: ContractEvent): Promise<void> { }

Пакетный режим

TransportFabricBatch меняет схему доставки для команд, изменяющих состояние. Команда отправляется, но ответ не ожидается сразу: chaincode накапливает такие команды и исполняет их одной транзакцией. Ответы возвращаются позже — из блока.

protected async commandRequestExecute(command, options, isNeedReply) {
    if (this.isCommandReadonly(command)) {
        return super.commandRequestExecute(command, options, isNeedReply);   // readonly — как обычно
    }
    let payload = this.createRequestPayload(command, options, isNeedReply);
    TransportFabricRequestPayload.clear(payload);
    return this.transactionSend(...);   // ответ придёт из блока
}

Когда приходит блок, транспорт ищет в нём успешную пакетную транзакцию и разбирает её ответ как карту «хэш → ответ», после чего разрешает промисы ожидающих команд:

let payload = TransformUtil.toClass(TransportFabricResponsePayload, batch.response);
for (let hash in payload.response) {
    let item = TransformUtil.toClass(TransportFabricResponsePayload, payload.response[hash]);
    this.responseMessageReceived(item.id, TransformUtil.fromClassBuffer(item));
}

Команды на чтение в пакет не попадают — они по-прежнему выполняются немедленно через evaluate.

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

TransportFabric

class TransportFabric<T extends ITransportFabricConnectionSettings> extends TransportImpl<T, ITransportFabricCommandOptions>

| Метод | Назначение | |---|---| | connect(): Promise<void> | подключение к сети с переподключением; повторный вызов возвращает тот же промис | | disconnect(error?): void | отписка от блоков и событий, разрыв подключения | | destroy(): void | уничтожение транспорта; внутри вызывает disconnect() | | isConnected: boolean | состояние подключения | | api: FabricApiClient | доступ к клиенту Fabric для прямых операций |

Команды отправляются унаследованными send и sendListen из TransportImpl.

Методы для переопределения в наследниках:

| Метод | Назначение | |---|---| | blockEventCallback(block) | вызывается на каждый блок сети | | contractEventCallback(event) | вызывается на каждое событие контракта | | isCommandReadonly(command) | определение способа отправки | | parseError(error) / parseChaincodeError(error) | разбор ошибок Fabric |

ITransportFabricConnectionSettings

export interface ITransportFabricConnectionSettings extends IFabricConnectionSettings, ITransportSettings {
    reconnectDelay?: number;
    reconnectMaxAttempts?: number;
    isExitApplicationOnDisconnect?: boolean;
}

| Параметр | По умолчанию | Назначение | |---|---|---| | reconnectDelay | 1000 мс | задержка между попытками подключения | | reconnectMaxAttempts | 0 | максимум повторов; ноль означает отсутствие повторов | | isExitApplicationOnDisconnect | true | завершать процесс при разрыве |

Остальные параметры подключения (идентичность, канал, имя chaincode, сертификаты) наследуются из IFabricConnectionSettings пакета @hlf-core/api.

TransportFabricBlockParser

class TransportFabricBlockParser<U extends ITransportFabricTransaction, V extends ITransportFabricEvent, T extends ITransportFabricBlock<U, V>>

| Метод | Назначение | |---|---| | parse(block): Promise<T> | разбор блока в транзакции и события | | parseTransaction(data): U | разбор одной транзакции с кодом валидации | | checkEventsTransaction(events, transactions) | связывание событий с транзакциями по хэшу | | isTransactionValid(item) | код валидации равен VALID | | isTransactionError(item) | ответ транзакции является ошибкой | | isTransactionSucceed(item) | транзакция валидна и не содержит ошибки |

Разбирается только то, что относится к транспорту: транзакция учитывается, если вызван метод transportFabricExecute ровно с двумя аргументами.

Структура разобранного блока:

export interface ITransportFabricBlock<U, V> {
    hash: string;
    date: Date;
    number: number;
    events: Array<V>;
    transactions: Array<U>;
}

export interface ITransportFabricTransaction<U = any, V = any> {
    hash: string;
    date: Date;
    channel: string;
    requestId: string;
    chaincode: ITransportFabricTransactionChaincode;
    validationCode: FabricTransactionValidationCode;

    request: ITransportFabricRequestPayload<U>;
    response: ITransportFabricResponsePayload<V>;
}

Событие несёт ссылку на породившую его транзакцию:

export interface ITransportFabricEvent<T = any> extends ITransportEvent<T> {
    date: Date;
    channel: string;
    requestId: string;
    chaincode: string;
    transactionHash: string;
    transactionValidationCode: FabricTransactionValidationCode;
}

TransportFabricBatch

class TransportFabricBatch<T extends ITransportFabricConnectionSettings> extends TransportFabric<T>

Наследник, доставляющий изменяющие команды пакетами. Интерфейс отправки не меняется — прикладной код по-прежнему использует send и sendListen, но ответ на изменяющую команду приходит после того, как chaincode исполнит пакет и он попадёт в блок.

TransportFabricBlockParserBatch

class TransportFabricBlockParserBatch extends TransportFabricBlockParser<ITransportFabricTransactionBatch, ITransportFabricEvent, ITransportFabricBlockBatch>

Разбирает блок с пакетной транзакцией. Помимо обычного разбора он раскрывает пакет: для каждого хэша из ответа догружает оригинальную транзакцию и её блок через qscc, чтобы восстановить полную картину.

export interface ITransportFabricTransactionBatch<U = any, V = any> extends ITransportFabricTransaction<U, V> {
    blockReceived: number;
}

Поле blockReceived — номер блока, в котором команда была принята, в отличие от блока, в котором она была исполнена в составе пакета.

| Метод | Назначение | |---|---| | getBatchTransaction(block) | найти пакетную транзакцию в блоке | | getSucceedBatchTransaction(block) | найти успешную пакетную транзакцию |

Фабрики

class TransportFabricFactory extends LoggerWrapper {
    public async get(uid: string): Promise<TransportFabric>;
}

Хранит транспорты по идентификатору и переиспользует их: если транспорт уже создан, но не подключён, get выполнит подключение. Настройки берутся из TransportFabricConnectionSettingsFactory, наследника фабрики настроек из @hlf-core/api.

Полезно, когда приложение работает с несколькими каналами или идентичностями одновременно.

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

Отправка команды с ответом

let transport = new TransportFabric(logger, settings);
await transport.connect();

let user = await transport.sendListen(new UserGetCommand(uid));

Подпись команды

await transport.sendListen(new UserAddCommand(dto), {
    userId: user.uid,
    signature: { value, algorithm, publicKey, nonce }
});

Опции команды расширены полями userId и signature — chaincode проверит подпись и nonce перед выполнением. Текущее значение nonce запрашивается командой NonceGetCommand из @hlf-core/transport-common.

Обработка блоков

class MyTransport extends TransportFabric {
    protected async blockEventCallback(block: IFabricBlock): Promise<void> {
        let parser = new TransportFabricBlockParser();
        let item = await parser.parse(block);

        for (let transaction of item.transactions) {
            if (!TransportFabricBlockParser.isTransactionSucceed(transaction)) {
                continue;
            }
            console.log(transaction.request.name, transaction.requestId);
        }

        for (let event of item.events) {
            console.log(event.name, event.requestId, event.transactionValidationCode);
        }
    }
}

Несколько подключений

let factory = new TransportFabricFactory(logger, settingsFactory);

let first = await factory.get('channel-one');
let second = await factory.get('channel-two');

Важные особенности

Завершение процесса при разрыве. isExitApplicationOnDisconnect включён по умолчанию, поэтому disconnect() вызывает process.exit(1). Учтите, что destroy() внутри вызывает disconnect() — уничтожение транспорта при включённой настройке завершит приложение. Если такое поведение нежелательно, задайте параметр явно:

new TransportFabric(logger, { ...settings, isExitApplicationOnDisconnect: false });

Повторы подключения отключены по умолчанию. reconnectMaxAttempts равен нулю, значит первая же неудачная попытка приводит к разрыву — а при включённой настройке выше и к завершению процесса. Для сервисов, стартующих одновременно с сетью, значение стоит задавать явно.

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

Транспорт односторонний. eventRequestExecute и commandResponseExecute выбрасывают исключение: отправить событие или ответ в сторону chaincode нельзя. Обратное направление — только через блоки.

Опции команды изменяются при отправке. getCommandOptions вызывает clearDefaultOptions для переданного объекта опций, то есть очищает его от значений по умолчанию. Не переиспользуйте один и тот же объект опций для нескольких команд, рассчитывая на его неизменность.

Структура проекта

src/
├── TransportFabric.ts                         клиентский транспорт
├── ITransportFabricConnectionSettings.ts      настройки подключения
├── public-api.ts                              публичный API пакета
├── block/
│   ├── TransportFabricBlockParser.ts          разбор блоков, транзакций и событий
│   ├── ITransportFabricBlock.ts               структура разобранного блока
│   ├── ITransportFabricTransaction.ts         структура транзакции
│   ├── ITransportFabricTransactionChaincode.ts данные chaincode транзакции
│   └── ITransportFabricEvent.ts               структура события
├── batch/
│   ├── TransportFabricBatch.ts                транспорт с пакетной доставкой
│   └── block/
│       ├── TransportFabricBlockParserBatch.ts разбор блока с пакетом
│       ├── ITransportFabricBlockBatch.ts      структура блока с пакетом
│       └── ITransportFabricTransactionBatch.ts транзакция с номером блока приёма
└── factory/
    ├── TransportFabricFactory.ts              переиспользование транспортов
    └── TransportFabricConnectionSettingsFactory.ts  фабрика настроек

Связанные пакеты

| Пакет | Роль | |---|---| | @hlf-core/transport-common | общий протокол: конверты запроса и ответа, опции с подписью | | @hlf-core/transport-chaincode | сторона chaincode: приём команд, проверка подписи | | @hlf-core/api | клиент Fabric: подключение, контракты, запросы к qscc |

Лицензия

ISC © Renat Gubaev