@signaltree/core 4.2.1 → 5.0.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.
@@ -0,0 +1,106 @@
1
+ class PathNotifier {
2
+ subscribers = new Map();
3
+ interceptors = new Map();
4
+ subscribe(pattern, handler) {
5
+ if (!this.subscribers.has(pattern)) {
6
+ this.subscribers.set(pattern, new Set());
7
+ }
8
+ const handlers = this.subscribers.get(pattern);
9
+ if (!handlers) {
10
+ return () => {};
11
+ }
12
+ handlers.add(handler);
13
+ return () => {
14
+ handlers.delete(handler);
15
+ if (handlers.size === 0) {
16
+ this.subscribers.delete(pattern);
17
+ }
18
+ };
19
+ }
20
+ intercept(pattern, interceptor) {
21
+ if (!this.interceptors.has(pattern)) {
22
+ this.interceptors.set(pattern, new Set());
23
+ }
24
+ const interceptors = this.interceptors.get(pattern);
25
+ if (!interceptors) {
26
+ return () => {};
27
+ }
28
+ interceptors.add(interceptor);
29
+ return () => {
30
+ interceptors.delete(interceptor);
31
+ if (interceptors.size === 0) {
32
+ this.interceptors.delete(pattern);
33
+ }
34
+ };
35
+ }
36
+ notify(path, value, prev) {
37
+ let blocked = false;
38
+ let transformed = value;
39
+ for (const [pattern, interceptorSet] of this.interceptors) {
40
+ if (this.matches(pattern, path)) {
41
+ for (const interceptor of interceptorSet) {
42
+ const result = interceptor(transformed, prev, path);
43
+ if (result.block) {
44
+ blocked = true;
45
+ }
46
+ if (result.transform !== undefined) {
47
+ transformed = result.transform;
48
+ }
49
+ }
50
+ }
51
+ }
52
+ if (blocked) {
53
+ return {
54
+ blocked: true,
55
+ value: prev
56
+ };
57
+ }
58
+ for (const [pattern, handlers] of this.subscribers) {
59
+ if (this.matches(pattern, path)) {
60
+ for (const handler of handlers) {
61
+ handler(transformed, prev, path);
62
+ }
63
+ }
64
+ }
65
+ return {
66
+ blocked: false,
67
+ value: transformed
68
+ };
69
+ }
70
+ matches(pattern, path) {
71
+ if (pattern === '**') return true;
72
+ if (pattern === path) return true;
73
+ if (pattern.endsWith('.*')) {
74
+ const prefix = pattern.slice(0, -2);
75
+ return path.startsWith(prefix + '.');
76
+ }
77
+ return false;
78
+ }
79
+ clear() {
80
+ this.subscribers.clear();
81
+ this.interceptors.clear();
82
+ }
83
+ getSubscriberCount() {
84
+ let count = 0;
85
+ for (const handlers of this.subscribers.values()) {
86
+ count += handlers.size;
87
+ }
88
+ return count;
89
+ }
90
+ getInterceptorCount() {
91
+ let count = 0;
92
+ for (const interceptors of this.interceptors.values()) {
93
+ count += interceptors.size;
94
+ }
95
+ return count;
96
+ }
97
+ }
98
+ let globalPathNotifier = null;
99
+ function getPathNotifier() {
100
+ if (!globalPathNotifier) {
101
+ globalPathNotifier = new PathNotifier();
102
+ }
103
+ return globalPathNotifier;
104
+ }
105
+
106
+ export { PathNotifier, getPathNotifier };
@@ -185,6 +185,10 @@ function createSignalStore(obj, equalityFn) {
185
185
  for (const [key, value] of Object.entries(obj)) {
186
186
  try {
187
187
  if (typeof key === 'symbol') continue;
188
+ if (isEntityMapMarker(value)) {
189
+ store[key] = value;
190
+ continue;
191
+ }
188
192
  if (isSignal(value)) {
189
193
  store[key] = value;
190
194
  continue;
@@ -228,6 +232,10 @@ function createSignalStore(obj, equalityFn) {
228
232
  for (const sym of symbols) {
229
233
  const value = obj[sym];
230
234
  try {
235
+ if (isEntityMapMarker(value)) {
236
+ store[sym] = value;
237
+ continue;
238
+ }
231
239
  if (isSignal(value)) {
232
240
  store[sym] = value;
233
241
  } else {
@@ -244,6 +252,9 @@ function createSignalStore(obj, equalityFn) {
244
252
  }
245
253
  return store;
246
254
  }
255
+ function isEntityMapMarker(value) {
256
+ return Boolean(value && typeof value === 'object' && value['__isEntityMap'] === true);
257
+ }
247
258
  function enhanceTree(tree, config = {}) {
248
259
  const isLazy = config.useLazySignals ?? shouldUseLazy(tree.state, config);
249
260
  tree.with = (...enhancers) => {
@@ -395,8 +406,7 @@ function addStubMethods(tree, config) {
395
406
  if (config.debugMode) {
396
407
  console.warn(SIGNAL_TREE_MESSAGES.SUBSCRIBE_NO_CONTEXT, error);
397
408
  }
398
- fn(tree());
399
- return () => {};
409
+ throw error;
400
410
  }
401
411
  };
402
412
  tree.optimize = () => {
@@ -448,16 +458,6 @@ function addStubMethods(tree, config) {
448
458
  averageUpdateTime: 0
449
459
  };
450
460
  };
451
- tree.addTap = middleware => {
452
- if (config.debugMode) {
453
- console.warn(SIGNAL_TREE_MESSAGES.MIDDLEWARE_NOT_AVAILABLE);
454
- }
455
- };
456
- tree.removeTap = id => {
457
- if (config.debugMode) {
458
- console.warn(SIGNAL_TREE_MESSAGES.MIDDLEWARE_NOT_AVAILABLE);
459
- }
460
- };
461
461
  tree.entities = () => {
462
462
  if (config.debugMode) {
463
463
  console.warn(SIGNAL_TREE_MESSAGES.ENTITY_HELPERS_NOT_AVAILABLE);
package/dist/lib/types.js CHANGED
@@ -1,3 +1,9 @@
1
1
  const ENHANCER_META = Symbol('signaltree:enhancer:meta');
2
+ function entityMap(config) {
3
+ return {
4
+ __isEntityMap: true,
5
+ __entityMapConfig: config ?? {}
6
+ };
7
+ }
2
8
 
3
- export { ENHANCER_META };
9
+ export { ENHANCER_META, entityMap };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaltree/core",
3
- "version": "4.2.1",
3
+ "version": "5.0.0",
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,
@@ -1,22 +1,20 @@
1
- import type { SignalTree, EntityHelpers } from '../../../lib/types';
2
- interface EntityConfig {
1
+ import type { EntitySignal, SignalTree, EntityAwareTreeNode } from '../../../lib/types';
2
+ interface EntitiesEnhancerConfig {
3
3
  enabled?: boolean;
4
- trackChanges?: boolean;
5
- validateIds?: boolean;
6
4
  }
7
- export declare function withEntities(config?: EntityConfig): <T>(tree: SignalTree<T>) => SignalTree<T> & {
8
- entities<E extends {
9
- id: string | number;
10
- }>(entityKey: keyof T | string): EntityHelpers<E>;
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>;
11
9
  };
12
- export declare function enableEntities(): <T>(tree: SignalTree<T>) => SignalTree<T> & {
13
- entities<E extends {
14
- id: string | number;
15
- }>(entityKey: keyof T | string): EntityHelpers<E>;
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>;
16
14
  };
17
- export declare function withHighPerformanceEntities(): <T>(tree: SignalTree<T>) => SignalTree<T> & {
18
- entities<E extends {
19
- id: string | number;
20
- }>(entityKey: keyof T | string): EntityHelpers<E>;
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>;
21
19
  };
22
20
  export {};
@@ -7,5 +7,5 @@ export declare function getAvailablePresets(): TreePreset[];
7
7
  export declare function combinePresets(presets: TreePreset[], overrides?: Partial<TreeConfig>): TreeConfig;
8
8
  export declare function createDevTree(overrides?: Partial<TreeConfig>): {
9
9
  readonly config: TreeConfig;
10
- readonly enhancer: (tree: import("../../../lib/types").SignalTree<unknown>) => import("../../../lib/types").SignalTree<unknown>;
10
+ readonly enhancer: (tree: import("../../memoization/lib/memoization").MemoizedSignalTree<unknown>) => import("../../memoization/lib/memoization").MemoizedSignalTree<unknown>;
11
11
  };
@@ -16,18 +16,6 @@ export interface ComputedConfig {
16
16
  lazy?: boolean;
17
17
  memoize?: boolean;
18
18
  }
19
- export interface MiddlewareConfig {
20
- beforeUpdate?: (path: string[], value: unknown) => void | boolean;
21
- afterUpdate?: (path: string[], value: unknown, previousValue: unknown) => void;
22
- beforeRead?: (path: string[]) => void;
23
- afterRead?: (path: string[], value: unknown) => void;
24
- }
25
- export interface MiddlewareContext {
26
- path: string[];
27
- value: unknown;
28
- previousValue?: unknown;
29
- operation: 'read' | 'write';
30
- }
31
19
  export interface MemoizationConfig {
32
20
  maxSize?: number;
33
21
  ttl?: number;
@@ -73,25 +61,6 @@ export interface EntityConfig {
73
61
  indexes?: string[];
74
62
  relations?: Record<string, string>;
75
63
  }
76
- export interface EntityCollection<T = unknown> {
77
- add(entity: T): void;
78
- remove(id: string | number): boolean;
79
- update(id: string | number, updates: Partial<T>): boolean;
80
- get(id: string | number): T | undefined;
81
- find(predicate: (entity: T) => boolean): T | undefined;
82
- filter(predicate: (entity: T) => boolean): T[];
83
- count(): number;
84
- }
85
- export interface AsyncConfig {
86
- timeout?: number;
87
- retryAttempts?: number;
88
- retryDelay?: number;
89
- }
90
- export interface AsyncAction<T = unknown> {
91
- execute(): Promise<T>;
92
- cancel(): void;
93
- status: 'pending' | 'fulfilled' | 'rejected' | 'cancelled';
94
- }
95
64
  export interface SerializationConfig {
96
65
  includeComputed?: boolean;
97
66
  includeMeta?: boolean;
package/src/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { signalTree } from './lib/signal-tree';
2
- export type { SignalTree, TreeNode, RemoveSignalMethods, Primitive, BuiltInObject, NotFn, DeepPath, DeepAccess, TreeConfig, TreePreset, Enhancer, EnhancerMeta, EnhancerWithMeta, ChainResult, WithMethod, Middleware, PerformanceMetrics, EntityHelpers, TimeTravelEntry, } from './lib/types';
2
+ export type { SignalTree, TreeNode, RemoveSignalMethods, Primitive, BuiltInObject, NotFn, DeepPath, DeepAccess, TreeConfig, TreePreset, Enhancer, EnhancerMeta, EnhancerWithMeta, ChainResult, WithMethod, EntitySignal, EntityMapMarker, EntityConfig, MutationOptions, AddOptions, AddManyOptions, PerformanceMetrics, EntityHelpers, TimeTravelEntry, } from './lib/types';
3
+ export { entityMap } from './lib/types';
3
4
  export { equal, deepEqual, isNodeAccessor, isAnySignal, toWritableSignal, parsePath, composeEnhancers, isBuiltInObject, createLazySignalTree, } from './lib/utils';
4
5
  export { SecurityValidator, SecurityPresets, type SecurityEvent, type SecurityEventType, type SecurityValidatorConfig, } from './lib/security/security-validator';
5
6
  export { createEnhancer, resolveEnhancerOrder } from './enhancers/index';
@@ -10,8 +11,7 @@ export { withTimeTravel, enableTimeTravel, getTimeTravel, type TimeTravelInterfa
10
11
  export { withEntities, enableEntities, withHighPerformanceEntities, } from './enhancers/entities/lib/entities';
11
12
  export { withSerialization, enableSerialization, withPersistence, createStorageAdapter, createIndexedDBAdapter, applySerialization, applyPersistence, } from './enhancers/serialization/lib/serialization';
12
13
  export { withDevTools, enableDevTools, withFullDevTools, withProductionDevTools, } from './enhancers/devtools/lib/devtools';
13
- export { withMiddleware, createLoggingMiddleware, createValidationMiddleware, } from './enhancers/middleware/lib/middleware';
14
- export { createAsyncOperation, trackAsync, } from './enhancers/middleware/lib/async-helpers';
14
+ export { createAsyncOperation, trackAsync } from './lib/async-helpers';
15
15
  export { TREE_PRESETS, createPresetConfig, validatePreset, getAvailablePresets, combinePresets, createDevTree, } from './enhancers/presets/lib/presets';
16
16
  export { computedEnhancer, createComputed, type ComputedConfig, type ComputedSignal, type ComputedSignalTree, } from './enhancers/computed/lib/computed';
17
17
  export { SIGNAL_TREE_CONSTANTS, SIGNAL_TREE_MESSAGES } from './lib/constants';
@@ -1,4 +1,4 @@
1
- import type { SignalTree } from '../../../lib/types';
1
+ import type { SignalTree } from './types';
2
2
  export declare function createAsyncOperation<T, TResult>(name: string, operation: () => Promise<TResult>): (tree: SignalTree<T>) => Promise<TResult>;
3
3
  export declare function trackAsync<T>(operation: () => Promise<T>): {
4
4
  pending: import("@angular/core").Signal<boolean>;
@@ -0,0 +1 @@
1
+ export declare function entityMap<E extends Record<string, unknown>, K extends string | number = string>(config?: Partial<EntityConfig<E, K>>): unknown;
@@ -0,0 +1,4 @@
1
+ export type PathNotifierInterceptor = (value: unknown, prev: unknown, path: string) => {
2
+ block?: boolean;
3
+ transform?: unknown;
4
+ };
@@ -90,8 +90,6 @@ export type SignalTree<T> = NodeAccessor<T> & {
90
90
  };
91
91
  };
92
92
  getMetrics(): PerformanceMetrics;
93
- addTap(middleware: Middleware<T>): void;
94
- removeTap(id: string): void;
95
93
  entities<E extends {
96
94
  id: string | number;
97
95
  }>(entityKey?: keyof T): EntityHelpers<E>;
@@ -120,11 +118,6 @@ export interface TreeConfig {
120
118
  useStructuralSharing?: boolean;
121
119
  security?: SecurityValidatorConfig;
122
120
  }
123
- export interface Middleware<T> {
124
- id: string;
125
- before?: (action: string, payload: unknown, state: T) => boolean;
126
- after?: (action: string, payload: unknown, state: T, newState: T) => void;
127
- }
128
121
  export interface PerformanceMetrics {
129
122
  updates: number;
130
123
  computations: number;
@@ -132,6 +125,83 @@ export interface PerformanceMetrics {
132
125
  cacheMisses: number;
133
126
  averageUpdateTime: number;
134
127
  }
128
+ export interface EntityConfig<E, K extends string | number = string> {
129
+ selectId?: (entity: E) => K;
130
+ hooks?: {
131
+ beforeAdd?: (entity: E) => E | false;
132
+ beforeUpdate?: (id: K, changes: Partial<E>) => Partial<E> | false;
133
+ beforeRemove?: (id: K, entity: E) => boolean;
134
+ };
135
+ }
136
+ export interface EntityMapMarker<E, K extends string | number> {
137
+ readonly __entityType?: E;
138
+ readonly __keyType?: K;
139
+ readonly __isEntityMap?: true;
140
+ readonly __entityMapConfig?: EntityConfig<E, K>;
141
+ }
142
+ export declare function entityMap<E, K extends string | number = E extends {
143
+ id: infer I extends string | number;
144
+ } ? I : string>(config?: EntityConfig<E, K>): EntityMapMarker<E, K>;
145
+ export interface MutationOptions {
146
+ onError?: (error: Error) => void;
147
+ }
148
+ export interface AddOptions<E, K> extends MutationOptions {
149
+ selectId?: (entity: E) => K;
150
+ }
151
+ export interface AddManyOptions<E, K> extends AddOptions<E, K> {
152
+ mode?: 'strict' | 'skip' | 'overwrite';
153
+ }
154
+ export interface TapHandlers<E, K extends string | number> {
155
+ onAdd?: (entity: E, id: K) => void;
156
+ onUpdate?: (id: K, changes: Partial<E>, entity: E) => void;
157
+ onRemove?: (id: K, entity: E) => void;
158
+ onChange?: () => void;
159
+ }
160
+ export interface InterceptContext<T> {
161
+ block(reason?: string): void;
162
+ transform(value: T): void;
163
+ readonly blocked: boolean;
164
+ readonly blockReason: string | undefined;
165
+ }
166
+ export interface InterceptHandlers<E, K extends string | number> {
167
+ onAdd?: (entity: E, ctx: InterceptContext<E>) => void | Promise<void>;
168
+ onUpdate?: (id: K, changes: Partial<E>, ctx: InterceptContext<Partial<E>>) => void | Promise<void>;
169
+ onRemove?: (id: K, entity: E, ctx: InterceptContext<void>) => void | Promise<void>;
170
+ }
171
+ export type EntityNode<E> = {
172
+ (): E;
173
+ (value: E): void;
174
+ (updater: (current: E) => E): void;
175
+ } & {
176
+ [P in keyof E]: E[P] extends object ? E[P] extends readonly unknown[] ? CallableWritableSignal<E[P]> : EntityNode<E[P]> : CallableWritableSignal<E[P]>;
177
+ };
178
+ export interface EntitySignal<E, K extends string | number = string> {
179
+ byId(id: K): EntityNode<E> | undefined;
180
+ byIdOrFail(id: K): EntityNode<E>;
181
+ all(): Signal<E[]>;
182
+ count(): Signal<number>;
183
+ ids(): Signal<K[]>;
184
+ has(id: K): Signal<boolean>;
185
+ isEmpty(): Signal<boolean>;
186
+ map(): Signal<ReadonlyMap<K, E>>;
187
+ where(predicate: (entity: E) => boolean): Signal<E[]>;
188
+ find(predicate: (entity: E) => boolean): Signal<E | undefined>;
189
+ addOne(entity: E, opts?: AddOptions<E, K>): K;
190
+ addMany(entities: E[], opts?: AddManyOptions<E, K>): K[];
191
+ updateOne(id: K, changes: Partial<E>, opts?: MutationOptions): void;
192
+ updateMany(ids: K[], changes: Partial<E>, opts?: MutationOptions): void;
193
+ updateWhere(predicate: (entity: E) => boolean, changes: Partial<E>): number;
194
+ upsertOne(entity: E, opts?: AddOptions<E, K>): K;
195
+ upsertMany(entities: E[], opts?: AddOptions<E, K>): K[];
196
+ removeOne(id: K, opts?: MutationOptions): void;
197
+ removeMany(ids: K[], opts?: MutationOptions): void;
198
+ removeWhere(predicate: (entity: E) => boolean): number;
199
+ clear(): void;
200
+ removeAll(): void;
201
+ setAll(entities: E[], opts?: AddOptions<E, K>): void;
202
+ tap(handlers: TapHandlers<E, K>): () => void;
203
+ intercept(handlers: InterceptHandlers<E, K>): () => void;
204
+ }
135
205
  export interface EntityHelpers<E extends {
136
206
  id: string | number;
137
207
  }> {
@@ -146,6 +216,56 @@ export interface EntityHelpers<E extends {
146
216
  selectTotal(): Signal<number>;
147
217
  clear(): void;
148
218
  }
219
+ export interface LoggingConfig {
220
+ name?: string;
221
+ filter?: (path: string) => boolean;
222
+ collapsed?: boolean;
223
+ onLog?: (entry: LogEntry) => void;
224
+ }
225
+ export interface LogEntry {
226
+ path: string;
227
+ prev: unknown;
228
+ value: unknown;
229
+ timestamp: number;
230
+ }
231
+ export interface ValidationConfig<T> {
232
+ validators: Array<{
233
+ match: (path: string) => boolean;
234
+ validate: (value: unknown, path: string) => void | never;
235
+ }>;
236
+ onError?: (error: Error, path: string) => void;
237
+ }
238
+ export interface PersistenceConfig {
239
+ key: string;
240
+ storage?: Storage;
241
+ debounceMs?: number;
242
+ filter?: (path: string) => boolean;
243
+ serialize?: (state: unknown) => string;
244
+ deserialize?: (json: string) => unknown;
245
+ }
246
+ export interface DevToolsConfig {
247
+ name?: string;
248
+ maxAge?: number;
249
+ features?: {
250
+ jump?: boolean;
251
+ skip?: boolean;
252
+ reorder?: boolean;
253
+ };
254
+ }
255
+ export type EntityType<T> = T extends EntitySignal<infer E, any> ? E : never;
256
+ export type EntityKeyType<T> = T extends EntitySignal<unknown, infer K> ? K : never;
257
+ export type IsEntityMap<T> = T extends EntityMapMarker<unknown, any> ? true : false;
258
+ export type EntityAwareTreeNode<T> = {
259
+ [K in keyof T]: T[K] extends EntityMapMarker<infer E, infer Key> ? EntitySignal<E, Key> : T[K] extends object ? EntityAwareTreeNode<T[K]> : CallableWritableSignal<T[K]>;
260
+ };
261
+ export type PathHandler = (value: unknown, prev: unknown, path: string) => void;
262
+ export type PathInterceptor = (ctx: {
263
+ path: string;
264
+ value: unknown;
265
+ prev: unknown;
266
+ blocked: boolean;
267
+ blockReason?: string;
268
+ }, next: () => void) => void | Promise<void>;
149
269
  export interface TimeTravelEntry<T> {
150
270
  action: string;
151
271
  timestamp: number;
@@ -1,156 +0,0 @@
1
- const middlewareMap = new WeakMap();
2
- function withMiddleware(middlewares = []) {
3
- return tree => {
4
- middlewareMap.set(tree, [...middlewares]);
5
- const originalTreeCall = tree.bind(tree);
6
- const enhancedTree = function (...args) {
7
- if (args.length === 0) {
8
- return originalTreeCall();
9
- } else {
10
- const action = 'UPDATE';
11
- const currentState = originalTreeCall();
12
- const treeMiddlewares = middlewareMap.get(tree) || [];
13
- let updateResult;
14
- if (args.length === 1) {
15
- const arg = args[0];
16
- if (typeof arg === 'function') {
17
- updateResult = arg(currentState);
18
- } else {
19
- updateResult = arg;
20
- }
21
- } else {
22
- return;
23
- }
24
- for (const middleware of treeMiddlewares) {
25
- if (middleware.before && !middleware.before(action, updateResult, currentState)) {
26
- return;
27
- }
28
- }
29
- let isNoMutation = true;
30
- if (updateResult && typeof updateResult === 'object') {
31
- for (const k in updateResult) {
32
- if (!Object.prototype.hasOwnProperty.call(updateResult, k)) continue;
33
- const nextVal = updateResult[k];
34
- const prevVal = currentState[k];
35
- if (nextVal !== prevVal) {
36
- isNoMutation = false;
37
- break;
38
- }
39
- }
40
- }
41
- if (isNoMutation) {
42
- for (const middleware of treeMiddlewares) {
43
- if (middleware.after) {
44
- middleware.after(action, updateResult, currentState, currentState);
45
- }
46
- }
47
- return;
48
- }
49
- const previousState = currentState;
50
- if (args.length === 1) {
51
- const arg = args[0];
52
- if (typeof arg === 'function') {
53
- originalTreeCall(arg);
54
- } else {
55
- originalTreeCall(arg);
56
- }
57
- }
58
- const newState = originalTreeCall();
59
- for (const middleware of treeMiddlewares) {
60
- if (middleware.after) {
61
- middleware.after(action, updateResult, previousState, newState);
62
- }
63
- }
64
- }
65
- };
66
- Object.setPrototypeOf(enhancedTree, Object.getPrototypeOf(tree));
67
- Object.assign(enhancedTree, tree);
68
- if ('state' in tree) {
69
- Object.defineProperty(enhancedTree, 'state', {
70
- value: tree.state,
71
- enumerable: false,
72
- configurable: true
73
- });
74
- }
75
- if ('$' in tree) {
76
- Object.defineProperty(enhancedTree, '$', {
77
- value: tree['$'],
78
- enumerable: false,
79
- configurable: true
80
- });
81
- }
82
- enhancedTree.addTap = middleware => {
83
- const treeMiddlewares = middlewareMap.get(tree) || [];
84
- const existingIndex = treeMiddlewares.findIndex(m => m.id === middleware.id);
85
- if (existingIndex >= 0) {
86
- treeMiddlewares[existingIndex] = middleware;
87
- } else {
88
- treeMiddlewares.push(middleware);
89
- }
90
- middlewareMap.set(tree, treeMiddlewares);
91
- };
92
- enhancedTree.removeTap = id => {
93
- const treeMiddlewares = middlewareMap.get(tree) || [];
94
- const filtered = treeMiddlewares.filter(m => m.id !== id);
95
- middlewareMap.set(tree, filtered);
96
- };
97
- const originalBatchUpdate = tree.batchUpdate;
98
- if (originalBatchUpdate) {
99
- enhancedTree.batchUpdate = updater => {
100
- const action = 'BATCH_UPDATE';
101
- const currentState = originalTreeCall();
102
- const updateResult = updater(currentState);
103
- const treeMiddlewares = middlewareMap.get(tree) || [];
104
- for (const middleware of treeMiddlewares) {
105
- if (middleware.before && !middleware.before(action, updateResult, currentState)) {
106
- return;
107
- }
108
- }
109
- const previousState = currentState;
110
- originalBatchUpdate.call(tree, updater);
111
- const newState = originalTreeCall();
112
- for (const middleware of treeMiddlewares) {
113
- if (middleware.after) {
114
- middleware.after(action, updateResult, previousState, newState);
115
- }
116
- }
117
- };
118
- }
119
- const originalDestroy = tree.destroy;
120
- enhancedTree.destroy = () => {
121
- middlewareMap.delete(tree);
122
- if (originalDestroy) {
123
- originalDestroy.call(tree);
124
- }
125
- };
126
- return enhancedTree;
127
- };
128
- }
129
- function createLoggingMiddleware(treeName) {
130
- return {
131
- id: 'logging',
132
- before: (action, payload, state) => {
133
- console.group(`🏪 ${treeName}: ${action}`);
134
- console.log('Previous state:', state);
135
- console.log('Payload:', typeof payload === 'function' ? 'Function' : payload);
136
- return true;
137
- },
138
- after: (action, payload, state, newState) => {
139
- console.log('New state:', newState);
140
- console.groupEnd();
141
- }
142
- };
143
- }
144
- function createValidationMiddleware(validator) {
145
- return {
146
- id: 'validation',
147
- after: (action, payload, state, newState) => {
148
- const error = validator(newState);
149
- if (error) {
150
- console.error(`Validation failed after ${action}:`, error);
151
- }
152
- }
153
- };
154
- }
155
-
156
- export { createLoggingMiddleware, createValidationMiddleware, withMiddleware };
@@ -1,2 +0,0 @@
1
- export * from './lib/middleware';
2
- export * from './lib/async-helpers';
@@ -1,15 +0,0 @@
1
- declare const _default: {
2
- displayName: string;
3
- preset: string;
4
- setupFilesAfterEnv: string[];
5
- coverageDirectory: string;
6
- transform: {
7
- '^.+\\.(ts|mjs|js|html)$': (string | {
8
- tsconfig: string;
9
- stringifyContentPathRegex: string;
10
- })[];
11
- };
12
- transformIgnorePatterns: string[];
13
- snapshotSerializers: string[];
14
- };
15
- export default _default;
@@ -1,11 +0,0 @@
1
- import type { Middleware, SignalTree } from '../../../lib/types';
2
- export declare function withMiddleware<T>(middlewares?: Middleware<T>[]): (tree: SignalTree<T>) => SignalTree<T>;
3
- export declare function createLoggingMiddleware<T>(treeName: string): Middleware<T>;
4
- export declare function createPerformanceMiddleware<T>(): Middleware<T>;
5
- export declare function createValidationMiddleware<T>(validator: (state: T) => string | null): Middleware<T>;
6
- export declare function createPersistenceMiddleware<T>(config: {
7
- key: string;
8
- storage?: Storage;
9
- debounceMs?: number;
10
- actions?: string[];
11
- }): Middleware<T>;
@@ -1 +0,0 @@
1
- export {};