@montra-interactive/deepstate 0.3.0 → 0.3.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.
package/README.md CHANGED
@@ -9,7 +9,7 @@ Proxy-based reactive state management powered by RxJS. Each property is its own
9
9
  - **Type-safe**: Full TypeScript support with inferred types
10
10
  - **RxJS native**: Every node is an Observable - use `pipe()`, `combineLatest`, etc.
11
11
  - **Batched updates**: Group multiple changes into a single emission
12
- - **Immutable reads**: Values are deeply frozen to prevent accidental mutations
12
+ - **Mutable snapshots**: Reads return plain values; use `.set()` to update state
13
13
  - **Nullable objects**: First-class support for `T | null` properties with deep subscription
14
14
  - **Debug mode**: Optional logging for development
15
15
 
@@ -301,14 +301,13 @@ store.selectedId.get(); // string | null
301
301
  ### Type Exports
302
302
 
303
303
  ```ts
304
- import type { RxState, Draft, DeepReadonly } from "@montra-interactive/deepstate";
304
+ import type { RxState, Draft } from "@montra-interactive/deepstate";
305
305
  ```
306
306
 
307
307
  | Type | Description |
308
308
  |------|-------------|
309
309
  | `RxState<T>` | The reactive state type returned by `state()` |
310
310
  | `Draft<T>` | Type alias for values in update callbacks |
311
- | `DeepReadonly<T>` | Deep readonly type for returned values |
312
311
 
313
312
  ## Architecture
314
313
 
@@ -21,12 +21,9 @@ type IsNullish<T> = HasNull<T> extends true ? true : HasUndefined<T>;
21
21
  type NonNullPartIsObject<T> = NonNullablePart<T> extends object ? NonNullablePart<T> extends Array<unknown> ? false : true : false;
22
22
  type IsNullableObject<T> = IsNullish<T> extends true ? NonNullPartIsObject<T> : false;
23
23
  /**
24
- * Deep readonly type - makes all nested properties readonly.
25
- * Used for return types of get() and subscribe() to prevent accidental mutations.
24
+ * Type alias for values returned by get() and subscribe().
26
25
  */
27
- export type DeepReadonly<T> = [T] extends [Primitive] ? T : [T] extends [Array<infer U>] ? ReadonlyArray<DeepReadonly<U>> : [T] extends [object] ? {
28
- readonly [K in keyof T]: DeepReadonly<T[K]>;
29
- } : T;
26
+ export type DeepReadonly<T> = T;
30
27
  /**
31
28
  * A mutable draft of state T for use in update callbacks.
32
29
  */
@@ -38,20 +35,20 @@ interface NodeCore<T> {
38
35
  subscribeOnce?(callback: (value: T) => void): Subscription;
39
36
  }
40
37
  declare const NODE: unique symbol;
41
- type RxLeaf<T> = Observable<DeepReadonly<T>> & {
38
+ type RxLeaf<T> = Observable<T> & {
42
39
  /** Get current value synchronously */
43
- get(): DeepReadonly<T>;
40
+ get(): T;
44
41
  /** Set value */
45
42
  set(value: T): void;
46
43
  /** Subscribe to a single emission, then automatically unsubscribe */
47
- subscribeOnce(callback: (value: DeepReadonly<T>) => void): Subscription;
44
+ subscribeOnce(callback: (value: T) => void): Subscription;
48
45
  [NODE]: NodeCore<T>;
49
46
  };
50
47
  type RxObject<T extends object> = {
51
48
  [K in keyof T]: RxNodeFor<T[K]>;
52
- } & Observable<DeepReadonly<T>> & {
49
+ } & Observable<T> & {
53
50
  /** Get current value synchronously */
54
- get(): DeepReadonly<T>;
51
+ get(): T;
55
52
  /** Set value */
56
53
  set(value: T): void;
57
54
  /**
@@ -64,14 +61,14 @@ type RxObject<T extends object> = {
64
61
  * draft.age.set(31);
65
62
  * });
66
63
  */
67
- update(callback: (draft: RxObject<T>) => void): DeepReadonly<T>;
64
+ update(callback: (draft: RxObject<T>) => void): T;
68
65
  /** Subscribe to a single emission, then automatically unsubscribe */
69
- subscribeOnce(callback: (value: DeepReadonly<T>) => void): Subscription;
66
+ subscribeOnce(callback: (value: T) => void): Subscription;
70
67
  [NODE]: NodeCore<T>;
71
68
  };
72
- type RxArray<T> = Observable<DeepReadonly<T[]>> & {
69
+ type RxArray<T> = Observable<T[]> & {
73
70
  /** Get current value synchronously */
74
- get(): DeepReadonly<T[]>;
71
+ get(): T[];
75
72
  /** Set value */
76
73
  set(value: T[]): void;
77
74
  /**
@@ -84,9 +81,9 @@ type RxArray<T> = Observable<DeepReadonly<T[]>> & {
84
81
  * draft.push({ id: 2, name: "New" });
85
82
  * });
86
83
  */
87
- update(callback: (draft: RxArray<T>) => void): DeepReadonly<T[]>;
84
+ update(callback: (draft: RxArray<T>) => void): T[];
88
85
  /** Subscribe to a single emission, then automatically unsubscribe */
89
- subscribeOnce(callback: (value: DeepReadonly<T[]>) => void): Subscription;
86
+ subscribeOnce(callback: (value: T[]) => void): Subscription;
90
87
  /** Get reactive node for array element at index */
91
88
  at(index: number): RxNodeFor<T> | undefined;
92
89
  /** Get current length (also observable) */
@@ -96,11 +93,11 @@ type RxArray<T> = Observable<DeepReadonly<T[]>> & {
96
93
  /** Push items and return new length */
97
94
  push(...items: T[]): number;
98
95
  /** Pop last item */
99
- pop(): DeepReadonly<T> | undefined;
96
+ pop(): T | undefined;
100
97
  /** Map over current values (non-reactive, use .subscribe for reactive) */
101
- map<U>(fn: (item: DeepReadonly<T>, index: number) => U): U[];
98
+ map<U>(fn: (item: T, index: number) => U): U[];
102
99
  /** Filter current values */
103
- filter(fn: (item: DeepReadonly<T>, index: number) => boolean): DeepReadonly<T>[];
100
+ filter(fn: (item: T, index: number) => boolean): T[];
104
101
  [NODE]: NodeCore<T[]>;
105
102
  };
106
103
  /**
@@ -124,13 +121,13 @@ type RxArray<T> = Observable<DeepReadonly<T[]>> & {
124
121
  * store.user.name.get(); // "Alice"
125
122
  * store.user.name.set("Bob"); // Works!
126
123
  */
127
- type RxNullable<T, TNonNull extends object = NonNullablePart<T> & object> = Observable<DeepReadonly<T>> & {
124
+ type RxNullable<T, TNonNull extends object = NonNullablePart<T> & object> = Observable<T> & {
128
125
  /** Get current value (may be null/undefined) */
129
- get(): DeepReadonly<T>;
126
+ get(): T;
130
127
  /** Set value (can be null/undefined or the full object) */
131
128
  set(value: T): void;
132
129
  /** Subscribe to a single emission, then automatically unsubscribe */
133
- subscribeOnce(callback: (value: DeepReadonly<T>) => void): Subscription;
130
+ subscribeOnce(callback: (value: T) => void): Subscription;
134
131
  /**
135
132
  * Update multiple properties in a single emission.
136
133
  * @example
@@ -139,7 +136,7 @@ type RxNullable<T, TNonNull extends object = NonNullablePart<T> & object> = Obse
139
136
  * user.age.set(31);
140
137
  * });
141
138
  */
142
- update(callback: (draft: RxObject<TNonNull>) => void): DeepReadonly<T>;
139
+ update(callback: (draft: RxObject<TNonNull>) => void): T;
143
140
  [NODE]: NodeCore<T>;
144
141
  } & {
145
142
  [K in keyof TNonNull]: RxNullableChild<TNonNull[K]>;
@@ -156,21 +153,16 @@ type RxNullableChild<T> = IsNullableObject<T> extends true ? RxNullable<T> : [T]
156
153
  * The object itself might be undefined (if parent is null), but if present
157
154
  * it has all the normal object methods and children.
158
155
  */
159
- type RxNullableChildObject<T extends object> = Observable<DeepReadonly<T> | undefined> & {
160
- get(): DeepReadonly<T> | undefined;
156
+ type RxNullableChildObject<T extends object> = Observable<T | undefined> & {
157
+ get(): T | undefined;
161
158
  set(value: T): void;
162
- subscribeOnce(callback: (value: DeepReadonly<T> | undefined) => void): Subscription;
159
+ subscribeOnce(callback: (value: T | undefined) => void): Subscription;
163
160
  [NODE]: NodeCore<T | undefined>;
164
161
  } & {
165
162
  [K in keyof T]: RxNullableChild<T[K]>;
166
163
  };
167
164
  type RxNodeFor<T> = IsNullableObject<T> extends true ? RxNullable<T> : [T] extends [Primitive] ? RxLeaf<T> : [T] extends [Array<infer U>] ? RxArray<U> : [T] extends [object] ? RxObject<T> : RxLeaf<T>;
168
- export type RxState<T extends object> = RxObject<T> & {
169
- /** Reset the store to its initial state */
170
- reset(): void;
171
- /** Get the initial state (frozen copy) */
172
- getInitialState(): DeepReadonly<T>;
173
- };
165
+ export type RxState<T extends object> = RxObject<T>;
174
166
  export interface StateOptions {
175
167
  /** Enable debug logging for this store */
176
168
  debug?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"deepstate.d.ts","sourceRoot":"","sources":["../src/deepstate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"deepstate.d.ts","sourceRoot":"","sources":["../src/deepstate.ts"],"names":[],"mappings":"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"}
package/dist/helpers.d.ts CHANGED
@@ -56,6 +56,6 @@ export declare function select<T extends Record<string, Observable<unknown>>>(ob
56
56
  * @param selector - A function that extracts/derives a value from each item
57
57
  * @returns An Observable that emits an array of selected values
58
58
  */
59
- export declare function selectFromEach<T, U>(arrayNode: Observable<ReadonlyArray<T>>, selector: (item: T, index: number) => U): Observable<U[]>;
59
+ export declare function selectFromEach<T, U>(arrayNode: Observable<T[]>, selector: (item: T, index: number) => U): Observable<U[]>;
60
60
  export {};
61
61
  //# sourceMappingURL=helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAiB,MAAM,MAAM,CAAC;AAoCjD,KAAK,eAAe,CAAC,CAAC,IAAI,CAAC,SAAS,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAGpE,KAAK,gBAAgB,CAAC,CAAC,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,IAAI;KACtD,CAAC,IAAI,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACtC,CAAC;AAGF,KAAK,sBAAsB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI;KAC1E,CAAC,IAAI,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACtC,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,EACpD,GAAG,WAAW,EAAE,CAAC,GAChB,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;AACnC,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,EAClE,WAAW,EAAE,CAAC,GACb,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;AAyBzC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EACjC,SAAS,EAAE,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EACvC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,CAAC,GACtC,UAAU,CAAC,CAAC,EAAE,CAAC,CAOjB"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAiB,MAAM,MAAM,CAAC;AA+CjD,KAAK,eAAe,CAAC,CAAC,IAAI,CAAC,SAAS,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAGpE,KAAK,gBAAgB,CAAC,CAAC,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,IAAI;KACtD,CAAC,IAAI,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACtC,CAAC;AAGF,KAAK,sBAAsB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI;KAC1E,CAAC,IAAI,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACtC,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,EACpD,GAAG,WAAW,EAAE,CAAC,GAChB,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;AACnC,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,EAClE,WAAW,EAAE,CAAC,GACb,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;AAyBzC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EACjC,SAAS,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,EAC1B,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,CAAC,GACtC,UAAU,CAAC,CAAC,EAAE,CAAC,CAOjB"}
package/dist/helpers.js CHANGED
@@ -1,17 +1,24 @@
1
1
  // src/helpers.ts
2
2
  import { combineLatest } from "rxjs";
3
3
  import { distinctUntilChanged, map } from "rxjs/operators";
4
- function deepEqual(a, b) {
4
+ function deepEqual(a, b, seen = new WeakMap) {
5
5
  if (a === b)
6
6
  return true;
7
7
  if (a === null || b === null)
8
8
  return false;
9
9
  if (typeof a !== "object" || typeof b !== "object")
10
10
  return false;
11
+ const seenWithA = seen.get(a);
12
+ if (seenWithA?.has(b))
13
+ return true;
14
+ if (!seen.has(a)) {
15
+ seen.set(a, new WeakSet);
16
+ }
17
+ seen.get(a).add(b);
11
18
  if (Array.isArray(a) && Array.isArray(b)) {
12
19
  if (a.length !== b.length)
13
20
  return false;
14
- return a.every((item, i) => deepEqual(item, b[i]));
21
+ return a.every((item, i) => deepEqual(item, b[i], seen));
15
22
  }
16
23
  if (Array.isArray(a) !== Array.isArray(b))
17
24
  return false;
@@ -19,7 +26,7 @@ function deepEqual(a, b) {
19
26
  const keysB = Object.keys(b);
20
27
  if (keysA.length !== keysB.length)
21
28
  return false;
22
- return keysA.every((key) => deepEqual(a[key], b[key]));
29
+ return keysA.every((key) => deepEqual(a[key], b[key], seen));
23
30
  }
24
31
  function isObservable(obj) {
25
32
  return obj !== null && typeof obj === "object" && "subscribe" in obj && typeof obj.subscribe === "function";
package/dist/index.js CHANGED
@@ -1,17 +1,24 @@
1
1
  // src/helpers.ts
2
2
  import { combineLatest } from "rxjs";
3
3
  import { distinctUntilChanged, map } from "rxjs/operators";
4
- function deepEqual(a, b) {
4
+ function deepEqual(a, b, seen = new WeakMap) {
5
5
  if (a === b)
6
6
  return true;
7
7
  if (a === null || b === null)
8
8
  return false;
9
9
  if (typeof a !== "object" || typeof b !== "object")
10
10
  return false;
11
+ const seenWithA = seen.get(a);
12
+ if (seenWithA?.has(b))
13
+ return true;
14
+ if (!seen.has(a)) {
15
+ seen.set(a, new WeakSet);
16
+ }
17
+ seen.get(a).add(b);
11
18
  if (Array.isArray(a) && Array.isArray(b)) {
12
19
  if (a.length !== b.length)
13
20
  return false;
14
- return a.every((item, i) => deepEqual(item, b[i]));
21
+ return a.every((item, i) => deepEqual(item, b[i], seen));
15
22
  }
16
23
  if (Array.isArray(a) !== Array.isArray(b))
17
24
  return false;
@@ -19,7 +26,7 @@ function deepEqual(a, b) {
19
26
  const keysB = Object.keys(b);
20
27
  if (keysA.length !== keysB.length)
21
28
  return false;
22
- return keysA.every((key) => deepEqual(a[key], b[key]));
29
+ return keysA.every((key) => deepEqual(a[key], b[key], seen));
23
30
  }
24
31
  function isObservable(obj) {
25
32
  return obj !== null && typeof obj === "object" && "subscribe" in obj && typeof obj.subscribe === "function";
@@ -84,21 +91,6 @@ function countedDistinctUntilChanged(compareFn) {
84
91
  return a === b;
85
92
  });
86
93
  }
87
- function deepFreeze(obj) {
88
- if (obj === null || typeof obj !== "object")
89
- return obj;
90
- if (Object.isFrozen(obj))
91
- return obj;
92
- Object.freeze(obj);
93
- if (Array.isArray(obj)) {
94
- obj.forEach((item) => deepFreeze(item));
95
- } else {
96
- Object.keys(obj).forEach((key) => {
97
- deepFreeze(obj[key]);
98
- });
99
- }
100
- return obj;
101
- }
102
94
  var NODE = Symbol("node");
103
95
  function createLeafNode(value) {
104
96
  const subject$ = new BehaviorSubject(value);
@@ -147,25 +139,19 @@ function createObjectNode(value) {
147
139
  return result;
148
140
  }), shareReplay(1));
149
141
  $.subscribe();
150
- const frozen$ = $.pipe(map2(deepFreeze));
151
142
  return {
152
- $: frozen$,
143
+ $,
153
144
  children,
154
- get: () => deepFreeze(getCurrentValue()),
145
+ get: () => getCurrentValue(),
155
146
  set: (v) => {
156
- lock$.next(false);
157
- try {
158
- for (const [key, child] of children) {
159
- child.set(v[key]);
160
- }
161
- } finally {
162
- lock$.next(true);
147
+ for (const [key, child] of children) {
148
+ child.set(v[key]);
163
149
  }
164
150
  },
165
151
  lock: () => lock$.next(false),
166
152
  unlock: () => lock$.next(true),
167
153
  subscribeOnce: (callback) => {
168
- return frozen$.pipe(take(1)).subscribe(callback);
154
+ return $.pipe(take(1)).subscribe(callback);
169
155
  }
170
156
  };
171
157
  }
@@ -194,7 +180,7 @@ function createArrayNode(value, comparator) {
194
180
  };
195
181
  const lock$ = new BehaviorSubject(true);
196
182
  const baseLocked$ = combineLatest2([subject$, lock$]).pipe(filter(([_, unlocked]) => unlocked), map2(([arr, _]) => arr));
197
- const locked$ = (comparator ? baseLocked$.pipe(distinctUntilChanged2(comparator)) : baseLocked$).pipe(map2(deepFreeze), shareReplay(1));
183
+ const locked$ = (comparator ? baseLocked$.pipe(distinctUntilChanged2(comparator)) : baseLocked$).pipe(map2((arr) => [...arr]), shareReplay(1));
198
184
  locked$.subscribe();
199
185
  const length$ = locked$.pipe(map2((arr) => arr.length), distinctUntilChanged2(), shareReplay(1));
200
186
  length$.subscribe();
@@ -204,8 +190,11 @@ function createArrayNode(value, comparator) {
204
190
  return {
205
191
  $: locked$,
206
192
  childCache,
207
- get: () => deepFreeze([...subject$.getValue()]),
193
+ get: () => [...subject$.getValue()],
208
194
  set: (v) => {
195
+ if (hasCircularReference(v)) {
196
+ throw new Error("Circular reference detected in array value. " + "Deepstate does not support circular references. " + "Please flatten your data structure or remove the circular reference.");
197
+ }
209
198
  childCache.clear();
210
199
  subject$.next([...v]);
211
200
  },
@@ -235,13 +224,13 @@ function createArrayNode(value, comparator) {
235
224
  const last = current[current.length - 1];
236
225
  childCache.delete(current.length - 1);
237
226
  subject$.next(current.slice(0, -1));
238
- return deepFreeze(last);
227
+ return last;
239
228
  },
240
229
  mapItems: (fn) => {
241
- return subject$.getValue().map((item, i) => fn(deepFreeze(item), i));
230
+ return subject$.getValue().map((item, i) => fn(item, i));
242
231
  },
243
232
  filterItems: (fn) => {
244
- return deepFreeze(subject$.getValue().filter((item, i) => fn(deepFreeze(item), i)));
233
+ return subject$.getValue().filter((item, i) => fn(item, i));
245
234
  },
246
235
  lock: () => lock$.next(false),
247
236
  unlock: () => lock$.next(true)
@@ -289,7 +278,7 @@ function createNullableObjectNode(initialValue) {
289
278
  if (b === null || b === undefined)
290
279
  return false;
291
280
  return JSON.stringify(a) === JSON.stringify(b);
292
- }), map2(deepFreeze), shareReplay(1));
281
+ }), shareReplay(1));
293
282
  $.subscribe();
294
283
  const nodeState = { children };
295
284
  const updateChildrenRef = () => {
@@ -318,7 +307,7 @@ function createNullableObjectNode(initialValue) {
318
307
  get children() {
319
308
  return nodeState.children;
320
309
  },
321
- get: () => deepFreeze(getCurrentValue()),
310
+ get: () => getCurrentValue(),
322
311
  set: (value) => {
323
312
  if (value === null || value === undefined) {
324
313
  subject$.next(value);
@@ -553,6 +542,17 @@ function createNestedArrayProjection(parentArray$, index, key, initialValue) {
553
542
  }
554
543
  };
555
544
  }
545
+ function hasCircularReference(obj, seen = new WeakSet) {
546
+ if (obj === null || typeof obj !== "object")
547
+ return false;
548
+ if (seen.has(obj))
549
+ return true;
550
+ seen.add(obj);
551
+ if (Array.isArray(obj)) {
552
+ return obj.some((item) => hasCircularReference(item, seen));
553
+ }
554
+ return Object.values(obj).some((value) => hasCircularReference(value, seen));
555
+ }
556
556
  function createNodeForValue(value, maybeNullable = false) {
557
557
  if (isNullableMarked(value)) {
558
558
  delete value[NULLABLE_MARKER];
@@ -567,6 +567,9 @@ function createNodeForValue(value, maybeNullable = false) {
567
567
  if (typeof value !== "object") {
568
568
  return createLeafNode(value);
569
569
  }
570
+ if (hasCircularReference(value)) {
571
+ throw new Error("Circular reference detected in state value. " + "Deepstate does not support circular references because each property becomes a reactive node. " + "Please flatten your data structure or remove the circular reference.");
572
+ }
570
573
  if (Array.isArray(value)) {
571
574
  if (isArrayMarked(value)) {
572
575
  const options = value[ARRAY_MARKER];
@@ -658,7 +661,7 @@ function wrapNullableWithProxy(node, path = "", debugLog) {
658
661
  });
659
662
  return proxy;
660
663
  }
661
- function wrapWithProxy(node, path = "", debugLog, rootMethods) {
664
+ function wrapWithProxy(node, path = "", debugLog) {
662
665
  if (isNullableNode(node)) {
663
666
  return wrapNullableWithProxy(node, path, debugLog);
664
667
  }
@@ -731,12 +734,6 @@ function wrapWithProxy(node, path = "", debugLog, rootMethods) {
731
734
  return node.subscribeOnce;
732
735
  if (prop === NODE)
733
736
  return node;
734
- if (rootMethods) {
735
- if (prop === "reset")
736
- return rootMethods.reset;
737
- if (prop === "getInitialState")
738
- return rootMethods.getInitialState;
739
- }
740
737
  if (prop === Symbol.observable || prop === "@@observable") {
741
738
  return () => node.$;
742
739
  }
@@ -787,21 +784,8 @@ function wrapWithProxy(node, path = "", debugLog, rootMethods) {
787
784
  }
788
785
  function state(initialState, options) {
789
786
  const debugLog = options?.debug ? createDebugLog({ enabled: true, storeName: options.name }) : undefined;
790
- const frozenInitialState = deepFreeze(structuredClone(initialState));
791
787
  const node = createObjectNode(initialState);
792
- const rootMethods = {
793
- reset: () => {
794
- debugLog?.("root", "reset", node.get(), frozenInitialState);
795
- node.lock();
796
- try {
797
- node.set(structuredClone(frozenInitialState));
798
- } finally {
799
- node.unlock();
800
- }
801
- },
802
- getInitialState: () => frozenInitialState
803
- };
804
- return wrapWithProxy(node, "", debugLog, rootMethods);
788
+ return wrapWithProxy(node, "", debugLog);
805
789
  }
806
790
  var NULLABLE_MARKER = Symbol("nullable");
807
791
  function nullable(value) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@montra-interactive/deepstate",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Proxy-based reactive state management with RxJS. Deep nested state observation with full TypeScript support.",
5
5
  "keywords": [
6
6
  "state",
@@ -19,7 +19,7 @@
19
19
  "homepage": "https://github.com/Montra-Interactive/deepstate/tree/main/packages/core",
20
20
  "repository": {
21
21
  "type": "git",
22
- "url": "https://github.com/Montra-Interactive/deepstate",
22
+ "url": "git+https://github.com/Montra-Interactive/deepstate.git",
23
23
  "directory": "packages/core"
24
24
  },
25
25
  "module": "dist/index.js",
package/src/deepstate.ts CHANGED
@@ -75,27 +75,6 @@ function countedDistinctUntilChanged<T>(compareFn?: (a: T, b: T) => boolean) {
75
75
  });
76
76
  }
77
77
 
78
- // =============================================================================
79
- // Deep Freeze
80
- // =============================================================================
81
-
82
- function deepFreeze<T>(obj: T): T {
83
- if (obj === null || typeof obj !== "object") return obj;
84
- if (Object.isFrozen(obj)) return obj;
85
-
86
- Object.freeze(obj);
87
-
88
- if (Array.isArray(obj)) {
89
- obj.forEach((item) => deepFreeze(item));
90
- } else {
91
- Object.keys(obj).forEach((key) => {
92
- deepFreeze((obj as Record<string, unknown>)[key]);
93
- });
94
- }
95
-
96
- return obj;
97
- }
98
-
99
78
  // =============================================================================
100
79
  // Types
101
80
  // =============================================================================
@@ -123,16 +102,9 @@ type IsNullableObject<T> = IsNullish<T> extends true
123
102
  : false;
124
103
 
125
104
  /**
126
- * Deep readonly type - makes all nested properties readonly.
127
- * Used for return types of get() and subscribe() to prevent accidental mutations.
105
+ * Type alias for values returned by get() and subscribe().
128
106
  */
129
- export type DeepReadonly<T> = [T] extends [Primitive]
130
- ? T
131
- : [T] extends [Array<infer U>]
132
- ? ReadonlyArray<DeepReadonly<U>>
133
- : [T] extends [object]
134
- ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
135
- : T;
107
+ export type DeepReadonly<T> = T;
136
108
 
137
109
  /**
138
110
  * A mutable draft of state T for use in update callbacks.
@@ -151,21 +123,21 @@ interface NodeCore<T> {
151
123
  const NODE = Symbol("node");
152
124
 
153
125
  // External API types
154
- type RxLeaf<T> = Observable<DeepReadonly<T>> & {
126
+ type RxLeaf<T> = Observable<T> & {
155
127
  /** Get current value synchronously */
156
- get(): DeepReadonly<T>;
128
+ get(): T;
157
129
  /** Set value */
158
130
  set(value: T): void;
159
131
  /** Subscribe to a single emission, then automatically unsubscribe */
160
- subscribeOnce(callback: (value: DeepReadonly<T>) => void): Subscription;
132
+ subscribeOnce(callback: (value: T) => void): Subscription;
161
133
  [NODE]: NodeCore<T>;
162
134
  };
163
135
 
164
136
  type RxObject<T extends object> = {
165
137
  [K in keyof T]: RxNodeFor<T[K]>;
166
- } & Observable<DeepReadonly<T>> & {
138
+ } & Observable<T> & {
167
139
  /** Get current value synchronously */
168
- get(): DeepReadonly<T>;
140
+ get(): T;
169
141
  /** Set value */
170
142
  set(value: T): void;
171
143
  /**
@@ -178,15 +150,15 @@ type RxObject<T extends object> = {
178
150
  * draft.age.set(31);
179
151
  * });
180
152
  */
181
- update(callback: (draft: RxObject<T>) => void): DeepReadonly<T>;
153
+ update(callback: (draft: RxObject<T>) => void): T;
182
154
  /** Subscribe to a single emission, then automatically unsubscribe */
183
- subscribeOnce(callback: (value: DeepReadonly<T>) => void): Subscription;
155
+ subscribeOnce(callback: (value: T) => void): Subscription;
184
156
  [NODE]: NodeCore<T>;
185
157
  };
186
158
 
187
- type RxArray<T> = Observable<DeepReadonly<T[]>> & {
159
+ type RxArray<T> = Observable<T[]> & {
188
160
  /** Get current value synchronously */
189
- get(): DeepReadonly<T[]>;
161
+ get(): T[];
190
162
  /** Set value */
191
163
  set(value: T[]): void;
192
164
  /**
@@ -199,9 +171,9 @@ type RxArray<T> = Observable<DeepReadonly<T[]>> & {
199
171
  * draft.push({ id: 2, name: "New" });
200
172
  * });
201
173
  */
202
- update(callback: (draft: RxArray<T>) => void): DeepReadonly<T[]>;
174
+ update(callback: (draft: RxArray<T>) => void): T[];
203
175
  /** Subscribe to a single emission, then automatically unsubscribe */
204
- subscribeOnce(callback: (value: DeepReadonly<T[]>) => void): Subscription;
176
+ subscribeOnce(callback: (value: T[]) => void): Subscription;
205
177
  /** Get reactive node for array element at index */
206
178
  at(index: number): RxNodeFor<T> | undefined;
207
179
  /** Get current length (also observable) */
@@ -209,11 +181,11 @@ type RxArray<T> = Observable<DeepReadonly<T[]>> & {
209
181
  /** Push items and return new length */
210
182
  push(...items: T[]): number;
211
183
  /** Pop last item */
212
- pop(): DeepReadonly<T> | undefined;
184
+ pop(): T | undefined;
213
185
  /** Map over current values (non-reactive, use .subscribe for reactive) */
214
- map<U>(fn: (item: DeepReadonly<T>, index: number) => U): U[];
186
+ map<U>(fn: (item: T, index: number) => U): U[];
215
187
  /** Filter current values */
216
- filter(fn: (item: DeepReadonly<T>, index: number) => boolean): DeepReadonly<T>[];
188
+ filter(fn: (item: T, index: number) => boolean): T[];
217
189
  [NODE]: NodeCore<T[]>;
218
190
  };
219
191
 
@@ -238,13 +210,13 @@ type RxArray<T> = Observable<DeepReadonly<T[]>> & {
238
210
  * store.user.name.get(); // "Alice"
239
211
  * store.user.name.set("Bob"); // Works!
240
212
  */
241
- type RxNullable<T, TNonNull extends object = NonNullablePart<T> & object> = Observable<DeepReadonly<T>> & {
213
+ type RxNullable<T, TNonNull extends object = NonNullablePart<T> & object> = Observable<T> & {
242
214
  /** Get current value (may be null/undefined) */
243
- get(): DeepReadonly<T>;
215
+ get(): T;
244
216
  /** Set value (can be null/undefined or the full object) */
245
217
  set(value: T): void;
246
218
  /** Subscribe to a single emission, then automatically unsubscribe */
247
- subscribeOnce(callback: (value: DeepReadonly<T>) => void): Subscription;
219
+ subscribeOnce(callback: (value: T) => void): Subscription;
248
220
  /**
249
221
  * Update multiple properties in a single emission.
250
222
  * @example
@@ -253,7 +225,7 @@ type RxNullable<T, TNonNull extends object = NonNullablePart<T> & object> = Obse
253
225
  * user.age.set(31);
254
226
  * });
255
227
  */
256
- update(callback: (draft: RxObject<TNonNull>) => void): DeepReadonly<T>;
228
+ update(callback: (draft: RxObject<TNonNull>) => void): T;
257
229
  [NODE]: NodeCore<T>;
258
230
  } & {
259
231
  /**
@@ -291,10 +263,10 @@ type RxNullableChild<T> =
291
263
  * The object itself might be undefined (if parent is null), but if present
292
264
  * it has all the normal object methods and children.
293
265
  */
294
- type RxNullableChildObject<T extends object> = Observable<DeepReadonly<T> | undefined> & {
295
- get(): DeepReadonly<T> | undefined;
266
+ type RxNullableChildObject<T extends object> = Observable<T | undefined> & {
267
+ get(): T | undefined;
296
268
  set(value: T): void;
297
- subscribeOnce(callback: (value: DeepReadonly<T> | undefined) => void): Subscription;
269
+ subscribeOnce(callback: (value: T | undefined) => void): Subscription;
298
270
  [NODE]: NodeCore<T | undefined>;
299
271
  } & {
300
272
  [K in keyof T]: RxNullableChild<T[K]>;
@@ -316,12 +288,7 @@ type RxNodeFor<T> =
316
288
  // Fallback
317
289
  : RxLeaf<T>;
318
290
 
319
- export type RxState<T extends object> = RxObject<T> & {
320
- /** Reset the store to its initial state */
321
- reset(): void;
322
- /** Get the initial state (frozen copy) */
323
- getInitialState(): DeepReadonly<T>;
324
- };
291
+ export type RxState<T extends object> = RxObject<T>;
325
292
 
326
293
  // =============================================================================
327
294
  // Node Creation
@@ -408,29 +375,20 @@ function createObjectNode<T extends object>(value: T): NodeCore<T> & {
408
375
  // Force subscription to make it hot (so emissions work even before external subscribers)
409
376
  $.subscribe();
410
377
 
411
- // Create a version that freezes on emission
412
- const frozen$ = $.pipe(map(deepFreeze));
413
-
414
378
  return {
415
- $: frozen$,
379
+ $,
416
380
  children: children as Map<string, NodeCore<unknown>>,
417
- get: () => deepFreeze(getCurrentValue()),
381
+ get: () => getCurrentValue(),
418
382
  set: (v: T) => {
419
- // Lock to batch all child updates into single emission
420
- lock$.next(false);
421
- try {
422
- for (const [key, child] of children) {
423
- child.set(v[key]);
424
- }
425
- } finally {
426
- lock$.next(true);
383
+ for (const [key, child] of children) {
384
+ child.set(v[key]);
427
385
  }
428
386
  },
429
387
  lock: () => lock$.next(false),
430
388
  unlock: () => lock$.next(true),
431
389
  // Note: update() is implemented in wrapWithProxy since it needs the proxy reference
432
390
  subscribeOnce: (callback: (value: T) => void): Subscription => {
433
- return frozen$.pipe(take(1)).subscribe(callback);
391
+ return $.pipe(take(1)).subscribe(callback);
434
392
  },
435
393
  };
436
394
  }
@@ -499,11 +457,11 @@ function createArrayNode<T>(
499
457
  );
500
458
 
501
459
  // Apply distinct comparison if provided
502
- const locked$ = (comparator
460
+ const locked$ = (comparator
503
461
  ? baseLocked$.pipe(distinctUntilChanged(comparator))
504
462
  : baseLocked$
505
463
  ).pipe(
506
- map(deepFreeze),
464
+ map((arr) => [...arr]),
507
465
  shareReplay(1)
508
466
  );
509
467
  locked$.subscribe(); // Keep hot
@@ -523,8 +481,16 @@ function createArrayNode<T>(
523
481
  return {
524
482
  $: locked$ as Observable<T[]>,
525
483
  childCache,
526
- get: () => deepFreeze([...subject$.getValue()]) as T[],
484
+ get: () => [...subject$.getValue()] as T[],
527
485
  set: (v: T[]) => {
486
+ // Check for circular references in array items
487
+ if (hasCircularReference(v)) {
488
+ throw new Error(
489
+ 'Circular reference detected in array value. ' +
490
+ 'Deepstate does not support circular references. ' +
491
+ 'Please flatten your data structure or remove the circular reference.'
492
+ );
493
+ }
528
494
  // Clear child cache when array is replaced
529
495
  childCache.clear();
530
496
  subject$.next([...v]);
@@ -555,13 +521,13 @@ function createArrayNode<T>(
555
521
  // Clear cached node for popped index
556
522
  childCache.delete(current.length - 1);
557
523
  subject$.next(current.slice(0, -1));
558
- return deepFreeze(last) as T;
524
+ return last as T;
559
525
  },
560
526
  mapItems: <U>(fn: (item: T, index: number) => U): U[] => {
561
- return subject$.getValue().map((item, i) => fn(deepFreeze(item) as T, i));
527
+ return subject$.getValue().map((item, i) => fn(item as T, i));
562
528
  },
563
529
  filterItems: (fn: (item: T, index: number) => boolean): T[] => {
564
- return deepFreeze(subject$.getValue().filter((item, i) => fn(deepFreeze(item) as T, i))) as T[];
530
+ return subject$.getValue().filter((item, i) => fn(item as T, i)) as T[];
565
531
  },
566
532
  lock: () => lock$.next(false),
567
533
  unlock: () => lock$.next(true),
@@ -663,7 +629,6 @@ function createNullableObjectNode<T>(
663
629
  if (b === null || b === undefined) return false;
664
630
  return JSON.stringify(a) === JSON.stringify(b);
665
631
  }),
666
- map(deepFreeze),
667
632
  shareReplay(1)
668
633
  );
669
634
  $.subscribe(); // Keep hot
@@ -706,7 +671,7 @@ function createNullableObjectNode<T>(
706
671
  $,
707
672
  get children() { return nodeState.children; },
708
673
 
709
- get: () => deepFreeze(getCurrentValue()),
674
+ get: () => getCurrentValue(),
710
675
 
711
676
  set: (value: T) => {
712
677
  if (value === null || value === undefined) {
@@ -1078,6 +1043,19 @@ function createNestedArrayProjection<T>(
1078
1043
  };
1079
1044
  }
1080
1045
 
1046
+ // Helper to detect circular references in an object
1047
+ function hasCircularReference(obj: unknown, seen = new WeakSet<object>()): boolean {
1048
+ if (obj === null || typeof obj !== 'object') return false;
1049
+ if (seen.has(obj as object)) return true;
1050
+ seen.add(obj as object);
1051
+
1052
+ if (Array.isArray(obj)) {
1053
+ return obj.some(item => hasCircularReference(item, seen));
1054
+ }
1055
+
1056
+ return Object.values(obj).some(value => hasCircularReference(value, seen));
1057
+ }
1058
+
1081
1059
  // Factory to create the right node type
1082
1060
  // When maybeNullable is true and value is null/undefined, creates a NullableNodeCore
1083
1061
  // that can later be upgraded to an object with children
@@ -1099,6 +1077,16 @@ function createNodeForValue<T>(value: T, maybeNullable: boolean = false): NodeCo
1099
1077
  if (typeof value !== "object") {
1100
1078
  return createLeafNode(value as Primitive) as NodeCore<T>;
1101
1079
  }
1080
+
1081
+ // Check for circular references before creating nodes
1082
+ if (hasCircularReference(value)) {
1083
+ throw new Error(
1084
+ 'Circular reference detected in state value. ' +
1085
+ 'Deepstate does not support circular references because each property becomes a reactive node. ' +
1086
+ 'Please flatten your data structure or remove the circular reference.'
1087
+ );
1088
+ }
1089
+
1102
1090
  if (Array.isArray(value)) {
1103
1091
  // Check if array was marked with options via array() helper
1104
1092
  if (isArrayMarked(value)) {
@@ -1217,12 +1205,7 @@ function wrapNullableWithProxy<T>(node: NullableNodeCore<T>, path: string = '',
1217
1205
  return proxy as unknown as RxNullable<T>;
1218
1206
  }
1219
1207
 
1220
- interface RootMethods<T> {
1221
- reset: () => void;
1222
- getInitialState: () => DeepReadonly<T>;
1223
- }
1224
-
1225
- function wrapWithProxy<T>(node: NodeCore<T>, path: string = '', debugLog?: DebugLogFn, rootMethods?: RootMethods<T>): RxNodeFor<T> {
1208
+ function wrapWithProxy<T>(node: NodeCore<T>, path: string = '', debugLog?: DebugLogFn): RxNodeFor<T> {
1226
1209
  // Check for nullable node first (before checking value, since value might be null)
1227
1210
  if (isNullableNode(node)) {
1228
1211
  return wrapNullableWithProxy(node, path, debugLog) as RxNodeFor<T>;
@@ -1319,12 +1302,6 @@ function wrapWithProxy<T>(node: NodeCore<T>, path: string = '', debugLog?: Debug
1319
1302
  if (prop === "update") return updateFn;
1320
1303
  if (prop === "subscribeOnce") return node.subscribeOnce;
1321
1304
  if (prop === NODE) return node;
1322
-
1323
- // Root store methods (reset, getInitialState)
1324
- if (rootMethods) {
1325
- if (prop === "reset") return rootMethods.reset;
1326
- if (prop === "getInitialState") return rootMethods.getInitialState;
1327
- }
1328
1305
 
1329
1306
  // Symbol.observable for RxJS interop
1330
1307
  if (prop === Symbol.observable || prop === "@@observable") {
@@ -1404,28 +1381,8 @@ export function state<T extends object>(initialState: T, options?: StateOptions)
1404
1381
  ? createDebugLog({ enabled: true, storeName: options.name })
1405
1382
  : undefined;
1406
1383
 
1407
- // Deep clone and freeze the initial state for later reset
1408
- const frozenInitialState = deepFreeze(structuredClone(initialState));
1409
-
1410
1384
  const node = createObjectNode(initialState);
1411
-
1412
- // Create reset functions to pass to wrapWithProxy
1413
- const rootMethods = {
1414
- reset: () => {
1415
- debugLog?.('root', 'reset', node.get(), frozenInitialState);
1416
- // Batch the reset so only one emission occurs
1417
- node.lock();
1418
- try {
1419
- // Deep clone so we don't pass frozen objects to set()
1420
- node.set(structuredClone(frozenInitialState) as T);
1421
- } finally {
1422
- node.unlock();
1423
- }
1424
- },
1425
- getInitialState: () => frozenInitialState as DeepReadonly<T>,
1426
- };
1427
-
1428
- return wrapWithProxy(node as NodeCore<T>, '', debugLog, rootMethods) as unknown as RxState<T>;
1385
+ return wrapWithProxy(node as NodeCore<T>, '', debugLog) as RxState<T>;
1429
1386
  }
1430
1387
 
1431
1388
  // Symbol to mark a value as nullable
package/src/helpers.ts CHANGED
@@ -8,15 +8,26 @@
8
8
  import { Observable, combineLatest } from "rxjs";
9
9
  import { distinctUntilChanged, map } from "rxjs/operators";
10
10
 
11
- // Deep equality check (duplicated to keep helpers independent)
12
- function deepEqual(a: unknown, b: unknown): boolean {
11
+ // Deep equality check with circular reference protection
12
+ function deepEqual(a: unknown, b: unknown, seen = new WeakMap<object, WeakSet<object>>()): boolean {
13
13
  if (a === b) return true;
14
14
  if (a === null || b === null) return false;
15
15
  if (typeof a !== "object" || typeof b !== "object") return false;
16
16
 
17
+ // Circular reference protection: if we've already compared these two objects, return true
18
+ // (they're equal as far as we've seen, and going deeper would be infinite)
19
+ const seenWithA = seen.get(a as object);
20
+ if (seenWithA?.has(b as object)) return true;
21
+
22
+ // Track this comparison
23
+ if (!seen.has(a as object)) {
24
+ seen.set(a as object, new WeakSet());
25
+ }
26
+ seen.get(a as object)!.add(b as object);
27
+
17
28
  if (Array.isArray(a) && Array.isArray(b)) {
18
29
  if (a.length !== b.length) return false;
19
- return a.every((item, i) => deepEqual(item, b[i]));
30
+ return a.every((item, i) => deepEqual(item, b[i], seen));
20
31
  }
21
32
 
22
33
  if (Array.isArray(a) !== Array.isArray(b)) return false;
@@ -26,7 +37,7 @@ function deepEqual(a: unknown, b: unknown): boolean {
26
37
  if (keysA.length !== keysB.length) return false;
27
38
 
28
39
  return keysA.every((key) =>
29
- deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])
40
+ deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key], seen)
30
41
  );
31
42
  }
32
43
 
@@ -126,7 +137,7 @@ export function select(
126
137
  * @returns An Observable that emits an array of selected values
127
138
  */
128
139
  export function selectFromEach<T, U>(
129
- arrayNode: Observable<ReadonlyArray<T>>,
140
+ arrayNode: Observable<T[]>,
130
141
  selector: (item: T, index: number) => U
131
142
  ): Observable<U[]> {
132
143
  return arrayNode.pipe(