@signaltree/core 3.0.2 → 3.1.0

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