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/chaincode

v3.6.7

Published

TypeScript library for developing Hyperledger Fabric chaincode with ORM-like entity management, relations, pagination and automatic validation

Readme

English · Русский

@hlf-core/chaincode

TypeScript library for developing chaincode in Hyperledger Fabric

npm version Docs License: ISC

Full documentation: https://hlf.gnosisai.ru/docs/packages/chaincode

Overview

@hlf-core/chaincode is a high-level TypeScript library for developing chaincode (smart contracts) in Hyperledger Fabric. It provides abstractions and tools for managing data, entities, and their relationships in the blockchain ledger.

Key Features

  • ** Database management** — working with the Hyperledger Fabric key-value store
  • ** Entity management** — an ORM-like API for working with business objects
  • ** Entity relationships** — managing parent-child links between objects
  • ** Pagination** — efficient handling of large data sets
  • ** Validation and transformation** — automatic serialization/deserialization with validation
  • ** TypeScript** — full typing and support for modern language features
  • ** Dual Package** — support for both CommonJS and ES Modules

Installation

npm install @hlf-core/chaincode

Dependencies

The library requires the following packages:

npm install @hlf-core/common @ts-core/common fabric-shim reflect-metadata

@hlf-core/common provides StateKey — the assembly of state keys on which entity relationships are built.

Quick Start

1. Defining an entity

import { IUIDable } from '@ts-core/common';
import { IsString, IsNumber } from 'class-validator';

export class User implements IUIDable {
    @IsString()
    uid: string;

    @IsString()
    name: string;

    @IsNumber()
    age: number;

    @IsString()
    email: string;
}

2. Creating an entity manager

import { EntityManagerImpl } from '@hlf-core/chaincode';
import { ILogger, TransformUtil } from '@ts-core/common';
import { IStub } from '@hlf-core/chaincode';

export class UserManager extends EntityManagerImpl<User> {
    constructor(logger: ILogger, stub: IStub) {
        super(logger, stub);
    }

    // Required method: convert a plain object into a class
    public toEntity(item: any): User {
        return TransformUtil.toClass(User, item);
    }

    // Prefix for storage in the blockchain
    public get prefix(): string {
        return 'user:';
    }

    // Optional: load related data
    public async loadDetails(item: User, details?: Array<keyof User>): Promise<void> {
        // Load additional data if needed
    }
}

3. Using it in chaincode

import { Context } from 'fabric-contract-api';

export class MyContract {
    async createUser(ctx: Context, userId: string, name: string, age: number, email: string): Promise<void> {
        const logger = // your logger
        const stub = // adapter for ctx.stub

        const userManager = new UserManager(logger, stub);

        const user = new User();
        user.uid = userId;
        user.name = name;
        user.age = age;
        user.email = email;

        // Save with automatic validation and serialization
        await userManager.save(user);
    }

    async getUser(ctx: Context, userId: string): Promise<User> {
        const logger = // your logger
        const stub = // adapter for ctx.stub

        const userManager = new UserManager(logger, stub);

        // Retrieve with automatic deserialization and validation
        return await userManager.get(userId);
    }

    async getAllUsers(ctx: Context, pageSize: number, bookmark?: string): Promise<any> {
        const logger = // your logger
        const stub = // adapter for ctx.stub

        const userManager = new UserManager(logger, stub);

        // Retrieve with pagination
        const result = await userManager.findPaginated({
            pageSize,
            pageBookmark: bookmark
        });

        return {
            users: result.items,
            bookmark: result.pageBookmark,
            hasMore: !result.isAllLoaded
        };
    }
}

API Documentation

DatabaseManager

Base class for working with the key-value store.

Methods

getKV(start: string, finish?: string): Promise<Array<IKeyValue>>

Retrieves all key-value pairs within a range.

const kv = await databaseManager.getKV('user:', 'user:￰');
getKeys(start: string, finish?: string): Promise<Array<string>>

Retrieves only the keys within a range.

const keys = await databaseManager.getKeys('user:');
getValues(start: string, finish?: string): Promise<Array<string>>

Retrieves only the values within a range.

const values = await databaseManager.getValues('user:');
getPaginatedKV(request: IPageBookmark, start: string, finish?: string): Promise<IPaginationBookmark<IKeyValue>>

Retrieves key-value pairs with pagination.

const result = await databaseManager.getPaginatedKV(
    { pageSize: 10, pageBookmark: '' },
    'user:'
);

EntityManager

Abstract class for managing entities. Inherits from DatabaseManager.

CRUD Methods

get(item: UID, details?: Array<keyof T>): Promise<T>

Retrieves an entity by ID.

const user = await userManager.get('user123');
getMany(items: Array<UID>, details?: Array<keyof T>): Promise<Array<T>>

Retrieves multiple entities by ID.

const users = await userManager.getMany(['user1', 'user2', 'user3']);
save(item: T): Promise<T>

Saves an entity (create or update).

await userManager.save(user);
saveMany(items: Array<T>): Promise<Array<T>>

Saves multiple entities.

await userManager.saveMany([user1, user2, user3]);
saveIfNotExists(item: T): Promise<T>

Saves an entity only if it does not already exist.

await userManager.saveIfNotExists(user);
has(item: UID): Promise<boolean>

Checks whether an entity exists.

const exists = await userManager.has('user123');
remove(item: UID): Promise<void>

Removes an entity.

await userManager.remove('user123');
removeMany(items: Array<UID>): Promise<void>

Removes multiple entities.

await userManager.removeMany(['user1', 'user2']);

Query Methods

Both methods accept a single query object:

interface IEntityQuery<U = any, T = any> {
    prefix?: string;                              // where to search; defaults to the manager's prefix
    details?: Array<keyof U>;                     // which related data to load
    transform?: (item: string) => T | Promise<T>; // how to turn a record's value into an entity
}

interface IEntityPageQuery<U = any, T = any> extends IEntityQuery<U, T>, IPageBookmark { }
find(query?: IEntityQuery<T>): Promise<Array<T>>

Finds all entities with the given prefix.

const allUsers = await userManager.find();

const withDetails = await userManager.find({ details: ['company'] });

const byPrefix = await userManager.find({ prefix: 'user_2026' });

This method reads the entire range at once. In chaincode this means the read set grows linearly with the number of records — for large result sets use findPaginated.

findPaginated(query: IEntityPageQuery<T>): Promise<IPaginationBookmark<T>>

Finds entities page by page.

const result = await userManager.findPaginated({
    pageSize: 20,
    pageBookmark: 'bookmark123',
    details: ['company']
});

console.log(result.items);           // Array<User>
console.log(result.pageBookmark);    // Next bookmark
console.log(result.isAllLoaded);     // Whether everything is loaded

IPaginableBookmark from @ts-core/common is structurally compatible with IEntityPageQuery, so a list query DTO can be passed directly. Its conditions and sorting are not applied in chaincode — only pageSize, pageBookmark, and details are used.

Abstract Methods

When inheriting from EntityManager you must implement:

// Prefix for keys in the blockchain
abstract get prefix(): string;

// Serialize an entity for storage
protected abstract serialize<V = any>(item: T): Promise<V>;

// Deserialize an entity from storage
protected abstract deserialize(item: any, details?: Array<keyof T>): Promise<T>;

// Load additional data
public abstract loadDetails(item: T, details?: Array<keyof T>): Promise<void>;

EntityManagerImpl

An implementation of EntityManager with automatic serialization and validation via class-validator and class-transformer.

When inheriting you only need to implement:

// Convert a plain object into a class
public abstract toEntity(item: any): T;

// Prefix for keys
public abstract get prefix(): string;

// Optional: load related data
public async loadDetails(item: T, details?: Array<keyof T>): Promise<void> {
    // Implementation is optional
}

EntityRelation

Managing relationships between entities.

import { EntityRelation } from '@hlf-core/chaincode';

// Create a relation between a post and comments
const relation = new EntityRelation(
    stub,
    'post',      // parent prefix
    'comment'    // child prefix
);

// Add a link
await relation.childLinkAdd('comment123', 'post456');

// Check a link
const hasLink = await relation.childLinkHas('comment123', 'post456');

// Remove a link
await relation.childLinkRemove('comment123', 'post456');

// Remove all links for a child element
await relation.childRemove('comment123');

// Remove a parent and get all child IDs
const childIds = await relation.parentRemove('post456');

How links are stored

Two keys are written for each link — forward and reverse:

→link:post~comment:post456~comment123    → comment123
→link:comment~post:comment123~post456    → post456

The forward key serves the retrieval of children by parent: a range query on the prefix →link:post~comment:post456~ returns all children. The reverse key is needed to remove links knowing only the child — childRemove does not require knowing the parent.

Keys are assembled by StateKey from @hlf-core/common, so key parts are validated and the search prefix always ends with a separator — the parent post4 will not capture the children of the parent post45.

Composite parent

A parent can consist of several parts — in that case the search is possible by any left-hand part. This is convenient when a relation is addressed by a pair of values:

// linking an order to an account: coin + owner
await relation.childAdd(otcUid, coinUid, ownerUid);

// key: →link:account~otc:<coinUid>~<ownerUid>~<otcUid>

// all orders by coin
relation.childListQuery(coinUid);

// a specific owner's orders for this coin
relation.childListQuery(coinUid, ownerUid);

The parts are passed as separate arguments rather than as a concatenated string: a separator inside a value would shift the part boundaries, so such a string is rejected with StateKeySegmentInvalidError.

await relation.childAdd(otcUid, `${coinUid}~${ownerUid}`);   // error
await relation.childAdd(otcUid, coinUid, ownerUid);          // correct

EntityRelationChild<T, M>

An extension of EntityRelation with automatic management of child entities.

import { EntityRelationChild } from '@hlf-core/chaincode';

const commentRelation = new EntityRelationChild(
    'post',              // parent prefix
    'comment',           // child prefix
    commentManager       // manager for Comment
);

// Get all comments of a post with pagination
const comments = await commentRelation.childList({ pageSize: 10 }, 'post456');

// Remove a post and all its comments
const deletedCommentIds = await commentRelation.parentRemove('post456');

Architecture

┌─────────────────────────────────────────┐
│           IStub Interface               │
│  (Abstraction over fabric-shim)         │
└───────────────┬─────────────────────────┘
                │
                │ uses
                ▼
┌─────────────────────────────────────────┐
│        DatabaseManager                  │
│  • getKV / putKV                        │
│  • getKeys / getValues                  │
│  • getPaginatedKV                       │
└───────────────┬─────────────────────────┘
                │
                │ inherits
                ▼
┌─────────────────────────────────────────┐
│        EntityManager<T>                 │
│  • get / save / remove                  │
│  • find / findPaginated                 │
│  • serialize / deserialize (abstract)   │
└───────────────┬─────────────────────────┘
                │
                │ inherits
                ▼
┌─────────────────────────────────────────┐
│      EntityManagerImpl<T>               │
│  • Automatic serialization              │
│  • Automatic validation                 │
│  • toEntity(item) (abstract)            │
└─────────────────────────────────────────┘
                │
                │ used in
                ▼
        ┌───────────────┐
        │ Your Chaincode│
        └───────────────┘

Entity relationships

┌──────────────────────┐
│   EntityRelation     │
│  • Manage relations  │
│  • childAdd/Remove   │
│  • parentRemove      │
└──────────┬───────────┘
           │
           │ inherits
           ▼
┌──────────────────────────────┐
│  EntityRelationChild<T, M>   │
│  • childList (paginated)     │
│  • Cascade deletion          │
└──────────────────────────────┘

Usage Examples

Example 1: A simple CRUD application

import { EntityManagerImpl } from '@hlf-core/chaincode';
import { IsString, IsNumber } from 'class-validator';
import { IUIDable, TransformUtil } from '@ts-core/common';

// Model
class Product implements IUIDable {
    @IsString()
    uid: string;

    @IsString()
    name: string;

    @IsNumber()
    price: number;

    @IsNumber()
    quantity: number;
}

// Manager
class ProductManager extends EntityManagerImpl<Product> {
    public toEntity(item: any): Product {
        return TransformUtil.toClass(Product, item);
    }

    public get prefix(): string {
        return 'product:';
    }
}

// Using it in chaincode
async function createProduct(ctx, id, name, price, quantity) {
    const manager = new ProductManager(logger, stub);

    const product = new Product();
    product.uid = id;
    product.name = name;
    product.price = price;
    product.quantity = quantity;

    await manager.save(product);
}

async function updateProductQuantity(ctx, id, newQuantity) {
    const manager = new ProductManager(logger, stub);

    const product = await manager.get(id);
    product.quantity = newQuantity;

    await manager.save(product);
}

async function deleteProduct(ctx, id) {
    const manager = new ProductManager(logger, stub);
    await manager.remove(id);
}

Example 2: Entity relationships

// Models
class Post implements IUIDable {
    @IsString()
    uid: string;

    @IsString()
    title: string;

    @IsString()
    content: string;
}

class Comment implements IUIDable {
    @IsString()
    uid: string;

    @IsString()
    postId: string;

    @IsString()
    text: string;

    @IsString()
    authorId: string;
}

// Managers
class PostManager extends EntityManagerImpl<Post> {
    public toEntity(item: any): Post {
        return TransformUtil.toClass(Post, item);
    }
    public get prefix(): string { return 'post:'; }
}

class CommentManager extends EntityManagerImpl<Comment> {
    public toEntity(item: any): Comment {
        return TransformUtil.toClass(Comment, item);
    }
    public get prefix(): string { return 'comment:'; }
}

// Creating a relation
const commentManager = new CommentManager(logger, stub);
const commentRelation = new EntityRelationChild(
    'post',
    'comment',
    commentManager
);

// Adding a comment to a post
async function addComment(ctx, commentId, postId, text, authorId) {
    const comment = new Comment();
    comment.uid = commentId;
    comment.postId = postId;
    comment.text = text;
    comment.authorId = authorId;

    await commentManager.save(comment);
    await commentRelation.childLinkAdd(commentId, postId);
}

// Getting all comments of a post
async function getPostComments(ctx, postId, pageSize = 10, bookmark = '') {
    const result = await commentRelation.childList({ pageSize, pageBookmark: bookmark }, postId);

    return {
        comments: result.items,
        nextBookmark: result.pageBookmark,
        hasMore: !result.isAllLoaded
    };
}

// Removing a post along with all its comments
async function deletePost(ctx, postId) {
    const postManager = new PostManager(logger, stub);

    // Removes the post and all related comments
    await commentRelation.parentRemove(postId);
    await postManager.remove(postId);
}

Example 3: Custom serialization

class CustomEntityManager extends EntityManager<MyEntity> {
    protected async serialize(item: MyEntity): Promise<any> {
        // Custom serialization logic
        return {
            id: item.uid,
            data: encrypt(JSON.stringify(item)),
            timestamp: Date.now()
        };
    }

    protected async deserialize(item: any, details?: Array<keyof MyEntity>): Promise<MyEntity> {
        if (!item) return null;

        // Custom deserialization logic
        const decrypted = decrypt(item.data);
        const entity = JSON.parse(decrypted);

        await this.loadDetails(entity, details);
        return entity;
    }

    public async loadDetails(item: MyEntity, details?: Array<keyof MyEntity>): Promise<void> {
        // Load related data
        if (details?.includes('author')) {
            item.author = await this.loadAuthor(item.authorId);
        }
    }

    public get prefix(): string {
        return 'custom:';
    }
}

Interfaces

IStub

Interface for working with the Hyperledger Fabric stub. You need to create an adapter that implements this interface.

interface IStub {
    readonly user: IStubUser;
    readonly requestId: string;
    readonly transaction: IStubTransaction;

    getState<U>(key: string, type?: ClassType<U>): Promise<U>;
    getStateRaw(key: string): Promise<string>;
    getStateByRange(startKey: string, endKey: string): Promise<StateQueryIterator>;
    getStateByRangeWithPagination(startKey: string, endKey: string, pageSize: number, bookmark?: string): Promise<StateQueryResponse<StateQueryIterator>>;

    putState<U>(key: string, value: U, options: IPutStateOptions): Promise<U>;
    putStateRaw(key: string, value: string): Promise<void>;

    hasState(key: string): Promise<boolean>;
    removeState(key: string): Promise<void>;

    loadKV(iterator: StateQueryIterator): Promise<Array<IKeyValue>>;
    getPaginatedKV(request: IPageBookmark, start: string, finish: string): Promise<IPaginationBookmark<IKeyValue>>;
}

IKeyValue

interface IKeyValue {
    key: string;
    value?: string;
}

Development

Requirements

  • Node.js >= 14
  • TypeScript >= 4.0
  • Hyperledger Fabric >= 2.0

Building the Project

# Install dependencies
npm install

# Build (creates dist/cjs and dist/esm)
make build

# Clean
make clean

Project Structure

hlf-core-chaincode/
├── src/
│   ├── database/
│   │   ├── entity/
│   │   │   ├── EntityManager.ts           # Abstract entity manager
│   │   │   ├── EntityManagerImpl.ts       # Implementation with validation
│   │   │   ├── EntityRelation.ts          # Relationship management
│   │   │   └── EntityRelationChild.ts     # Relations with cascade deletion
│   │   └── DatabaseManager.ts             # Base database manager
│   ├── stub/
│   │   ├── IStub.ts                       # Stub interface
│   │   └── IStubHolder.ts                 # Holder for stub
│   └── public-api.ts                      # Public API
├── dist/                                  # Compiled code
│   ├── cjs/                               # CommonJS
│   └── esm/                               # ES Modules
├── package.json
├── tsconfig.json
└── README.md

License

ISC

Author

Renat Gubaev

Links

Support

If you found a bug or have a suggestion for improvement, please open an issue in GitHub Issues.