@signaltree/core 8.0.2 → 9.0.1

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.
@@ -9,6 +9,7 @@ import { SignalMemoryManager } from './memory/memory-manager.js';
9
9
  import { getPathNotifier } from './path-notifier.js';
10
10
  import { SecurityValidator } from './security/security-validator.js';
11
11
  import { createLazySignalTree, unwrap } from './utils.js';
12
+ import { ENHANCER_META } from './types.js';
12
13
  import { deepEqual } from '../shared/lib/deep-equal.js';
13
14
  import { isBuiltInObject } from '../shared/lib/is-built-in-object.js';
14
15
 
@@ -228,6 +229,9 @@ function create(initialState, config) {
228
229
  }
229
230
  };
230
231
  tree[NODE_ACCESSOR_SYMBOL] = true;
232
+ const cleanupFns = [];
233
+ const destroyedSig = signal(false);
234
+ const appliedEnhancers = new Set();
231
235
  Object.defineProperty(tree, 'state', {
232
236
  value: signalState,
233
237
  enumerable: false,
@@ -243,6 +247,20 @@ function create(initialState, config) {
243
247
  if (typeof enhancer !== 'function') {
244
248
  throw new Error('Enhancer must be a function');
245
249
  }
250
+ const meta = enhancer[ENHANCER_META] ?? enhancer.metadata;
251
+ if (meta?.name) {
252
+ if (appliedEnhancers.has(meta.name)) {
253
+ throw new Error(`Enhancer "${meta.name}" has already been applied to this tree. ` + `Each enhancer can only be applied once.`);
254
+ }
255
+ if (meta.requires) {
256
+ for (const dep of meta.requires) {
257
+ if (!appliedEnhancers.has(dep)) {
258
+ throw new Error(`Enhancer "${meta.name}" requires "${dep}" to be applied first.`);
259
+ }
260
+ }
261
+ }
262
+ appliedEnhancers.add(meta.name);
263
+ }
246
264
  return enhancer(tree);
247
265
  },
248
266
  enumerable: false,
@@ -259,6 +277,14 @@ function create(initialState, config) {
259
277
  });
260
278
  Object.defineProperty(tree, 'destroy', {
261
279
  value: function () {
280
+ if (destroyedSig()) return;
281
+ destroyedSig.set(true);
282
+ for (const fn of cleanupFns) {
283
+ try {
284
+ fn();
285
+ } catch {}
286
+ }
287
+ cleanupFns.length = 0;
262
288
  if (memoryManager) {
263
289
  memoryManager.dispose();
264
290
  }
@@ -270,6 +296,22 @@ function create(initialState, config) {
270
296
  writable: true,
271
297
  configurable: true
272
298
  });
299
+ Object.defineProperty(tree, 'destroyed', {
300
+ value: destroyedSig.asReadonly(),
301
+ enumerable: false,
302
+ writable: false,
303
+ configurable: true
304
+ });
305
+ Object.defineProperty(tree, 'registerCleanup', {
306
+ value: function (fn) {
307
+ if (typeof fn === 'function') {
308
+ cleanupFns.push(fn);
309
+ }
310
+ },
311
+ enumerable: false,
312
+ writable: false,
313
+ configurable: true
314
+ });
273
315
  Object.defineProperty(tree, 'clearCache', {
274
316
  value: () => {},
275
317
  enumerable: false,
@@ -353,7 +395,7 @@ function createBuilder(baseTree) {
353
395
  const enhanced = baseTree.with(enhancer);
354
396
  const newBuilder = createBuilder(enhanced);
355
397
  for (const key of Object.keys(enhanced)) {
356
- if (key !== '$' && key !== 'state' && key !== 'with' && key !== 'bind' && key !== 'destroy' && key !== 'derived') {
398
+ if (key !== '$' && key !== 'state' && key !== 'with' && key !== 'bind' && key !== 'destroy' && key !== 'destroyed' && key !== 'registerCleanup' && key !== 'derived') {
357
399
  try {
358
400
  newBuilder[key] = enhanced[key];
359
401
  } catch {}
@@ -395,6 +437,22 @@ function createBuilder(baseTree) {
395
437
  configurable: true
396
438
  });
397
439
  }
440
+ if (baseTree.destroyed) {
441
+ Object.defineProperty(builder, 'destroyed', {
442
+ value: baseTree.destroyed,
443
+ enumerable: false,
444
+ writable: false,
445
+ configurable: true
446
+ });
447
+ }
448
+ if (typeof baseTree.registerCleanup === 'function') {
449
+ Object.defineProperty(builder, 'registerCleanup', {
450
+ value: baseTree.registerCleanup.bind(baseTree),
451
+ enumerable: false,
452
+ writable: false,
453
+ configurable: true
454
+ });
455
+ }
398
456
  Object.defineProperty(builder, 'derived', {
399
457
  value: function (factory) {
400
458
  if (isFinalized) {
@@ -0,0 +1 @@
1
+ export { SecurityPresets, SecurityValidator } from './lib/security/security-validator.js';
@@ -0,0 +1 @@
1
+ export { createIndexedDBAdapter, createStorageAdapter } from './enhancers/serialization/serialization.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaltree/core",
3
- "version": "8.0.2",
3
+ "version": "9.0.1",
4
4
  "description": "Reactive JSON for Angular. JSON branches, reactive leaves. No actions. No reducers. No selectors.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,6 +14,21 @@
14
14
  "import": "./dist/index.js",
15
15
  "default": "./dist/index.js"
16
16
  },
17
+ "./security": {
18
+ "types": "./src/security.d.ts",
19
+ "import": "./dist/security.js",
20
+ "default": "./dist/security.js"
21
+ },
22
+ "./edit-session": {
23
+ "types": "./src/edit-session.d.ts",
24
+ "import": "./dist/edit-session.js",
25
+ "default": "./dist/edit-session.js"
26
+ },
27
+ "./storage": {
28
+ "types": "./src/storage.d.ts",
29
+ "import": "./dist/storage.js",
30
+ "default": "./dist/storage.js"
31
+ },
17
32
  "./package.json": "./package.json"
18
33
  },
19
34
  "peerDependencies": {
@@ -0,0 +1 @@
1
+ export { createEditSession, type EditSession, type UndoRedoHistory, } from './lib/edit-session';
@@ -1 +1 @@
1
- export { BatchingMethods, BatchingConfig, MemoizationMethods, MemoizationConfig, TimeTravelMethods, TimeTravelConfig, EffectsMethods, DevToolsMethods, DevToolsConfig, EntitiesEnabled, Enhancer, EnhancerWithMeta, EnhancerMeta, } from '../lib/types';
1
+ export { BatchingMethods, BatchingConfig, TimeTravelMethods, TimeTravelConfig, EffectsMethods, DevToolsMethods, DevToolsConfig, EntitiesEnabled, Enhancer, EnhancerWithMeta, EnhancerMeta, } from '../lib/types';
@@ -1,2 +1,2 @@
1
- export type Equals<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
1
+ export type Equals<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;
2
2
  export type Assert<T extends true> = T;
package/src/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { signalTree } from './lib/signal-tree';
2
- export type { ISignalTree, SignalTree, SignalTreeBase, FullSignalTree, ProdSignalTree, TreeNode, CallableWritableSignal, AccessibleNode, NodeAccessor, Primitive, NotFn, TreeConfig, TreePreset, Enhancer, EnhancerMeta, EnhancerWithMeta, EntitySignal, EntityMapMarker, EntityConfig, MutationOptions, AddOptions, AddManyOptions, TimeTravelEntry, TimeTravelMethods, } from './lib/types';
2
+ export type { ISignalTree, SignalTree, SignalTreeBase, TreeNode, CallableWritableSignal, AccessibleNode, NodeAccessor, Primitive, NotFn, TreeConfig, Enhancer, EnhancerMeta, EnhancerWithMeta, EntitySignal, EntityMapMarker, EntityConfig, MutationOptions, AddOptions, AddManyOptions, TimeTravelEntry, TimeTravelMethods, EnhancerCleanup, } from './lib/types';
3
3
  export { entityMap } from './lib/types';
4
4
  export type { ProcessDerived, DeepMergeTree, DerivedFactory, WithDerived, } from './lib/internals/derived-types';
5
5
  export { derivedFrom, externalDerived } from './lib/internals/derived-types';
@@ -9,19 +9,13 @@ export { status, isStatusMarker, LoadingState, type StatusMarker, type StatusSig
9
9
  export { stored, isStoredMarker, createStorageKeys, clearStoragePrefix, type StoredMarker, type StoredSignal, type StoredOptions, } from './lib/markers/stored';
10
10
  export { form, isFormMarker, createFormSignal, validators, FORM_MARKER, type FormMarker, type FormSignal, type FormConfig, type FormFields, type FormWizard, type WizardConfig, type WizardStepConfig, type Validator, type AsyncValidator, } from './lib/markers/form';
11
11
  export { registerMarkerProcessor } from './lib/internals/materialize-markers';
12
- export { equal, deepEqual, isNodeAccessor, isAnySignal, toWritableSignal, parsePath, composeEnhancers, isBuiltInObject, createLazySignalTree, } from './lib/utils';
13
- export { createEditSession, type EditSession, type UndoRedoHistory, } from './lib/edit-session';
12
+ export { equal, deepEqual, isNodeAccessor, isAnySignal, toWritableSignal, parsePath, composeEnhancers, isBuiltInObject, } from './lib/utils';
14
13
  export { getPathNotifier } from './lib/path-notifier';
15
- export { SecurityValidator, SecurityPresets, type SecurityEvent, type SecurityEventType, type SecurityValidatorConfig, } from './lib/security/security-validator';
16
14
  export { createEnhancer, resolveEnhancerOrder } from './enhancers/index';
17
15
  export { ENHANCER_META } from './lib/types';
18
- export { batching, batchingWithConfig, highPerformanceBatching, flushBatchedUpdates, hasPendingUpdates, getBatchQueueSize, } from './enhancers/batching/batching';
16
+ export { batching } from './enhancers/batching/batching';
19
17
  export type { BatchingConfig, BatchingMethods } from './lib/types';
20
- export { memoization, selectorMemoization, computedMemoization, deepStateMemoization, highFrequencyMemoization, highPerformanceMemoization, lightweightMemoization, shallowMemoization, memoize, memoizeShallow, memoizeReference, clearAllCaches, getGlobalCacheStats, } from './enhancers/memoization/memoization';
21
- export { timeTravel, enableTimeTravel, } from './enhancers/time-travel/time-travel';
22
- export { entities, enableEntities, highPerformanceEntities, } from './enhancers/entities/entities';
23
- export { serialization, enableSerialization, persistence, createStorageAdapter, createIndexedDBAdapter, applySerialization, applyPersistence, } from './enhancers/serialization/serialization';
24
- export { devTools, enableDevTools, fullDevTools, productionDevTools, } from './enhancers/devtools/devtools';
25
- export { createAsyncOperation, trackAsync } from './lib/async-helpers';
26
- export { TREE_PRESETS, createPresetConfig, validatePreset, getAvailablePresets, combinePresets, createDevTree, } from './enhancers/presets/lib/presets';
27
- export { SIGNAL_TREE_CONSTANTS, SIGNAL_TREE_MESSAGES } from './lib/constants';
18
+ export { timeTravel } from './enhancers/time-travel/time-travel';
19
+ export { serialization, persistence, } from './enhancers/serialization/serialization';
20
+ export { devTools } from './enhancers/devtools/devtools';
21
+ export { SIGNAL_TREE_CONSTANTS, SIGNAL_TREE_MESSAGES, isDev } from './lib/constants';
@@ -14,13 +14,6 @@ export interface TimeTravelConfig {
14
14
  [key: string]: string | undefined;
15
15
  };
16
16
  }
17
- export interface MemoizationConfig {
18
- enabled?: boolean;
19
- maxCacheSize?: number;
20
- ttl?: number;
21
- enableLRU?: boolean;
22
- equality?: 'deep' | 'shallow' | 'reference';
23
- }
24
17
  export type Primitive = string | number | boolean | null | undefined | bigint | symbol;
25
18
  export type NotFn<T> = T extends (...args: unknown[]) => unknown ? never : T;
26
19
  declare module '@angular/core' {
@@ -43,7 +36,10 @@ export interface ISignalTree<T> extends NodeAccessor<T> {
43
36
  with<TAdded>(enhancer: (tree: ISignalTree<T>) => ISignalTree<T> & TAdded): this & TAdded;
44
37
  bind(thisArg?: unknown): NodeAccessor<T>;
45
38
  destroy(): void;
39
+ readonly destroyed: Signal<boolean>;
40
+ registerCleanup(fn: EnhancerCleanup): void;
46
41
  }
42
+ export type EnhancerCleanup = () => void;
47
43
  export interface EffectsMethods<T> {
48
44
  effect(fn: (state: T) => void | (() => void)): () => void;
49
45
  subscribe(fn: (state: T) => void): () => void;
@@ -58,20 +54,6 @@ export interface BatchingMethods<T = unknown> {
58
54
  hasPendingNotifications(): boolean;
59
55
  flushNotifications(): void;
60
56
  }
61
- export interface MemoizationMethods<T> {
62
- memoize<R>(fn: (state: T) => R, cacheKey?: string): Signal<R>;
63
- memoizedUpdate?: (updater: (current: T) => Partial<T>, cacheKey?: string) => void;
64
- clearMemoCache(key?: string): void;
65
- clearCache?: (key?: string) => void;
66
- getCacheStats(): CacheStats;
67
- }
68
- export type CacheStats = {
69
- size: number;
70
- hitRate: number;
71
- totalHits: number;
72
- totalMisses: number;
73
- keys: string[];
74
- };
75
57
  export interface TimeTravelMethods<T = unknown> {
76
58
  undo(): void;
77
59
  redo(): void;
@@ -122,10 +104,8 @@ export interface TimeTravelEntry<T> {
122
104
  state: T;
123
105
  payload?: unknown;
124
106
  }
125
- export type TreePreset = 'basic' | 'performance' | 'development' | 'production';
126
107
  export interface TreeConfig {
127
108
  batchUpdates?: boolean;
128
- useMemoization?: boolean;
129
109
  enableTimeTravel?: boolean;
130
110
  useLazySignals?: boolean;
131
111
  useShallowComparison?: boolean;
@@ -307,9 +287,6 @@ export interface EnhancerMeta {
307
287
  provides?: string[];
308
288
  description?: string;
309
289
  }
310
- export type FullSignalTree<T> = ISignalTree<T> & EffectsMethods<T> & BatchingMethods<T> & MemoizationMethods<T> & TimeTravelMethods<T> & DevToolsMethods & EntitiesEnabled & OptimizedUpdateMethods<T>;
311
- export type ProdSignalTree<T> = ISignalTree<T> & EffectsMethods<T> & BatchingMethods<T> & MemoizationMethods<T> & EntitiesEnabled & OptimizedUpdateMethods<T>;
312
- export type MinimalSignalTree<T> = ISignalTree<T> & EffectsMethods<T>;
313
290
  export type SignalTree<T> = ISignalTree<T> & TreeNode<T>;
314
291
  export type SignalTreeBase<T> = ISignalTree<T> & TreeNode<T>;
315
292
  export declare function isSignalTree<T>(value: unknown): value is ISignalTree<T>;
@@ -0,0 +1 @@
1
+ export { SecurityValidator, SecurityPresets, type SecurityEvent, type SecurityEventType, type SecurityValidatorConfig, } from './lib/security/security-validator';
@@ -0,0 +1,2 @@
1
+ export { createStorageAdapter, createIndexedDBAdapter, } from './enhancers/serialization/serialization';
2
+ export type { StorageAdapter } from './enhancers/serialization/serialization';
@@ -1,66 +0,0 @@
1
- import { effect, untracked } from '@angular/core';
2
-
3
- function effects(config = {}) {
4
- const {
5
- enabled = true
6
- } = config;
7
- return tree => {
8
- const cleanupFns = [];
9
- const methods = {
10
- effect(effectFn) {
11
- if (!enabled) {
12
- return () => {};
13
- }
14
- let innerCleanup;
15
- const effectRef = effect(() => {
16
- const state = tree();
17
- if (innerCleanup) {
18
- untracked(() => innerCleanup());
19
- }
20
- innerCleanup = untracked(() => effectFn(state));
21
- });
22
- const cleanup = () => {
23
- if (innerCleanup) {
24
- innerCleanup();
25
- }
26
- effectRef.destroy();
27
- };
28
- cleanupFns.push(cleanup);
29
- return cleanup;
30
- },
31
- subscribe(fn) {
32
- if (!enabled) {
33
- return () => {};
34
- }
35
- const effectRef = effect(() => {
36
- const state = tree();
37
- untracked(() => fn(state));
38
- });
39
- const cleanup = () => {
40
- effectRef.destroy();
41
- };
42
- cleanupFns.push(cleanup);
43
- return cleanup;
44
- }
45
- };
46
- const originalDestroy = tree.destroy?.bind(tree);
47
- tree.destroy = () => {
48
- cleanupFns.forEach(fn => fn());
49
- cleanupFns.length = 0;
50
- if (originalDestroy) {
51
- originalDestroy();
52
- }
53
- };
54
- return Object.assign(tree, methods);
55
- };
56
- }
57
- function enableEffects() {
58
- return effects({
59
- enabled: true
60
- });
61
- }
62
- Object.assign((config = {}) => effects(config), {
63
- enable: enableEffects
64
- });
65
-
66
- export { effects, enableEffects };
@@ -1,7 +0,0 @@
1
- function entities(config = {}) {
2
- throw new Error('entities() has been removed. Remove `.with(entities())` from your code; v7+ auto-processes EntityMap markers.');
3
- }
4
- const enableEntities = entities;
5
- const highPerformanceEntities = entities;
6
-
7
- export { enableEntities, entities, highPerformanceEntities };