@signaltree/core 5.1.2 → 5.1.4

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
@@ -81,6 +81,73 @@ Performance and bundle size vary by app shape, build tooling, device, and runtim
81
81
  - 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.
82
82
  - Use the bundle analysis scripts in `scripts/` to measure your min+gz sizes. Sizes are approximate and depend on tree-shaking and configuration.
83
83
 
84
+ ## Best Practices (SignalTree-First)
85
+
86
+ > 📖 **Full guide**: [Implementation Patterns](https://github.com/JBorgia/signaltree/blob/main/docs/IMPLEMENTATION_PATTERNS.md)
87
+
88
+ Follow these principles for idiomatic SignalTree code:
89
+
90
+ ### 1. Expose signals directly (no computed wrappers)
91
+
92
+ ```typescript
93
+ const tree = signalTree(initialState).with(withEntities());
94
+ const $ = tree.$; // Shorthand for state access
95
+
96
+ // ✅ SignalTree-first: Direct signal exposure
97
+ return {
98
+ selectedUserId: $.selected.userId, // Direct from $ tree
99
+ loadingState: $.loading.state,
100
+ selectedUser, // Actual derived state (computed)
101
+ };
102
+
103
+ // ❌ Anti-pattern: Unnecessary computed wrappers
104
+ return {
105
+ selectedUserId: computed(() => $.selected.userId()), // Adds indirection
106
+ };
107
+ ```
108
+
109
+ ### 2. Use `ReturnType` inference (SignalTree-first)
110
+
111
+ ```typescript
112
+ // Let SignalTree infer the type - no manual interface needed!
113
+ import type { createUserTree } from './user.tree';
114
+ export type UserTree = ReturnType<typeof createUserTree>;
115
+
116
+ // Factory function - no explicit return type needed
117
+ export function createUserTree() {
118
+ const tree = signalTree(initialState).with(withEntities());
119
+ return {
120
+ selectedUserId: tree.$.selected.userId, // Type inferred automatically
121
+ // ...
122
+ };
123
+ }
124
+ ```
125
+
126
+ ### 3. Use `computed()` only for derived state
127
+
128
+ ```typescript
129
+ // ✅ Correct: Derived from multiple signals
130
+ const selectedUser = computed(() => {
131
+ const id = $.selected.userId();
132
+ return id ? $.users.byId(id)() : null;
133
+ });
134
+
135
+ // ❌ Wrong: Wrapping an existing signal
136
+ const selectedUserId = computed(() => $.selected.userId()); // Unnecessary!
137
+ ```
138
+
139
+ ### 4. Use EntitySignal API directly
140
+
141
+ ```typescript
142
+ // ✅ SignalTree-native
143
+ const user = $.users.byId(123)(); // O(1) lookup
144
+ const allUsers = $.users.all; // Get all
145
+ $.users.setAll(usersFromApi); // Replace all
146
+
147
+ // ❌ NgRx-style (avoid)
148
+ const user = entityMap()[123]; // Requires intermediate object
149
+ ```
150
+
84
151
  ## Quick start
85
152
 
86
153
  ### Installation
@@ -426,7 +493,7 @@ const tree = signalTree({
426
493
  users: [] as User[],
427
494
  });
428
495
 
429
- // Manual CRUD operations using core methods
496
+ // Entity CRUD operations using core methods
430
497
  function addUser(user: User) {
431
498
  tree.$.users.update((users) => [...users, user]);
432
499
  }
@@ -552,22 +619,18 @@ const tree = signalTree({ count: 0 }).with(
552
619
  import { signalTree, withBatching, withMemoization, withEntities } from '@signaltree/core';
553
620
 
554
621
  const tree = signalTree({
555
- products: [] as Product[],
622
+ products: entityMap<Product>(),
556
623
  ui: { loading: false },
557
- }).with(
558
- withBatching(), // Batch updates for optimal rendering
559
- withMemoization(), // Cache expensive computations
560
- withEntities() // Efficient CRUD operations
561
- );
624
+ })
625
+ .with(withEntities()) // Efficient CRUD operations (auto-detects entityMap)
626
+ .with(withBatching()); // Batch updates for optimal rendering
562
627
 
563
- // Now supports advanced operations
564
- tree.batchUpdate((state) => ({
565
- products: [...state.products, newProduct],
566
- ui: { loading: false },
567
- }));
628
+ // Entity CRUD operations
629
+ tree.$.products.addOne(newProduct);
630
+ tree.$.products.setAll(productsFromApi);
568
631
 
569
- const products = tree.entities<Product>('products');
570
- products.selectBy((p) => p.category === 'electronics');
632
+ // Entity queries
633
+ const electronics = tree.$.products.all.filter((p) => p.category === 'electronics');
571
634
  ```
572
635
 
573
636
  **Full-Stack Application:**
@@ -644,17 +707,20 @@ const customTree = signalTree(state, TREE_PRESETS.DASHBOARD);
644
707
  SignalTree Core includes all enhancer functionality built-in. No separate packages needed:
645
708
 
646
709
  ```typescript
647
- import { signalTree, withEntities } from '@signaltree/core';
710
+ import { signalTree, entityMap, withEntities } from '@signaltree/core';
648
711
 
649
- const tree = signalTree({ users: [] as User[] });
712
+ // Without entityMap - use manual array updates
713
+ const basic = signalTree({ users: [] as User[] });
714
+ basic.$.users.update((users) => [...users, newUser]);
650
715
 
651
- // Without enhancer - use manual CRUD
652
- tree.$.users.update((users) => [...users, newUser]);
716
+ // With entityMap + withEntities - use entity helpers
717
+ const enhanced = signalTree({
718
+ users: entityMap<User>(),
719
+ }).with(withEntities());
653
720
 
654
- // With enhancer - use entity helpers
655
- const enhanced = tree.with(withEntities());
656
- const users = enhanced.entities<User>('users');
657
- users.add(newUser); // ✅ Advanced CRUD operations
721
+ enhanced.$.users.addOne(newUser); // Advanced CRUD operations
722
+ enhanced.$.users.byId(123)(); // ✅ O(1) lookups
723
+ enhanced.$.users.all; // ✅ Get all as array
658
724
  ```
659
725
 
660
726
  Core includes several performance optimizations:
@@ -843,58 +909,49 @@ const filteredProducts = computed(() => {
843
909
  ### Data Management Composition
844
910
 
845
911
  ```typescript
846
- import { signalTree, withEntities } from '@signaltree/core';
912
+ import { signalTree, entityMap, withEntities } from '@signaltree/core';
847
913
 
848
914
  // Add data management capabilities (+2.77KB total)
849
915
  const tree = signalTree({
850
- users: [] as User[],
851
- posts: [] as Post[],
852
- ui: { loading: false, error: null },
853
- }).with(
854
- withEntities() // Advanced CRUD operations
855
- );
916
+ users: entityMap<User>(),
917
+ posts: entityMap<Post>(),
918
+ ui: { loading: false, error: null as string | null },
919
+ }).with(withEntities());
856
920
 
857
- // Advanced entity operations
858
- const users = tree.entities<User>('users');
859
- users.add(newUser);
860
- users.selectBy((u) => u.active);
861
- users.updateMany([{ id: '1', changes: { status: 'active' } }]);
921
+ // Advanced entity operations via tree.$ accessor
922
+ tree.$.users.addOne(newUser);
923
+ tree.$.users.selectBy((u) => u.active);
924
+ tree.$.users.updateMany([{ id: '1', changes: { status: 'active' } }]);
862
925
 
863
- // Entity helpers support nested paths for organized state structures
926
+ // Entity helpers work with nested structures
864
927
  // Example: deeply nested entities in a domain-driven design pattern
865
928
  const appTree = signalTree({
866
929
  app: {
867
930
  data: {
868
- users: [] as User[],
869
- products: [] as Product[],
931
+ users: entityMap<User>(),
932
+ products: entityMap<Product>(),
870
933
  },
871
934
  },
872
935
  admin: {
873
936
  data: {
874
- logs: [] as AuditLog[],
875
- reports: [] as Report[],
937
+ logs: entityMap<AuditLog>(),
938
+ reports: entityMap<Report>(),
876
939
  },
877
940
  },
878
- });
941
+ }).with(withEntities());
879
942
 
880
- // Access deeply nested entities using dot notation
881
- const appUsers = appTree.entities<User>('app.data.users');
882
- const appProducts = appTree.entities<Product>('app.data.products');
883
- const adminLogs = appTree.entities<AuditLog>('admin.data.logs');
884
- const adminReports = appTree.entities<Report>('admin.data.reports');
885
-
886
- // All entity methods work seamlessly with nested paths
887
- appUsers.selectBy((u) => u.isAdmin); // Filtered signal
888
- appProducts.selectTotal(); // Count signal
889
- adminLogs.selectAll(); // All items signal
890
- adminReports.selectIds(); // ID array signal
943
+ // Access nested entities using tree.$ accessor
944
+ appTree.$.app.data.users.selectBy((u) => u.isAdmin); // Filtered signal
945
+ appTree.$.app.data.products.selectTotal(); // Count signal
946
+ appTree.$.admin.data.logs.all; // All items as array
947
+ appTree.$.admin.data.reports.selectIds(); // ID array signal
891
948
 
892
949
  // For async operations, use manual async or async helpers
893
950
  async function fetchUsers() {
894
951
  tree.$.ui.loading.set(true);
895
952
  try {
896
953
  const users = await api.getUsers();
897
- tree.$.users.set(users);
954
+ tree.$.users.setAll(users);
898
955
  } catch (error) {
899
956
  tree.$.ui.error.set(error.message);
900
957
  } finally {
@@ -936,10 +993,10 @@ const tree = signalTree({
936
993
  );
937
994
 
938
995
  // Rich feature set available
939
- const users = tree.entities<User>('app.data.users');
940
996
  async function fetchUser(id: string) {
941
997
  return await api.getUser(id);
942
998
  }
999
+ tree.$.app.data.users.byId(userId)(); // O(1) lookup
943
1000
  tree.undo(); // Time travel
944
1001
  tree.save(); // Persistence
945
1002
  ```
@@ -1308,9 +1365,11 @@ tree.effect(fn); // Create reactive effects
1308
1365
  tree.subscribe(fn); // Manual subscriptions
1309
1366
  tree.destroy(); // Cleanup resources
1310
1367
 
1311
- // Extended features (built into @signaltree/core)
1312
- tree.entities<T>(key); // Entity helpers (use withEntities enhancer)
1313
- // For async operations, use manual async or async helpers like createAsyncOperation or trackAsync
1368
+ // Entity helpers (when using entityMap + withEntities)
1369
+ // tree.$.users.addOne(user); // Add single entity
1370
+ // tree.$.users.byId(id)(); // O(1) lookup by ID
1371
+ // tree.$.users.all; // Get all as array
1372
+ // tree.$.users.selectBy(pred); // Filtered signal
1314
1373
  ```
1315
1374
 
1316
1375
  ## Extending with enhancers
@@ -1,4 +1,4 @@
1
- import { EntitySignalImpl } from '../../../lib/entity-signal.js';
1
+ import { createEntitySignal } from '../../../lib/entity-signal.js';
2
2
  import { getPathNotifier } from '../../../lib/path-notifier.js';
3
3
  import { isNodeAccessor } from '../../../lib/utils.js';
4
4
 
@@ -6,7 +6,7 @@ function isEntityMapMarker(value) {
6
6
  return Boolean(value && typeof value === 'object' && value['__isEntityMap'] === true);
7
7
  }
8
8
  function isEntitySignal(value) {
9
- return !!value && typeof value === 'object' && typeof value['addOne'] === 'function' && typeof value['all'] === 'function';
9
+ return !!value && typeof value === 'object' && typeof value['addOne'] === 'function' && typeof value['all'] !== 'undefined';
10
10
  }
11
11
  function materializeEntities(tree, notifier = getPathNotifier()) {
12
12
  const registry = new Map();
@@ -16,7 +16,7 @@ function materializeEntities(tree, notifier = getPathNotifier()) {
16
16
  if (isEntityMapMarker(value)) {
17
17
  const basePath = nextPath.join('.');
18
18
  const config = value.__entityMapConfig ?? {};
19
- const entitySignal = new EntitySignalImpl(config, notifier, basePath);
19
+ const entitySignal = createEntitySignal(config, notifier, basePath);
20
20
  if (parent) {
21
21
  try {
22
22
  parent[key] = entitySignal;
@@ -20,14 +20,6 @@ class EntitySignalImpl {
20
20
  this.countSignal = signal(0);
21
21
  this.idsSignal = signal([]);
22
22
  this.mapSignal = signal(new Map());
23
- return new Proxy(this, {
24
- get: (target, prop) => {
25
- if (typeof prop === 'string' && !isNaN(Number(prop))) {
26
- return target.byId(Number(prop));
27
- }
28
- return target[prop];
29
- }
30
- });
31
23
  }
32
24
  byId(id) {
33
25
  const entity = this.storage.get(id);
@@ -41,22 +33,22 @@ class EntitySignalImpl {
41
33
  }
42
34
  return node;
43
35
  }
44
- all() {
36
+ get all() {
45
37
  return this.allSignal;
46
38
  }
47
- count() {
39
+ get count() {
48
40
  return this.countSignal;
49
41
  }
50
- ids() {
42
+ get ids() {
51
43
  return this.idsSignal;
52
44
  }
53
- map() {
45
+ get map() {
54
46
  return this.mapSignal;
55
47
  }
56
48
  has(id) {
57
49
  return computed(() => this.storage.has(id));
58
50
  }
59
- isEmpty() {
51
+ get isEmpty() {
60
52
  return computed(() => this.storage.size === 0);
61
53
  }
62
54
  where(predicate) {
@@ -276,5 +268,16 @@ class EntitySignalImpl {
276
268
  return node;
277
269
  }
278
270
  }
271
+ function createEntitySignal(config, pathNotifier, basePath) {
272
+ const impl = new EntitySignalImpl(config, pathNotifier, basePath);
273
+ return new Proxy(impl, {
274
+ get: (target, prop) => {
275
+ if (typeof prop === 'string' && !isNaN(Number(prop))) {
276
+ return target.byId(Number(prop));
277
+ }
278
+ return target[prop];
279
+ }
280
+ });
281
+ }
279
282
 
280
- export { EntitySignalImpl };
283
+ export { EntitySignalImpl, createEntitySignal };
@@ -459,6 +459,7 @@ function addStubMethods(tree, config) {
459
459
  };
460
460
  };
461
461
  tree.entities = () => {
462
+ console.warn('[@signaltree/core] tree.entities() is deprecated and will be removed in v6.0. ' + 'Use entityMap<E>() + withEntities() + tree.$.collectionName instead. ' + 'See https://signaltree.dev/docs/migration for migration guide.');
462
463
  if (config.debugMode) {
463
464
  console.warn(SIGNAL_TREE_MESSAGES.ENTITY_HELPERS_NOT_AVAILABLE);
464
465
  }
package/dist/lib/utils.js CHANGED
@@ -71,6 +71,9 @@ function createLazySignalTree(obj, equalityFn, basePath = '', memoryManager) {
71
71
  if (!(key in target)) return undefined;
72
72
  const value = target[key];
73
73
  if (isSignal(value)) return value;
74
+ if (value && typeof value === 'object' && typeof value.addOne === 'function' && typeof value.all === 'function') {
75
+ return value;
76
+ }
74
77
  if (isEntityMapMarker(value)) return value;
75
78
  if (memoryManager) {
76
79
  const cached = memoryManager.getSignal(path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaltree/core",
3
- "version": "5.1.2",
3
+ "version": "5.1.4",
4
4
  "description": "Lightweight, type-safe signal-based state management for Angular. Core package providing hierarchical signal trees, basic entity management, and async actions.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -0,0 +1,8 @@
1
+ import type { SignalTree } from './types';
2
+ export declare function createAsyncOperation<T, TResult>(name: string, operation: () => Promise<TResult>): (tree: SignalTree<T>) => Promise<TResult>;
3
+ export declare function trackAsync<T>(operation: () => Promise<T>): {
4
+ pending: import("@angular/core").Signal<boolean>;
5
+ error: import("@angular/core").Signal<Error | null>;
6
+ result: import("@angular/core").Signal<T | null>;
7
+ execute: () => Promise<T>;
8
+ };
@@ -0,0 +1,16 @@
1
+ import type { SignalTree } from '../../../lib/types';
2
+ interface BatchingConfig {
3
+ enabled?: boolean;
4
+ maxBatchSize?: number;
5
+ autoFlushDelay?: number;
6
+ batchTimeoutMs?: number;
7
+ }
8
+ interface BatchingSignalTree<T> extends SignalTree<T> {
9
+ batchUpdate(updater: (current: T) => Partial<T>): void;
10
+ }
11
+ export declare function withBatching<T>(config?: BatchingConfig): (tree: SignalTree<T>) => BatchingSignalTree<T>;
12
+ export declare function withHighPerformanceBatching<T>(): (tree: SignalTree<T>) => BatchingSignalTree<T>;
13
+ export declare function flushBatchedUpdates(): void;
14
+ export declare function hasPendingUpdates(): boolean;
15
+ export declare function getBatchQueueSize(): number;
16
+ export {};
@@ -0,0 +1,12 @@
1
+ import { Signal } from '@angular/core';
2
+ import type { TreeNode, SignalTree } from '../../../lib/types';
3
+ export interface ComputedConfig {
4
+ lazy?: boolean;
5
+ memoize?: boolean;
6
+ }
7
+ export type ComputedSignal<T> = Signal<T>;
8
+ export interface ComputedSignalTree<T extends Record<string, unknown>> extends SignalTree<T> {
9
+ computed<U>(computeFn: (tree: TreeNode<T>) => U): ComputedSignal<U>;
10
+ }
11
+ export declare function computedEnhancer(_config?: ComputedConfig): import("../../../lib/types").EnhancerWithMeta<SignalTree<Record<string, unknown>>, ComputedSignalTree<Record<string, unknown>>>;
12
+ export declare function createComputed<T>(dependencies: readonly Signal<unknown>[], computeFn: () => T): ComputedSignal<T>;
@@ -0,0 +1,14 @@
1
+ export declare const TYPE_MARKERS: {
2
+ readonly DATE: "§d";
3
+ readonly REGEXP: "§r";
4
+ readonly MAP: "§m";
5
+ readonly SET: "§s";
6
+ readonly UNDEFINED: "§u";
7
+ readonly NAN: "§n";
8
+ readonly INFINITY: "§i";
9
+ readonly NEG_INFINITY: "§-i";
10
+ readonly BIGINT: "§b";
11
+ readonly SYMBOL: "§y";
12
+ readonly FUNCTION: "§f";
13
+ readonly CIRCULAR: "§c";
14
+ };
@@ -0,0 +1,77 @@
1
+ import { Signal } from '@angular/core';
2
+ import type { SignalTree } from '../../../lib/types';
3
+ export interface ModuleMetadata {
4
+ name: string;
5
+ methods: string[];
6
+ addedAt: Date;
7
+ lastActivity: Date;
8
+ operationCount: number;
9
+ averageExecutionTime: number;
10
+ errorCount: number;
11
+ }
12
+ export interface ModularPerformanceMetrics {
13
+ totalUpdates: number;
14
+ moduleUpdates: Record<string, number>;
15
+ modulePerformance: Record<string, number>;
16
+ compositionChain: string[];
17
+ signalGrowth: Record<string, number>;
18
+ memoryDelta: Record<string, number>;
19
+ moduleCacheStats: Record<string, {
20
+ hits: number;
21
+ misses: number;
22
+ }>;
23
+ }
24
+ export interface ModuleActivityTracker {
25
+ trackMethodCall: (module: string, method: string, duration: number) => void;
26
+ trackError: (module: string, error: Error, context?: string) => void;
27
+ getModuleActivity: (module: string) => ModuleMetadata | undefined;
28
+ getAllModules: () => ModuleMetadata[];
29
+ }
30
+ export interface CompositionLogger {
31
+ logComposition: (modules: string[], action: 'with' | 'enhance') => void;
32
+ logMethodExecution: (module: string, method: string, args: unknown[], result: unknown) => void;
33
+ logStateChange: (module: string, path: string, oldValue: unknown, newValue: unknown) => void;
34
+ logPerformanceWarning: (module: string, operation: string, duration: number, threshold: number) => void;
35
+ exportLogs: () => Array<{
36
+ timestamp: Date;
37
+ module: string;
38
+ type: 'composition' | 'method' | 'state' | 'performance';
39
+ data: unknown;
40
+ }>;
41
+ }
42
+ export interface ModularDevToolsInterface<_T = unknown> {
43
+ activityTracker: ModuleActivityTracker;
44
+ logger: CompositionLogger;
45
+ metrics: Signal<ModularPerformanceMetrics>;
46
+ trackComposition: (modules: string[]) => void;
47
+ startModuleProfiling: (module: string) => string;
48
+ endModuleProfiling: (profileId: string) => void;
49
+ connectDevTools: (treeName: string) => void;
50
+ exportDebugSession: () => {
51
+ metrics: ModularPerformanceMetrics;
52
+ modules: ModuleMetadata[];
53
+ logs: Array<unknown>;
54
+ compositionHistory: Array<{
55
+ timestamp: Date;
56
+ chain: string[];
57
+ }>;
58
+ };
59
+ }
60
+ export declare function withDevTools<T>(config?: {
61
+ enabled?: boolean;
62
+ treeName?: string;
63
+ enableBrowserDevTools?: boolean;
64
+ enableLogging?: boolean;
65
+ performanceThreshold?: number;
66
+ }): (tree: SignalTree<T>) => SignalTree<T> & {
67
+ __devTools: ModularDevToolsInterface<T>;
68
+ };
69
+ export declare function enableDevTools<T>(treeName?: string): (tree: SignalTree<T>) => SignalTree<T> & {
70
+ __devTools: ModularDevToolsInterface<T>;
71
+ };
72
+ export declare function withFullDevTools<T>(treeName?: string): (tree: SignalTree<T>) => SignalTree<T> & {
73
+ __devTools: ModularDevToolsInterface<T>;
74
+ };
75
+ export declare function withProductionDevTools<T>(): (tree: SignalTree<T>) => SignalTree<T> & {
76
+ __devTools: ModularDevToolsInterface<T>;
77
+ };
@@ -0,0 +1,33 @@
1
+ import type { Path } from './path-index';
2
+ export declare enum ChangeType {
3
+ ADD = "add",
4
+ UPDATE = "update",
5
+ DELETE = "delete",
6
+ REPLACE = "replace"
7
+ }
8
+ export interface Change {
9
+ type: ChangeType;
10
+ path: Path;
11
+ value?: unknown;
12
+ oldValue?: unknown;
13
+ }
14
+ export interface Diff {
15
+ changes: Change[];
16
+ hasChanges: boolean;
17
+ }
18
+ export interface DiffOptions {
19
+ maxDepth?: number;
20
+ detectDeletions?: boolean;
21
+ ignoreArrayOrder?: boolean;
22
+ equalityFn?: (a: unknown, b: unknown) => boolean;
23
+ keyValidator?: (key: string) => boolean;
24
+ }
25
+ export declare class DiffEngine {
26
+ private defaultOptions;
27
+ diff(current: unknown, updates: unknown, options?: DiffOptions): Diff;
28
+ private traverse;
29
+ private diffArrays;
30
+ private diffArraysOrdered;
31
+ private diffArraysUnordered;
32
+ private stringify;
33
+ }
@@ -0,0 +1,20 @@
1
+ import type { EntitySignal, SignalTree, EntityAwareTreeNode } from '../../../lib/types';
2
+ interface EntitiesEnhancerConfig {
3
+ enabled?: boolean;
4
+ }
5
+ export declare function withEntities(config?: EntitiesEnhancerConfig): <T>(tree: SignalTree<T>) => Omit<SignalTree<T>, "state" | "$"> & {
6
+ state: EntityAwareTreeNode<T>;
7
+ $: EntityAwareTreeNode<T>;
8
+ entities<E, K extends string | number>(path: keyof T | string): EntitySignal<E, K>;
9
+ };
10
+ export declare function enableEntities(): <T>(tree: SignalTree<T>) => Omit<SignalTree<T>, "state" | "$"> & {
11
+ state: EntityAwareTreeNode<T>;
12
+ $: EntityAwareTreeNode<T>;
13
+ entities<E, K extends string | number>(path: keyof T | string): EntitySignal<E, K>;
14
+ };
15
+ export declare function withHighPerformanceEntities(): <T>(tree: SignalTree<T>) => Omit<SignalTree<T>, "state" | "$"> & {
16
+ state: EntityAwareTreeNode<T>;
17
+ $: EntityAwareTreeNode<T>;
18
+ entities<E, K extends string | number>(path: keyof T | string): EntitySignal<E, K>;
19
+ };
20
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -180,12 +180,12 @@ export type EntityNode<E> = {
180
180
  export interface EntitySignal<E, K extends string | number = string> {
181
181
  byId(id: K): EntityNode<E> | undefined;
182
182
  byIdOrFail(id: K): EntityNode<E>;
183
- all(): Signal<E[]>;
184
- count(): Signal<number>;
185
- ids(): Signal<K[]>;
183
+ readonly all: Signal<E[]>;
184
+ readonly count: Signal<number>;
185
+ readonly ids: Signal<K[]>;
186
186
  has(id: K): Signal<boolean>;
187
- isEmpty(): Signal<boolean>;
188
- map(): Signal<ReadonlyMap<K, E>>;
187
+ readonly isEmpty: Signal<boolean>;
188
+ readonly map: Signal<ReadonlyMap<K, E>>;
189
189
  where(predicate: (entity: E) => boolean): Signal<E[]>;
190
190
  find(predicate: (entity: E) => boolean): Signal<E | undefined>;
191
191
  addOne(entity: E, opts?: AddOptions<E, K>): K;
@@ -0,0 +1,65 @@
1
+ import type { SignalTree } from '../../../lib/types';
2
+ export interface MemoizedSignalTree<T> extends SignalTree<T> {
3
+ memoizedUpdate: (updater: (current: T) => Partial<T>, cacheKey?: string) => void;
4
+ clearMemoCache: (key?: string) => void;
5
+ getCacheStats: () => {
6
+ size: number;
7
+ hitRate: number;
8
+ totalHits: number;
9
+ totalMisses: number;
10
+ keys: string[];
11
+ };
12
+ }
13
+ export declare function cleanupMemoizationCache(): void;
14
+ export interface MemoizationConfig {
15
+ enabled?: boolean;
16
+ maxCacheSize?: number;
17
+ ttl?: number;
18
+ equality?: 'deep' | 'shallow' | 'reference';
19
+ enableLRU?: boolean;
20
+ }
21
+ export declare function memoize<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => TReturn, keyFn?: (...args: TArgs) => string, config?: MemoizationConfig): (...args: TArgs) => TReturn;
22
+ export declare function memoizeShallow<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => TReturn, keyFn?: (...args: TArgs) => string): (...args: TArgs) => TReturn;
23
+ export declare function memoizeReference<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => TReturn, keyFn?: (...args: TArgs) => string): (...args: TArgs) => TReturn;
24
+ export declare const MEMOIZATION_PRESETS: {
25
+ readonly selector: {
26
+ readonly equality: "reference";
27
+ readonly maxCacheSize: 10;
28
+ readonly enableLRU: false;
29
+ readonly ttl: undefined;
30
+ };
31
+ readonly computed: {
32
+ readonly equality: "shallow";
33
+ readonly maxCacheSize: 100;
34
+ readonly enableLRU: false;
35
+ readonly ttl: undefined;
36
+ };
37
+ readonly deepState: {
38
+ readonly equality: "deep";
39
+ readonly maxCacheSize: 1000;
40
+ readonly enableLRU: true;
41
+ readonly ttl: number;
42
+ };
43
+ readonly highFrequency: {
44
+ readonly equality: "reference";
45
+ readonly maxCacheSize: 5;
46
+ readonly enableLRU: false;
47
+ readonly ttl: undefined;
48
+ };
49
+ };
50
+ export declare function withSelectorMemoization<T>(): (tree: SignalTree<T>) => MemoizedSignalTree<T>;
51
+ export declare function withComputedMemoization<T>(): (tree: SignalTree<T>) => MemoizedSignalTree<T>;
52
+ export declare function withDeepStateMemoization<T>(): (tree: SignalTree<T>) => MemoizedSignalTree<T>;
53
+ export declare function withHighFrequencyMemoization<T>(): (tree: SignalTree<T>) => MemoizedSignalTree<T>;
54
+ export declare function withMemoization<T>(config?: MemoizationConfig): (tree: SignalTree<T>) => MemoizedSignalTree<T>;
55
+ export declare function enableMemoization<T>(): (tree: SignalTree<T>) => MemoizedSignalTree<T>;
56
+ export declare function withHighPerformanceMemoization<T>(): (tree: SignalTree<T>) => MemoizedSignalTree<T>;
57
+ export declare function withLightweightMemoization<T>(): (tree: SignalTree<T>) => MemoizedSignalTree<T>;
58
+ export declare function withShallowMemoization<T>(): (tree: SignalTree<T>) => MemoizedSignalTree<T>;
59
+ export declare function clearAllCaches(): void;
60
+ export declare function getGlobalCacheStats(): {
61
+ treeCount: number;
62
+ totalSize: number;
63
+ totalHits: number;
64
+ averageCacheSize: number;
65
+ };