Skip to content

Builder Pattern

The OstrichDB SDK provides an intuitive builder pattern that makes working with the hierarchical data structure natural and chainable. Instead of passing multiple string parameters to methods, the builder pattern lets you construct operations step by step, following the data hierarchy.

Traditional Method-Based API:

// Verbose and error-prone
await db.create_project('ecommerce');
await db.create_collection('ecommerce', 'products');
await db.create_cluster('ecommerce', 'products', 'electronics');
await db.create_record('ecommerce', 'products', 'electronics', 'laptop-name', 'STRING', 'MacBook Pro');

Builder Pattern API:

// Intuitive and chainable
const laptop = db.project('ecommerce')
.collection('products')
.cluster('electronics')
.record('laptop-name', 'STRING', 'MacBook Pro');
await laptop.create('laptop-name', 'STRING', 'MacBook Pro');
  • 🔗 Chainable: Natural flow following the data hierarchy
  • 📖 Readable: Code reads like the data structure
  • 🛡️ Type Safe: Full TypeScript support with auto-completion
  • 🚀 Reusable: Build once, use for multiple operations
  • 🎯 Intuitive: Mirrors how you think about organizing data

The builder pattern follows OstrichDB’s hierarchy:

OstrichDB → ProjectBuilder → CollectionBuilder → ClusterBuilder → RecordBuilder

Each level provides methods relevant to that level of the hierarchy:

const db = new OstrichDB({ /* config */ });
const project = db.project('my-app'); // ProjectBuilder
const collection = project.collection('user-data'); // CollectionBuilder
const cluster = collection.cluster('active-users'); // ClusterBuilder
const record = cluster.record('user-name', 'STRING', 'John'); // RecordBuilder
const project = db.project('ecommerce-app');
// Create the project
await project.create();
// Delete the project
await project.delete();
const project = db.project('blog-app');
// List collections in this project
const collections = await project.listCollections();
console.log('Collections:', collections);
// Access a specific collection
const posts = project.collection('posts');
MethodDescriptionReturns
create()Create the projectPromise<void>
delete()Delete the projectPromise<void>
listCollections()List collections in projectPromise<Collection[]>
collection(name)Get collection builderCollectionBuilder
const posts = db.project('blog').collection('posts');
// Create the collection
await posts.create();
// Get collection information
const info = await posts.get();
console.log('Collection info:', info);
// Delete the collection
await posts.delete();
const products = db.project('ecommerce').collection('products');
// List all clusters in this collection
const clusters = await products.listClusters();
// Access specific clusters
const electronics = products.cluster('electronics');
const clothing = products.cluster('clothing');
const books = products.cluster('books');
MethodDescriptionReturns
create()Create the collectionPromise<void>
delete()Delete the collectionPromise<void>
get()Get collection dataPromise<CollectionData>
listClusters()List clusters in collectionPromise<Cluster[]>
cluster(name)Get cluster builderClusterBuilder
const activeUsers = db.project('app')
.collection('users')
.cluster('active');
// Create the cluster
await activeUsers.create();
// Get cluster information
const clusterData = await activeUsers.get();
// Delete the cluster
await activeUsers.delete();
const electronics = db.project('store')
.collection('products')
.cluster('electronics');
// List all records in the cluster
const records = await electronics.listRecords();
console.log('Records:', records);
// Search for specific records
const laptops = await electronics.searchRecords({
search: 'laptop',
type: 'STRING',
limit: 10
});
// Access specific records
const laptop = electronics.record('macbook-pro', 'STRING', 'MacBook Pro 16"');
MethodDescriptionReturns
create()Create the clusterPromise<void>
delete()Delete the clusterPromise<void>
get()Get cluster dataPromise<ClusterData>
listRecords()List records in clusterPromise<Record[]>
searchRecords(options)Search recordsPromise<Record[]>
record(name, type, value)Get record builderRecordBuilder
const userName = db.project('app')
.collection('users')
.cluster('active')
.record('user-123-name', 'STRING', 'John Doe');
// Create the record
await userName.create('user-123-name', 'STRING', 'John Doe');
// Get the record value
const record = await userName.get('user-123-name');
console.log('User name:', record.value);
// Delete the record
await userName.delete('user-123-name');
MethodDescriptionReturns
create(name, type, value)Create the recordPromise<void>
get(identifier)Get record by name/IDPromise<Record>
delete(name)Delete the recordPromise<void>
class ProductManager {
private db: OstrichDB;
private products: ClusterBuilder;
constructor(db: OstrichDB) {
this.db = db;
this.products = db.project('ecommerce')
.collection('inventory')
.cluster('products');
}
async addProduct(id: string, name: string, price: number, category: string) {
// Create product records using builder pattern
await this.products.record(`${id}-name`, 'STRING', name)
.create(`${id}-name`, 'STRING', name);
await this.products.record(`${id}-price`, 'FLOAT', price.toString())
.create(`${id}-price`, 'FLOAT', price.toString());
await this.products.record(`${id}-category`, 'STRING', category)
.create(`${id}-category`, 'STRING', category);
await this.products.record(`${id}-created`, 'DATETIME', new Date().toISOString())
.create(`${id}-created`, 'DATETIME', new Date().toISOString());
console.log(`Product ${name} added successfully`);
}
async getProduct(id: string) {
try {
const name = await this.products.record(`${id}-name`).get(`${id}-name`);
const price = await this.products.record(`${id}-price`).get(`${id}-price`);
const category = await this.products.record(`${id}-category`).get(`${id}-category`);
const created = await this.products.record(`${id}-created`).get(`${id}-created`);
return {
id,
name: name.value,
price: parseFloat(price.value),
category: category.value,
created: created.value
};
} catch (error) {
throw new Error(`Product ${id} not found`);
}
}
async findProductsByCategory(category: string) {
const categoryRecords = await this.products.searchRecords({
search: 'category',
valueContains: category,
type: 'STRING'
});
// Group by product ID
const productIds = new Set(
categoryRecords.map(record => record.name.split('-')[0])
);
const products = [];
for (const id of productIds) {
try {
const product = await this.getProduct(id);
products.push(product);
} catch (error) {
// Skip incomplete products
}
}
return products;
}
async updateProductPrice(id: string, newPrice: number) {
await this.products.record(`${id}-price`, 'FLOAT', newPrice.toString())
.create(`${id}-price`, 'FLOAT', newPrice.toString());
}
async deleteProduct(id: string) {
await this.products.record(`${id}-name`).delete(`${id}-name`);
await this.products.record(`${id}-price`).delete(`${id}-price`);
await this.products.record(`${id}-category`).delete(`${id}-category`);
await this.products.record(`${id}-created`).delete(`${id}-created`);
}
}
// Usage
const productManager = new ProductManager(db);
await productManager.addProduct('prod-001', 'MacBook Pro', 2499.99, 'Electronics');
await productManager.addProduct('prod-002', 'iPhone 15', 999.99, 'Electronics');
const laptop = await productManager.getProduct('prod-001');
console.log('Laptop:', laptop);
const electronics = await productManager.findProductsByCategory('Electronics');
console.log('Electronics:', electronics);
class BlogManager {
private posts: ClusterBuilder;
private drafts: ClusterBuilder;
constructor(db: OstrichDB) {
const blog = db.project('blog-platform');
this.posts = blog.collection('content').cluster('published');
this.drafts = blog.collection('content').cluster('drafts');
}
async createDraft(title: string, content: string, author: string) {
const draftId = `draft-${Date.now()}`;
const draft = this.drafts;
// Create draft using builder pattern
await draft.record(`${draftId}-title`, 'STRING', title)
.create(`${draftId}-title`, 'STRING', title);
await draft.record(`${draftId}-content`, 'STRING', content)
.create(`${draftId}-content`, 'STRING', content);
await draft.record(`${draftId}-author`, 'STRING', author)
.create(`${draftId}-author`, 'STRING', author);
await draft.record(`${draftId}-created`, 'DATETIME', new Date().toISOString())
.create(`${draftId}-created`, 'DATETIME', new Date().toISOString());
return draftId;
}
async publishDraft(draftId: string) {
// Get draft data
const title = await this.drafts.record(`${draftId}-title`).get(`${draftId}-title`);
const content = await this.drafts.record(`${draftId}-content`).get(`${draftId}-content`);
const author = await this.drafts.record(`${draftId}-author`).get(`${draftId}-author`);
// Create published post
const postId = `post-${Date.now()}`;
await this.posts.record(`${postId}-title`, 'STRING', title.value)
.create(`${postId}-title`, 'STRING', title.value);
await this.posts.record(`${postId}-content`, 'STRING', content.value)
.create(`${postId}-content`, 'STRING', content.value);
await this.posts.record(`${postId}-author`, 'STRING', author.value)
.create(`${postId}-author`, 'STRING', author.value);
await this.posts.record(`${postId}-published`, 'DATETIME', new Date().toISOString())
.create(`${postId}-published`, 'DATETIME', new Date().toISOString());
// Delete draft
await this.deleteDraft(draftId);
return postId;
}
async listPublishedPosts() {
const records = await this.posts.listRecords();
// Group by post ID
const posts = new Map();
records.forEach(record => {
const parts = record.name.split('-');
const postId = parts.slice(0, 2).join('-'); // "post-timestamp"
const field = parts.slice(2).join('-'); // remaining parts
if (!posts.has(postId)) {
posts.set(postId, { id: postId });
}
posts.get(postId)[field] = record.value;
});
return Array.from(posts.values());
}
async searchPosts(query: string) {
const titleMatches = await this.posts.searchRecords({
search: 'title',
valueContains: query,
limit: 20
});
const contentMatches = await this.posts.searchRecords({
search: 'content',
valueContains: query,
limit: 20
});
// Combine and deduplicate results
const allMatches = [...titleMatches, ...contentMatches];
const uniquePostIds = new Set(
allMatches.map(record => record.name.split('-').slice(0, 2).join('-'))
);
const results = [];
for (const postId of uniquePostIds) {
try {
const post = await this.getPost(postId);
results.push(post);
} catch (error) {
// Skip incomplete posts
}
}
return results;
}
private async getPost(postId: string) {
const title = await this.posts.record(`${postId}-title`).get(`${postId}-title`);
const content = await this.posts.record(`${postId}-content`).get(`${postId}-content`);
const author = await this.posts.record(`${postId}-author`).get(`${postId}-author`);
const published = await this.posts.record(`${postId}-published`).get(`${postId}-published`);
return {
id: postId,
title: title.value,
content: content.value,
author: author.value,
published: published.value
};
}
private async deleteDraft(draftId: string) {
await this.drafts.record(`${draftId}-title`).delete(`${draftId}-title`);
await this.drafts.record(`${draftId}-content`).delete(`${draftId}-content`);
await this.drafts.record(`${draftId}-author`).delete(`${draftId}-author`);
await this.drafts.record(`${draftId}-created`).delete(`${draftId}-created`);
}
}
class UserManager {
private activeUsers: ClusterBuilder;
private inactiveUsers: ClusterBuilder;
constructor(db: OstrichDB) {
const userSystem = db.project('user-management');
const users = userSystem.collection('users');
this.activeUsers = users.cluster('active');
this.inactiveUsers = users.cluster('inactive');
}
async createUser(userData: {
username: string;
email: string;
age: number;
roles: string[];
}) {
const userId = `user-${Date.now()}`;
const users = this.activeUsers;
// Create user records with proper types
await users.record(`${userId}-username`, 'STRING', userData.username)
.create(`${userId}-username`, 'STRING', userData.username);
await users.record(`${userId}-email`, 'STRING', userData.email)
.create(`${userId}-email`, 'STRING', userData.email);
await users.record(`${userId}-age`, 'INTEGER', userData.age.toString())
.create(`${userId}-age`, 'INTEGER', userData.age.toString());
await users.record(`${userId}-roles`, '[]STRING', JSON.stringify(userData.roles))
.create(`${userId}-roles`, '[]STRING', JSON.stringify(userData.roles));
await users.record(`${userId}-created`, 'DATETIME', new Date().toISOString())
.create(`${userId}-created`, 'DATETIME', new Date().toISOString());
await users.record(`${userId}-active`, 'BOOLEAN', 'true')
.create(`${userId}-active`, 'BOOLEAN', 'true');
return userId;
}
async getUser(userId: string) {
try {
const username = await this.activeUsers.record(`${userId}-username`).get(`${userId}-username`);
const email = await this.activeUsers.record(`${userId}-email`).get(`${userId}-email`);
const age = await this.activeUsers.record(`${userId}-age`).get(`${userId}-age`);
const roles = await this.activeUsers.record(`${userId}-roles`).get(`${userId}-roles`);
const created = await this.activeUsers.record(`${userId}-created`).get(`${userId}-created`);
return {
id: userId,
username: username.value,
email: email.value,
age: parseInt(age.value),
roles: JSON.parse(roles.value),
created: created.value,
active: true
};
} catch (error) {
// Try inactive users
try {
const username = await this.inactiveUsers.record(`${userId}-username`).get(`${userId}-username`);
const email = await this.inactiveUsers.record(`${userId}-email`).get(`${userId}-email`);
const age = await this.inactiveUsers.record(`${userId}-age`).get(`${userId}-age`);
const roles = await this.inactiveUsers.record(`${userId}-roles`).get(`${userId}-roles`);
const created = await this.inactiveUsers.record(`${userId}-created`).get(`${userId}-created`);
return {
id: userId,
username: username.value,
email: email.value,
age: parseInt(age.value),
roles: JSON.parse(roles.value),
created: created.value,
active: false
};
} catch (inactiveError) {
throw new Error(`User ${userId} not found`);
}
}
}
async deactivateUser(userId: string) {
// Get user data from active cluster
const user = await this.getUser(userId);
if (!user.active) {
throw new Error('User is already inactive');
}
// Move to inactive cluster
await this.inactiveUsers.record(`${userId}-username`, 'STRING', user.username)
.create(`${userId}-username`, 'STRING', user.username);
await this.inactiveUsers.record(`${userId}-email`, 'STRING', user.email)
.create(`${userId}-email`, 'STRING', user.email);
await this.inactiveUsers.record(`${userId}-age`, 'INTEGER', user.age.toString())
.create(`${userId}-age`, 'INTEGER', user.age.toString());
await this.inactiveUsers.record(`${userId}-roles`, '[]STRING', JSON.stringify(user.roles))
.create(`${userId}-roles`, '[]STRING', JSON.stringify(user.roles));
await this.inactiveUsers.record(`${userId}-created`, 'DATETIME', user.created)
.create(`${userId}-created`, 'DATETIME', user.created);
await this.inactiveUsers.record(`${userId}-deactivated`, 'DATETIME', new Date().toISOString())
.create(`${userId}-deactivated`, 'DATETIME', new Date().toISOString());
// Remove from active cluster
await this.activeUsers.record(`${userId}-username`).delete(`${userId}-username`);
await this.activeUsers.record(`${userId}-email`).delete(`${userId}-email`);
await this.activeUsers.record(`${userId}-age`).delete(`${userId}-age`);
await this.activeUsers.record(`${userId}-roles`).delete(`${userId}-roles`);
await this.activeUsers.record(`${userId}-created`).delete(`${userId}-created`);
await this.activeUsers.record(`${userId}-active`).delete(`${userId}-active`);
}
async findUsersByRole(role: string) {
const roleRecords = await this.activeUsers.searchRecords({
search: 'roles',
valueContains: role,
type: '[]STRING'
});
const userIds = new Set(
roleRecords.map(record => record.name.split('-').slice(0, 2).join('-'))
);
const users = [];
for (const userId of userIds) {
try {
const user = await this.getUser(userId);
users.push(user);
} catch (error) {
// Skip users that couldn't be loaded
}
}
return users;
}
}

Create builders once and reuse them for multiple operations:

// Good - reuse builders
const users = db.project('app').collection('users').cluster('active');
await users.record('user1-name', 'STRING', 'Alice').create('user1-name', 'STRING', 'Alice');
await users.record('user2-name', 'STRING', 'Bob').create('user2-name', 'STRING', 'Bob');
await users.record('user3-name', 'STRING', 'Charlie').create('user3-name', 'STRING', 'Charlie');
// Bad - recreate builders each time
await db.project('app').collection('users').cluster('active')
.record('user1-name', 'STRING', 'Alice').create('user1-name', 'STRING', 'Alice');
await db.project('app').collection('users').cluster('active')
.record('user2-name', 'STRING', 'Bob').create('user2-name', 'STRING', 'Bob');

Make your code self-documenting with clear names:

// Good - descriptive names
const blogPosts = db.project('blog-platform')
.collection('content')
.cluster('published-posts');
const userProfiles = db.project('social-app')
.collection('user-data')
.cluster('active-profiles');
// Bad - unclear names
const data = db.project('app').collection('stuff').cluster('things');

Organize related operations together:

class OrderManager {
private orders: ClusterBuilder;
constructor(db: OstrichDB) {
this.orders = db.project('ecommerce')
.collection('transactions')
.cluster('orders');
}
async createOrder(orderData: any) {
const orderId = `order-${Date.now()}`;
// Group all order-related operations
await this.orders.record(`${orderId}-customer`, 'STRING', orderData.customer)
.create(`${orderId}-customer`, 'STRING', orderData.customer);
await this.orders.record(`${orderId}-total`, 'FLOAT', orderData.total.toString())
.create(`${orderId}-total`, 'FLOAT', orderData.total.toString());
await this.orders.record(`${orderId}-items`, '[]STRING', JSON.stringify(orderData.items))
.create(`${orderId}-items`, '[]STRING', JSON.stringify(orderData.items));
await this.orders.record(`${orderId}-status`, 'STRING', 'pending')
.create(`${orderId}-status`, 'STRING', 'pending');
return orderId;
}
}

Implement proper error handling in your builder operations:

async function safeCreateUser(db: OstrichDB, userData: any) {
const users = db.project('app').collection('users').cluster('active');
try {
const userId = `user-${Date.now()}`;
await users.record(`${userId}-username`, 'STRING', userData.username)
.create(`${userId}-username`, 'STRING', userData.username);
await users.record(`${userId}-email`, 'STRING', userData.email)
.create(`${userId}-email`, 'STRING', userData.email);
return userId;
} catch (error) {
console.error('Failed to create user:', error);
throw error;
}
}

The builder pattern inherently promotes connection reuse:

// Efficient - same underlying connection
const app = db.project('my-app');
const users = app.collection('users');
const activeUsers = users.cluster('active');
const inactiveUsers = users.cluster('inactive');
// Multiple operations use the same connection
await activeUsers.record('user1', 'STRING', 'Alice').create('user1', 'STRING', 'Alice');
await activeUsers.record('user2', 'STRING', 'Bob').create('user2', 'STRING', 'Bob');
await inactiveUsers.record('old-user', 'STRING', 'Charlie').create('old-user', 'STRING', 'Charlie');

Use the builder pattern to organize batch operations:

async function initializeApp(db: OstrichDB) {
// Build the structure
const app = db.project('new-app');
const users = app.collection('users');
const products = app.collection('products');
// Create structure
await app.create();
await users.create();
await products.create();
// Create clusters
const activeUsers = users.cluster('active');
const electronics = products.cluster('electronics');
await activeUsers.create();
await electronics.create();
console.log('App structure initialized');
}

Now that you understand the builder pattern:

  1. Framework Integration - See how to use builders in real applications
  2. Error Handling - Learn comprehensive error management
  3. Configuration - Optimize your setup for different environments
  4. API Reference - Explore all available methods

The builder pattern makes working with OstrichDB’s hierarchical structure intuitive and maintainable. Use it as your primary method for interacting with the database to write cleaner, more readable code.