Documentation

Zenty Documentation

The powerful Zustand-based library that simplifies CRUD operations with elegant state management

Installation
Get started with Zenty in seconds

npm

1npm install zenty zustand

yarn

1yarn add zenty zustand

pnpm

1pnpm add zenty zustand
Quick Start
Get up and running with Zenty in under 2 minutes
1

Managing Collections with createEntitiesStore

ProductList.tsx
1import { createEntitiesStore } from 'zenty'; 2 3interface Product { 4 id: string; 5 name: string; 6 price: number; 7 category: string; 8} 9 10// Create a store for managing products 11const useProductStore = createEntitiesStore<Product>(); 12 13// Use in your React component 14function ProductList() { 15 const { 16 entities, 17 loading, 18 error, 19 add, 20 update, 21 delete: deleteProduct 22 } = useProductStore(); 23 24 const handleAddProduct = () => { 25 add({ 26 id: crypto.randomUUID(), 27 name: 'New Product', 28 price: 29.99, 29 category: 'Electronics' 30 }); 31 }; 32 33 return ( 34 <div> 35 {loading && <p>Loading...</p>} 36 {error && <p>Error: {error}</p>} 37 38 <button onClick={handleAddProduct}>Add Product</button> 39 40 {entities.map(product => ( 41 <div key={product.id}> 42 <h3>{product.name}</h3> 43 <p>Price: ${product.price}</p> 44 <button onClick={() => deleteProduct(product.id)}> 45 Delete 46 </button> 47 </div> 48 ))} 49 </div> 50 ); 51}
2

Managing Single Entities with createEntityStore

UserProfile.tsx
1import { createEntityStore } from 'zenty'; 2 3interface User { 4 id: string; 5 name: string; 6 email: string; 7 preferences: { 8 theme: 'light' | 'dark'; 9 notifications: boolean; 10 }; 11} 12 13// Create a store for managing a single user 14const useUserStore = createEntityStore<User>({ 15 deepMerge: true // Enable deep merging for nested objects 16}); 17 18function UserProfile() { 19 const { entity: user, loading, set, update } = useUserStore(); 20 21 const updateTheme = (theme: 'light' | 'dark') => { 22 update({ 23 preferences: { 24 theme 25 } 26 }); 27 }; 28 29 return ( 30 <div> 31 {user && ( 32 <div> 33 <h2>{user.name}</h2> 34 <p>{user.email}</p> 35 <button onClick={() => updateTheme('dark')}> 36 Switch to Dark Mode 37 </button> 38 </div> 39 )} 40 </div> 41 ); 42}
API Reference
Complete reference for all Zenty functions and methods

createEntitiesStore<T>()

Collections

Creates a Zustand store for managing collections of entities with built-in CRUD operations.

Signature

1// For entities with 'id' field 2function createEntitiesStore<T extends { id: string | number }>( 3 options?: EntitiesStoreOptions<T, 'id'> 4): UseBoundStore<StoreApi<EntitiesState<T, 'id'> & EntitiesActions<T, 'id'>>>; 5 6// For entities with custom ID field 7function createEntitiesStore<T, K extends keyof T>( 8 options: EntitiesStoreOptions<T, K> 9): UseBoundStore<StoreApi<EntitiesState<T, K> & EntitiesActions<T, K>>>;

Options

1interface EntitiesStoreOptions<T, K extends keyof T> { 2 idKey: K; // Which field is the unique identifier 3 initialState?: T[]; // Initial list of entities (defaults to []) 4 5 // Optional custom implementations for each action 6 add?: (item: T, state: EntitiesState<T, K>) => T[]; 7 addMany?: (items: T[], state: EntitiesState<T, K>) => T[]; 8 update?: (uid: T[K], patch: Partial<T>, state: EntitiesState<T, K>) => T[]; 9 updateMany?: (patches: (Pick<T, K> & Partial<T>)[], state: EntitiesState<T, K>) => T[]; 10 delete?: (uid: T[K], state: EntitiesState<T, K>) => T[]; 11 deleteMany?: (uids: T[K][], state: EntitiesState<T, K>) => T[]; 12}

State Properties

1interface EntitiesState<T, K extends keyof T> { 2 entities: T[]; // Array of entities 3 loaded: boolean; // Whether data has been loaded 4 loading: boolean; // Whether an operation is in progress 5 error: string | null; // Error message if any 6}

Actions

1interface EntitiesActions<T, K extends keyof T> { 2 // CRUD Operations 3 add: (item: T) => void; 4 addMany: (items: T[]) => void; 5 update: (uid: T[K], patch: Partial<T>) => void; 6 updateMany: (patches: (Pick<T, K> & Partial<T>)[]) => void; 7 delete: (uid: T[K]) => void; 8 deleteMany: (uids: T[K][]) => void; 9 10 // Utility Functions 11 clear: () => void; 12 find: (uid: T[K]) => T | undefined; 13 has: (uid: T[K]) => boolean; 14 replaceAll: (items: T[]) => void; 15 16 // State Helpers 17 setError: (error: string | null) => void; 18 setLoading: (loading: boolean) => void; 19}

createEntityStore<T>()

Single Entity

Creates a Zustand store for managing a single entity with set, update, and clear operations.

Signature

1function createEntityStore<T>( 2 options?: EntityStoreOptions<T> 3): UseBoundStore<StoreApi<EntityState<T> & EntityActions<T>>>;

Options & State

1interface EntityStoreOptions<T> { 2 initialState?: T | null; // Initial entity state 3 deepMerge?: boolean; // Whether updates should deep merge 4} 5 6interface EntityState<T> { 7 entity: T | null; // The entity or null 8 loaded: boolean; // Whether entity has been loaded 9 loading: boolean; // Whether an operation is in progress 10 error: string | null; // Error message if any 11}

Actions

1interface EntityActions<T> { 2 set: (entity: T) => void; // Set the entire entity 3 update: (updated: Partial<T>) => void; // Update parts of the entity 4 clear: () => void; // Clear the entity 5 setError: (error: string | null) => void; // Set error state 6 setLoading: (loading: boolean) => void; // Set loading state 7}
Advanced Examples
Real-world usage patterns and advanced features

Custom ID Field

custom-id-field.ts
1interface User { 2 userId: string; 3 name: string; 4 email: string; 5} 6 7const useUserStore = createEntitiesStore<User, 'userId'>({ 8 idKey: 'userId', 9 initialState: [ 10 { userId: '1', name: 'John', email: 'john@example.com' } 11 ] 12});

Custom CRUD Operations

custom-crud.ts
1const useProductStore = createEntitiesStore<Product>({ 2 // Custom add logic with validation 3 add: (item, state) => { 4 if (state.entities.some(p => p.name === item.name)) { 5 throw new Error('Product name must be unique'); 6 } 7 return [...state.entities, { ...item, createdAt: new Date() }]; 8 }, 9 10 // Custom delete with soft delete 11 delete: (id, state) => { 12 return state.entities.map(p => 13 p.id === id ? { ...p, deleted: true } : p 14 ); 15 } 16});

Deep Merging

deep-merge.ts
1interface Settings { 2 ui: { 3 theme: string; 4 language: string; 5 }; 6 notifications: { 7 email: boolean; 8 push: boolean; 9 }; 10} 11 12const useSettingsStore = createEntityStore<Settings>({ 13 deepMerge: true, 14 initialState: { 15 ui: { theme: 'light', language: 'en' }, 16 notifications: { email: true, push: false } 17 } 18}); 19 20// This will only update the theme, keeping language intact 21const { update } = useSettingsStore(); 22update({ ui: { theme: 'dark' } });

Async Operations

async-operations.ts
1const useProductStore = createEntitiesStore<Product>(); 2 3async function fetchProducts() { 4 const { setLoading, setError, replaceAll } = useProductStore.getState(); 5 6 try { 7 setLoading(true); 8 const response = await fetch('/api/products'); 9 const products = await response.json(); 10 replaceAll(products); 11 } catch (error) { 12 setError(error.message); 13 } finally { 14 setLoading(false); 15 } 16} 17 18async function createProduct(product: Omit<Product, 'id'>) { 19 const { setLoading, setError, add } = useProductStore.getState(); 20 21 try { 22 setLoading(true); 23 const response = await fetch('/api/products', { 24 method: 'POST', 25 body: JSON.stringify(product) 26 }); 27 const newProduct = await response.json(); 28 add(newProduct); 29 } catch (error) { 30 setError(error.message); 31 } finally { 32 setLoading(false); 33 } 34}
Real-World Example: Complete Todo App
See how easy it is to build a complete todo application with Zenty - just a few lines of code!
1

Define Your Types

types/todo.ts
1// types/todo.ts 2interface Todo { 3 id: number; 4 title: string; 5 completed: boolean; 6 category: 'work' | 'personal' | 'shopping'; 7 priority: 'low' | 'medium' | 'high'; 8 dueDate?: Date; 9 createdAt: Date; 10} 11 12interface TodoFilters { 13 category: string; 14 showCompleted: boolean; 15 priority: string; 16}
2

Create Your Stores (2 lines!)

stores/todo.ts
1// stores/todo.ts 2import { createEntitiesStore, createEntityStore } from 'zenty'; 3 4// All todos with full CRUD operations 5export const useTodoStore = createEntitiesStore<Todo>(); 6 7// Filter settings 8export const useFilterStore = createEntityStore<TodoFilters>({ 9 initialState: { 10 category: 'all', 11 showCompleted: true, 12 priority: 'all' 13 } 14});
3

Build Your Todo App Component

components/TodoApp.tsx
1// components/TodoApp.tsx 2import { useTodoStore, useFilterStore } from '@/stores/todo'; 3 4export function TodoApp() { 5 const { 6 entities: todos, 7 add, 8 update, 9 delete: deleteTodo, 10 loading, 11 error 12 } = useTodoStore(); 13 14 const { 15 entity: filters, 16 update: updateFilters 17 } = useFilterStore(); 18 19 // Add new todo 20 const handleAddTodo = (title: string, category: Todo['category']) => { 21 add({ 22 id: Date.now(), 23 title, 24 completed: false, 25 category, 26 priority: 'medium', 27 createdAt: new Date() 28 }); 29 }; 30 31 // Toggle completion 32 const handleToggleComplete = (id: number) => { 33 const todo = todos.find(t => t.id === id); 34 if (todo) { 35 update(id, { completed: !todo.completed }); 36 } 37 }; 38 39 // Filter todos based on current filters 40 const filteredTodos = todos.filter(todo => { 41 if (filters.category !== 'all' && todo.category !== filters.category) return false; 42 if (!filters.showCompleted && todo.completed) return false; 43 if (filters.priority !== 'all' && todo.priority !== filters.priority) return false; 44 return true; 45 }); 46 47 // Statistics 48 const stats = { 49 total: todos.length, 50 completed: todos.filter(t => t.completed).length, 51 pending: todos.filter(t => !t.completed).length, 52 highPriority: todos.filter(t => t.priority === 'high' && !t.completed).length 53 }; 54 55 return ( 56 <div className="max-w-4xl mx-auto p-6"> 57 <header className="mb-8"> 58 <h1 className="text-3xl font-bold mb-2">My Todo App</h1> 59 <div className="flex gap-4 text-sm text-muted-foreground"> 60 <span>Total: {stats.total}</span> 61 <span>Completed: {stats.completed}</span> 62 <span>Pending: {stats.pending}</span> 63 <span className="text-red-500">High Priority: {stats.highPriority}</span> 64 </div> 65 </header> 66 67 {/* Add Todo Form */} 68 <TodoForm onAdd={handleAddTodo} /> 69 70 {/* Filters */} 71 <TodoFilters 72 filters={filters} 73 onUpdateFilters={updateFilters} 74 /> 75 76 {/* Todo List */} 77 {loading && <div>Loading todos...</div>} 78 {error && <div className="text-red-500">Error: {error}</div>} 79 80 <div className="space-y-2"> 81 {filteredTodos.map(todo => ( 82 <div key={todo.id} className="flex items-center gap-3 p-3 border rounded"> 83 <input 84 type="checkbox" 85 checked={todo.completed} 86 onChange={() => handleToggleComplete(todo.id)} 87 /> 88 <span className={todo.completed ? 'line-through' : ''}> 89 {todo.title} 90 </span> 91 <button 92 onClick={() => deleteTodo(todo.id)} 93 className="ml-auto text-red-500" 94 > 95 Delete 96 </button> 97 </div> 98 ))} 99 </div> 100 101 {filteredTodos.length === 0 && ( 102 <div className="text-center py-12 text-muted-foreground"> 103 No todos found. Add some tasks to get started! 104 </div> 105 )} 106 </div> 107 ); 108}

What You Get With Zenty

  • • Complete CRUD operations in 2 lines of code
  • • Automatic state management (loading, error, loaded)
  • • Built-in filtering and search capabilities
  • • Type-safe operations with full TypeScript support
  • • Optimistic updates and error handling
  • • Zero boilerplate - just focus on your UI

Advanced Features Available

  • • Bulk operations (addMany, updateMany, deleteMany)
  • • Advanced search with find() and has() methods
  • • State persistence with localStorage integration
  • • Real-time updates and synchronization
  • • Custom validation and error handling
  • • Performance optimizations built-in
Best Practices
Recommended patterns and practices for using Zenty effectively

1Entity Design

Design your entities with clear, consistent ID fields and optional properties.

1// ✅ Good: Clear ID field, optional properties 2interface Product { 3 id: number // Required, unique identifier 4 name: string // Required fields 5 price?: number // Optional fields with ? 6 category?: string 7 createdAt?: Date 8} 9 10// ❌ Avoid: Unclear ID field, all required 11interface Product { 12 productId: string // Unclear naming 13 name: string 14 price: number // Should be optional 15 category: string // Should be optional 16}

2Error Handling

Always handle loading and error states in your components.

1function ProductList() { 2 const { entities, loading, error, setError } = useProductStore() 3 4 // ✅ Handle all states 5 if (loading) return <LoadingSpinner /> 6 if (error) return <ErrorMessage error={error} onDismiss={() => setError(null)} /> 7 if (entities.length === 0) return <EmptyState /> 8 9 return ( 10 <div> 11 {entities.map(product => ( 12 <ProductCard key={product.id} product={product} /> 13 ))} 14 </div> 15 ) 16}

3Async Operations

Use the built-in loading states for async operations.

1async function fetchAndAddProducts() { 2 const { addMany, setLoading, setError } = useProductStore() 3 4 try { 5 setLoading(true) 6 setError(null) 7 const response = await fetch('/api/products') 8 const products = await response.json() 9 addMany(products) 10 } catch (error) { 11 setError('Failed to fetch products') 12 } finally { 13 setLoading(false) 14 } 15}

4Store Organization

Organize your stores in separate files for better maintainability.

1// stores/products.ts 2export const useProductStore = createEntitiesStore<Product>() 3 4// stores/users.ts 5export const useUserStore = createEntitiesStore<User>() 6 7// stores/settings.ts 8export const useSettingsStore = createEntityStore<Settings>({ 9 initialState: { theme: 'light', notifications: true } 10}) 11 12// stores/index.ts 13export * from './products' 14export * from './users' 15export * from './settings'
Migration Guide
How to migrate from traditional Zustand to Zenty

Step-by-Step Migration:

1

Install Zenty

1npm install zenty
2

Replace your existing store

❌ Before (50+ lines)
1const useProductStore = create((set) => ({ 2 products: [], 3 loading: false, 4 error: null, 5 addProduct: (product) => set((state) => ({ 6 products: [...state.products, product] 7 })), 8 updateProduct: (id, updates) => set((state) => ({ 9 products: state.products.map(p => 10 p.id === id ? { ...p, ...updates } : p 11 ) 12 })), 13 deleteProduct: (id) => set((state) => ({ 14 products: state.products.filter(p => p.id !== id) 15 })), 16 // ... 40+ more lines 17}))
✅ After (1 line)
1const useProductStore = createEntitiesStore<Product>()
3

Update your component usage

1// Before 2const { products, addProduct, loading } = useProductStore() 3 4// After (same destructuring, different method names) 5const { entities: products, add: addProduct, loading } = useProductStore()
4

Test and deploy

Your app should work exactly the same, but with less code and more features!

Support & Resources
Get help and stay updated with Zenty

Package Information