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

@ondb/sdk

v1.0.1

Published

OnDB Platform TypeScript SDK

Readme

OnDB TypeScript SDK

OnDB Platform TypeScript SDK.

Installation

npm install @ondb/sdk

Quick Start

import { createClient } from '@ondb/sdk';

const db = createClient({
  endpoint: 'https://api.onchaindb.io',
  appId: 'my_app',
  appKey: 'your_app_key'
});

// Store data
await db.store({
  collection: 'messages',
  data: [{ message: 'Hello OnDB!', author: 'alice' }]
});

// Query data
const result = await db.queryBuilder()
  .collection('messages')
  .whereField('author').equals('alice')
  .selectAll()
  .limit(10)
  .execute();

console.log(result.records);

Configuration

interface OnDBConfig {
  endpoint: string;      // OnDB server endpoint
  appId?: string;        // Application ID
  appKey?: string;       // App key for write operations (X-App-Key header)
  agentKey?: string;     // Agent key with Pay permission (X-Agent-Key header)
  timeout?: number;      // Request timeout in ms (default: 30000)
  retryCount?: number;   // Retry attempts (default: 3)
  retryDelay?: number;   // Retry delay in ms (default: 1000)
}

Agent Keys

Keys with the Pay permission are Agent Keys — they can pay other apps inline using USDC via EIP-3009.

const db = createClient({
  endpoint: 'https://api.onchaindb.io',
  appId: 'my_app',
  agentKey: 'agent_key_with_pay_permission'
});

Storing Data

// Basic store
await db.store({
  collection: 'tweets',
  data: [
    { id: 'tweet_1', content: 'Hello!', author: 'alice' },
    { id: 'tweet_2', content: 'World!', author: 'bob' }
  ]
});

// Store with x402 payment callback
await db.store(
  { collection: 'tweets', data: [{ content: 'Paid tweet' }] },
  async (quote) => {
    // quote is an X402Quote — sign and broadcast payment via your wallet
    const txHash = await wallet.sendTransaction({
      to: quote.payTo,
      value: quote.maxAmountRequired
    });
    return {
      txHash,
      network: quote.network,
      sender: wallet.address,
      chainType: quote.chainType,
      paymentMethod: quote.paymentMethod
    };
  }
);

Query Builder

The SDK provides a fluent query builder for constructing queries. See the Query SDK Reference for the full operator and aggregation reference.

Basic Queries

const result = await db.queryBuilder()
  .collection('users')
  .whereField('status').equals('active')
  .selectFields(['id', 'name', 'email'])
  .limit(50)
  .execute();

result.records.forEach(user => console.log(user.name, user.email));

All Field Operators

// Comparison
.equals(value)           .notEquals(value)
.greaterThan(value)      .greaterThanOrEqual(value)
.lessThan(value)         .lessThanOrEqual(value)
.between(min, max)

// String
.contains(value)         .startsWith(value)        .endsWith(value)
.regExpMatches(pattern)
.includesCaseInsensitive(value)
.startsWithCaseInsensitive(value)
.endsWithCaseInsensitive(value)

// Array
.in(values)              .notIn(values)

// Existence
.exists()    .notExists()    .isNull()    .isNotNull()
.isTrue()    .isFalse()

// Network / security
.isLocalIp()    .isExternalIp()    .inCountry(code)    .cidr(range)
.b64(value)     .inDataset(name)

Complex Queries with Logical Operators

import { LogicalOperator } from '@ondb/sdk';

const result = await db.queryBuilder()
  .collection('posts')
  .find(builder =>
    LogicalOperator.And([
      builder.field('published').isTrue(),
      LogicalOperator.Or([
        builder.field('category').equals('tech'),
        builder.field('views').greaterThan(1000)
      ]),
      LogicalOperator.Not([
        builder.field('archived').isTrue()
      ])
    ])
  )
  .selectFields(['title', 'content', 'author'])
  .limit(20)
  .execute();

Sorting & Pagination

const result = await db.queryBuilder()
  .collection('posts')
  .whereField('published').isTrue()
  .orderBy('created_at', 'DESC')
  .limit(20)
  .offset(40)
  .selectAll()
  .execute();

Server-Side Aggregations

// Count
const total = await db.queryBuilder()
  .collection('users')
  .whereField('active').isTrue()
  .count();

// Sum / average
const revenue = await db.queryBuilder().collection('orders').sumBy('amount');
const avg     = await db.queryBuilder().collection('orders').avgBy('amount');

// Group by
const byCountry = await db.queryBuilder()
  .collection('users')
  .groupBy('country')
  .count();
// { "USA": 150, "UK": 75, ... }

Server-Side JOINs

JOINs execute on the backend in a single request. Use $data.fieldname to reference parent record fields.

joinOne (one-to-one)

const result = await db.queryBuilder()
  .collection('tweets')
  .joinOne('author_info', 'users')
    .onField('address').equals('$data.author')
    .selectFields(['display_name', 'avatar_url', 'verified'])
    .build()
  .selectAll()
  .limit(20)
  .execute();

// result.records[0].author_info => { display_name, avatar_url, verified } | null

joinMany (one-to-many)

const result = await db.queryBuilder()
  .collection('users')
  .whereField('address').equals(userAddress)
  .joinMany('tweets', 'tweets')
    .onField('author').equals('$data.address')
    .selectFields(['id', 'content', 'created_at'])
    .build()
  .selectAll()
  .execute();

// result.records[0].tweets => [{ id, content, created_at }, ...]

Nested JOINs

const result = await db.queryBuilder()
  .collection('tweets')
  .joinMany('replies', 'tweets')
    .onField('reply_to_id').equals('$data.id')
    .selectAll()
    .joinOne('author_info', 'users')
      .onField('address').equals('$data.author')
      .selectFields(['display_name', 'avatar_url'])
      .build()
    .build()
  .selectAll()
  .execute();

Model API

db.model<T>(collection) returns a typed delegate with structured query and write methods — a clean, declarative API on top of QueryBuilder and store.

interface User {
  id: string;
  email: string;
  role: string;
  age: number;
  deletedAt?: number;
}

const users = db.model<User>('users');

Reading

// findMany — returns T[]
const active = await users.findMany({
  where: { role: 'admin', age: { gte: 18 } },
  select: { id: true, email: true },
  orderBy: { age: 'desc' },
  take: 50,
  skip: 0,
});

// findFirst — returns T | null
const user = await users.findFirst({ where: { email: '[email protected]' } });

// findUnique — returns T | null
const user = await users.findUnique({ where: { id: 'usr_1' } });

Where clause

Top-level fields are implicitly ANDed. All field operators from QueryBuilder are available:

const results = await users.findMany({
  where: {
    // shorthand equality
    role: 'admin',

    // field operators
    age:       { gte: 18, lt: 65 },
    email:     { endsWith: '@example.com' },
    status:    { in: ['active', 'pending'] },
    bio:       { containsCaseInsensitive: 'engineer' },
    ipAddress: { isLocalIp: true },
    score:     { between: { from: 10, to: 90 } },
    tags:      { keywords: ['rust', 'typescript'] },

    // logical operators
    AND: [{ age: { gte: 18 } }, { age: { lt: 65 } }],
    OR:  [{ role: 'admin' }, { role: 'moderator' }],
    NOT: [{ status: 'banned' }],
  }
});

Full FieldFilter reference

| Operator | Description | |----------|-------------| | equals | Exact equality | | not | Not equal | | in | Case-insensitive membership | | notIn | Exclude values | | inDataset | Case-sensitive membership (string[]) | | lt, lte, gt, gte | Numeric comparisons | | between | Exclusive range { from, to } | | contains | Substring (case-sensitive) | | startsWith / endsWith | Prefix / suffix | | containsCaseInsensitive / startsWithCaseInsensitive / endsWithCaseInsensitive | Case-insensitive string ops | | regex | Regular expression match | | isNotNull | Field is not null | | exists | true → field exists, false → field absent | | isLocalIp / isExternalIp | IP classification | | inCountry | GeoIP country code | | cidr | CIDR range check (string or string[]) | | keywords | Any keyword matches (case-insensitive substring) | | b64 | Base64-encoded value match |

Exclude soft-deleted records

// true → filters out records where deletedAt exists
const live = await users.findMany({
  where: { role: 'admin' },
  excludeDeleted: true,
});

// string → use a custom delete marker field
const live = await users.findMany({
  excludeDeleted: 'archivedAt',
});

Writing

OnDB is an append-only log with server-side deduplication on unique fields. "Update" and "delete" are implemented by writing a new version of the document.

// Create one record
await users.create({ data: { id: 'usr_1', email: '[email protected]', role: 'user', age: 30 } });

// Create many records in a single request
await users.createMany({
  data: [
    { id: 'usr_2', email: '[email protected]', role: 'user', age: 25 },
    { id: 'usr_3', email: '[email protected]', role: 'admin', age: 40 },
  ]
});

// Update — finds the record, merges patch, stores the new version
await users.updateDocument(
  { id: 'usr_1' },          // identifier (WhereInput)
  { role: 'admin', age: 31 } // patch
);

// Soft-delete — sets deletedAt: Date.now() on the existing record
await users.deleteDocument({ id: 'usr_1' });

// Soft-delete with extra patch fields
await users.deleteDocument({ id: 'usr_1' }, { deletedBy: 'moderator' });

Count & Aggregate

const total = await users.count({ where: { role: 'admin' } });

const stats = await users.aggregate({
  where: { role: 'user' },
  _count: true,
  _avg:   { age: true },
  _sum:   { age: true },
  _min:   { age: true },
  _max:   { age: true },
});
// { _count: 42, _avg_age: 28.5, _sum_age: 1197, _min_age: 18, _max_age: 64 }

Collection Management

Create & Sync Collections

import { SimpleCollectionSchema } from '@ondb/sdk';

const schema: SimpleCollectionSchema = {
  name: 'users',
  fields: {
    email:          { type: 'string', index: true },
    age:            { type: 'number', index: true },
    status:         { type: 'string', index: true, indexType: 'hash' },
    bio:            { type: 'string', index: true, indexType: 'fulltext' },
    'address.city': { type: 'string', index: true },
  },
  useBaseFields: true, // Auto-index id, createdAt, updatedAt, deletedAt
};

// Create collection with indexes
await db.createCollection(schema);

// Sync: creates new indexes, removes none (broker handles upsert)
await db.syncCollection(schema);

Sharding

Partition large collections across multiple files for massive datasets.

import { SimpleCollectionSchemaWithSharding, ShardingStrategy } from '@ondb/sdk';

const schema: SimpleCollectionSchemaWithSharding = {
  name: 'orderbook_snapshots',
  fields: {
    market:    { type: 'string', index: true },
    timestamp: { type: 'number', index: true },
    mid_price: { type: 'number' }
  },
  sharding: {
    keys: [
      { field: 'market',    type: 'discrete' },
      { field: 'timestamp', type: 'time_range', granularity: 'hour' }
    ],
    enforce_in_queries: true
  }
};

// syncCollection applies sharding automatically if present
await db.syncCollection(schema);

// Or configure sharding separately
await db.setupSharding('orderbook_snapshots', schema.sharding!);

Update & Delete Collections

// Update collection properties
await db.updateCollection('users', {
  public: true,
  description: 'User profiles',
  default_sort_column: 'created_at',
  collection_type: 'local' // 'local' | 'offline' | 'api' | 'hybrid'
});

// Delete a collection
await db.deleteCollection('old_collection');

// Get collection info by ID (ID format: "{appId}_{index}", from listCollections)
const info = await db.getCollectionInfo('myapp_0');
// { id, name, namespace, primary_column, sort_column, status, collection_type,
//   document_count, size_kb, created_at, last_modified }

Data Retention

Configure per-collection retention policies. First 30 days are always free.

// Get current retention config
const config = await db.getRetention('orders');
// { collection, retention_days, monthly_cost_per_kb, last_cleanup, status }

// Set retention (null = permanent storage)
const result = await db.setRetention('orders', { retention_days: 90 });
// { message, config: RetentionConfig }

// Get cost estimate for all collections
const costs = await db.getRetentionCost();
// { app_id, collections: CollectionRetentionCost[], total_monthly_cost, projected_next_month_cost }

SQL Interface

Query and insert data using SQL syntax. Table references use app::collection format.

// SQL query
const result = await db.sql(
  'SELECT * FROM myapp::orders WHERE amount > 100 ORDER BY created_at DESC LIMIT 50'
);
// { data: any[], count, query, app_id, collection }

// SQL query with history (includes all versions of records)
const withHistory = await db.sql(
  'SELECT * FROM myapp::orders',
  { includeHistory: true }
);

// SQL insert
await db.sqlInsert(
  "INSERT INTO myapp::orders (id, amount, status) VALUES ('ord_1', 250, 'pending')"
);

Predefined Queries

Named, parameterized queries that can be executed publicly without authentication.

import { CreateQueryRequest } from '@ondb/sdk';

// Create a predefined query
const created = await db.createQuery({
  name: 'active_markets',
  source_collection: 'orderbook_snapshots',
  base_query: { find: { status: { $eq: 'active' } } },
  parameters: [
    {
      name: 'market',
      field_path: 'market',   // maps to find.market.$eq
      required: true,
      description: 'Market symbol (e.g. BTC, ETH)'
    },
    {
      name: 'min_price',
      field_path: 'price.$gte',
      default: 0,
      description: 'Minimum price filter'
    }
  ],
  description: 'Query active orderbook snapshots by market'
});

// List all queries
const queries = await db.listQueries();
// QueryDefinition[]

// Get a specific query definition
const query = await db.getQuery('active_markets');

// Execute a predefined query (public endpoint — no auth required)
const data = await db.executeQuery(
  'active_markets',
  { market: 'BTC', min_price: '1000' }  // params as Record<string, string>
);
// { success, query_name, data, count, query_time_ms }

// Execute a specific version
const v2data = await db.executeQuery('active_markets', { market: 'ETH' }, 2);

// Delete a query
await db.deleteQuery('active_markets');

Materialized Views

Create via JSON Query

const db_manager = db.database();

await db_manager.createView(
  'top_sellers',
  ['products', 'orders'],
  { find: { status: { $eq: 'completed' } }, sort_by: ['-total'], limit: 100 }
);

Create via SQL

await db_manager.createViewSql(
  'SELECT market, AVG(price) as avg_price FROM myapp::trades GROUP BY market',
  'live' // 'live' | 'lazy'
);

Query & Manage Views

// Query view data
const result = await db_manager.queryView<TradeStats>('top_sellers', {
  find: { avg_price: { $gte: 1000 } },
  sort_by: ['-avg_price'],
  limit: 20
});
// { success, view_name, data: T[], count, query_time_ms }

// Count records in a view
const count = await db_manager.countView('top_sellers', { market: 'BTC' });

await db_manager.listViews();
await db_manager.getView('top_sellers');
await db_manager.refreshView('top_sellers');
await db_manager.deleteView('top_sellers');

API Collections

Collections backed by external APIs instead of on-chain data.

const db_manager = db.database();

await db_manager.createApiCollection({
  name: 'live_prices',
  description: 'Real-time price feed from external API',
  api_config: {
    base_url: 'https://api.example.com',
    authentication: { type: 'bearer', token: 'xxx' },
    query_mapping: { path_template: '/v1/prices/{market}' },
    response_mapping: { records_path: 'data.prices' }
  }
});

const collections = await db_manager.listApiCollections();
const collection  = await db_manager.getApiCollection('live_prices');
await db_manager.deleteApiCollection('live_prices');

Index Management

Access the DatabaseManager via db.database() for direct index control.

const dbm = db.database();

// List all indexes
const indexes = await dbm.listIndexes();

// Create an index
await dbm.createIndex({
  name: 'users_email_idx',
  collection: 'users',
  field_name: 'email',
  index_type: 'btree'
});

// Create multiple indexes at once
await dbm.createIndexes('users', [
  { name: 'email_idx',    field_name: 'email',    index_type: 'btree' },
  { name: 'status_idx',   field_name: 'status',   index_type: 'hash' },
  { name: 'bio_idx',      field_name: 'bio',      index_type: 'fulltext' },
]);

// Create a price index (enables payment splits on write)
await dbm.createPriceIndex('orders', 'total_price', {
  app_owner_percentage: 0.80,
  platform_percentage: 0.20,
  app_owner_address: 'celestia1abc...'
});

// Drop an index by ID
await dbm.dropIndex('users_email_idx');

Blob Storage

// Upload
const upload = await db.uploadBlob({
  collection: 'images',
  blob: file,
  metadata: { user_address: userAddress }
});

// Wait for on-chain confirmation
const task = await db.waitForTaskCompletion(upload.ticket_id);
console.log('Blob ID:', upload.blob_id);

// Retrieve
const blob = await db.retrieveBlob({
  collection: 'images',
  blob_id: upload.blob_id
});

Task Tracking

const task = await db.getTaskStatus(ticketId);
// status: 'Pending' | 'PaymentBroadcast' | 'PaymentConfirming' |
//         'PaymentConfirmed' | 'StoringData' | 'Completed' | { Failed: { error } }

const completed = await db.waitForTaskCompletion(ticketId, 2000, 600000);
// Polls every 2s, times out after 600s

Pricing Quotes

const quote = await db.getPricingQuote({
  app_id: 'my_app',
  operation_type: 'write',
  size_kb: 10,
  collection: 'orders',
  data: { amount: 150 } // for price index calculation
});
// { total_cost, total_cost_utia, indexing_costs, ... }

Error Handling

import { OnDBError, ValidationError, PaymentRequiredError } from '@ondb/sdk';

try {
  await db.store({ collection: 'test', data: [{ value: 1 }] });
} catch (error) {
  if (error instanceof ValidationError)      console.log('Validation:', error.message);
  if (error instanceof PaymentRequiredError) console.log('Payment required:', error.paymentRequired);
  if (error instanceof OnDBError)            console.log(error.code, error.message);
}

API Reference

OnDBClient

| Method | Description | |--------|-------------| | store(request, paymentCallback?) | Store data on-chain | | model<T>(collection) | Typed model delegate (structured queries & writes) | | queryBuilder() | Fluent query builder | | createCollection(schema) | Create collection with indexes | | syncCollection(schema) | Sync indexes to schema (supports sharding) | | setupSharding(collection, sharding) | Configure sharding on an existing collection | | deleteCollection(collection) | Delete a collection | | updateCollection(collection, updates) | Update collection properties | | getCollectionInfo(collectionId) | Get collection details by ID | | getRetention(collection) | Get retention config | | setRetention(collection, request) | Set retention period | | getRetentionCost() | Get retention cost estimate for all collections | | sql(query, options?) | Execute SQL query | | sqlInsert(sql) | Execute SQL insert | | listQueries() | List predefined queries | | createQuery(request) | Create a predefined query | | getQuery(name) | Get query definition | | deleteQuery(name) | Delete a predefined query | | executeQuery(name, params?, version?) | Execute a predefined query | | createRelation(request) | Create a collection relation | | createIndex(request) | Create a single index | | getPricingQuote(request) | Estimate storage cost | | uploadBlob(request, paymentCallback?) | Upload binary file | | retrieveBlob(request) | Download binary file | | getTaskStatus(ticketId) | Get async task status | | waitForTaskCompletion(ticketId) | Poll until task completes | | health() | Server health check | | database(appId?) | Access DatabaseManager |

DatabaseManager (db.database())

| Method | Description | |--------|-------------| | listIndexes() | List all indexes for the app | | createIndex(definition) | Create an index | | createIndexes(collection, indexes) | Bulk create indexes | | createPriceIndex(collection, field, config?) | Create payment-split price index | | dropIndex(indexId) | Delete an index by ID | | createView(name, sources, query) | Create materialized view | | createViewSql(sql, refreshMode?) | Create view from SQL | | queryView(viewName, query?) | Query view data | | countView(viewName, filter?) | Count view records | | listViews() | List all views | | getView(name) | Get view definition | | refreshView(name) | Rebuild view | | deleteView(name) | Delete a view | | createApiCollection(request) | Create API-backed collection | | listApiCollections() | List API collections | | getApiCollection(name) | Get API collection details | | deleteApiCollection(name) | Delete API collection |

ModelDelegate (db.model<T>(collection))

| Method | Description | |--------|-------------| | findMany(args?) | Query records, returns T[] | | findFirst(args?) | First matching record or null | | findUnique(args) | Alias for findFirst with a required where | | create(args) | Store a single record | | createMany(args) | Store multiple records in one request | | updateDocument(identifierFind, patch) | Find, merge patch, re-store | | deleteDocument(identifierFind, patch?) | Soft-delete by setting deletedAt | | count(args?) | Count matching records | | aggregate(args) | Server-side aggregations (_count, _sum, _avg, _min, _max) |

QueryBuilder

| Method | Description | |--------|-------------| | collection(name) | Set target collection | | whereField(name) | Start a field condition | | find(builderFn) | Set complex logical conditions | | selectFields(fields) | Select specific fields | | selectAll() | Select all fields | | joinOne(alias, model) | One-to-one server JOIN | | joinMany(alias, model) | One-to-many server JOIN | | orderBy(field, dir?) | Sort results | | limit(n) | Limit result count | | offset(n) | Skip results | | execute() | Execute and return records | | executeUnique() | Return latest single record | | count() | Count matching records | | sumBy(field) | Sum numeric field | | avgBy(field) | Average numeric field | | maxBy(field) | Maximum field value | | minBy(field) | Minimum field value | | distinctBy(field) | Distinct field values | | groupBy(field) | Start grouped aggregation | | buildRawQuery() | Inspect raw query object | | clone() | Clone builder |

License

MIT