Skip to main content

Authentication

Use Dual-Key Auth

Use appKey for writes, userKey for Auto-Pay reads. Never expose appKey in client-side code.
// Server-side
const client = createClient({
  endpoint: process.env.ONCHAINDB_ENDPOINT,
  appId: process.env.ONCHAINDB_APP_ID,
  appKey: process.env.ONCHAINDB_APP_KEY
});

Indexes

Create Indexes FirstYou MUST create at least one index per collection. Without indexes, queries perform full collection scans and the dashboard cannot display collections. Create indexes BEFORE storing data.
// Always create indexes before storing data
const db = client.database('my-app');

await db.createIndex({
  name: 'idx_users_email',
  collection: 'users',
  field_name: 'email',
  index_type: 'hash',
  options: { unique: true }
});

// Index fields you'll query
await db.createIndex({
  name: 'idx_users_createdAt',
  collection: 'users',
  field_name: 'createdAt',
  index_type: 'btree'
});

// Now store data
await client.store({ collection: 'users', data: [...] });

Payment Handling

Handle x402 Callbacks

Implement payment callbacks for store operations. Handle PaymentRequiredError for read operations.
// Write operations
await client.store(
  { collection: 'data', data },
  async (quote) => {
    const txHash = await wallet.pay(quote.brokerAddress, quote.totalCostTia);
    return { txHash, network: 'mocha-4' };
  }
);

// Read operations
try {
  const result = await client.queryBuilder()
    .collection('premium_data')
    .execute();
} catch (error) {
  if (error instanceof PaymentRequiredError) {
    // Handle payment flow
    const quote = error.quote;
    // ...
  }
}

Error Handling

Use Specific Error Types

Always implement proper error handling with specific error types.
import {
  ValidationError,
  TransactionError,
  PaymentRequiredError,
  OnchainDBError
} from '@onchaindb/sdk';

try {
  await client.store(data, paymentCallback);
} catch (error) {
  if (error instanceof ValidationError) {
    // Handle validation errors
  } else if (error instanceof TransactionError) {
    // Handle transaction failures
  } else if (error instanceof PaymentRequiredError) {
    // Handle payment flow
  } else if (error instanceof OnchainDBError) {
    // Handle other SDK errors
  }
}

Query Optimization

Use Query Builder for Complex Queries

Use QueryBuilder for complex queries, helper methods for simple operations.
// Simple queries - use helper methods
const user = await client.findUnique('users', { email: 'alice@example.com' });

// Complex queries - use QueryBuilder
const results = await client.queryBuilder()
  .collection('orders')
  .whereField('status').equals('completed')
  .whereField('createdAt').dateThisMonth()
  .joinOne('customer', 'users')
    .onField('id').equals('$data.customerId')
    .selectFields(['name', 'email'])
    .build()
  .selectAll()
  .limit(50)
  .execute();

Async Operations

Use Task Tickets for Long Operations

Use task tickets for long-running operations and monitor progress.
// Non-blocking store
const response = await client.store(data, paymentCallback, false);

// Monitor progress
const status = await client.getTaskStatus(response.ticket_id);

// Or wait for completion
const task = await client.waitForTaskCompletion(response.ticket_id);

Cost Estimation

Get Quotes Before Operations

Get pricing quotes before operations to estimate costs.
const quote = await client.getPricingQuote({
  app_id: 'my_app',
  operation_type: 'write',
  size_kb: Math.ceil(JSON.stringify(data).length / 1024),
  collection: 'users'
});

if (quote.total_cost > maxBudget) {
  throw new Error('Operation exceeds budget');
}

PriceIndex for Commerce

Use PriceIndex for Revenue Sharing

Use PriceIndex when building commerce platforms, ticketing systems, or apps with revenue sharing.
// Create PriceIndex on payment field
await db.createIndex({
  name: 'idx_orders_total',
  collection: 'orders',
  field_name: 'totalPrice',
  index_type: 'Price'
});

// Payment = field value, not storage cost
await client.createDocument('orders', {
  totalPrice: 100000000, // User pays 100 TIA
  items: [...]
}, paymentProof);

TypeScript

Leverage Type Safety

Use TypeScript for full type safety and better development experience.
interface User {
  id: string;
  email: string;
  name: string;
  createdAt: string;
}

const user = await client.findUnique<User>('users', { email: 'alice@example.com' });
// user is User | null with full IntelliSense

const users = await client.queryBuilder()
  .collection('users')
  .whereField('active').isTrue()
  .execute<User>();
// users.records is User[]

Batch Operations

Use Batches for Bulk Data

Use batch operations for large datasets to improve efficiency.
import { BulkBuilder } from '@onchaindb/sdk';

const builder = new BulkBuilder().collection('products');
products.forEach(p => builder.add(p));

const batch = client.batch();
await batch.store(builder.build(), {
  concurrency: 5,
  waitForConfirmation: true,
  onProgress: (completed, total) => {
    console.log(`${completed}/${total}`);
  }
});

Transaction Monitoring

Monitor Transactions in Real-Time

Use event listeners to track transaction status in real-time.
client.on('transaction:queued', (ticket) => {
  console.log(`Task ${ticket.ticket_id} queued`);
});

client.on('transaction:confirmed', (tx) => {
  console.log(`Confirmed at block ${tx.block_height}`);
});

client.on('transaction:failed', (tx) => {
  console.error(`Failed: ${tx.error}`);
});

Checklist

  • Create indexes before storing data
  • Use environment variables for keys
  • Implement payment callbacks
  • Handle all error types
  • Use TypeScript for type safety
  • Get cost estimates before operations
  • Monitor transactions with events
  • Use batch operations for bulk data
  • Consider PriceIndex for commerce apps