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 First You 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