Configuration Guide
Complete guide to configuring Bindra DataSources and all features.
Table of Contents
- Overview
- Basic Configuration
- Data Modes
- Pagination Configuration
- Caching Configuration
- Real-time Configuration
- Retry Configuration
- Security Configuration
- Validation Configuration
- Complete Examples
Overview
Bindra DataSources are configured through the DataSourceConfig object passed to the constructor. Configuration is flexible and can be as simple or comprehensive as needed.
import { DataSource } from 'bindra';
import type { DataSourceConfig } from 'bindra';
const config: DataSourceConfig<User> = {
// Minimal configuration
url: '/api/users'
};
const users = new DataSource<User>(config);Basic Configuration
Minimal Remote DataSource
const users = new DataSource<User>({
url: '/api/users'
});Minimal Local DataSource
const users = new DataSource<User>({
data: [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
]
});URL and Data Are Mutually Exclusive
// ✅ Valid - Remote mode
new DataSource<User>({ url: '/api/users' });
// ✅ Valid - Local mode
new DataSource<User>({ data: [] });
// ❌ Invalid - Cannot specify both
new DataSource<User>({
url: '/api/users',
data: [] // Error: either url or data, not both
});
// ❌ Invalid - Must specify at least one
new DataSource<User>({}); // Error: url or data requiredData Modes
Local Data Mode
Use when working with client-side data (no API).
const todos = new DataSource<Todo>({
data: [
{ id: 1, title: 'Learn Bindra', completed: false },
{ id: 2, title: 'Build app', completed: false }
]
});
// All operations work on local data
await todos.create({ title: 'New todo', completed: false });
await todos.update(1, { completed: true });
await todos.delete(2);
// Data is reactive
console.log(todos.data); // Updated automaticallyFeatures in Local Mode:
- ✅ Full CRUD operations
- ✅ Reactive data updates
- ✅ Navigation (next, prev, goto)
- ✅ Client-side filtering and sorting
- ✅ Validation
- ❌ No network requests
- ❌ No caching (data always in memory)
- ❌ No real-time updates
Remote Data Mode
Use when working with a REST API.
const users = new DataSource<User>({
url: 'https://api.example.com/users'
});
// Operations make HTTP requests
await users.fetch(); // GET /users
await users.create(newUser); // POST /users
await users.update(1, changes); // PUT /users/1
await users.delete(1); // DELETE /users/1Features in Remote Mode:
- ✅ Full CRUD operations via HTTP
- ✅ Automatic config fetching from API
- ✅ Response caching
- ✅ Retry with exponential backoff
- ✅ Real-time WebSocket updates
- ✅ Validation
- ✅ Security features (CSRF, sanitization)
Expected API Endpoints:
| Operation | Method | Endpoint | Body |
|---|---|---|---|
| Config | GET | /users/config | - |
| Fetch all | GET | /users | - |
| Fetch one | GET | /users/:id | - |
| Create | POST | /users | Record |
| Update | PUT | /users/:id | Changes |
| Delete | DELETE | /users/:id | - |
| Batch create | POST | /users/batch | Records[] |
| Batch update | PUT | /users/batch | Updates[] |
| Batch delete | DELETE | /users/batch | IDs[] |
Pagination Configuration
Control how data is paginated.
Basic Pagination
const users = new DataSource<User>({
url: '/api/users',
pageSize: 20 // 20 records per page
});
// Access pagination state
const pagination = users.pagination.get();
console.log(pagination);
// {
// pageSize: 20,
// currentPage: 1,
// totalRecords: 142,
// totalPages: 8
// }
// Navigate pages
await users.fetchPage(2); // Load page 2
await users.fetchMore(); // Load next page (append)
// Update page size
users.pagination.set({
...users.pagination.get(),
pageSize: 50
});Pagination Strategies
Bindra supports two pagination strategies based on your API:
Offset-based (Default)
Uses page and limit query parameters.
const users = new DataSource<User>({
url: '/api/users',
pageSize: 20
});
// Generates: GET /api/users?page=1&limit=20
await users.fetch();
// Generates: GET /api/users?page=2&limit=20
await users.fetchPage(2);API Response Format:
{
"data": [...],
"page": 1,
"limit": 20,
"total": 142
}Cursor-based
Uses cursor tokens for pagination (better for real-time data).
const messages = new DataSource<Message>({
url: '/api/messages',
pageSize: 50,
pagination: {
strategy: 'cursor',
cursorField: 'id' // Field used for cursor
}
});
// First fetch: GET /api/messages?limit=50
await messages.fetch();
// Next fetch: GET /api/messages?after=<last_id>&limit=50
await messages.fetchMore();API Response Format:
{
"data": [...],
"cursor": {
"next": "cursor_token_here",
"hasMore": true
}
}Infinite Scroll
const products = new DataSource<Product>({
url: '/api/products',
pageSize: 20
});
// Initial load
await products.fetch();
// Load more on scroll
window.addEventListener('scroll', async () => {
if (isNearBottom() && products.hasMore) {
await products.fetchMore();
}
});Caching Configuration
Control response caching to reduce API calls.
Enable Caching
const users = new DataSource<User>({
url: '/api/users',
cache: {
enabled: true,
ttl: 300000, // 5 minutes (in milliseconds)
maxSize: 1000 // Max 1000 cached items
}
});
// First call: Makes HTTP request
await users.findById(1);
// Second call within 5 minutes: Returns from cache
await users.findById(1); // No HTTP request
// After 5 minutes: Cache expired, makes new request
await users.findById(1);Cache Management
// Clear all cache
users.clearCache();
// Invalidate specific cache key
users.invalidateCache('1');
// Cache is automatically invalidated on:
// - create()
// - update()
// - delete()
// - Any batch operationCache Configuration Options
interface CacheConfig {
enabled: boolean; // Enable/disable caching
ttl?: number; // Time to live in ms (default: no expiration)
maxSize?: number; // Max cached items (default: unlimited)
}
// Examples
// No expiration
cache: {
enabled: true
}
// 10 minute TTL
cache: {
enabled: true,
ttl: 600000
}
// Limited size
cache: {
enabled: true,
ttl: 300000,
maxSize: 500
}
// Disable caching
cache: {
enabled: false
}Best Practices
// ✅ Good - Cache read-heavy data
const products = new DataSource<Product>({
url: '/api/products',
cache: {
enabled: true,
ttl: 600000 // 10 minutes - products don't change often
}
});
// ✅ Good - Short TTL for frequently updated data
const notifications = new DataSource<Notification>({
url: '/api/notifications',
cache: {
enabled: true,
ttl: 30000 // 30 seconds
}
});
// ❌ Bad - Don't cache rapidly changing data
const liveScores = new DataSource<Score>({
url: '/api/live-scores',
cache: {
enabled: true,
ttl: 300000 // Too long for live data
}
});
// Use real-time updates instead (see Real-time Configuration)Real-time Configuration
Enable WebSocket-based real-time updates.
Basic Real-time Setup
const messages = new DataSource<Message>({
url: '/api/messages',
realtime: {
enabled: true,
url: 'wss://api.example.com/ws',
reconnect: true,
reconnectInterval: 5000
}
});
// WebSocket connects automatically
// Listen for real-time events
messages.on('realtime:message', (data) => {
console.log('New message:', data);
});
// Connection events
messages.on('ws:connected', () => {
console.log('WebSocket connected');
});
messages.on('ws:disconnected', () => {
console.log('WebSocket disconnected');
});
messages.on('ws:error', (error) => {
console.error('WebSocket error:', error);
});Real-time Configuration Options
interface RealtimeConfig {
enabled: boolean; // Enable/disable real-time
url?: string; // WebSocket server URL
reconnect?: boolean; // Auto-reconnect on disconnect
reconnectInterval?: number; // Time between reconnect attempts (ms)
}
// Examples
// Basic
realtime: {
enabled: true,
url: 'wss://api.example.com/ws'
}
// With auto-reconnect
realtime: {
enabled: true,
url: 'wss://api.example.com/ws',
reconnect: true,
reconnectInterval: 5000 // Try reconnecting every 5 seconds
}
// Disable
realtime: {
enabled: false
}Manual WebSocket Control
// Connect manually
messages.connectWebSocket();
// Send custom message
messages.sendWebSocketMessage({
type: 'subscribe',
channel: 'chat-room-1'
});
// Disconnect
messages.disconnectWebSocket();Real-time Message Handling
Expected WebSocket message format:
{
"type": "message.created",
"data": {
"id": 123,
"text": "Hello!",
"userId": 456
}
}Handle in your code:
messages.on('realtime:message', (payload) => {
const { type, data } = payload;
switch (type) {
case 'message.created':
// Add new message to UI
updateUI(data);
break;
case 'message.updated':
// Update existing message
updateMessage(data);
break;
case 'message.deleted':
// Remove message from UI
removeMessage(data.id);
break;
}
});Retry Configuration
Configure automatic retry for failed requests.
Basic Retry
const users = new DataSource<User>({
url: '/api/users',
retry: {
maxRetries: 3,
retryDelay: 1000,
backoffMultiplier: 2
}
});
// If request fails, automatically retries with exponential backoff:
// Attempt 1: immediate
// Attempt 2: after 1000ms
// Attempt 3: after 2000ms
// Attempt 4: after 4000msRetry Configuration Options
interface RetryConfig {
maxRetries?: number; // Max retry attempts (default: 3)
retryDelay?: number; // Initial delay in ms (default: 1000)
backoffMultiplier?: number; // Backoff multiplier (default: 2)
}
// Examples
// Default retry
retry: {
maxRetries: 3,
retryDelay: 1000,
backoffMultiplier: 2
}
// Aggressive retry (for critical operations)
retry: {
maxRetries: 5,
retryDelay: 500,
backoffMultiplier: 1.5
}
// Conservative retry (for expensive operations)
retry: {
maxRetries: 2,
retryDelay: 2000,
backoffMultiplier: 3
}
// Disable retry
retry: {
maxRetries: 0
}When Retry Happens
Retry is triggered for:
- ✅ Network errors (timeout, connection failed)
- ✅ 5xx server errors (500, 502, 503, 504)
- ❌ 4xx client errors (400, 401, 403, 404) - No retry
Security Configuration
Configure CSRF protection and input sanitization.
CSRF Token
const users = new DataSource<User>({
url: '/api/users',
security: {
csrfToken: document.querySelector('meta[name="csrf"]')?.content,
csrfHeader: 'X-CSRF-Token'
}
});
// All write operations (POST, PUT, DELETE) will include:
// Headers: { 'X-CSRF-Token': 'token_value' }Input Sanitization
Automatically remove HTML tags and scripts from specified fields.
const posts = new DataSource<Post>({
url: '/api/posts',
security: {
sanitizeFields: ['title', 'content', 'bio']
}
});
// Before sanitization:
await posts.create({
title: 'My Post',
content: '<script>alert("xss")</script>Hello'
});
// After sanitization (sent to API):
{
title: 'My Post',
content: 'Hello' // <script> tag removed
}Security Configuration Options
interface SecurityConfig {
csrfToken?: string; // CSRF token value
csrfHeader?: string; // Header name for CSRF token
sanitizeFields?: (keyof T)[]; // Fields to sanitize
}
// Complete example
security: {
csrfToken: getCsrfToken(),
csrfHeader: 'X-CSRF-Token',
sanitizeFields: ['title', 'content', 'bio', 'comments']
}Validation Configuration
Define validation rules for your data.
Basic Validation
import type { FieldConfig } from 'bindra';
const fields: FieldConfig<User>[] = [
{
name: 'email',
label: 'Email Address',
validation: {
required: true,
type: 'email',
message: 'Please enter a valid email'
}
},
{
name: 'age',
label: 'Age',
validation: {
required: true,
type: 'number',
min: 18,
max: 120,
message: 'Age must be between 18 and 120'
}
}
];
const users = new DataSource<User>({
url: '/api/users',
fields
});
// Validation runs automatically on create/update
try {
await users.create({
email: 'invalid-email',
age: 15
});
} catch (error) {
console.log(error.details);
// {
// email: 'Please enter a valid email',
// age: 'Age must be between 18 and 120'
// }
}Validation Rules
interface FieldValidation<T> {
required?: boolean;
type?: 'string' | 'number' | 'boolean' | 'date' | 'email' | 'url' | 'array' | 'object';
min?: number; // Min value (number) or length (string/array)
max?: number; // Max value (number) or length (string/array)
pattern?: RegExp;
custom?: (value: any, record: Partial<T>) => boolean | string | Promise<boolean | string>;
message?: string; // Custom error message
}Complete Field Configuration
const fields: FieldConfig<User>[] = [
// Required string with length validation
{
name: 'username',
label: 'Username',
validation: {
required: true,
type: 'string',
min: 3,
max: 20,
pattern: /^[a-zA-Z0-9_]+$/,
message: 'Username must be 3-20 alphanumeric characters'
}
},
// Email validation
{
name: 'email',
validation: {
required: true,
type: 'email'
}
},
// Number range
{
name: 'age',
validation: {
type: 'number',
min: 0,
max: 150
}
},
// Custom async validation
{
name: 'username',
validation: {
custom: async (value) => {
const available = await checkUsernameAvailable(value);
return available || 'Username already taken';
}
}
},
// Default value
{
name: 'status',
defaultValue: 'active'
},
// Computed field
{
name: 'fullName',
computed: (user) => `${user.firstName} ${user.lastName}`,
readOnly: true
},
// Transform before validation
{
name: 'email',
transform: (value) => value.toLowerCase().trim(),
validation: {
type: 'email'
}
}
];Complete Examples
Full-Featured E-commerce Product DataSource
import { DataSource } from 'bindra';
import type { DataSourceConfig, FieldConfig } from 'bindra';
interface Product {
id: number;
name: string;
description: string;
price: number;
category: string;
inStock: boolean;
imageUrl: string;
tags: string[];
createdAt: Date;
updatedAt: Date;
}
const fields: FieldConfig<Product>[] = [
{
name: 'name',
label: 'Product Name',
validation: {
required: true,
type: 'string',
min: 3,
max: 100
}
},
{
name: 'description',
label: 'Description',
validation: {
type: 'string',
max: 1000
}
},
{
name: 'price',
label: 'Price',
validation: {
required: true,
type: 'number',
min: 0,
custom: (value) => {
// Must be valid currency (2 decimals max)
return /^\d+(\.\d{1,2})?$/.test(value.toString()) || 'Invalid price format';
}
}
},
{
name: 'category',
label: 'Category',
validation: {
required: true,
custom: (value) => {
const validCategories = ['electronics', 'clothing', 'food', 'books', 'toys'];
return validCategories.includes(value) || 'Invalid category';
}
}
},
{
name: 'inStock',
defaultValue: true
},
{
name: 'imageUrl',
validation: {
type: 'url'
}
},
{
name: 'tags',
validation: {
type: 'array',
max: 10
},
defaultValue: []
}
];
const config: DataSourceConfig<Product> = {
// API endpoint
url: 'https://api.example.com/products',
// Field validation
fields,
// Pagination
pageSize: 24,
// Caching (products don't change frequently)
cache: {
enabled: true,
ttl: 600000, // 10 minutes
maxSize: 1000
},
// Retry configuration
retry: {
maxRetries: 3,
retryDelay: 1000,
backoffMultiplier: 2
},
// Security
security: {
csrfToken: document.querySelector('meta[name="csrf"]')?.content,
csrfHeader: 'X-CSRF-Token',
sanitizeFields: ['name', 'description']
}
};
const products = new DataSource<Product>(config);
// Usage
await products.fetch({
filter: { category: 'electronics', inStock: true },
sort: 'price',
limit: 24
});
// Create with validation
const newProduct = await products.create({
name: 'Wireless Mouse',
description: 'Ergonomic wireless mouse',
price: 29.99,
category: 'electronics',
imageUrl: 'https://example.com/mouse.jpg',
tags: ['wireless', 'ergonomic']
});Real-time Chat Application
interface Message {
id: number;
text: string;
userId: number;
roomId: number;
timestamp: Date;
}
const fields: FieldConfig<Message>[] = [
{
name: 'text',
validation: {
required: true,
type: 'string',
min: 1,
max: 500
}
},
{
name: 'roomId',
validation: {
required: true,
type: 'number'
}
}
];
const messages = new DataSource<Message>({
url: 'https://api.example.com/messages',
fields,
// Small page size for chat
pageSize: 50,
// Real-time updates
realtime: {
enabled: true,
url: 'wss://api.example.com/ws',
reconnect: true,
reconnectInterval: 3000
},
// No caching for real-time data
cache: {
enabled: false
},
// Security
security: {
csrfToken: getCsrfToken(),
csrfHeader: 'X-CSRF-Token',
sanitizeFields: ['text'] // Prevent XSS
}
});
// Listen for new messages
messages.on('realtime:message', (payload) => {
if (payload.type === 'message.created') {
displayMessage(payload.data);
}
});
// Send message
await messages.create({
text: 'Hello, world!',
roomId: 1
});Offline-First Todo App
interface Todo {
id: number;
title: string;
completed: boolean;
createdAt: Date;
}
// Start with local data
const todos = new DataSource<Todo>({
data: JSON.parse(localStorage.getItem('todos') || '[]'),
fields: [
{
name: 'title',
validation: {
required: true,
type: 'string',
min: 1,
max: 200
}
},
{
name: 'completed',
defaultValue: false
}
]
});
// Save to localStorage on changes
todos.on('dataChanged', (data) => {
localStorage.setItem('todos', JSON.stringify(data));
});
// Operations work entirely offline
await todos.create({ title: 'Learn Bindra' });
await todos.update(1, { completed: true });
await todos.delete(2);Configuration Cheat Sheet
// Minimal
{ url: '/api/users' }
// Read-heavy app
{
url: '/api/users',
cache: { enabled: true, ttl: 600000 },
pageSize: 50
}
// Real-time app
{
url: '/api/messages',
realtime: {
enabled: true,
url: 'wss://api.example.com/ws',
reconnect: true
},
cache: { enabled: false }
}
// Form with validation
{
url: '/api/users',
fields: [
{ name: 'email', validation: { required: true, type: 'email' } },
{ name: 'age', validation: { type: 'number', min: 18 } }
]
}
// Secure app
{
url: '/api/posts',
security: {
csrfToken: getCsrfToken(),
csrfHeader: 'X-CSRF-Token',
sanitizeFields: ['title', 'content']
}
}
// Unreliable network
{
url: '/api/data',
retry: {
maxRetries: 5,
retryDelay: 2000,
backoffMultiplier: 2
},
cache: { enabled: true, ttl: 300000 }
}
// Offline-first
{
data: getLocalData()
}Related Documentation
- DataSource API - Complete API reference
- Type Definitions - TypeScript types
- Validator API - Validation system
- Getting Started - Quick start guide