@signaltree/core 2.0.1 → 2.0.3

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.
package/README.md CHANGED
@@ -4,7 +4,7 @@ Foundation package for SignalTree. Provides recursive typing, deep nesting suppo
4
4
 
5
5
  ## What is @signaltree/core?
6
6
 
7
- SignalTree Core is a lightweight (about 7.25KB gzipped) package that provides:
7
+ SignalTree Core is a lightweight (about 7.20KB gzipped) package that provides:
8
8
 
9
9
  - Recursive typing with deep nesting and accurate type inference
10
10
  - Fast operations with sub‑millisecond measurements at 5–20+ levels
@@ -347,30 +347,156 @@ class UsersComponent {
347
347
 
348
348
  ### 6) Enhancers and composition
349
349
 
350
- Enhancers are optional, tree‑shakable extensions that augment a `SignalTree` via `tree.with(...)`.
350
+ 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.
351
351
 
352
- - `createEnhancer(meta, fn)` attaches metadata to an enhancer function
353
- - Metadata fields: `name`, `requires`, `provides`
354
- - Enhancers may mutate the passed tree (preferred) or return a new object
355
- - A topological sort orders enhancers that declare metadata; on cycles, fallback to user order in debug mode
352
+ #### Available Enhancers
356
353
 
357
- Examples:
354
+ **Performance Enhancers:**
355
+
356
+ - `@signaltree/batching` (+1.27KB) - Batch updates for 455.8x performance improvement
357
+ - `@signaltree/memoization` (+1.80KB) - Intelligent caching with 197.9x speedup
358
+
359
+ **Data Management:**
360
+
361
+ - `@signaltree/entities` (+0.97KB) - Advanced CRUD operations for collections
362
+ - `@signaltree/async` (+1.80KB) - Async actions with loading states
363
+ - `@signaltree/serialization` (+4.62KB) - State persistence and SSR support
364
+
365
+ **Development Tools:**
366
+
367
+ - `@signaltree/devtools` (+2.49KB) - Redux DevTools integration
368
+ - `@signaltree/time-travel` (+1.75KB) - Undo/redo functionality
369
+
370
+ **Integration:**
371
+
372
+ - `@signaltree/ng-forms` (+3.38KB) - Angular Forms integration
373
+ - `@signaltree/middleware` (+1.38KB) - State interceptors and logging
374
+ - `@signaltree/presets` (+0.84KB) - Pre-configured common patterns
375
+
376
+ #### Composition Patterns
377
+
378
+ **Basic Enhancement:**
358
379
 
359
380
  ```typescript
360
381
  import { signalTree } from '@signaltree/core';
361
382
  import { withBatching } from '@signaltree/batching';
362
383
  import { withDevtools } from '@signaltree/devtools';
363
384
 
364
- // Apply in explicit order
365
- const tree = signalTree({ count: 0 }).with(withBatching, withDevtools);
385
+ // Apply enhancers in order
386
+ const tree = signalTree({ count: 0 }).with(
387
+ withBatching(), // Performance optimization
388
+ withDevtools() // Development tools
389
+ );
366
390
  ```
367
391
 
368
- Presets can provide composed enhancers for quicker onboarding:
392
+ **Performance-Focused Stack:**
369
393
 
370
394
  ```typescript
371
- import { createDevTree } from '@signaltree/presets';
372
- const { enhancer } = createDevTree();
373
- const tree = signalTree({ count: 0 }).with(enhancer);
395
+ import { withBatching } from '@signaltree/batching';
396
+ import { withMemoization } from '@signaltree/memoization';
397
+ import { withEntities } from '@signaltree/entities';
398
+
399
+ const tree = signalTree({
400
+ products: [] as Product[],
401
+ ui: { loading: false },
402
+ }).with(
403
+ withBatching(), // Batch updates for optimal rendering
404
+ withMemoization(), // Cache expensive computations
405
+ withEntities() // Efficient CRUD operations
406
+ );
407
+
408
+ // Now supports advanced operations
409
+ tree.batchUpdate((state) => ({
410
+ products: [...state.products, newProduct],
411
+ ui: { loading: false },
412
+ }));
413
+
414
+ const products = tree.entities<Product>('products');
415
+ products.selectBy((p) => p.category === 'electronics');
416
+ ```
417
+
418
+ **Full-Stack Application:**
419
+
420
+ ```typescript
421
+ import { withAsync } from '@signaltree/async';
422
+ import { withSerialization } from '@signaltree/serialization';
423
+ import { withTimeTravel } from '@signaltree/time-travel';
424
+
425
+ const tree = signalTree({
426
+ user: null as User | null,
427
+ preferences: { theme: 'light' },
428
+ }).with(
429
+ withAsync(), // API integration
430
+ withSerialization({
431
+ // Auto-save to localStorage
432
+ autoSave: true,
433
+ storage: 'localStorage',
434
+ }),
435
+ withTimeTravel() // Undo/redo support
436
+ );
437
+
438
+ // Advanced async operations
439
+ const fetchUser = tree.asyncAction(async (id: string) => api.getUser(id), {
440
+ loadingKey: 'loading',
441
+ onSuccess: (user) => ({ user }),
442
+ });
443
+
444
+ // Automatic state persistence
445
+ tree.$.preferences.theme('dark'); // Auto-saved
446
+
447
+ // Time travel
448
+ tree.undo(); // Revert changes
449
+ ```
450
+
451
+ #### Enhancer Metadata & Ordering
452
+
453
+ Enhancers can declare metadata for automatic dependency resolution:
454
+
455
+ ```typescript
456
+ // Enhancers are automatically ordered based on requirements
457
+ const tree = signalTree(state).with(
458
+ withDevtools(), // Requires: core, provides: debugging
459
+ withBatching(), // Requires: core, provides: batching
460
+ withMemoization() // Requires: batching, provides: caching
461
+ );
462
+ // Automatically ordered: batching -> memoization -> devtools
463
+ ```
464
+
465
+ #### Quick Start with Presets
466
+
467
+ For common patterns, use presets that combine multiple enhancers:
468
+
469
+ ```typescript
470
+ import { ecommercePreset, dashboardPreset } from '@signaltree/presets';
471
+
472
+ // E-commerce preset includes: entities, async, batching, serialization
473
+ const ecommerceTree = ecommercePreset({
474
+ products: [] as Product[],
475
+ cart: { items: [], total: 0 },
476
+ });
477
+
478
+ // Dashboard preset includes: batching, memoization, devtools
479
+ const dashboardTree = dashboardPreset({
480
+ metrics: { users: 0, revenue: 0 },
481
+ charts: { data: [] },
482
+ });
483
+ ```
484
+
485
+ #### Core Stubs
486
+
487
+ 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:
488
+
489
+ ```typescript
490
+ const tree = signalTree({ users: [] as User[] });
491
+
492
+ // Works but warns: "Feature requires @signaltree/entities"
493
+ const users = tree.entities<User>('users');
494
+ users.add(newUser); // Warns: "Method requires @signaltree/entities"
495
+
496
+ // Install the enhancer to enable full functionality
497
+ const enhanced = tree.with(withEntities());
498
+ const realUsers = enhanced.entities<User>('users');
499
+ realUsers.add(newUser); // ✅ Works perfectly
374
500
  ```
375
501
 
376
502
  Core includes several performance optimizations:
@@ -509,24 +635,230 @@ function safeUpdateItem(id: string, updates: Partial<Item>) {
509
635
 
510
636
  ## Package composition patterns
511
637
 
638
+ SignalTree Core is designed for modular composition. Start minimal and add features as needed.
639
+
512
640
  ### Basic Composition
513
641
 
514
642
  ```typescript
515
643
  import { signalTree } from '@signaltree/core';
516
644
 
517
- // Core provides the foundation
645
+ // Core provides the foundation (7.25KB)
518
646
  const tree = signalTree({
519
- state: 'initial',
647
+ users: [] as User[],
648
+ ui: { loading: false },
520
649
  });
521
650
 
522
- // Extend with additional packages via .with(...)
523
- const enhancedTree = tree.with(
524
- // Add features as needed
525
- someFeatureFunction()
651
+ // Basic operations included in core
652
+ tree.$.users.set([...users, newUser]);
653
+ tree.$.ui.loading.set(true);
654
+ tree.effect(() => console.log('State changed'));
655
+ ```
656
+
657
+ ### Performance-Enhanced Composition
658
+
659
+ ```typescript
660
+ import { signalTree } from '@signaltree/core';
661
+ import { withBatching } from '@signaltree/batching';
662
+ import { withMemoization } from '@signaltree/memoization';
663
+
664
+ // Add performance optimizations (+3.07KB total)
665
+ const tree = signalTree({
666
+ products: [] as Product[],
667
+ filters: { category: '', search: '' },
668
+ }).with(
669
+ withBatching(), // 455.8x performance gain for multiple updates
670
+ withMemoization() // 197.9x speedup for expensive computations
526
671
  );
672
+
673
+ // Now supports batched updates
674
+ tree.batchUpdate((state) => ({
675
+ products: [...state.products, ...newProducts],
676
+ filters: { category: 'electronics', search: '' },
677
+ }));
678
+
679
+ // Expensive computations are automatically cached
680
+ const filteredProducts = computed(() => {
681
+ return tree.$.products()
682
+ .filter((p) => p.category.includes(tree.$.filters.category()))
683
+ .filter((p) => p.name.includes(tree.$.filters.search()));
684
+ });
685
+ ```
686
+
687
+ ### Data Management Composition
688
+
689
+ ```typescript
690
+ import { signalTree } from '@signaltree/core';
691
+ import { withEntities } from '@signaltree/entities';
692
+ import { withAsync } from '@signaltree/async';
693
+
694
+ // Add data management capabilities (+2.77KB total)
695
+ const tree = signalTree({
696
+ users: [] as User[],
697
+ posts: [] as Post[],
698
+ ui: { loading: false, error: null },
699
+ }).with(
700
+ withEntities(), // Advanced CRUD operations
701
+ withAsync() // Async state management
702
+ );
703
+
704
+ // Advanced entity operations
705
+ const users = tree.entities<User>('users');
706
+ users.add(newUser);
707
+ users.selectBy((u) => u.active);
708
+ users.updateMany([{ id: '1', changes: { status: 'active' } }]);
709
+
710
+ // Powerful async actions
711
+ const fetchUsers = tree.asyncAction(async () => api.getUsers(), {
712
+ loadingKey: 'ui.loading',
713
+ errorKey: 'ui.error',
714
+ onSuccess: (users) => ({ users }),
715
+ });
716
+ ```
717
+
718
+ ### Full-Featured Development Composition
719
+
720
+ ```typescript
721
+ import { signalTree } from '@signaltree/core';
722
+ import { withBatching } from '@signaltree/batching';
723
+ import { withEntities } from '@signaltree/entities';
724
+ import { withAsync } from '@signaltree/async';
725
+ import { withSerialization } from '@signaltree/serialization';
726
+ import { withTimeTravel } from '@signaltree/time-travel';
727
+ import { withDevtools } from '@signaltree/devtools';
728
+
729
+ // Full development stack (~22KB total)
730
+ const tree = signalTree({
731
+ app: {
732
+ user: null as User | null,
733
+ preferences: { theme: 'light' },
734
+ data: { users: [], posts: [] },
735
+ },
736
+ }).with(
737
+ withBatching(), // Performance
738
+ withEntities(), // Data management
739
+ withAsync(), // API calls
740
+ withSerialization({
741
+ // State persistence
742
+ autoSave: true,
743
+ storage: 'localStorage',
744
+ }),
745
+ withTimeTravel({
746
+ // Undo/redo
747
+ maxHistory: 50,
748
+ }),
749
+ withDevtools({
750
+ // Debug tools (dev only)
751
+ name: 'MyApp',
752
+ trace: true,
753
+ })
754
+ );
755
+
756
+ // Rich feature set available
757
+ const users = tree.entities<User>('app.data.users');
758
+ const fetchUser = tree.asyncAction(api.getUser);
759
+ tree.undo(); // Time travel
760
+ tree.save(); // Persistence
761
+ ```
762
+
763
+ ### Production-Ready Composition
764
+
765
+ ```typescript
766
+ import { signalTree } from '@signaltree/core';
767
+ import { withBatching } from '@signaltree/batching';
768
+ import { withEntities } from '@signaltree/entities';
769
+ import { withAsync } from '@signaltree/async';
770
+ import { withSerialization } from '@signaltree/serialization';
771
+
772
+ // Production build (no dev tools)
773
+ const tree = signalTree(initialState).with(
774
+ withBatching(), // Performance optimization
775
+ withEntities(), // Data management
776
+ withAsync(), // API integration
777
+ withSerialization({
778
+ // User preferences
779
+ autoSave: true,
780
+ storage: 'localStorage',
781
+ key: 'app-v1.2.3',
782
+ })
783
+ );
784
+
785
+ // Clean, efficient, production-ready
786
+ ```
787
+
788
+ ### Conditional Enhancement
789
+
790
+ ```typescript
791
+ import { signalTree } from '@signaltree/core';
792
+ import { withDevtools } from '@signaltree/devtools';
793
+ import { withTimeTravel } from '@signaltree/time-travel';
794
+
795
+ const isDevelopment = process.env['NODE_ENV'] === 'development';
796
+
797
+ // Conditional enhancement based on environment
798
+ const tree = signalTree(state).with(
799
+ withBatching(), // Always include performance
800
+ withEntities(), // Always include data management
801
+ ...(isDevelopment
802
+ ? [
803
+ // Development-only features
804
+ withDevtools(),
805
+ withTimeTravel(),
806
+ ]
807
+ : [])
808
+ );
809
+ ```
810
+
811
+ ### Preset-Based Composition
812
+
813
+ ```typescript
814
+ import { ecommercePreset, dashboardPreset } from '@signaltree/presets';
815
+
816
+ // Use presets for common patterns
817
+ const ecommerceTree = ecommercePreset({
818
+ products: [],
819
+ cart: { items: [], total: 0 },
820
+ user: null,
821
+ });
822
+ // Includes: entities, async, batching, serialization
823
+
824
+ const dashboardTree = dashboardPreset({
825
+ metrics: {},
826
+ charts: [],
827
+ filters: {},
828
+ });
829
+ // Includes: batching, memoization, devtools
527
830
  ```
528
831
 
529
- ### Modular enhancement pattern
832
+ ### Bundle Size Planning
833
+
834
+ | Composition Strategy | Bundle Size | Use Case |
835
+ | ------------------------ | ----------- | ------------------------------ |
836
+ | Core only | 7.25KB | Simple state, prototypes |
837
+ | + Batching + Memoization | 10.32KB | Performance-critical apps |
838
+ | + Entities + Async | 13.09KB | Data-driven applications |
839
+ | + Serialization | 17.71KB | Persistent state, SSR |
840
+ | + DevTools + TimeTravel | 21.95KB | Development (disabled in prod) |
841
+ | Full ecosystem | 27.55KB | Feature-rich applications |
842
+
843
+ ### Migration Strategy
844
+
845
+ Start with core and grow incrementally:
846
+
847
+ ```typescript
848
+ // Phase 1: Start with core
849
+ const tree = signalTree(state);
850
+
851
+ // Phase 2: Add performance when needed
852
+ const tree2 = tree.with(withBatching());
853
+
854
+ // Phase 3: Add data management for collections
855
+ const tree3 = tree2.with(withEntities());
856
+
857
+ // Phase 4: Add async for API integration
858
+ const tree4 = tree3.with(withAsync());
859
+
860
+ // Each phase is fully functional and production-ready
861
+ ```
530
862
 
531
863
  ```typescript
532
864
  // Start minimal, add features as needed
@@ -606,12 +938,12 @@ Note: Scaling depends on state shape and hardware.
606
938
 
607
939
  ### Feature impact
608
940
 
609
- | Feature | SignalTree Core | With Extensions | Notes |
610
- | ------------------- | ----------------- | ---------------------- | ---------------------- |
611
- | Batching efficiency | Standard | **455.8x improvement** | vs non-batched |
612
- | Memoization speedup | Basic | **197.9x speedup** | vs non-memoized |
613
- | Memory efficiency | **Optimized** | **Further optimized** | Lazy signals + cleanup |
614
- | Bundle impact | **7.1KB gzipped** | **+15KB max** | Tree-shakeable |
941
+ | Feature | SignalTree Core | With Extensions | Notes |
942
+ | ------------------- | ------------------ | ---------------------- | ---------------------- |
943
+ | Batching efficiency | Standard | **455.8x improvement** | vs non-batched |
944
+ | Memoization speedup | Basic | **197.9x speedup** | vs non-memoized |
945
+ | Memory efficiency | **Optimized** | **Further optimized** | Lazy signals + cleanup |
946
+ | Bundle impact | **7.25KB gzipped** | **+20KB max** | Tree-shakeable |
615
947
 
616
948
  ### Developer experience (core)
617
949
 
@@ -871,7 +1203,7 @@ tree.subscribe(fn); // Manual subscriptions
871
1203
  tree.destroy(); // Cleanup resources
872
1204
 
873
1205
  // Extended features (require additional packages)
874
- tree.asCrud<T>(key); // Entity helpers (requires @signaltree/entities)
1206
+ tree.entities<T>(key); // Entity helpers (requires @signaltree/entities)
875
1207
  tree.asyncAction(fn, config?); // Async actions (requires @signaltree/async)
876
1208
  tree.asyncAction(fn, config?); // Create async action
877
1209
  ```
@@ -892,16 +1224,16 @@ const tree = signalTree(initialState).with(withBatching(), withMemoization(), wi
892
1224
 
893
1225
  ### Available extensions
894
1226
 
895
- - **@signaltree/batching** (+1.1KB gzipped) - Batch multiple updates
896
- - **@signaltree/memoization** (+1.7KB gzipped) - Intelligent caching & performance
897
- - **@signaltree/middleware** (+1.2KB gzipped) - Middleware system & taps
898
- - **@signaltree/async** (+1.7KB gzipped) - Advanced async actions & states
1227
+ - **@signaltree/batching** (+1.27KB gzipped) - Batch multiple updates
1228
+ - **@signaltree/memoization** (+1.80KB gzipped) - Intelligent caching & performance
1229
+ - **@signaltree/middleware** (+1.38KB gzipped) - Middleware system & taps
1230
+ - **@signaltree/async** (+1.80KB gzipped) - Advanced async actions & states
899
1231
  - **@signaltree/entities** (+929B gzipped) - Advanced entity management
900
- - **@signaltree/devtools** (+2.3KB gzipped) - Redux DevTools integration
901
- - **@signaltree/time-travel** (+1.5KB gzipped) - Undo/redo functionality
902
- - **@signaltree/ng-forms** (+3.4KB gzipped) - Complete Angular forms integration
903
- - **@signaltree/serialization** (+3.6KB gzipped) - State persistence & SSR support
904
- - **@signaltree/presets** (+0.8KB gzipped) - Environment-based configurations
1232
+ - **@signaltree/devtools** (+2.49KB gzipped) - Redux DevTools integration
1233
+ - **@signaltree/time-travel** (+1.75KB gzipped) - Undo/redo functionality
1234
+ - **@signaltree/ng-forms** (+3.38KB gzipped) - Complete Angular forms integration
1235
+ - **@signaltree/serialization** (+4.62KB gzipped) - State persistence & SSR support
1236
+ - **@signaltree/presets** (+0.84KB gzipped) - Environment-based configurations
905
1237
 
906
1238
  ## When to use core only
907
1239
 
@@ -1034,8 +1366,8 @@ Extend the core with optional feature packages:
1034
1366
 
1035
1367
  ### Performance & Optimization
1036
1368
 
1037
- - **[@signaltree/batching](../batching)** (+1.1KB gzipped) - Batch multiple updates for better performance
1038
- - **[@signaltree/memoization](../memoization)** (+1.7KB gzipped) - Intelligent caching & performance optimization
1369
+ - **[@signaltree/batching](../batching)** (+1.27KB gzipped) - Batch multiple updates for better performance
1370
+ - **[@signaltree/memoization](../memoization)** (+1.80KB gzipped) - Intelligent caching & performance optimization
1039
1371
 
1040
1372
  ### Advanced Features
1041
1373
 
@@ -1,4 +1,4 @@
1
- import { signal, isSignal, computed, effect, inject, DestroyRef } from '@angular/core';
1
+ import { isSignal, signal, computed, effect, inject, DestroyRef } from '@angular/core';
2
2
 
3
3
  const SIGNAL_TREE_CONSTANTS = {
4
4
  MAX_PATH_CACHE_SIZE: 1000,
@@ -203,11 +203,6 @@ function equal(a, b) {
203
203
  return keysA.every((k) => k in objB && equal(objA[k], objB[k]));
204
204
  }
205
205
  const deepEqual = equal;
206
- function terminalSignal(value, customEqual) {
207
- return signal(value, {
208
- equal: customEqual || equal,
209
- });
210
- }
211
206
  function isBuiltInObject(v) {
212
207
  if (v === null || v === undefined)
213
208
  return false;
@@ -567,9 +562,6 @@ function unwrap(node) {
567
562
  }
568
563
  return result;
569
564
  }
570
- function cleanUnwrap(node) {
571
- return unwrap(node);
572
- }
573
565
 
574
566
  const NODE_ACCESSOR_SYMBOL = Symbol.for('NodeAccessor');
575
567
  function makeNodeAccessor() {
@@ -966,7 +958,7 @@ function addStubMethods(tree, config) {
966
958
  }
967
959
  void id;
968
960
  };
969
- tree.asCrud = () => {
961
+ tree.entities = () => {
970
962
  if (config.debugMode) {
971
963
  console.warn(SIGNAL_TREE_MESSAGES.ENTITY_HELPERS_NOT_AVAILABLE);
972
964
  }
@@ -1124,5 +1116,5 @@ function applyEnhancer(tree, enhancer) {
1124
1116
  return enhancer(tree);
1125
1117
  }
1126
1118
 
1127
- export { ENHANCER_META, SIGNAL_TREE_CONSTANTS, SIGNAL_TREE_MESSAGES, composeEnhancers, createEnhancer, createLazySignalTree, deepEqual, equal, isAnySignal, isBuiltInObject, isNodeAccessor$1 as isNodeAccessor, parsePath, resolveEnhancerOrder, signalTree, terminalSignal, unwrap };
1119
+ export { ENHANCER_META, SIGNAL_TREE_CONSTANTS, SIGNAL_TREE_MESSAGES, composeEnhancers, createEnhancer, createLazySignalTree, deepEqual, equal, isAnySignal, isBuiltInObject, isNodeAccessor$1 as isNodeAccessor, parsePath, resolveEnhancerOrder, signalTree, unwrap };
1128
1120
  //# sourceMappingURL=signaltree-core.mjs.map