Type Definitions API Reference
Complete TypeScript type definitions and interfaces for Bindra.
Table of Contents
- Overview
- Core Types
- Configuration Types
- Validation Types
- Observable Types
- Middleware Types
- Utility Types
- Usage Examples
Overview
Bindra is fully typed with TypeScript, providing comprehensive type safety for all operations. All types are exported from the main package:
import type {
// Core
DataSourceConfig,
QueryOptions,
Field,
Permissions,
// Configuration
RetryConfig,
CacheConfig,
PaginationConfig,
RealtimeConfig,
SecurityConfig,
// Validation
FieldConfig,
FieldValidation,
// Observable
Signal
} from 'bindra';Core Types
DataSourceConfig<T>
Main configuration interface for creating a DataSource instance.
interface DataSourceConfig<T extends Record<string, any> = any> {
/** Remote API endpoint URL */
url?: string | null;
/** Local data array (for client-side data) */
data?: T[] | null;
/** Retry configuration for failed requests */
retry?: RetryConfig;
/** Field configurations with validation rules */
fields?: FieldConfig<T>[];
/** Cache configuration */
cache?: CacheConfig;
/** Default page size for pagination */
pageSize?: number;
/** Real-time updates via WebSocket */
realtime?: RealtimeConfig;
/** Security configuration */
security?: SecurityConfig;
}Type Parameter
- T - The record type managed by the DataSource (must extend
Record<string, any>)
Example
interface User {
id: number;
name: string;
email: string;
age: number;
}
const config: DataSourceConfig<User> = {
url: 'https://api.example.com/users',
cache: { enabled: true, ttl: 300000 },
pageSize: 20
};
const users = new DataSource<User>(config);QueryOptions<T>
Options for querying and filtering data.
interface QueryOptions<T = any> {
/** Filter function or object to match records */
filter?: ((record: T) => boolean) | object | null;
/** Sort field name or comparison function */
sort?: string | ((a: T, b: T) => number) | null;
/** Maximum number of records to return */
limit?: number | null;
}Examples
// Function filter
await users.fetch({
filter: (user) => user.age >= 18,
sort: 'name',
limit: 10
});
// Object filter (for remote DataSources)
await users.fetch({
filter: { status: 'active' },
sort: 'createdAt',
limit: 50
});
// Custom sort function
await users.fetch({
sort: (a, b) => b.age - a.age // Sort by age descending
});Field
Field metadata describing a data field.
interface Field {
/** Field name */
name: string;
/** JavaScript type of the field */
type: string;
}Usage
Fields are automatically detected for local DataSources or fetched from remote API config endpoints.
const fields = dataSource.fields;
// [{ name: 'id', type: 'number' }, { name: 'name', type: 'string' }, ...]Permissions
Permission flags for CRUD operations.
interface Permissions {
/** Allow creating new records */
allowInsert?: boolean;
/** Allow updating existing records */
allowUpdate?: boolean;
/** Allow deleting records */
allowDelete?: boolean;
}Example
const permissions = dataSource.permissions;
// { allowInsert: true, allowUpdate: true, allowDelete: false }
if (permissions.allowDelete) {
await dataSource.delete(userId);
}Configuration Types
RetryConfig
Configuration for automatic retry of failed requests.
interface RetryConfig {
/** Maximum number of retry attempts (default: 3) */
maxRetries?: number;
/** Initial delay between retries in milliseconds (default: 1000) */
retryDelay?: number;
/** Multiplier for exponential backoff (default: 2) */
backoffMultiplier?: number;
}Example
const config: DataSourceConfig<User> = {
url: '/api/users',
retry: {
maxRetries: 5,
retryDelay: 2000, // Start with 2 seconds
backoffMultiplier: 1.5 // 2s, 3s, 4.5s, 6.75s, 10.125s
}
};CacheConfig
Configuration for response caching.
interface CacheConfig {
/** Enable/disable caching */
enabled: boolean;
/** Time to live in milliseconds (default: no expiration) */
ttl?: number;
/** Maximum cache size (default: unlimited) */
maxSize?: number;
}Example
const config: DataSourceConfig<User> = {
url: '/api/users',
cache: {
enabled: true,
ttl: 300000, // 5 minutes
maxSize: 1000 // Max 1000 cached items
}
};PaginationConfig
Pagination state and configuration.
interface PaginationConfig {
/** Number of records per page */
pageSize: number;
/** Current page number (1-indexed) */
currentPage: number;
/** Total number of records (optional) */
totalRecords?: number;
/** Total number of pages (optional) */
totalPages?: number;
}Usage
const pagination = dataSource.pagination.get();
console.log(pagination);
// {
// pageSize: 20,
// currentPage: 1,
// totalRecords: 142,
// totalPages: 8
// }
// Update page size
dataSource.pagination.set({
...pagination,
pageSize: 50
});RealtimeConfig
Configuration for WebSocket-based real-time updates.
interface RealtimeConfig {
/** Enable/disable real-time updates */
enabled: boolean;
/** WebSocket server URL */
url?: string;
/** Automatically reconnect on disconnect */
reconnect?: boolean;
/** Interval between reconnection attempts in milliseconds */
reconnectInterval?: number;
}Example
const config: DataSourceConfig<Message> = {
url: '/api/messages',
realtime: {
enabled: true,
url: 'wss://api.example.com/ws',
reconnect: true,
reconnectInterval: 5000
}
};SecurityConfig
Security and sanitization configuration.
interface SecurityConfig {
/** CSRF token for request authentication */
csrfToken?: string;
/** Header name for CSRF token */
csrfHeader?: string;
/** Fields to sanitize (remove HTML/scripts) */
sanitizeFields?: (keyof any)[];
}Example
const config: DataSourceConfig<Post> = {
url: '/api/posts',
security: {
csrfToken: document.querySelector('meta[name="csrf"]')?.content,
csrfHeader: 'X-CSRF-Token',
sanitizeFields: ['title', 'content', 'bio']
}
};Validation Types
FieldConfig<T>
Complete field configuration with validation rules.
interface FieldConfig<T extends Record<string, any>> {
/** Field name (must be a key of T) */
name: keyof T;
/** Display label for UI */
label?: string;
/** Validation rules */
validation?: FieldValidation<T>;
/** Default value when creating records */
defaultValue?: any;
/** Computed field function */
computed?: (record: T) => any;
/** Read-only field (won't be validated or saved) */
readOnly?: boolean;
/** Transform value before validation */
transform?: (value: any) => any;
}Example
interface User {
id: number;
email: string;
age: number;
status: string;
fullName: string;
}
const fields: FieldConfig<User>[] = [
{
name: 'email',
label: 'Email Address',
validation: {
required: true,
type: 'email'
}
},
{
name: 'age',
label: 'Age',
validation: {
type: 'number',
min: 18,
max: 120
}
},
{
name: 'status',
defaultValue: 'active'
},
{
name: 'fullName',
computed: (user) => `${user.firstName} ${user.lastName}`,
readOnly: true
}
];FieldValidation<T>
Validation rules for a single field.
interface FieldValidation<T extends Record<string, any>> {
/** Whether the field is required */
required?: boolean;
/** Data type validation */
type?: 'string' | 'number' | 'boolean' | 'date' | 'email' | 'url' | 'array' | 'object';
/** Minimum value (for numbers) or length (for strings/arrays) */
min?: number;
/** Maximum value (for numbers) or length (for strings/arrays) */
max?: number;
/** RegEx pattern for string validation */
pattern?: RegExp;
/** Custom validation function */
custom?: (value: any, record: Partial<T>) => boolean | string | Promise<boolean | string>;
/** Error message override */
message?: string;
}Examples
// Required field
const emailField: FieldConfig<User> = {
name: 'email',
validation: { required: true }
};
// Type validation
const ageField: FieldConfig<User> = {
name: 'age',
validation: { type: 'number', min: 0, max: 150 }
};
// Pattern validation
const phoneField: FieldConfig<User> = {
name: 'phone',
validation: {
pattern: /^\d{3}-\d{3}-\d{4}$/,
message: 'Phone must be in format: XXX-XXX-XXXX'
}
};
// Custom validation
const usernameField: FieldConfig<User> = {
name: 'username',
validation: {
custom: async (value) => {
const available = await checkUsernameAvailable(value);
return available || 'Username already taken';
}
}
};
// Multiple rules
const passwordField: FieldConfig<User> = {
name: 'password',
validation: {
required: true,
type: 'string',
min: 8,
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
message: 'Password must be at least 8 characters with uppercase, lowercase, and number'
}
};Observable Types
Signal<T>
Reactive signal interface for state management.
interface Signal<T> {
/** Get the current value */
get: () => T;
/** Set a new value (triggers subscribers) */
set: (newValue: T) => void;
/** Subscribe to value changes */
subscribe: (fn: (value: T) => void) => UnsubscribeFunction;
}
type UnsubscribeFunction = () => void;Example
import { createSignal } from 'bindra';
const count = createSignal(0);
// Get value
console.log(count.get()); // 0
// Subscribe to changes
const unsubscribe = count.subscribe((value) => {
console.log('Count changed:', value);
});
// Update value (triggers subscriber)
count.set(1); // Logs: "Count changed: 1"
// Cleanup
unsubscribe();Middleware Types
CRUDContext
Context object passed to CRUD middleware handlers.
interface CRUDContext {
/** The record being operated on */
record?: any;
/** The key/id for update/delete operations */
key?: any;
/** Changes being applied (for updates) */
changes?: any;
/** Reference to the DataSource instance */
ds?: any;
}Example
dataSource.middleware.useBefore('create', async (context: CRUDContext) => {
console.log('Creating record:', context.record);
console.log('DataSource:', context.ds.url);
// Modify record before creation
context.record.createdAt = new Date();
});
dataSource.middleware.useAfter('update', async (context: CRUDContext) => {
console.log('Updated record:', context.record);
console.log('Key:', context.key);
console.log('Changes:', context.changes);
});Middleware Handler Types
// Generic middleware for standalone validation
type GenericMiddlewareHandler = (
value: any,
next: (processedValue: any) => void
) => void;
// CRUD operation middleware
type CRUDMiddlewareHandler = (
context: CRUDContext
) => void | Promise<void>;Utility Types
Batch Operation Types
// Batch update
interface BatchUpdate<T> {
id: number;
changes: Partial<T>;
}
// Example
const updates: BatchUpdate<User>[] = [
{ id: 1, changes: { status: 'active' } },
{ id: 2, changes: { status: 'inactive' } }
];
await dataSource.updateBatch(updates);Event Types
// Event handler function
type EventHandler = (...args: any[]) => void;
// Unsubscribe function
type UnsubscribeFunction = () => void;Usage Examples
Complete Type-Safe DataSource
import { DataSource } from 'bindra';
import type {
DataSourceConfig,
FieldConfig,
QueryOptions
} from 'bindra';
// Define your model
interface Product {
id: number;
name: string;
price: number;
category: string;
inStock: boolean;
createdAt: Date;
}
// Define field configurations
const fields: FieldConfig<Product>[] = [
{
name: 'name',
label: 'Product Name',
validation: {
required: true,
type: 'string',
min: 3,
max: 100
}
},
{
name: 'price',
label: 'Price',
validation: {
required: true,
type: 'number',
min: 0
}
},
{
name: 'category',
label: 'Category',
validation: {
required: true,
custom: (value) => {
const validCategories = ['electronics', 'clothing', 'food'];
return validCategories.includes(value) || 'Invalid category';
}
}
},
{
name: 'inStock',
defaultValue: true
}
];
// Create configuration
const config: DataSourceConfig<Product> = {
url: '/api/products',
fields,
cache: {
enabled: true,
ttl: 300000
},
retry: {
maxRetries: 3,
retryDelay: 1000
},
pageSize: 20
};
// Create DataSource
const products = new DataSource<Product>(config);
// Type-safe queries
const query: QueryOptions<Product> = {
filter: (p) => p.inStock && p.price < 100,
sort: 'name',
limit: 10
};
await products.fetch(query);
// Type-safe operations
const newProduct: Partial<Product> = {
name: 'Laptop',
price: 999,
category: 'electronics'
};
const created = await products.create(newProduct);
// created is of type ProductUsing Signals with Types
import { createSignal } from 'bindra';
import type { Signal } from 'bindra';
interface AppState {
user: User | null;
loading: boolean;
error: string | null;
}
const state: Signal<AppState> = createSignal({
user: null,
loading: false,
error: null
});
// Subscribe with type safety
state.subscribe((value: AppState) => {
if (value.loading) {
showSpinner();
} else {
hideSpinner();
}
if (value.error) {
showError(value.error);
}
});
// Update state
state.set({
user: { id: 1, name: 'Alice', email: 'alice@example.com' },
loading: false,
error: null
});Generic DataSource Utility
import { DataSource } from 'bindra';
import type { DataSourceConfig } from 'bindra';
// Create a factory function with proper types
function createApiDataSource<T extends Record<string, any>>(
endpoint: string,
config?: Partial<DataSourceConfig<T>>
): DataSource<T> {
return new DataSource<T>({
url: `https://api.example.com${endpoint}`,
cache: { enabled: true, ttl: 300000 },
retry: { maxRetries: 3 },
...config
});
}
// Use with different types
interface User {
id: number;
name: string;
email: string;
}
interface Post {
id: number;
title: string;
content: string;
authorId: number;
}
const users = createApiDataSource<User>('/users');
const posts = createApiDataSource<Post>('/posts', {
pageSize: 50
});
// All operations are type-safe
const newUser = await users.create({
name: 'Alice',
email: 'alice@example.com'
// TypeScript error if we forget required fields or add invalid fields
});Type Guards
Checking Record Types
// Type guard for records
function isUser(record: any): record is User {
return (
typeof record === 'object' &&
typeof record.id === 'number' &&
typeof record.name === 'string' &&
typeof record.email === 'string'
);
}
// Usage
const data = await fetch('/api/user/1').then(r => r.json());
if (isUser(data)) {
// data is now typed as User
console.log(data.email);
}Checking Validation Errors
import { isBindraError, isValidationError } from 'bindra';
try {
await dataSource.create(newRecord);
} catch (error) {
if (isValidationError(error)) {
// error.details contains field errors
console.log('Validation failed:', error.details);
} else if (isBindraError(error)) {
console.log('Bindra error:', error.message);
} else {
console.log('Unknown error:', error);
}
}Best Practices
1. Define Interfaces for All Models
// ✅ Good - Clear type definitions
interface User {
id: number;
name: string;
email: string;
age: number;
}
const users = new DataSource<User>({ url: '/api/users' });
// ❌ Bad - No type safety
const users = new DataSource({ url: '/api/users' });2. Use Partial<T> for Creates/Updates
// ✅ Good - Allows partial data
async function updateUser(id: number, changes: Partial<User>) {
return await users.update(id, changes);
}
updateUser(1, { name: 'Bob' }); // OK
// ❌ Bad - Requires all fields
async function updateUser(id: number, changes: User) {
return await users.update(id, changes);
}
updateUser(1, { name: 'Bob' }); // TypeScript error3. Export and Reuse Type Definitions
// types/user.ts
export interface User {
id: number;
name: string;
email: string;
}
export type UserCreate = Omit<User, 'id'>;
export type UserUpdate = Partial<UserCreate>;
// services/userService.ts
import type { User, UserCreate, UserUpdate } from '../types/user';
export async function createUser(data: UserCreate): Promise<User> {
return await users.create(data);
}
export async function updateUser(id: number, data: UserUpdate): Promise<User> {
return await users.update(id, data);
}4. Leverage Type Inference
// ✅ Good - Type inferred from variable
const fields: FieldConfig<User>[] = [
{
name: 'email', // Type-safe: must be keyof User
validation: { type: 'email' }
}
];
// ✅ Good - Type inferred from generic
const validateEmail = async (email: string): Promise<boolean | string> => {
// Custom validation logic
return true;
};
const emailField: FieldConfig<User> = {
name: 'email',
validation: {
custom: validateEmail // Type matches automatically
}
};Related Documentation
- DataSource API - Main DataSource class documentation
- Configuration Guide - Detailed configuration options
- Validator API - Validation system
- Observable API - Signals and reactivity