Migration Guide: Upgrading to Bindra v2.0
This guide will help you upgrade your application from Bindra v1.x to v2.0.
Overview
Bindra v2.0 introduces several breaking changes focused on improved type safety, better error handling, and enhanced features. While there are breaking changes, the migration path is straightforward.
Table of Contents
What's New in v2.0
Major Additions
✨ Generic Type Support - Full TypeScript generics for type-safe operations
✨ Custom Error Types - ValidationError, NetworkError, NotFoundError
✨ Validation System - Comprehensive built-in validation
✨ Batch Operations - Efficient bulk CRUD operations
✨ Optimistic Updates - Instant UI feedback with auto-rollback
✨ Caching System - TTL-based cache with manual invalidation
✨ Pagination - Offset and cursor-based pagination
✨ WebSocket Support - Real-time bidirectional communication
✨ Security Features - XSS sanitization and CSRF tokens
✨ Performance Utilities - debounce, throttle, debounceWithMaxWait
Improvements
- Enhanced TypeScript strict mode compliance
- Better error messages with stack traces
- Improved JSDoc documentation
- Async initialization for Container
- Auto-reconnect for WebSocket connections
Breaking Changes
1. TypeScript Generic Requirement
Before (v1.x):
const ds = new DataSource({
data: [{ id: 1, name: 'Alice' }]
});After (v2.0):
interface User {
id: number;
name: string;
}
const ds = new DataSource<User>({
data: [{ id: 1, name: 'Alice' }]
});Migration: Add explicit type parameters to all DataSource instances.
2. Error Types Changed
Before (v1.x):
try {
await ds.create(record);
} catch (error) {
// Generic Error
console.error(error.message);
}After (v2.0):
import { ValidationError, NetworkError, NotFoundError } from 'bindra';
try {
await ds.create(record);
} catch (error) {
if (error instanceof ValidationError) {
// Handle validation errors
console.error('Validation failed:', error.errors);
} else if (error instanceof NetworkError) {
// Handle network errors
console.error('Network error:', error.statusCode);
} else if (error instanceof NotFoundError) {
// Handle not found errors
console.error('Record not found:', error.id);
}
}Migration: Update error handling to check for specific error types.
3. Container Initialization is Now Async
Before (v1.x):
const container = new Container();
container.register('users', userConfig);
const ds = container.get('users');After (v2.0):
const container = new Container();
await container.register('users', userConfig);
const ds = await container.get('users');Migration: Add await when calling register() and get().
4. Query Options Structure Changed
Before (v1.x):
await ds.query({
where: { active: true },
orderBy: 'name',
take: 10
});After (v2.0):
await ds.fetch({
filter: { active: true },
sort: { field: 'name', order: 'asc' },
limit: 10
});Migration: Update query option property names:
where→filterorderBy→sort(now an object)take→limit
5. Event Names Changed
Before (v1.x):
ds.on('fetch', () => {});
ds.on('create', () => {});After (v2.0):
ds.on('beforeFetch', () => {});
ds.on('afterFetch', () => {});
ds.on('beforeCreate', () => {});
ds.on('afterCreate', () => {});Migration: Add before or after prefix to all event names.
Migration Steps
Step 1: Update Package
npm install bindra@2.0.0
# or
pnpm update bindra
# or
yarn upgrade bindraStep 2: Add Type Definitions
Define TypeScript interfaces for all your data models:
// types.ts
export interface User {
id: number;
name: string;
email: string;
age: number;
}
export interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}Step 3: Update DataSource Instances
Add generic type parameters:
// Before
const users = new DataSource({ url: '/api/users' });
const products = new DataSource({ data: [] });
// After
const users = new DataSource<User>({ url: '/api/users' });
const products = new DataSource<Product>({ data: [] });Step 4: Update Error Handling
Replace generic error handling with specific error types:
import {
ValidationError,
NetworkError,
NotFoundError
} from 'bindra';
try {
const user = await users.create(newUser);
} catch (error) {
if (error instanceof ValidationError) {
// Show validation errors to user
showErrors(error.errors);
} else if (error instanceof NetworkError) {
// Show network error message
showNetworkError(error.statusCode);
} else if (error instanceof NotFoundError) {
// Handle not found
showNotFound();
}
}Step 5: Update Event Listeners
Add before/after prefixes:
// Before
users.on('fetch', handleFetch);
users.on('create', handleCreate);
users.on('update', handleUpdate);
// After
users.on('afterFetch', handleFetch);
users.on('afterCreate', handleCreate);
users.on('afterUpdate', handleUpdate);Step 6: Update Container Usage
Make Container operations async:
// Before
const container = new Container();
container.register('users', config);
const users = container.get('users');
// After
const container = new Container();
await container.register('users', config);
const users = await container.get('users');
// Or initialize all at once
await container.initialize();Step 7: Update Query Calls
Update query option names:
// Before
await users.query({
where: { active: true },
orderBy: 'name',
take: 20
});
// After
await users.fetch({
filter: { active: true },
sort: { field: 'name', order: 'asc' },
limit: 20
});New Features
Validation System
Add validation rules to your DataSource:
const users = new DataSource<User>({
url: '/api/users',
validation: {
rules: {
name: [
{ type: 'required', message: 'Name is required' },
{ type: 'minLength', value: 2, message: 'Name too short' }
],
email: [
{ type: 'required' },
{ type: 'email', message: 'Invalid email' }
],
age: [
{ type: 'min', value: 18, message: 'Must be 18+' }
]
}
}
});Batch Operations
Perform bulk operations efficiently:
// Create multiple records
const newUsers = await users.createBatch([
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' }
]);
// Update multiple records
await users.updateBatch([
{ id: 1, changes: { name: 'Alice Smith' } },
{ id: 2, changes: { name: 'Bob Jones' } }
]);
// Delete multiple records
await users.deleteBatch([1, 2, 3]);Optimistic Updates
Enable instant UI feedback:
const users = new DataSource<User>({
url: '/api/users',
optimisticUpdates: true
});
// Update happens instantly in UI
// Automatically rolled back on error
await users.update(1, { name: 'New Name' });Caching
Add TTL-based caching:
const users = new DataSource<User>({
url: '/api/users',
cache: {
enabled: true,
ttl: 300000 // 5 minutes
}
});
// First call hits API
await users.fetch();
// Second call uses cache (within 5 min)
await users.fetch();
// Manual cache invalidation
users.invalidateCache();Pagination
Use built-in pagination:
const users = new DataSource<User>({
url: '/api/users',
pagination: {
enabled: true,
pageSize: 20,
strategy: 'offset' // or 'cursor'
}
});
// Load first page
await users.fetch();
// Load more
await users.fetchMore();
// Check state
console.log(users.currentPage); // Current page number
console.log(users.hasMore); // More data available?WebSocket / Real-time
Add real-time updates:
const chat = new DataSource<Message>({
url: '/api/messages',
realtime: {
enabled: true,
url: 'wss://api.example.com/ws',
events: ['message.created', 'message.updated']
}
});
// Automatically receives real-time updates
// Auto-reconnects on disconnectSecurity Features
Enable XSS protection and CSRF tokens:
const users = new DataSource<User>({
url: '/api/users',
security: {
xssProtection: true,
csrfToken: true,
sanitizeFields: ['name', 'bio', 'comment']
}
});Performance Utilities
Use debounce and throttle:
import { debounce, throttle, debounceWithMaxWait } from 'bindra';
// Debounce search input
const handleSearch = debounce(async (query: string) => {
await users.fetch({ filter: { name: query } });
}, 300);
// Throttle scroll handler
const handleScroll = throttle(() => {
if (isAtBottom()) {
users.fetchMore();
}
}, 200);
// Debounce with max wait
const saveForm = debounceWithMaxWait(
async (data) => await users.update(1, data),
1000,
5000
);Deprecations
The following features are deprecated and will be removed in v3.0:
query() method
Deprecated: Use fetch() instead
Removal: v3.0
// Deprecated
await users.query({ where: { active: true } });
// Use this
await users.fetch({ filter: { active: true } });Generic event names
Deprecated: Use before* and after* events
Removal: v3.0
// Deprecated
users.on('create', handleCreate);
// Use this
users.on('afterCreate', handleCreate);Testing Your Migration
1. TypeScript Errors
Run TypeScript compiler to catch type errors:
npx tsc --noEmit2. Runtime Testing
Test all CRUD operations:
// Create
const created = await ds.create(record);
console.assert(created.id, 'Create failed');
// Read
await ds.fetch();
console.assert(ds.data?.length, 'Fetch failed');
// Update
const updated = await ds.update(1, { name: 'New' });
console.assert(updated.name === 'New', 'Update failed');
// Delete
await ds.delete(1);
console.assert(!ds.data?.find(r => r.id === 1), 'Delete failed');3. Error Handling
Test that errors are properly typed:
try {
await ds.create({});
} catch (error) {
console.assert(error instanceof ValidationError, 'Wrong error type');
}Need Help?
- Documentation: https://github.com/mohamad-j/Bindra/tree/main/docs
- Examples: https://github.com/mohamad-j/Bindra/tree/main/examples
- Issues: https://github.com/mohamad-j/Bindra/issues
- Discussions: https://github.com/mohamad-j/Bindra/discussions
Happy migrating! 🚀