@signaltree/core 4.0.6 → 4.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +1469 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,1469 @@
1
+ <div align="center">
2
+ <img src="https://raw.githubusercontent.com/JBorgia/signaltree/main/apps/demo/public/signaltree.svg" alt="SignalTree Logo" width="60" height="60" />
3
+ </div>
4
+
5
+ # SignalTree Core
6
+
7
+ Foundation package for SignalTree. Provides recursive typing, deep nesting support, and strong performance.
8
+
9
+ ## What is @signaltree/core?
10
+
11
+ SignalTree Core is a lightweight package that provides:
12
+
13
+ - Recursive typing with deep nesting and accurate type inference
14
+ - Fast operations with sub‑millisecond measurements at 5–20+ levels
15
+ - Strong TypeScript safety across nested structures
16
+ - Memory efficiency via structural sharing and lazy signals
17
+ - Small API surface with zero-cost abstractions
18
+ - Compact bundle size suited for production
19
+
20
+ ### Callable leaf signals (DX sugar only)
21
+
22
+ SignalTree provides TypeScript support for callable syntax on leaf signals as developer experience sugar:
23
+
24
+ ```typescript
25
+ // TypeScript accepts this syntax (with proper tooling):
26
+ tree.$.name('Jane'); // Set value
27
+ tree.$.count((n) => n + 1); // Update with function
28
+
29
+ // At build time, transforms convert to:
30
+ tree.$.name.set('Jane'); // Direct Angular signal API
31
+ tree.$.count.update((n) => n + 1); // Direct Angular signal API
32
+
33
+ // Reading always works directly:
34
+ const name = tree.$.name(); // No transform needed
35
+ ```
36
+
37
+ **Key Points:**
38
+
39
+ - **Zero runtime overhead**: No Proxy wrappers or runtime hooks
40
+ - **Build-time only**: AST transform converts callable syntax to direct `.set/.update` calls
41
+ - **Optional**: Use `@signaltree/callable-syntax` transform or stick with direct `.set/.update`
42
+ - **Type-safe**: Full TypeScript support via module augmentation
43
+
44
+ **Function-valued leaves:**
45
+ When a leaf stores a function as its value, use direct `.set(fn)` to assign. Callable `sig(fn)` is treated as an updater.
46
+
47
+ **Setup:**
48
+ Install `@signaltree/callable-syntax` and configure your build tool to apply the transform. Without the transform, use `.set/.update` directly.
49
+
50
+ ### Measuring performance and size
51
+
52
+ Performance and bundle size vary by app shape, build tooling, device, and runtime. To get meaningful results for your environment:
53
+
54
+ - Use the **Benchmark Orchestrator** in the demo app to run calibrated, scenario-based benchmarks across supported libraries with **real-world frequency weighting**. It applies research-based multipliers derived from 40,000+ developer surveys and GitHub analysis, reports statistical summaries (median/p95/p99/stddev), alternates runs to reduce bias, and can export CSV/JSON. When available, memory usage is also reported.
55
+ - Use the bundle analysis scripts in `scripts/` to measure your min+gz sizes. Sizes are approximate and depend on tree-shaking and configuration.
56
+
57
+ ## Quick start
58
+
59
+ ### Installation
60
+
61
+ ```bash
62
+ npm install @signaltree/core
63
+ ```
64
+
65
+ ### Deep nesting example
66
+
67
+ ```typescript
68
+ import { signalTree } from '@signaltree/core';
69
+
70
+ // Strong type inference at deep nesting levels
71
+ const tree = signalTree({
72
+ enterprise: {
73
+ divisions: {
74
+ technology: {
75
+ departments: {
76
+ engineering: {
77
+ teams: {
78
+ frontend: {
79
+ projects: {
80
+ signaltree: {
81
+ releases: {
82
+ v1: {
83
+ features: {
84
+ recursiveTyping: {
85
+ validation: {
86
+ tests: {
87
+ extreme: {
88
+ depth: 15,
89
+ typeInference: true,
90
+ },
91
+ },
92
+ },
93
+ },
94
+ },
95
+ },
96
+ },
97
+ },
98
+ },
99
+ },
100
+ },
101
+ },
102
+ },
103
+ },
104
+ },
105
+ },
106
+ });
107
+
108
+ // Type inference at deep nesting levels
109
+ const depth = tree.$.enterprise.divisions.technology.departments.engineering.teams.frontend.projects.signaltree.releases.v1.features.recursiveTyping.validation.tests.extreme.depth();
110
+ console.log(`Depth: ${depth}`);
111
+
112
+ // Type-safe updates at unlimited depth
113
+ tree.$.enterprise.divisions.technology.departments.engineering.teams.frontend.projects.signaltree.releases.v1.features.recursiveTyping.validation.tests.extreme.depth(25); // Perfect type safety!
114
+ ```
115
+
116
+ ### Basic usage
117
+
118
+ ```typescript
119
+ import { signalTree } from '@signaltree/core';
120
+
121
+ // Create a simple tree
122
+ const tree = signalTree({
123
+ count: 0,
124
+ message: 'Hello World',
125
+ });
126
+
127
+ // Read values (these are Angular signals)
128
+ console.log(tree.$.count()); // 0
129
+ console.log(tree.$.message()); // 'Hello World'
130
+
131
+ // Update values
132
+ tree.$.count(5);
133
+ tree.$.message('Updated!');
134
+
135
+ // Use in an Angular component
136
+ @Component({
137
+ template: ` <div>Count: {{ tree.$.count() }}</div>
138
+ <div>Message: {{ tree.$.message() }}</div>
139
+ <button (click)="increment()">+1</button>`,
140
+ })
141
+ class SimpleComponent {
142
+ tree = tree;
143
+
144
+ increment() {
145
+ this.tree.$.count((n) => n + 1);
146
+ }
147
+ }
148
+ ```
149
+
150
+ ### Intermediate usage (nested state)
151
+
152
+ ```typescript
153
+ // Create hierarchical state
154
+ const tree = signalTree({
155
+ user: {
156
+ name: 'John Doe',
157
+ email: 'john@example.com',
158
+ preferences: {
159
+ theme: 'dark',
160
+ notifications: true,
161
+ },
162
+ },
163
+ ui: {
164
+ loading: false,
165
+ errors: [] as string[],
166
+ },
167
+ });
168
+
169
+ // Access nested signals with full type safety
170
+ tree.$.user.name('Jane Doe');
171
+ tree.$.user.preferences.theme('light');
172
+ tree.$.ui.loading(true);
173
+
174
+ // Computed values from nested state
175
+ const userDisplayName = computed(() => {
176
+ const user = tree.$.user();
177
+ return `${user.name} (${user.email})`;
178
+ });
179
+
180
+ // Effects that respond to changes
181
+ effect(() => {
182
+ if (tree.$.ui.loading()) {
183
+ console.log('Loading started...');
184
+ }
185
+ });
186
+ ```
187
+
188
+ ### Reactive computations with computed()
189
+
190
+ SignalTree works seamlessly with Angular's `computed()` for creating efficient reactive computations. These computations automatically update when their dependencies change and are memoized for optimal performance.
191
+
192
+ ```typescript
193
+ import { computed, effect } from '@angular/core';
194
+ import { signalTree } from '@signaltree/core';
195
+
196
+ const tree = signalTree({
197
+ users: [
198
+ { id: '1', name: 'Alice', active: true, role: 'admin' },
199
+ { id: '2', name: 'Bob', active: false, role: 'user' },
200
+ { id: '3', name: 'Charlie', active: true, role: 'user' },
201
+ ],
202
+ filters: {
203
+ showActive: true,
204
+ role: 'all' as 'all' | 'admin' | 'user',
205
+ },
206
+ });
207
+
208
+ // Basic computed - automatically memoized
209
+ const userCount = computed(() => tree.$.users().length);
210
+
211
+ // Complex filtering computation
212
+ const filteredUsers = computed(() => {
213
+ const users = tree.$.users();
214
+ const filters = tree.$.filters();
215
+
216
+ return users.filter((user) => {
217
+ if (filters.showActive && !user.active) return false;
218
+ if (filters.role !== 'all' && user.role !== filters.role) return false;
219
+ return true;
220
+ });
221
+ });
222
+
223
+ // Derived computation from other computed values
224
+ const activeAdminCount = computed(() => filteredUsers().filter((user) => user.role === 'admin' && user.active).length);
225
+
226
+ // Performance-critical computation with complex logic
227
+ const userStatistics = computed(() => {
228
+ const users = tree.$.users();
229
+
230
+ return {
231
+ total: users.length,
232
+ active: users.filter((u) => u.active).length,
233
+ admins: users.filter((u) => u.role === 'admin').length,
234
+ averageNameLength: users.reduce((acc, u) => acc + u.name.length, 0) / users.length,
235
+ };
236
+ });
237
+
238
+ // Dynamic computed functions (factory pattern)
239
+ const userById = (id: string) => computed(() => tree.$.users().find((user) => user.id === id));
240
+
241
+ // Usage in effects
242
+ effect(() => {
243
+ console.log(`Filtered users: ${filteredUsers().length}`);
244
+ console.log(`Statistics:`, userStatistics());
245
+ });
246
+
247
+ // Best Practices:
248
+ // 1. Use computed() for derived state that depends on signals
249
+ // 2. Keep computations pure - no side effects
250
+ // 3. Leverage automatic memoization for expensive operations
251
+ // 4. Chain computed values for complex transformations
252
+ // 5. Use factory functions for parameterized computations
253
+ ```
254
+
255
+ ### Performance optimization with memoization
256
+
257
+ When combined with `@signaltree/memoization`, computed values become even more powerful:
258
+
259
+ ```typescript
260
+ import { withMemoization } from '@signaltree/core';
261
+
262
+ const tree = signalTree({
263
+ items: Array.from({ length: 10000 }, (_, i) => ({
264
+ id: i,
265
+ value: Math.random(),
266
+ category: `cat-${i % 10}`,
267
+ })),
268
+ }).with(withMemoization());
269
+
270
+ // Expensive computation - automatically cached by memoization enhancer
271
+ const expensiveComputation = computed(() => {
272
+ return tree.$.items()
273
+ .filter((item) => item.value > 0.5)
274
+ .reduce((acc, item) => acc + Math.sin(item.value * Math.PI), 0);
275
+ });
276
+
277
+ // The computation only runs when tree.$.items() actually changes
278
+ // Subsequent calls return cached result
279
+ ```
280
+
281
+ ### Advanced usage (full state tree)
282
+
283
+ ```typescript
284
+ interface AppState {
285
+ auth: {
286
+ user: User | null;
287
+ token: string | null;
288
+ isAuthenticated: boolean;
289
+ };
290
+ data: {
291
+ users: User[];
292
+ posts: Post[];
293
+ cache: Record<string, unknown>;
294
+ };
295
+ ui: {
296
+ theme: 'light' | 'dark';
297
+ sidebar: {
298
+ open: boolean;
299
+ width: number;
300
+ };
301
+ notifications: Notification[];
302
+ };
303
+ }
304
+
305
+ const tree = signalTree<AppState>({
306
+ auth: {
307
+ user: null,
308
+ token: null,
309
+ isAuthenticated: false,
310
+ },
311
+ data: {
312
+ users: [],
313
+ posts: [],
314
+ cache: {},
315
+ },
316
+ ui: {
317
+ theme: 'light',
318
+ sidebar: { open: true, width: 250 },
319
+ notifications: [],
320
+ },
321
+ });
322
+
323
+ // Complex updates with type safety
324
+ tree((state) => ({
325
+ auth: {
326
+ ...state.auth,
327
+ user: { id: '1', name: 'John' },
328
+ isAuthenticated: true,
329
+ },
330
+ ui: {
331
+ ...state.ui,
332
+ notifications: [...state.ui.notifications, { id: '1', message: 'Welcome!', type: 'success' }],
333
+ },
334
+ }));
335
+
336
+ // Get entire state as plain object
337
+ const currentState = tree();
338
+ console.log('Current app state:', currentState);
339
+ ```
340
+
341
+ ## Core features
342
+
343
+ ### 1) Hierarchical signal trees
344
+
345
+ Create deeply nested reactive state with automatic type inference:
346
+
347
+ ```typescript
348
+ const tree = signalTree({
349
+ user: { name: '', email: '' },
350
+ settings: { theme: 'dark', notifications: true },
351
+ todos: [] as Todo[],
352
+ });
353
+
354
+ // Access nested signals with full type safety
355
+ tree.$.user.name(); // string signal
356
+ tree.$.settings.theme.set('light'); // type-checked value
357
+ tree.$.todos.update((todos) => [...todos, newTodo]); // array operations
358
+ ```
359
+
360
+ ### 2) TypeScript inference
361
+
362
+ SignalTree provides complete type inference without manual typing:
363
+
364
+ ```typescript
365
+ // Automatic inference from initial state
366
+ const tree = signalTree({
367
+ count: 0, // Inferred as WritableSignal<number>
368
+ name: 'John', // Inferred as WritableSignal<string>
369
+ active: true, // Inferred as WritableSignal<boolean>
370
+ items: [] as Item[], // Inferred as WritableSignal<Item[]>
371
+ config: {
372
+ theme: 'dark' as const, // Inferred as WritableSignal<'dark'>
373
+ settings: {
374
+ nested: true, // Deep nesting maintained
375
+ },
376
+ },
377
+ });
378
+
379
+ // Type-safe access and updates
380
+ tree.$.count.set(5); // ✅ number
381
+ tree.$.count.set('invalid'); // ❌ Type error
382
+ tree.$.config.theme.set('light'); // ❌ Type error ('dark' const)
383
+ tree.$.config.settings.nested.set(false); // ✅ boolean
384
+ ```
385
+
386
+ ### 3) Manual state management
387
+
388
+ Core provides basic state updates - entity management requires `@signaltree/entities`:
389
+
390
+ ```typescript
391
+ interface User {
392
+ id: string;
393
+ name: string;
394
+ email: string;
395
+ active: boolean;
396
+ }
397
+
398
+ const tree = signalTree({
399
+ users: [] as User[],
400
+ });
401
+
402
+ // Manual CRUD operations using core methods
403
+ function addUser(user: User) {
404
+ tree.$.users.update((users) => [...users, user]);
405
+ }
406
+
407
+ function updateUser(id: string, updates: Partial<User>) {
408
+ tree.$.users.update((users) => users.map((user) => (user.id === id ? { ...user, ...updates } : user)));
409
+ }
410
+
411
+ function removeUser(id: string) {
412
+ tree.$.users.update((users) => users.filter((user) => user.id !== id));
413
+ }
414
+
415
+ // Manual queries using computed signals
416
+ const userById = (id: string) => computed(() => tree.$.users().find((user) => user.id === id));
417
+ const activeUsers = computed(() => tree.$.users().filter((user) => user.active));
418
+ ```
419
+
420
+ ### 4) Manual async state management
421
+
422
+ Core provides basic state updates - async helpers are now provided via `@signaltree/middleware` helpers:
423
+
424
+ ```typescript
425
+ const tree = signalTree({
426
+ users: [] as User[],
427
+ loading: false,
428
+ error: null as string | null,
429
+ });
430
+
431
+ // Manual async operation management
432
+ async function loadUsers() {
433
+ tree.$.loading.set(true);
434
+ tree.$.error.set(null);
435
+
436
+ try {
437
+ const users = await api.getUsers();
438
+ tree.$.users.set(users);
439
+ } catch (error) {
440
+ tree.$.error.set(error instanceof Error ? error.message : 'Unknown error');
441
+ } finally {
442
+ tree.$.loading.set(false);
443
+ }
444
+ }
445
+
446
+ // Usage in component
447
+ @Component({
448
+ template: `
449
+ @if (tree.$.loading()) {
450
+ <div>Loading...</div>
451
+ } @else if (tree.$.error()) {
452
+ <div class="error">{{ tree.$.error() }}</div>
453
+ } @else { @for (user of tree.$.users(); track user.id) {
454
+ <user-card [user]="user" />
455
+ } }
456
+ <button (click)="loadUsers()">Refresh</button>
457
+ `,
458
+ })
459
+ class UsersComponent {
460
+ tree = tree;
461
+ loadUsers = loadUsers;
462
+ }
463
+ ```
464
+
465
+ ### 5) Performance considerations
466
+
467
+ ### 6) Enhancers and composition
468
+
469
+ SignalTree Core provides the foundation, but its real power comes from composable enhancers. Each enhancer is a focused, tree-shakeable extension that adds specific functionality.
470
+
471
+ #### Available Enhancers
472
+
473
+ **Performance Enhancers:**
474
+
475
+ - `@signaltree/batching` - Batch updates to reduce recomputation and rendering
476
+ - `@signaltree/memoization` - Intelligent caching for expensive computations
477
+
478
+ **Data Management:**
479
+
480
+ - `@signaltree/entities` - Advanced CRUD operations for collections
481
+ - Async helpers are provided via `@signaltree/middleware` (createAsyncOperation / trackAsync)
482
+ - `@signaltree/serialization` - State persistence and SSR support
483
+
484
+ **Development Tools:**
485
+
486
+ - `@signaltree/devtools` - Redux DevTools integration
487
+ - `@signaltree/time-travel` - Undo/redo functionality
488
+
489
+ **Integration:**
490
+
491
+ - `@signaltree/ng-forms` - Angular Forms integration
492
+ - `@signaltree/middleware` - State interceptors and logging
493
+ - `@signaltree/presets` - Pre-configured common patterns
494
+
495
+ #### Composition Patterns
496
+
497
+ **Basic Enhancement:**
498
+
499
+ ```typescript
500
+ import { signalTree } from '@signaltree/core';
501
+ import { withBatching } from '@signaltree/core';
502
+ import { withDevTools } from '@signaltree/core';
503
+
504
+ // Apply enhancers in order
505
+ const tree = signalTree({ count: 0 }).with(
506
+ withBatching(), // Performance optimization
507
+ withDevTools() // Development tools
508
+ );
509
+ ```
510
+
511
+ **Performance-Focused Stack:**
512
+
513
+ ```typescript
514
+ import { signalTree, withBatching } from '@signaltree/core';
515
+ import { withMemoization } from '@signaltree/core';
516
+ import { withEntities } from '@signaltree/core';
517
+
518
+ const tree = signalTree({
519
+ products: [] as Product[],
520
+ ui: { loading: false },
521
+ }).with(
522
+ withBatching(), // Batch updates for optimal rendering
523
+ withMemoization(), // Cache expensive computations
524
+ withEntities() // Efficient CRUD operations
525
+ );
526
+
527
+ // Now supports advanced operations
528
+ tree.batchUpdate((state) => ({
529
+ products: [...state.products, newProduct],
530
+ ui: { loading: false },
531
+ }));
532
+
533
+ const products = tree.entities<Product>('products');
534
+ products.selectBy((p) => p.category === 'electronics');
535
+ ```
536
+
537
+ **Full-Stack Application:**
538
+
539
+ ```typescript
540
+ import { withSerialization } from '@signaltree/core';
541
+ import { withTimeTravel } from '@signaltree/core';
542
+
543
+ const tree = signalTree({
544
+ user: null as User | null,
545
+ preferences: { theme: 'light' },
546
+ }).with(
547
+ // withAsync removed — API integration patterns are now covered by middleware helpers
548
+ withSerialization({
549
+ // Auto-save to localStorage
550
+ autoSave: true,
551
+ storage: 'localStorage',
552
+ }),
553
+ withTimeTravel() // Undo/redo support
554
+ );
555
+
556
+ // Advanced async operations
557
+ const fetchUser = tree.asyncAction(async (id: string) => api.getUser(id), {
558
+ loadingKey: 'loading',
559
+ onSuccess: (user) => ({ user }),
560
+ });
561
+
562
+ // Automatic state persistence
563
+ tree.$.preferences.theme('dark'); // Auto-saved
564
+
565
+ // Time travel
566
+ tree.undo(); // Revert changes
567
+ ```
568
+
569
+ #### Enhancer Metadata & Ordering
570
+
571
+ Enhancers can declare metadata for automatic dependency resolution:
572
+
573
+ ```typescript
574
+ // Enhancers are automatically ordered based on requirements
575
+ const tree = signalTree(state).with(
576
+ withDevTools(), // Requires: core, provides: debugging
577
+ withBatching(), // Requires: core, provides: batching
578
+ withMemoization() // Requires: batching, provides: caching
579
+ );
580
+ // Automatically ordered: batching -> memoization -> devtools
581
+ ```
582
+
583
+ #### Quick Start with Presets
584
+
585
+ For common patterns, use presets that combine multiple enhancers:
586
+
587
+ ```typescript
588
+ import { ecommercePreset, dashboardPreset } from '@signaltree/core';
589
+
590
+ // E-commerce preset includes: entities, async, batching, serialization
591
+ const ecommerceTree = ecommercePreset({
592
+ products: [] as Product[],
593
+ cart: { items: [], total: 0 },
594
+ });
595
+
596
+ // Dashboard preset includes: batching, memoization, devtools
597
+ const dashboardTree = dashboardPreset({
598
+ metrics: { users: 0, revenue: 0 },
599
+ charts: { data: [] },
600
+ });
601
+ ```
602
+
603
+ #### Core Stubs
604
+
605
+ SignalTree Core includes lightweight stubs for all enhancer methods. This allows you to write code that uses advanced features, and the methods will warn when the actual enhancer isn't installed:
606
+
607
+ ```typescript
608
+ const tree = signalTree({ users: [] as User[] });
609
+
610
+ // Works but warns: "Feature requires @signaltree/entities"
611
+ const users = tree.entities<User>('users');
612
+ users.add(newUser); // Warns: "Method requires @signaltree/entities"
613
+
614
+ // Install the enhancer to enable full functionality
615
+ const enhanced = tree.with(withEntities());
616
+ const realUsers = enhanced.entities<User>('users');
617
+ realUsers.add(newUser); // ✅ Works perfectly
618
+ ```
619
+
620
+ Core includes several performance optimizations:
621
+
622
+ ```typescript
623
+ // Lazy signal creation (default)
624
+ const tree = signalTree(
625
+ {
626
+ largeObject: {
627
+ // Signals only created when accessed
628
+ level1: { level2: { level3: { data: 'value' } } },
629
+ },
630
+ },
631
+ {
632
+ useLazySignals: true, // Default: true
633
+ }
634
+ );
635
+
636
+ // Custom equality function
637
+ const tree2 = signalTree(
638
+ {
639
+ items: [] as Item[],
640
+ },
641
+ {
642
+ useShallowComparison: false, // Deep equality (default)
643
+ }
644
+ );
645
+
646
+ // Structural sharing for memory efficiency
647
+ tree.update((state) => ({
648
+ ...state, // Reuses unchanged parts
649
+ newField: 'value',
650
+ }));
651
+ ```
652
+
653
+ ## Error handling examples
654
+
655
+ ### Manual async error handling
656
+
657
+ ```typescript
658
+ const tree = signalTree({
659
+ data: null as ApiData | null,
660
+ loading: false,
661
+ error: null as Error | null,
662
+ retryCount: 0,
663
+ });
664
+
665
+ async function loadDataWithRetry(attempt = 0) {
666
+ tree.$.loading.set(true);
667
+ tree.$.error.set(null);
668
+
669
+ try {
670
+ const data = await api.getData();
671
+ tree.$.data.set(data);
672
+ tree.$.loading.set(false);
673
+ tree.$.retryCount.set(0);
674
+ } catch (error) {
675
+ if (attempt < 3) {
676
+ // Retry logic
677
+ await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
678
+ return loadDataWithRetry(attempt + 1);
679
+ }
680
+
681
+ tree.$.loading.set(false);
682
+ tree.$.error.set(error instanceof Error ? error : new Error('Unknown error'));
683
+ tree.$.retryCount.update((count) => count + 1);
684
+ }
685
+ }
686
+
687
+ // Error boundary component
688
+ @Component({
689
+ template: `
690
+ @if (tree.$.error()) {
691
+ <div class="error-boundary">
692
+ <h3>Something went wrong</h3>
693
+ <p>{{ tree.$.error()?.message }}</p>
694
+ <p>Attempts: {{ tree.$.retryCount() }}</p>
695
+ <button (click)="retry()">Retry</button>
696
+ <button (click)="clear()">Clear Error</button>
697
+ </div>
698
+ } @else {
699
+ <!-- Normal content -->
700
+ }
701
+ `,
702
+ })
703
+ class ErrorHandlingComponent {
704
+ tree = tree;
705
+
706
+ retry() {
707
+ loadDataWithRetry();
708
+ }
709
+
710
+ clear() {
711
+ this.tree.$.error.set(null);
712
+ }
713
+ }
714
+ ```
715
+
716
+ ### State update error handling
717
+
718
+ ```typescript
719
+ const tree = signalTree({
720
+ items: [] as Item[],
721
+ validationErrors: [] as string[],
722
+ });
723
+
724
+ // Safe update with validation
725
+ function safeUpdateItem(id: string, updates: Partial<Item>) {
726
+ try {
727
+ tree.update((state) => {
728
+ const itemIndex = state.items.findIndex((item) => item.id === id);
729
+ if (itemIndex === -1) {
730
+ throw new Error(`Item with id ${id} not found`);
731
+ }
732
+
733
+ const updatedItem = { ...state.items[itemIndex], ...updates };
734
+
735
+ // Validation
736
+ if (!updatedItem.name?.trim()) {
737
+ throw new Error('Item name is required');
738
+ }
739
+
740
+ const newItems = [...state.items];
741
+ newItems[itemIndex] = updatedItem;
742
+
743
+ return {
744
+ items: newItems,
745
+ validationErrors: [], // Clear errors on success
746
+ };
747
+ });
748
+ } catch (error) {
749
+ tree.$.validationErrors.update((errors) => [...errors, error instanceof Error ? error.message : 'Unknown error']);
750
+ }
751
+ }
752
+ ```
753
+
754
+ ## Package composition patterns
755
+
756
+ SignalTree Core is designed for modular composition. Start minimal and add features as needed.
757
+
758
+ ### Basic Composition
759
+
760
+ ```typescript
761
+ import { signalTree } from '@signaltree/core';
762
+
763
+ // Core provides the foundation
764
+ const tree = signalTree({
765
+ users: [] as User[],
766
+ ui: { loading: false },
767
+ });
768
+
769
+ // Basic operations included in core
770
+ tree.$.users.set([...users, newUser]);
771
+ tree.$.ui.loading.set(true);
772
+ tree.effect(() => console.log('State changed'));
773
+ ```
774
+
775
+ ### Performance-Enhanced Composition
776
+
777
+ ```typescript
778
+ import { signalTree } from '@signaltree/core';
779
+ import { withBatching } from '@signaltree/core';
780
+ import { withMemoization } from '@signaltree/core';
781
+
782
+ // Add performance optimizations
783
+ const tree = signalTree({
784
+ products: [] as Product[],
785
+ filters: { category: '', search: '' },
786
+ }).with(
787
+ withBatching(), // Batch updates for optimal rendering
788
+ withMemoization() // Cache expensive computations
789
+ );
790
+
791
+ // Now supports batched updates
792
+ tree.batchUpdate((state) => ({
793
+ products: [...state.products, ...newProducts],
794
+ filters: { category: 'electronics', search: '' },
795
+ }));
796
+
797
+ // Expensive computations are automatically cached
798
+ const filteredProducts = computed(() => {
799
+ return tree.$.products()
800
+ .filter((p) => p.category.includes(tree.$.filters.category()))
801
+ .filter((p) => p.name.includes(tree.$.filters.search()));
802
+ });
803
+ ```
804
+
805
+ ### Data Management Composition
806
+
807
+ ```typescript
808
+ import { signalTree } from '@signaltree/core';
809
+ import { withEntities } from '@signaltree/core';
810
+
811
+ // Add data management capabilities (+2.77KB total)
812
+ const tree = signalTree({
813
+ users: [] as User[],
814
+ posts: [] as Post[],
815
+ ui: { loading: false, error: null },
816
+ }).with(
817
+ withEntities() // Advanced CRUD operations
818
+ );
819
+
820
+ // Advanced entity operations
821
+ const users = tree.entities<User>('users');
822
+ users.add(newUser);
823
+ users.selectBy((u) => u.active);
824
+ users.updateMany([{ id: '1', changes: { status: 'active' } }]);
825
+
826
+ // Powerful async actions
827
+ const fetchUsers = tree.asyncAction(async () => api.getUsers(), {
828
+ loadingKey: 'ui.loading',
829
+ errorKey: 'ui.error',
830
+ onSuccess: (users) => ({ users }),
831
+ });
832
+ ```
833
+
834
+ ### Full-Featured Development Composition
835
+
836
+ ```typescript
837
+ import { signalTree } from '@signaltree/core';
838
+ import { withBatching } from '@signaltree/core';
839
+ import { withEntities } from '@signaltree/core';
840
+ import { withSerialization } from '@signaltree/core';
841
+ import { withTimeTravel } from '@signaltree/core';
842
+ import { withDevTools } from '@signaltree/core';
843
+
844
+ // Full development stack (example)
845
+ const tree = signalTree({
846
+ app: {
847
+ user: null as User | null,
848
+ preferences: { theme: 'light' },
849
+ data: { users: [], posts: [] },
850
+ },
851
+ }).with(
852
+ withBatching(), // Performance
853
+ withEntities(), // Data management
854
+ // withAsync removed — use middleware helpers for API integration
855
+ withSerialization({
856
+ // State persistence
857
+ autoSave: true,
858
+ storage: 'localStorage',
859
+ }),
860
+ withTimeTravel({
861
+ // Undo/redo
862
+ maxHistory: 50,
863
+ }),
864
+ withDevTools({
865
+ // Debug tools (dev only)
866
+ name: 'MyApp',
867
+ trace: true,
868
+ })
869
+ );
870
+
871
+ // Rich feature set available
872
+ const users = tree.entities<User>('app.data.users');
873
+ const fetchUser = tree.asyncAction(api.getUser);
874
+ tree.undo(); // Time travel
875
+ tree.save(); // Persistence
876
+ ```
877
+
878
+ ### Production-Ready Composition
879
+
880
+ ```typescript
881
+ import { signalTree } from '@signaltree/core';
882
+ import { withBatching } from '@signaltree/core';
883
+ import { withEntities } from '@signaltree/core';
884
+ import { withSerialization } from '@signaltree/core';
885
+
886
+ // Production build (no dev tools)
887
+ const tree = signalTree(initialState).with(
888
+ withBatching(), // Performance optimization
889
+ withEntities(), // Data management
890
+ // withAsync removed — use middleware helpers for API integration
891
+ withSerialization({
892
+ // User preferences
893
+ autoSave: true,
894
+ storage: 'localStorage',
895
+ key: 'app-v1.2.3',
896
+ })
897
+ );
898
+
899
+ // Clean, efficient, production-ready
900
+ ```
901
+
902
+ ### Conditional Enhancement
903
+
904
+ ```typescript
905
+ import { signalTree } from '@signaltree/core';
906
+ import { withDevTools } from '@signaltree/core';
907
+ import { withTimeTravel } from '@signaltree/core';
908
+
909
+ const isDevelopment = process.env['NODE_ENV'] === 'development';
910
+
911
+ // Conditional enhancement based on environment
912
+ const tree = signalTree(state).with(
913
+ withBatching(), // Always include performance
914
+ withEntities(), // Always include data management
915
+ ...(isDevelopment
916
+ ? [
917
+ // Development-only features
918
+ withDevTools(),
919
+ withTimeTravel(),
920
+ ]
921
+ : [])
922
+ );
923
+ ```
924
+
925
+ ### Preset-Based Composition
926
+
927
+ ```typescript
928
+ import { ecommercePreset, dashboardPreset } from '@signaltree/core';
929
+
930
+ // Use presets for common patterns
931
+ const ecommerceTree = ecommercePreset({
932
+ products: [],
933
+ cart: { items: [], total: 0 },
934
+ user: null,
935
+ });
936
+ // Includes: entities, async, batching, serialization
937
+
938
+ const dashboardTree = dashboardPreset({
939
+ metrics: {},
940
+ charts: [],
941
+ filters: {},
942
+ });
943
+ // Includes: batching, memoization, devtools
944
+ ```
945
+
946
+ ### Measuring bundle size
947
+
948
+ Bundle sizes depend on your build, tree-shaking, and which enhancers you include. Use the scripts in `scripts/` to analyze min+gz for your configuration.
949
+
950
+ ### Migration Strategy
951
+
952
+ Start with core and grow incrementally:
953
+
954
+ ```typescript
955
+ // Phase 1: Start with core
956
+ const tree = signalTree(state);
957
+
958
+ // Phase 2: Add performance when needed
959
+ const tree2 = tree.with(withBatching());
960
+
961
+ // Phase 3: Add data management for collections
962
+ const tree3 = tree2.with(withEntities());
963
+
964
+ // Phase 4: Add async for API integration
965
+ // withAsync removed — no explicit async enhancer; use middleware helpers instead
966
+
967
+ // Each phase is fully functional and production-ready
968
+ ```
969
+
970
+ ```typescript
971
+ // Start minimal, add features as needed
972
+ let tree = signalTree(initialState);
973
+
974
+ if (isDevelopment) {
975
+ tree = tree.with(withDevTools());
976
+ }
977
+
978
+ if (needsPerformance) {
979
+ tree = tree.with(withBatching(), withMemoization());
980
+ }
981
+
982
+ if (needsTimeTravel) {
983
+ tree = tree.with(withTimeTravel());
984
+ }
985
+ ```
986
+
987
+ ### Service-based pattern
988
+
989
+ ```typescript
990
+ @Injectable()
991
+ class AppStateService {
992
+ private tree = signalTree({
993
+ user: null as User | null,
994
+ settings: { theme: 'light' as const },
995
+ });
996
+
997
+ // Expose specific parts
998
+ readonly user$ = this.tree.$.user;
999
+ readonly settings$ = this.tree.$.settings;
1000
+
1001
+ // Expose specific actions
1002
+ setUser(user: User) {
1003
+ this.tree.$.user.set(user);
1004
+ }
1005
+
1006
+ updateSettings(settings: Partial<Settings>) {
1007
+ this.tree.$.settings.update((current) => ({
1008
+ ...current,
1009
+ ...settings,
1010
+ }));
1011
+ }
1012
+
1013
+ // For advanced features, return the tree
1014
+ getTree() {
1015
+ return this.tree;
1016
+ }
1017
+ }
1018
+ ```
1019
+
1020
+ ## Measuring performance
1021
+
1022
+ For fair, reproducible measurements that reflect your app and hardware, use the **Benchmark Orchestrator** in the demo. It calibrates runs per scenario and library, applies **real-world frequency weighting** based on research analysis, reports robust statistics, and supports CSV/JSON export. Avoid copying fixed numbers from docs; results vary.
1023
+
1024
+ ## Example
1025
+
1026
+ ```typescript
1027
+ // Complete user management component
1028
+ @Component({
1029
+ template: `
1030
+ <div class="user-manager">
1031
+ <!-- User List -->
1032
+ <div class="user-list">
1033
+ @if (userTree.$.loading()) {
1034
+ <div class="loading">Loading users...</div>
1035
+ } @else if (userTree.$.error()) {
1036
+ <div class="error">
1037
+ {{ userTree.$.error() }}
1038
+ <button (click)="loadUsers()">Retry</button>
1039
+ </div>
1040
+ } @else { @for (user of users.selectAll()(); track user.id) {
1041
+ <div class="user-card">
1042
+ <h3>{{ user.name }}</h3>
1043
+ <p>{{ user.email }}</p>
1044
+ <button (click)="editUser(user)">Edit</button>
1045
+ <button (click)="deleteUser(user.id)">Delete</button>
1046
+ </div>
1047
+ } }
1048
+ </div>
1049
+
1050
+ <!-- User Form -->
1051
+ <form (ngSubmit)="saveUser()" #form="ngForm">
1052
+ <input [(ngModel)]="userTree.$.form.name()" name="name" placeholder="Name" required />
1053
+ <input [(ngModel)]="userTree.$.form.email()" name="email" type="email" placeholder="Email" required />
1054
+ <button type="submit" [disabled]="form.invalid">{{ userTree.$.form.id() ? 'Update' : 'Create' }} User</button>
1055
+ <button type="button" (click)="clearForm()">Clear</button>
1056
+ </form>
1057
+ </div>
1058
+ `,
1059
+ })
1060
+ class UserManagerComponent implements OnInit {
1061
+ userTree = signalTree({
1062
+ users: [] as User[],
1063
+ loading: false,
1064
+ error: null as string | null,
1065
+ form: { id: '', name: '', email: '' },
1066
+ });
1067
+
1068
+ constructor(private userService: UserService) {}
1069
+
1070
+ ngOnInit() {
1071
+ this.loadUsers();
1072
+ }
1073
+
1074
+ async loadUsers() {
1075
+ this.userTree.$.loading.set(true);
1076
+ this.userTree.$.error.set(null);
1077
+
1078
+ try {
1079
+ const users = await this.userService.getUsers();
1080
+ this.userTree.$.users.set(users);
1081
+ } catch (error) {
1082
+ this.userTree.$.error.set(error instanceof Error ? error.message : 'Load failed');
1083
+ } finally {
1084
+ this.userTree.$.loading.set(false);
1085
+ }
1086
+ }
1087
+
1088
+ editUser(user: User) {
1089
+ this.userTree.$.form.set(user);
1090
+ }
1091
+
1092
+ async saveUser() {
1093
+ try {
1094
+ const form = this.userTree.$.form();
1095
+ if (form.id) {
1096
+ await this.userService.updateUser(form.id, form);
1097
+ this.updateUser(form.id, form);
1098
+ } else {
1099
+ const newUser = await this.userService.createUser(form);
1100
+ this.addUser(newUser);
1101
+ }
1102
+ this.clearForm();
1103
+ } catch (error) {
1104
+ this.userTree.$.error.set(error instanceof Error ? error.message : 'Save failed');
1105
+ }
1106
+ }
1107
+
1108
+ private addUser(user: User) {
1109
+ this.userTree.$.users.update((users) => [...users, user]);
1110
+ }
1111
+
1112
+ private updateUser(id: string, updates: Partial<User>) {
1113
+ this.userTree.$.users.update((users) => users.map((user) => (user.id === id ? { ...user, ...updates } : user)));
1114
+ }
1115
+
1116
+ deleteUser(id: string) {
1117
+ if (confirm('Delete user?')) {
1118
+ this.removeUser(id);
1119
+ this.userService.deleteUser(id).catch((error) => {
1120
+ this.userTree.$.error.set(error.message);
1121
+ this.loadUsers(); // Reload on error
1122
+ });
1123
+ }
1124
+ }
1125
+
1126
+ private removeUser(id: string) {
1127
+ this.userTree.$.users.update((users) => users.filter((user) => user.id !== id));
1128
+ }
1129
+
1130
+ clearForm() {
1131
+ this.userTree.$.form.set({ id: '', name: '', email: '' });
1132
+ }
1133
+ }
1134
+ ```
1135
+
1136
+ ]
1137
+
1138
+ }
1139
+ }));
1140
+
1141
+ // Get entire state as plain object
1142
+ const currentState = tree.unwrap();
1143
+ console.log('Current app state:', currentState);
1144
+
1145
+ ```
1146
+ });
1147
+ ```
1148
+
1149
+ ## Core features
1150
+
1151
+ ### Hierarchical signal trees
1152
+
1153
+ ```typescript
1154
+ const tree = signalTree({
1155
+ user: { name: '', email: '' },
1156
+ settings: { theme: 'dark', notifications: true },
1157
+ todos: [] as Todo[],
1158
+ });
1159
+
1160
+ // Access nested signals with full type safety
1161
+ tree.$.user.name(); // string
1162
+ tree.$.settings.theme.set('light');
1163
+ tree.$.todos.update((todos) => [...todos, newTodo]);
1164
+ ```
1165
+
1166
+ ### Manual entity management
1167
+
1168
+ ```typescript
1169
+ // Manual CRUD operations
1170
+ const tree = signalTree({
1171
+ todos: [] as Todo[],
1172
+ });
1173
+
1174
+ function addTodo(todo: Todo) {
1175
+ tree.$.todos.update((todos) => [...todos, todo]);
1176
+ }
1177
+
1178
+ function updateTodo(id: string, updates: Partial<Todo>) {
1179
+ tree.$.todos.update((todos) => todos.map((todo) => (todo.id === id ? { ...todo, ...updates } : todo)));
1180
+ }
1181
+
1182
+ function removeTodo(id: string) {
1183
+ tree.$.todos.update((todos) => todos.filter((todo) => todo.id !== id));
1184
+ }
1185
+
1186
+ // Manual queries with computed signals
1187
+ const todoById = (id: string) => computed(() => tree.$.todos().find((todo) => todo.id === id));
1188
+ const allTodos = computed(() => tree.$.todos());
1189
+ const todoCount = computed(() => tree.$.todos().length);
1190
+ ```
1191
+
1192
+ ### Manual async state management
1193
+
1194
+ ```typescript
1195
+ async function loadUsers() {
1196
+ tree.$.loading.set(true);
1197
+
1198
+ try {
1199
+ const users = await api.getUsers();
1200
+ tree.$.users.set(users);
1201
+ } catch (error) {
1202
+ tree.$.error.set(error instanceof Error ? error.message : 'Unknown error');
1203
+ } finally {
1204
+ tree.$.loading.set(false);
1205
+ }
1206
+ }
1207
+
1208
+ // Use in components
1209
+ async function handleLoadUsers() {
1210
+ await loadUsers();
1211
+ }
1212
+ ```
1213
+
1214
+ ### Reactive effects
1215
+
1216
+ ```typescript
1217
+ // Create reactive effects
1218
+ tree.effect((state) => {
1219
+ console.log(`User: ${state.user.name}, Theme: ${state.settings.theme}`);
1220
+ });
1221
+
1222
+ // Manual subscriptions
1223
+ const unsubscribe = tree.subscribe((state) => {
1224
+ // Handle state changes
1225
+ });
1226
+ ```
1227
+
1228
+ ## Core API reference
1229
+
1230
+ ### signalTree()
1231
+
1232
+ ```typescript
1233
+ const tree = signalTree(initialState, config?);
1234
+ ```
1235
+
1236
+ ### Tree Methods
1237
+
1238
+ ```typescript
1239
+ // State access
1240
+ tree.$.property(); // Read signal value
1241
+ tree.$.property.set(value); // Update signal
1242
+ tree.unwrap(); // Get plain object
1243
+
1244
+ // Tree operations
1245
+ tree.update(updater); // Update entire tree
1246
+ tree.effect(fn); // Create reactive effects
1247
+ tree.subscribe(fn); // Manual subscriptions
1248
+ tree.destroy(); // Cleanup resources
1249
+
1250
+ // Extended features (require additional packages)
1251
+ tree.entities<T>(key); // Entity helpers (requires @signaltree/entities)
1252
+ // tree.asyncAction(fn, config?) example removed — use middleware helpers instead
1253
+ tree.asyncAction(fn, config?); // Create async action
1254
+ ```
1255
+
1256
+ ## Extending with optional packages
1257
+
1258
+ SignalTree Core can be extended with additional features:
1259
+
1260
+ ```typescript
1261
+ import { signalTree } from '@signaltree/core';
1262
+ import { withBatching } from '@signaltree/core';
1263
+ import { withMemoization } from '@signaltree/core';
1264
+ import { withTimeTravel } from '@signaltree/core';
1265
+
1266
+ // Compose features using .with(...)
1267
+ const tree = signalTree(initialState).with(withBatching(), withMemoization(), withTimeTravel());
1268
+ ```
1269
+
1270
+ ### Available extensions
1271
+
1272
+ All enhancers are now consolidated in the core package:
1273
+
1274
+ - **withBatching()** - Batch multiple updates for better performance
1275
+ - **withMemoization()** - Intelligent caching & performance optimization
1276
+ - **withMiddleware()** - Middleware system & state interceptors
1277
+ - **withEntities()** - Advanced entity management & CRUD operations
1278
+ - **withDevTools()** - Redux DevTools integration for debugging
1279
+ - **withTimeTravel()** - Undo/redo functionality & state history
1280
+ - **withSerialization()** - State persistence & SSR support
1281
+ - **ecommercePreset()** - Pre-configured setup for e-commerce applications
1282
+ - **dashboardPreset()** - Pre-configured setup for dashboard applications
1283
+
1284
+ ## When to use core only
1285
+
1286
+ Perfect for:
1287
+
1288
+ - ✅ Simple to medium applications
1289
+ - ✅ Prototype and MVP development
1290
+ - ✅ When bundle size is critical
1291
+ - ✅ Learning signal-based state management
1292
+ - ✅ Applications with basic state needs
1293
+
1294
+ Consider extensions when you need:
1295
+
1296
+ - ⚡ Performance optimization (batching, memoization)
1297
+ - 🐛 Advanced debugging (devtools, time-travel)
1298
+ - 📝 Complex forms (ng-forms)
1299
+ - 🔧 Middleware patterns (middleware)
1300
+
1301
+ ## Migration from NgRx
1302
+
1303
+ ```typescript
1304
+ // Step 1: Create parallel tree
1305
+ const tree = signalTree(initialState);
1306
+
1307
+ // Step 2: Gradually migrate components
1308
+ // Before (NgRx)
1309
+ users$ = this.store.select(selectUsers);
1310
+
1311
+ // After (SignalTree)
1312
+ users = this.tree.$.users;
1313
+
1314
+ // Step 3: Replace effects with manual async operations
1315
+ // Before (NgRx)
1316
+ loadUsers$ = createEffect(() =>
1317
+ this.actions$.with(
1318
+ ofType(loadUsers),
1319
+ switchMap(() => this.api.getUsers())
1320
+ )
1321
+ );
1322
+
1323
+ // After (SignalTree Core)
1324
+ async loadUsers() {
1325
+ try {
1326
+ const users = await api.getUsers();
1327
+ tree.$.users.set(users);
1328
+ } catch (error) {
1329
+ tree.$.error.set(error.message);
1330
+ }
1331
+ }
1332
+
1333
+ // Or use middleware helpers for async actions (createAsyncOperation / trackAsync)
1334
+ loadUsers = tree.asyncAction(() => api.getUsers(), {
1335
+ onSuccess: (users) => ({ users }),
1336
+ });
1337
+ ```
1338
+
1339
+ ## Examples
1340
+
1341
+ ### Simple Counter
1342
+
1343
+ ```typescript
1344
+ const counter = signalTree({ count: 0 });
1345
+
1346
+ // In component
1347
+ @Component({
1348
+ template: ` <button (click)="increment()">{{ counter.$.count() }}</button> `,
1349
+ })
1350
+ class CounterComponent {
1351
+ counter = counter;
1352
+
1353
+ increment() {
1354
+ this.counter.$.count.update((n) => n + 1);
1355
+ }
1356
+ }
1357
+ ```
1358
+
1359
+ ### User Management
1360
+
1361
+ ```typescript
1362
+ const userTree = signalTree({
1363
+ users: [] as User[],
1364
+ loading: false,
1365
+ error: null as string | null,
1366
+ });
1367
+
1368
+ async function loadUsers() {
1369
+ userTree.$.loading.set(true);
1370
+ try {
1371
+ const users = await api.getUsers();
1372
+ userTree.$.users.set(users);
1373
+ userTree.$.error.set(null);
1374
+ } catch (error) {
1375
+ userTree.$.error.set(error instanceof Error ? error.message : 'Load failed');
1376
+ } finally {
1377
+ userTree.$.loading.set(false);
1378
+ }
1379
+ }
1380
+
1381
+ function addUser(user: User) {
1382
+ userTree.$.users.update((users) => [...users, user]);
1383
+ }
1384
+
1385
+ // In component
1386
+ @Component({
1387
+ template: `
1388
+ @if (userTree.$.loading()) {
1389
+ <spinner />
1390
+ } @else { @for (user of userTree.$.users(); track user.id) {
1391
+ <user-card [user]="user" />
1392
+ } }
1393
+ `,
1394
+ })
1395
+ class UsersComponent {
1396
+ userTree = userTree;
1397
+
1398
+ ngOnInit() {
1399
+ loadUsers();
1400
+ }
1401
+
1402
+ addUser(userData: Partial<User>) {
1403
+ const newUser = { id: crypto.randomUUID(), ...userData } as User;
1404
+ addUser(newUser);
1405
+ }
1406
+ }
1407
+ ```
1408
+
1409
+ ## Available extension packages
1410
+
1411
+ All enhancers are now consolidated in the core package. The following features are available directly from `@signaltree/core`:
1412
+
1413
+ ### Performance & Optimization
1414
+
1415
+ - **withBatching()** (+1.27KB gzipped) - Batch multiple updates for better performance
1416
+ - **withMemoization()** (+2.33KB gzipped) - Intelligent caching & performance optimization
1417
+
1418
+ ### Advanced Features
1419
+
1420
+ - **withMiddleware()** (+1.89KB gzipped) - Middleware system & state interceptors
1421
+ - **withEntities()** (+0.97KB gzipped) - Enhanced CRUD operations & entity management
1422
+
1423
+ ### Development Tools
1424
+
1425
+ - **withDevTools()** (+2.49KB gzipped) - Development tools & Redux DevTools integration
1426
+ - **withTimeTravel()** (+1.75KB gzipped) - Undo/redo functionality & state history
1427
+
1428
+ ### Integration & Convenience
1429
+
1430
+ - **withSerialization()** (+0.84KB gzipped) - State persistence & SSR support
1431
+ - **ecommercePreset()** - Pre-configured setups for e-commerce applications
1432
+ - **dashboardPreset()** - Pre-configured setups for dashboard applications
1433
+
1434
+ ### Quick Start with Extensions
1435
+
1436
+ All enhancers are now available from the core package:
1437
+
1438
+ ```bash
1439
+ # Install only the core package - all features included
1440
+ npm install @signaltree/core
1441
+
1442
+ # Everything is available from @signaltree/core:
1443
+ import {
1444
+ signalTree,
1445
+ withBatching,
1446
+ withMemoization,
1447
+ withEntities,
1448
+ withDevTools,
1449
+ withTimeTravel,
1450
+ withSerialization,
1451
+ ecommercePreset,
1452
+ dashboardPreset
1453
+ } from '@signaltree/core';
1454
+ ```
1455
+
1456
+ ## Links
1457
+
1458
+ - [SignalTree Documentation](https://signaltree.io)
1459
+ - [GitHub Repository](https://github.com/JBorgia/signaltree)
1460
+ - [NPM Package](https://www.npmjs.com/package/@signaltree/core)
1461
+ - [Interactive Examples](https://signaltree.io/examples)
1462
+
1463
+ ## 📄 License
1464
+
1465
+ MIT License with AI Training Restriction - see the [LICENSE](../../LICENSE) file for details.
1466
+
1467
+ ---
1468
+
1469
+ **Ready to get started?** This core package provides everything you need for most applications. Add extensions only when you need them! 🚀